Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Large list rendering in JavaScript

I am trying to render the list based on virtual rendering concept. I am facing some minor issues, but they are not blocking the behaviour. Here is the working fiddle http://jsfiddle.net/53N36/9/ and Here are my problems

  1. Last items are not visible, I assume some where I missed indexing.(Fixed, Please see the edit)
  2. How to calculate scrollPosition if I want to add custom scroll to this.
  3. Is this the best method or any other?

I have tested it with 700000 items and 70 items in chrome. Below is the code

(function () {
var list = (function () {
    var temp = [];
    for (var i = 0, l = 70; i < l; i++) {
        temp.push("list-item-" + (i + 1));
    }
    return temp;
}());

function listItem(text, id) {
    var _div = document.createElement('div');
    _div.innerHTML = text;
    _div.className = "listItem";
    _div.id = id;
    return _div;
}
var listHold = document.getElementById('listHolder'),
    ht = listHold.clientHeight,
    wt = listHold.clientWidth,
    ele = listItem(list[0], 'item0'),
    frag = document.createDocumentFragment();
listHold.appendChild(ele);
var ht_ele = ele.clientHeight,
    filled = ht_ele,
    filledIn = [0];
for (var i = 1, l = list.length; i < l; i++) {
    if (filled + ht_ele < ht) {
        filled += ht_ele;
        ele = listItem(list[i], 'item' + i);
        frag.appendChild(ele);
    } else {
        filledIn.push(i);
        break;
    }
}
listHold.appendChild(frag.cloneNode(true));
var elements = document.querySelectorAll('#listHolder .listItem');

function MouseWheelHandler(e) {
    var e = window.event || e;
    var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
    console.log(delta);
    //if(filledIn[0] != 0 && filledIn[0] != list.length){
    if (delta == -1) {
        var start = filledIn[0] + 1,
            end = filledIn[1] + 1,
            counter = 0;
        if (list[start] && list[end]) {
            for (var i = filledIn[0]; i < filledIn[1]; i++) {
                if (list[i]) {
                    (function (a) {
                        elements[counter].innerHTML = list[a];
                    }(i));
                    counter++;
                }
            }
            filledIn[0] = start;
            filledIn[1] = end;
        }
    } else {
        var start = filledIn[0] - 1,
            end = filledIn[1] - 1,
            counter = 0;
        if (list[start] && list[end]) {
            for (var i = start; i < end; i++) {
                if (list[i]) {
                    (function (a) {
                        elements[counter].innerHTML = list[a];
                    }(i));
                    counter++;
                }
            }
            filledIn[0] = start;
            filledIn[1] = end;
        }
    }
    //}
}
if (listHold.addEventListener) {
    listHold.addEventListener("mousewheel", MouseWheelHandler, false);
    listHold.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
} else listHold.attachEvent("onmousewheel", MouseWheelHandler);
}());

Please suggest me on this.

EDIT: I have tried again and I am able to fix the indexing issue. http://jsfiddle.net/53N36/26/ But how can I calculate the scroll position based on the array list currently displayed.

like image 712
Exception Avatar asked Dec 26 '22 00:12

Exception


1 Answers

Is this the best method or any other?
I think something that would make this much easier is not to try to handle scrolling yourself.
In this fiddle I show that you can let the browser handle scrolling for you, even though we are using virtual rendering.

Using .scrollTop I detect where the browser thinks the user is looking, and I draw in items based on that.
You'll note that if you set hidescrollbar to false and the user uses it to scroll, my method still runs fine.

Therefore, to calculate scroll position you can just use .scrollTop.
And as for custom scrolling, just make sure you influence the .scrollTop of #listHolder and recall refreshWindow()

CODE FROM FIDDLE

(function () {
    //CHANGE THESE IF YOU WANT
    var hidescrollbar = false;
    var numberofitems = 700000;
    //

    var holder = document.getElementById('listHolder');
    var view = null;

    //get the height of a single item
    var itemHeight = (function() {
        //generate a fake item
        var div = document.createElement('div');
        div.className = 'listItem';
        div.innerHTML = 'testing height';
        holder.appendChild(div);

        //get its height and remove it
        var output = div.offsetHeight;
        holder.removeChild(div);
        return output;
    })();

    //faster to instantiate empty-celled array
    var items = Array(numberofitems);
    //fill it in with data
    for (var index = 0; index < items.length; ++index)
        items[index] = 'item-' + index;

    //displays a suitable number of items
    function refreshWindow() {
        //remove old view
        if (view != null)
            holder.removeChild(view);
        //create new view
        view = holder.appendChild(document.createElement('div'));

        var firstItem = Math.floor(holder.scrollTop / itemHeight);
        var lastItem = firstItem + Math.ceil(holder.offsetHeight / itemHeight) + 1;
        if (lastItem + 1 >= items.length)
            lastItem = items.length - 1;

        //position view in users face
        view.id = 'view';
        view.style.top = (firstItem * itemHeight) + 'px';

        var div;
        //add the items
        for (var index = firstItem; index <= lastItem; ++index) {
            div = document.createElement('div');
            div.innerHTML = items[index];
            div.className = "listItem";
            view.appendChild(div);
        }
        console.log('viewing items ' + firstItem + ' to ' + lastItem);
    }

    refreshWindow();

    document.getElementById('heightForcer').style.height = (items.length * itemHeight) + 'px';
    if (hidescrollbar) {
        //work around for non-chrome browsers, hides the scrollbar
        holder.style.width = (holder.offsetWidth * 2 - view.offsetWidth) + 'px';
    }

    function delayingHandler() {
        //wait for the scroll to finish
        setTimeout(refreshWindow, 10);
    }
    if (holder.addEventListener)
        holder.addEventListener("scroll", delayingHandler, false);
    else
        holder.attachEvent("onscroll", delayingHandler);
}());
<div id="listHolder">
    <div id="heightForcer"></div>
</div>
html, body {
    width:100%;
    height:100%;
    padding:0;
    margin:0
}
body{
    overflow:hidden;
}
.listItem {
    border:1px solid gray;
    padding:0 5px;
    width: margin : 1px 0px;
}
#listHolder {
    position:relative;
    height:100%;
    width:100%;
    background-color:#CCC;
    box-sizing:border-box;
    overflow:auto;
}
/*chrome only
#listHolder::-webkit-scrollbar{
    display:none;
}*/
#view{
    position:absolute;
    width:100%;
}
like image 198
Hashbrown Avatar answered Jan 05 '23 05:01

Hashbrown