2017-04-19 127 views
16

我正在以編程方式更新表頭和第一列位置,這是基於用戶如何滾動以保持對齊方式。在大型表上提高iScroll性能

我遇到的問題是,只要我的數據集足夠大,滾動會越來越多/不太平滑。

相關的代碼是在小提琴的最底部:

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'}); 
}); 

我知道,這可以通過緩存等元素被寫了很多比較有效。例如,我曾嘗試保存的元素添加到數組和更新更多的「香草」的時尚自己的立場:

headerElements[i].style.left = left + 'px'; // etc... 

不管我怎麼做快速回調,我仍然對結果不滿意。你有什麼建議嗎?

https://jsfiddle.net/0qv1kjac/16/

+0

是否100%必須使用工具創建滾動條? – ihodonald

+0

您可以創建一個包含html塊的數組。根據當前滾動位置加載數組的每個索引,假設pos爲前一個位置的正值。這個應該做的是加載大量的數據(比如說100行),並且在到達下一個「檢查點」之前不會加載數據。心連心! – Programmer

回答

3

這會不會是你正在尋找的答案,但這裏是我的2分錢呢。

Javascript動畫(尤其是給定DOM渲染量)永遠不會像您想要的那樣流暢。即使你可以在你的機器上順利運行,它可能會在其他人(舊PC,瀏覽器等)上發生巨大變化。

如果我自己解決這個問題,我會看到2個選項。

  1. 去舊學校,並添加一個水平和垂直滾動條。我知道這不是一個漂亮的解決方案,但它會工作。

  2. 只渲染一定數量的行並放棄屏幕外的行。這可能有點複雜,但實際上你會渲染10行。一旦用戶滾動到第11位應該在那裏的位置,渲染該位置並移除第1位。你會根據需要彈出它們。

就實際的JS而言(你提到要將元素放入數組中),這不會有幫助。實際的不穩定性是由於瀏覽器需要首先呈現多個元素。

+1

爲了得到一個切實可行的方法:關於第二個選項,可能需要查看一下Clusterize.js(https://clusterize.js.org/),它只爲您提供部分列表。 – Loilo

+0

「就實際的JS而言(您提到要將元素放入數組中),這不會有幫助。」:這裏的要點是,DOM API比通過jQuery更快。我應該提到在最新的Chrome中加速這一點對我來說已經足夠了。即使我用「1」去,我仍然必須像現在這樣重新定位標題和第一列。 2是一個可行的選擇,但我仍然感興趣,如果上面的代碼可以優化,這樣就沒有必要,因此賞金:) – Johan

+0

更改JS會(可能)具有最小的影響,因爲它似乎更多的圖形渲染問題。我會看看提到的集羣方法,它似乎更合乎邏輯 –

4

爲了能夠處理大量的數據,您需要數據虛擬化。不過,它有一些限制。

首先,您需要確定視口的大小。假設您想要呈現一行中的10個項目和列中的20個項目。那將是10x20項目。在你搗鼓它與ID包裝的div。

然後你需要知道你的數據總量。從你的小提琴它將是100x100項目。而且,您還需要知道物品(單元格)的高度和寬度。我們拿40x120(px)。

所以div#wrapper是一個視圖端口,它應該有固定大小像10x20項目。然後,您需要爲設置正確的寬度和高度。表格的高度將等於包括逐項高度在內的列中的數據總量。表格的寬度將是單個行項目寬度中的項目總數。

一旦你設置了這些,div#wrapper將會收到水平和垂直的卷軸。現在你可以向左和向下滾動,但它只是空的空間。但是,這個空白空間能夠保存您擁有的精確數據量。

然後,您需要將滾動數據左側和頂部(位置)以像素爲單位並將其歸一化爲項目數量,以便您不必知道已滾動的像素數量,但是您已經確定了多少項目滾動(如果我們從上到下滾動,則爲行)。

它可以通過在項目高度上滾動的像素的劃分來完成。例如,您向左滾動了80px,這是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

6

只需使用ClusterizeJS!它可以處理數十萬行,併爲此建立了正好是

它是如何工作的,你問?

其主要思想是不污染所有使用過的標籤的DOM。取而代之的是 - 它將該列表集羣,則顯示當前滾動位置的元素,並增加了額外的行頂部和列表的底部效仿表的全高度,使瀏覽器顯示滾動條爲完整列表

1

是!以下是對JS小提琴代碼的一些改進。https://jsfiddle.net/briankueck/u63maywa/

一些改進建議是:你可以查看我的編輯

  1. 在CSS層交換的JS層position:relativeposition:fixed
  2. 縮短jQuery DOM鏈,以便代碼不會從根元素&開始,每次通過$ lookup遍歷dom。滾輪現在是根元素。一切都使用該元素的.find(),這創建了較短的樹& jQuery可以更快地遍歷這些分支。
  3. 將日誌代碼從DOM &移出到console.log中。我添加了一個調試開關來禁用它,因爲您正在尋找桌面上最快的滾動。如果它足夠快地爲您運行,那麼您可以隨時重新啓用它以在JSFiddle中查看它。如果你真的需要在iPhone上看到它,那麼它可以被添加到DOM中。雖然,可能沒有必要在iPhone中查看左邊的&頂部位置值。
  4. 刪除所有無關的$值,它們沒有映射到jQuery對象。像$ scroller之類的東西會讓人困惑,因爲後者是jQuery庫,但前者不是。
  5. 切換到ES6語法,使用let而不是var將使您的代碼看起來更現代。
  6. <th>標籤中有一個新的左側計算,您需要查看該標籤。
  7. iScroll事件偵聽器已被清除。通過position:fixed,頂部<th>標籤只需要將top屬性應用於它們。左邊的<td>標籤只需要將left屬性應用於它們。角落<th>需要同時對其應用top & left屬性。
  8. 刪除所有不必要的東西,如用於記錄目的的無關HTML標記。
  9. 如果你真的想去更多的香草,改變了.css()方法的實際.style.left= -pos.left + 'px';.style.top= -pos.top + 'px';財產的JS代碼。

嘗試使用像WinMergeBeyond Compare一個diff工具來比較你的版本有什麼在我的編輯代碼,這樣就可以很容易地看到差異。

希望這會使滾動更順暢,因爲滾動事件不必處理任何它不需要做的事情......就像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 
    }); 
}); 
+0

感謝您的建議。一些很好的建議,雖然我沒有注意到任何性能改進。 – Johan

+0

你也可以儘量遠離jQuery的代碼和使用原生JS調用。例如:'$('#scroller');'會變成'document.getElementById('scroller');'爲了提高滾動的平滑度,您需要將所有的scroller.find()方法重寫爲本機JS 。您還可以使用JS中的.style.left和.style.top屬性來避免使用jQuery的'.css()'方法。這聽起來像只轉換'iScroll.on('scroll',function(){'event listener會給你最佳性能提升的5個非調試行。 – Clomp

+0

是的,這就是我已經嘗試過的。 :)我正在尋找一些其他方法來改善它,但我想更多的分頁方法的目標是更好的... – Johan

0

是不是必要的,你創建自己的scrol LER?你爲什麼不用HTML/CSS來設計數據,只使用overflow屬性? JavaScript需要它能夠調整幀率。我早些時候使用過你的jFiddle,它在本地溢出處理程序中工作得很好。

0

在手冊中找到了。可能不是你想聽到的,但它是這樣的:

IScroll是一個需要爲每個滾動區域啓動的類。如果不是由設備CPU /內存強加的,每頁中iScrolls的數量是沒有限制的。 儘量保持DOM儘可能簡單。 iScroll使用硬件合成層,但硬件可以處理的元素有限制。

2

由於scroll事件觸發非常高的速度,您正在經歷波濤洶涌的/不平滑的滾動。

而且每次觸發要調整的許多元素位置時:這是昂貴的,而且直到瀏覽器已經完成重繪它的反應遲鈍(這裏波濤洶涌的滾動)。

我看到兩個選項:

選項一:只顯示整個數據集的子集可見(這已經在另一個答案已經被提出,所以我不會去futher)


上選項二號(容易)

首先,讓動畫10和top通過轉換髮生的css更改發生。這是更有效的,是非阻塞的,往往讓瀏覽器利用GPU的

然後代替repeteadly調整lefttop,做一次就可以了一會兒;例如0.5秒。這由功能ScrollWorker()(參見下面的代碼)完成,該功能通過setTimeout()回憶自己。

最後使用滾動事件調用的回調來更新#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秒。您可以用更高或更低的時間調整數值,最終以基於表格中元素數量的方式進行調整。

0

性能下降發生的原因是您的滾動事件處理程序一次又一次地觸發,而不是等待合理的和不可察覺的時間間隔。

invocations of scroll event handler

的屏幕截圖顯示發生了什麼事時,我跟蹤事件處理程序有多少次發射,同時滾動只是幾秒鐘。計算量大的事件處理程序被解僱了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小提琴的第二個鏈接顯示了這個樣子,在這裏:https://jsfiddle.net/5vcqv7nq/2/