网页加载超大数据量列表初探

发现不足

前端发展了这么多年,已经有了很多优秀的框架、库,很多库都开发了代替原生select标签的组件。这些组件在小数据量时简直不要太好用,然而有时我们将大量数据全部一次性加载到页面上,网页卡死了,除了懒加载,别无他法。

寻找灵感

Android应用里的列表浏览功能跟这个需要几乎一样,我们可以向下滑动浏览上万,上百万条数据。Android是怎么实现的呢?原来Android原生有一个ListAdapter用来重用item。

思路

网页加载超大数据量列表初探
用头部和尾部的两个div代替被容器挡住的item,监听容器滚动条的位置变化,动态设置两个div的高度,同时items中的内容从js数据取对应位置的内容填充。

代码

CSS
.combo,
.combo * {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
.combo {
    position: relative;
    width: 200px;
    height: 260px;
    border: 1px solid #666666;
    background: #FFFFFF;
}
.combo .combo-container {
    height: 100%;
    position: relative;
    overflow: auto;
}
.combo ul {
    margin: 0;
    padding: 0;
}
.combo li {
    height: 32px;
    list-style: none;
    margin: 0;
    padding: 2px;
}
.combo li span {
    height: 100%;
    font-family: sans-serif;
    white-space: nowrap;
    display: inline-block;
    font-size: 16px;
    line-height: 30px;
    cursor: pointer;
}
.combo li span.even {
    background-color: #DDDDDD;
}
HTML
<div id="combo" class="combo">
    <div id="combo-container" class="combo-container">
        <div id="combo-before1"></div>
        <div id="combo-before2"></div>
        <div id="combo-before3"></div>
        <div id="combo-before4"></div>
        <ul>
        </ul>
        <div id="combo-after1"></div>
        <div id="combo-after2"></div>
        <div id="combo-after3"></div>
        <div id="combo-after4"></div>
    </div>
</div>
Javascript
window.onload = function () {
    var perLiHeight = 32;
    var arr = [];
    for (var i = 0; i < 190000; i++) {
        arr.push(i + 'abc defg hijkl mno pq rst '+ (i % 80 > 77 ? '54345 6547 67654' : ''));
    }
    var comboContainer = document.getElementById('combo-container');
    var comboBefore1 = document.getElementById('combo-before1');
    var comboBefore2 = document.getElementById('combo-before2');
    var comboBefore3 = document.getElementById('combo-before3');
    var comboBefore4 = document.getElementById('combo-before4');
    var comboAfter1 = document.getElementById('combo-after1');
    var comboAfter2 = document.getElementById('combo-after2');
    var comboAfter3 = document.getElementById('combo-after3');
    var comboAfter4 = document.getElementById('combo-after4');
    var ul = comboContainer.getElementsByTagName('ul')[0];
    var comboHeight = comboContainer.clientHeight;
    var liMaxCount = Math.max(Math.floor(comboHeight / perLiHeight) + 2, 80);
    var lisHeight = liMaxCount * perLiHeight;
    var realHeight = arr.length * perLiHeight;
    var realCount = Math.min(arr.length, liMaxCount);
    var li = document.createElement('li');
    var span = document.createElement('span');
    for (var i = 0; i < realCount; i++) {
        var node = li.cloneNode();
        var sp = span.cloneNode();
        sp.innerText = arr[i];
        node.appendChild(sp);
        if (i % 2 === 0) {
            sp.className = 'even';
        }
        ul.appendChild(node);
    }
    ul.onclick = function (e) {
        var target = e ? e.target : window.event.srcElement;
        alert(target.innerText);
    };
    comboBefore1.style.height = 0 + 'px';
    comboBefore2.style.height = 0 + 'px';
    comboBefore3.style.height = 0 + 'px';
    comboBefore4.style.height = 0 + 'px';
    comboAfter1.style.height = Math.max(Math.floor((realHeight - lisHeight) / 4), 0) + 'px';
    comboAfter2.style.height = Math.max(Math.floor((realHeight - lisHeight) / 4), 0) + 'px';
    comboAfter3.style.height = Math.max(Math.floor((realHeight - lisHeight) / 4), 0) + 'px';
    comboAfter4.style.height = Math.max(Math.floor((realHeight - lisHeight) / 4), 0) + 'px';
    var lis = ul.getElementsByTagName('li');
    comboContainer.onscroll = function () {
        var scrollTop = comboContainer.scrollTop;
        var index = Math.floor(scrollTop / perLiHeight);
        var top = index * perLiHeight;
        var comboBeforeFloorHeight = Math.max(Math.min(Math.floor(top / 4), Math.floor((realHeight - lisHeight) / 4)), 0);
        var comboBeforeCeilHeight = Math.max(Math.min(Math.ceil(top / 4), Math.ceil((realHeight - lisHeight) / 4)), 0);
        var comboAfterFloorHeight = Math.max(Math.floor((realHeight - lisHeight - top) / 4), 0);
        var comboAfterCeilHeight = Math.max(Math.ceil((realHeight - lisHeight - top) / 4), 0);
        comboBefore1.style.height = comboBeforeFloorHeight + 'px';
        comboBefore2.style.height = comboBeforeFloorHeight + 'px';
        comboBefore3.style.height = comboBeforeCeilHeight + 'px';
        comboBefore4.style.height = comboBeforeCeilHeight + 'px';
        comboAfter1.style.height = comboAfterFloorHeight + 'px';
        comboAfter2.style.height = comboAfterFloorHeight + 'px';
        comboAfter3.style.height = comboAfterCeilHeight + 'px';
        comboAfter4.style.height = comboAfterCeilHeight + 'px';
        comboContainer.scrollTop = scrollTop;
        var sp = null, i = 0;
        if (realCount + index < arr.length) {
            for (i = 0; i < realCount; i++) {
                sp = lis[i].getElementsByTagName('span')[0];
                sp.innerText = arr[i + index];
                if ((i + index) % 2 === 0) {
                    sp.className = 'even';
                } else {
                    sp.className = '';
                }
            }
        } else {
            for (i = 0; i < realCount; i++) {
                sp = lis[i].getElementsByTagName('span')[0];
                sp.innerText = arr[i + arr.length - realCount];
                if ((i + arr.length - realCount) % 2 === 0) {
                    sp.className = 'even';
                } else {
                    sp.className = '';
                }
            }
        }
    }
}

说明

为什么要用4个头部,4个尾部, 而不是用一个呢?因为浏览器对元素的高度,尤其动态设置高度是有限制的。微软系浏览器性能就呵呵了。item高度越大,浏览器能正常显示的数据就越少。如果只是一个头部一个尾部,且item高度为32px时,微软系浏览器只能正常显示50000条数据。面4个头部4个尾部,微软系浏览器能正常显示190000条数据。
为什么不用8个头部尾部或16个头部尾部呢?很不幸,经测试头部尾部大于4个时,浏览器能正常显示的item并没有增加。不知道以后会不会找到更好的办法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值