Javascript 提高大型工作台上的iScroll性能
我正在根据用户的滚动方式以编程方式更新表格标题及其第一列位置,以保持它们对齐 我遇到的问题是,一旦我的数据集变得足够大,滚动就会变得越来越不平稳 相关代码位于最底层:Javascript 提高大型工作台上的iScroll性能,javascript,jquery,iscroll,Javascript,Jquery,Iscroll,我正在根据用户的滚动方式以编程方式更新表格标题及其第一列位置,以保持它们对齐 我遇到的问题是,一旦我的数据集变得足够大,滚动就会变得越来越不平稳 相关代码位于最底层: iScroll.on('scroll', function(){ var pos = $('#scroller').position(); $('#pos').text('pos.left=' + pos.left + ' pos.top=' + pos.top); // code to hold fir
iScroll.on('scroll', function(){
var pos = $('#scroller').position();
$('#pos').text('pos.left=' + pos.left + ' pos.top=' + pos.top);
// code to hold first row and first column
$('#scroller th:nth-child(1)').css({top: (-pos.top), left: (-pos.left), position:'relative'});
$('#scroller th:nth-child(n+1)').css({top: (-pos.top), position:'relative'});
// this seems to be the most expensive operation:
$('#scroller td:nth-child(1)').css({left: (-pos.left), position:'relative'});
});
我知道,通过缓存元素等方式,可以更高效地编写这篇文章。例如,我尝试将元素保存到数组中,并以更“普通”的方式更新它们的位置:
不管我回电话的速度有多快,我还是对结果不满意。你有什么建议吗
这不是你想要的答案,但这是我的2美分 Javascript动画(特别是考虑到DOM必须渲染的数量)永远不会像您希望的那样平滑。即使你能在你的机器上顺利运行,在其他人(旧电脑、浏览器等)上也会有很大的不同 如果我自己解决这个问题,我会看到两种选择
对于实际的JS(您提到将元素放入数组),这并没有帮助。实际的起伏是由于浏览器首先需要呈现那么多的元素。要能够处理大量数据,您需要数据虚拟化。不过,它有一些限制 首先,需要确定视图端口的大小。假设您希望在一行中呈现10项,在一列中呈现20项。那么它将是10x20项。在你的小提琴它的id包装的div 然后你需要知道你拥有的数据总量。从你的小提琴它将是100x100项目。此外,您还需要知道项目(单元格)的高度和宽度。让我们以40x120(像素)为例 因此,div#wrapper是一个视图端口,它应该具有固定大小,如10x20项。然后您需要为表格设置正确的宽度和高度。表格的高度等于列中的数据总量,包括按项目高度划分的标题。表格的宽度是按项目宽度计算的单行项目总数 一旦设置了这些选项,div#wrapper将收到水平和垂直滚动条。现在您可以向左和向下滚动,但它只是一个空白。但是,这个空白空间能够容纳您所拥有的确切数量的数据 然后,您需要将滚动数据向左和向上(位置),以像素为单位,并将其规格化为项目数量,这样您就可以不知道滚动了多少像素,而是知道滚动了多少项目(如果从上到下滚动,则为行) 它可以通过划分项目高度上滚动的像素来完成。例如,您向左滚动了80px,即2项。这意味着这些项目应该是不可见的,因为你已经滚动过去了。所以您知道您滚动了2个项目,并且您知道您应该在一行中看到10个项目。这意味着您需要使用数据数组,该数组包含包含100项的行的数据,并按如下方式对其进行切片:
var visibleItems = rowData.slice(itemsScrolled, itemsScrolled + 10);
它将为您提供在当前滚动位置应在视口中可见的项目。一旦有了这些项,就需要构造html并将其附加到表中
此外,在每个滚动事件中,您需要为t正文和thead设置顶部和左侧位置,以便它们随滚动一起移动,否则您将拥有数据,但数据将位于视口内的(0;0)
不管怎么说,代码能说千言万语,所以这里有一把小提琴:
注意,此方法要求高度和宽度精确,否则将无法正常工作。此外,如果您有不同尺寸的物品,也应考虑到这一点,因此,如果您有固定和相等尺寸的物品,则效果更佳。在JSFIDLE中,我注释掉了强制第一列保持不变的代码,但您可以单独呈现它
坚持使用评论中建议的库是一个很好的解决方案,因为它可以为您处理很多案例
如果使用react.js或vue.js,可以使渲染速度更快,只需使用!它可以处理数十万行,并且正是为此目的而构建的
你问,它是如何工作的
主要思想是不要用所有使用过的标记污染DOM。相反,它将列表拆分为多个簇,然后显示当前滚动位置的元素,并在列表的顶部和底部添加额外的行,以模拟表格的完整高度,这样浏览器就可以像显示完整列表一样显示滚动条
对!!下面是对jsfiddle代码的一些改进。您可以在以下位置查看我的编辑: 建议的改善措施包括:
position:relative
值切换到CSS层中的position:fixed
.find()
,这会创建更短的树&jQuery可以更快地遍历这些分支var visibleItems = rowData.slice(itemsScrolled, itemsScrolled + 10);
<body>
<div id="wrapper">
<table id="scroller">
<thead>
</thead>
<tbody>
</tbody>
</table>
</div>
</body>
/* ... only the relevant bits ... */
thead th {
background-color: #99a;
min-width: 120px;
height: 32px;
border: 1px solid #222;
position: fixed; /* New */
z-index: 9;
}
thead th:nth-child(1) {/*first cell in the header*/
border-left: 1px solid #222; /* New: Border fix */
border-right: 2px solid #222; /* New: Border fix */
position: fixed; /* New */
display: block; /*seperates the first cell in the header from the header*/
background-color: #88b;
z-index: 10;
}
// main code
let debug = false;
$(function(){
let scroller = $('#scroller');
let top = $('<tr/>');
for (var i = 0; i < 100; i++) {
let left = (i === 0) ? 0 : 1;
top.append('<th style="left:' + ((123*i)+left) + 'px;">'+ Math.random().toString(36).substring(7) +'</th>');
}
scroller.find('thead').append(top);
for (let i = 0; i < 100; i++) {
let row = $('<tr/>');
for (let j = 0; j < 100; j++) {
row.append('<td>'+ Math.random().toString(36).substring(7) +'</td>');
}
scroller.find('tbody').append(row);
}
if (debug) console.log('initialize iscroll');
let iScroll = null;
try {
iScroll = new IScroll('#wrapper', {
interactiveScrollbars: true,
scrollbars: true,
scrollX: true,
probeType: 3,
useTransition:false,
bounce:false
});
} catch(e) {
if (debug) console.error(e.name + ":" + e.message + "\n" + e.stack);
}
if (debug) console.log('initialized');
iScroll.on('scroll', function(){
let pos = scroller.position();
if (debug) console.log('pos.left=' + pos.left + ' pos.top=' + pos.top);
// code to hold first row and first column
scroller.find('th').css({top:-pos.top}); // Top Row
scroller.find('th:nth-child(1)').css({left:-pos.left}); // Corner
scroller.find('td:nth-child(1)').css({left:-pos.left}); // 1st Left Column
});
});
// Position of the `#scroller` element
// (I used two globals that may pollute the global namespace
// that piece of code is just for explanation purpose)
var oldPosition,
newPosition;
// Use transition to perform animations
// You may set this in the stylesheet
$('th').css( { 'transition': 'left 0.5s, top 0.5s' } );
$('td').css( { 'transition': 'left 0.5s, top 0.5s' } );
// Save the initial position
newPosition = $('#scroller').position();
oldPosition = $('#scroller').position();
// Upon scroll just set the position value
iScroll.on('scroll', function() {
newPosition = $('#scroller').position();
} );
// Start the scroll worker
ScrollWorker();
function ScrollWorker() {
// Adjust the layout if position changed (your original code)
if( newPosition.left != oldPosition.left || newPosition.top != oldPosition.top ) {
$('#scroller th:nth-child(1)').css({top: (-newPosition.top), left: (-newPosition.left), position:'relative'});
$('#scroller th:nth-child(n+1)').css({top: (-newPosition.top), position:'relative'});
$('#scroller td:nth-child(1)').css({left: (-newPosition.left), position:'relative'});
// Update the stored position
oldPosition.left = newPosition.left;
oldPosition.top = newPosition.top;
// Let animation complete then check again
// You may adjust the timer value
// The timer value must be higher or equal the transition time
setTimeout( ScrollWorker, 500 );
} else {
// No changes
// Check again after just 0.1secs
setTimeout( ScrollWorker, 100 );
}
}
// stores the scrolling operation for a tiny delay to prevent redundancy
var fresh;
// stores time of last scrolling refresh
var lastfresh = new Date();
// clears redundant scrolling operations before they are applied
if (fresh) clearTimeout(fresh);
var x = function() {
// stores new time of scrolling refresh
lastfresh = new Date();
// perform scrolling update operations here...
};
// refresh instantly if it is more than 50ms out of date
if (new Date() - lastfresh > 50) x();
// otherwise, pause for half of that time to avoid wasted runs
else fresh = setTimeout(x, 25);
fresh = setTimeout(x, 25);