我根据用户如何滚动以保持对齐方式,以编程方式更新表标题及其第一列位置。
我遇到的问题是,只要我的数据集足够大,滚动就会越来越不稳定/不太流畅。
相关代码位于小提琴的最底层:
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'});
});
我知道通过缓存元素等可以更好地编写这些内容。例如,我已经尝试将元素保存到数组中并在更多" vanilla"中更新它们的位置。时尚:
headerElements[i].style.left = left + 'px'; // etc...
无论我多快进行回调,我对结果仍然不满意。你有什么建议吗?
答案 0 :(得分:6)
只需使用ClusterizeJS!它可以处理数十万行,并为此目的构建完全。
你问,它是如何运作的?
主要思想不是用所有使用的标签污染DOM。而不是 - 它将列表拆分为集群,然后显示当前滚动位置的元素,并在列表的顶部和底部添加额外的行以模拟表的完整高度,以便浏览器显示滚动条和完整列表
答案 1 :(得分:4)
为了能够处理大量数据,您需要数据虚拟化。但它有一些限制。
首先,您需要确定视口的大小。我们假设你要连续渲染10个项目,列中有20个项目。那将是10x20件。在你用id包装器调整它的div。
然后您需要知道您拥有的总数据量。从你的小提琴,它将是100x100项目。而且,您还需要知道项目(单元格)的高度和宽度。我们取40x120(以px为单位)。
所以 div#wrapper 是一个视口,它应该有固定大小的10x20项。然后,您需要为表格设置正确的宽度和高度。 表的高度将等于列中的数据总量,包括逐项高度。 表格的宽度将是按行项目宽度排列的项目总数。
设置完毕后, div#wrapper 会收到水平和垂直滚动。现在你可以向左和向下滚动,但它只是空的空间。但是,这个空白区域可以容纳您拥有的确切数据量。
然后你需要将滚动数据保留为左上角(位置),以像素为单位并将其标准化为项目数量,这样你就可以知道你滚动了多少像素,但你知道多少项目&# 39;滚动(如果我们从上到下滚动,则为行)。
可以通过在项目高度上滚动的像素分割来完成。例如,您向左滚动了80px,即2个项目。这意味着这些项目应该是不可见的,因为您已经滚过它们。所以你知道你滚动了2个项目,你知道你应该连续看到10个项目。这意味着你获取包含100个项目的行数据的数据数组,并将其切片为:
var visibleItems = rowData.slice(itemsScrolled, itemsScrolled + 10);
它将为您提供在当前滚动位置的视口中应该可见的项目。一旦你有这些项目,你需要构建html并将其附加到表格。
同样在每个滚动事件中,您需要为 tbody和thead 设置顶部和左侧位置,以便它们随着滚动移动,否则您将获得数据,但它将在(0; 0) )在视口内。
无论如何,代码说了千言万语,所以这里是小提琴:https://jsfiddle.net/Ldfjrg81/9/
注意,这种方法要求高度和宽度要精确,否则会无法正常工作。此外,如果您有不同尺寸的物品,也应该考虑这一点,如果您有固定和相同尺寸的物品,那就更好了。在jsfiddle中,我注释掉了强制第一列保持原位的代码,但你可以单独渲染它。
根据评论中的建议,这是一个很好的解决方案,因为它会为你处理很多案例。
如果使用react.js或vue.js
,您可以更快地进行渲染答案 2 :(得分:3)
这不是您正在寻找的答案,但无论如何这里是我的2美分。
Javascript动画(特别是考虑到DOM必须渲染的数量)永远不会像你想要的那样平滑。即使你可以在你的机器上顺利运行,也可能会在其他人(旧PC,浏览器等)上发生剧烈变化。
如果我自己要解决这个问题,我会看到两个选项。
去旧学校并添加水平和垂直滚动条。我知道这不是一个漂亮的解决方案,但它会运作良好。
仅渲染一定数量的行并将其丢弃在屏幕外。这可能有点复杂,但实质上你会渲染10行。一旦用户滚动到第11个应该在那里的点,渲染那个并删除第1个。你可以根据需要弹出它们。
就实际的JS(你提到将元素放入数组)而言,这没有帮助。实际的不稳定性是由于浏览器需要首先渲染那么多元素。
答案 3 :(得分:2)
由于scroll
事件以非常高的速度触发,您会遇到不连贯/不平滑的滚动。
并且每次触发你都会调整多个元素的位置:这是昂贵的,而且直到浏览器完成重绘它之后才会显示出来。 s 无响应(这里不稳定的滚动)。
我看到两个选项:
第一个选项:只显示整个数据集的可见子集(这已在另一个答案中提出,所以我不会再进一步了)
选项二(更简单)
首先,让left
和top
css更改的动画通过过渡发生。这样效率更高,非阻塞,并且经常让浏览器利用 gpu
然后,不要重复调整left
和top
,而是暂时执行;例如0.5秒。这是通过ScrollWorker()
函数setTimeout()
完成的(见下面的代码)。
最后使用scroll事件调用的回调来更新#scroller
位置(存储在变量中)。
// 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 );
}
}
以下是Fiddle
我将工作人员速度和过渡时间设置为 0.5秒。您可以使用更高或更低的时间调整值,最终以基于表中元素数量的动态方式调整。
答案 4 :(得分:1)
是的!以下是对JS Fiddle代码的一些改进。您可以在https://jsfiddle.net/briankueck/u63maywa/
查看我的修改一些建议的改进是:
position:relative
值切换为CSS图层中的position:fixed
。.find()
的元素,这会创建更短的树和& jQuery可以更快地遍历这些分支。let
代替var
切换到ES6语法将使您的代码看起来更现代。<th>
标记中有一个新的左计算,您希望查看该计算。position:fixed
,热门<th>
代码只需要应用top
属性。左<td>
标记只需要应用left
属性。角落<th>
需要同时拥有top
&amp; left
属性适用于它。.css()
和.style.left= -pos.left + 'px';
属性的.style.top= -pos.top + 'px';
方法。尝试使用WinMerge或Beyond Compare之类的差异工具来比较您的版本中的代码与我编辑中的代码,以便您轻松查看差异。
希望这会使滚动变得更加平滑,因为滚动事件不必处理它不需要做的任何事情......比如5个完整的DOM遍历查找,而不是3个短树搜索。
享受! :)
<强> HTML:强>
<body>
<div id="wrapper">
<table id="scroller">
<thead>
</thead>
<tbody>
</tbody>
</table>
</div>
</body>
<强> CSS:强>
/* ... 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;
}
<强> JS:强>
// 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
});
});
答案 5 :(得分:0)
您是否有必要创建自己的卷轴?为什么不在HTML / CSS中设置数据样式,只使用overflow
属性? JavaScript需要能够调整帧速率。我之前使用的是你的jFiddle,它与原生溢出处理程序一起工作得很好。
答案 6 :(得分:0)
在手册中找到了这个。可能不是你想听到的,但它是这样的:
IScroll是需要为每个滚动区域启动的类。如果不是设备CPU /内存强加的,那么每页中的iScroll数量没有限制。 尽量保持DOM尽可能简单。 iScroll使用硬件合成层,但硬件可以处理的元素有限制。
答案 7 :(得分:0)
性能下降的原因是您的滚动事件处理程序一次又一次地触发,而不是等待合理且不易察觉的间隔。
屏幕截图显示了在跟踪事件处理程序触发的次数时发生的情况,同时滚动了几秒钟。计算量大的事件处理程序被解雇超过600次!这是每秒超过60次!!!
这可能看似违反直觉,但降低表格更新的频率将大大增加感知响应时间。如果您的用户滚动了几分之一秒,大约150毫秒,并且表格更新十次,在滚动期间冻结显示,最终结果远比表格仅更新三次并且流畅地移动而不是冻结。只是浪费了处理器刻录来更新浏览器可以处理的次数而不会冻结。
那么,你如何制作一个以最高频率发射的事件处理程序,例如每秒25次,即使它被更频繁地触发,比如每秒100次?
这种天真的方式是运行setInterval事件。这样更好,但效率也非常低。有一种更好的方法,通过设置延迟事件处理程序,并在再次设置之前将其清除,直到最小时间间隔已经过去。这样,它的运行频率不会超过最大所需频率。这是为什么发明``clearInterval''方法的一个主要案例。
这是实时工作代码:
https://jsfiddle.net/pgjvf7pb/7/
注意:像这样连续刷新时,标题列可能显示在位置之外。
我建议仅在滚动暂停约25ms左右时进行更新,而不是连续进行。这样,用户看起来标题列是动态计算的,也是固定到位的,因为它在滚动后立即显示,而不是似乎与数据一起滚动。
https://jsfiddle.net/5vcqv7nq/2/
逻辑是这样的:
事件处理程序之外的变量
// 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);
演示:https://jsfiddle.net/pgjvf7pb/7/
我再次建议您删除立即刷新数据的代码行,然后删除其他条件,并简单地使用一行
fresh = setTimeout(x, 25);
这将在任何滚动完成时立即计算标题列,并节省更多操作。我对JS Fiddle的第二个链接显示了这个样子,在这里:https://jsfiddle.net/5vcqv7nq/2/