技術不侷限於框架,相同的原理只是實現方式略有不一樣。
首先,虛擬列表只是一個概念,本人對虛擬列表這個表述不置能否。
虛擬列表是對於列表形態數據展現的一種按需渲染,是對長列表渲染的一種優化。html
虛擬列表不會一次性完整地渲染長列表,而是按需顯示的一種方案,以提升無限滾動的性能。webpack
根據容器元素的高度 clientHeight
以及列表項元素的高度 offsetHeight
來顯示長列表數據中的某一個部分,而不是去完整地渲染整個長列表。web
實現一個虛擬列表須要:編程
clientHeight
offsetHeight
count = clientHeight / offsetHeight
start
end
sliceList = dataList.slice(start, end)
懶加載與虛擬列表其實都是延時加載的一種實現,原理相同但場景略有不一樣。segmentfault
IntersectionObserver 提供了一種異步觀察目標元素與視口的交叉狀態,簡單地說就是能監聽到某個元素是否會被咱們看到,當咱們看到這個元素時,能夠執行一些回調函數來處理某些事務。瀏覽器
let io = new IntersectionObserver(callback, option);
callback
會觸發兩次。一次是目標元素剛剛進入視口(開始可見),另外一次是徹底離開視口(開始不可見)。緩存
更多介紹 Intersection Observer性能優化
1. 生成十萬條數據:網絡
function getDataList() { let data = [] for(let i = 0; i < 100000; i++) { data.push({id: "item" + i, value: Math.random() * i}) } return data; }
2. Dom 建立及列表渲染:app
不依賴框架的狀況下,須要命令性的去建立 DOM 以及操做 DOM
。
<ul class="container"> <span class="sentinels">....</span> </ul>
function $(selector) { return document.querySelector(selector) } function loadData(start, end) { // 截取數據 let sliceData = getDataList().slice(start, end) // 現代瀏覽器下,createDocumentFragment 和 createElement 的區別其實沒有那麼大 let fragment = document.createDocumentFragment(); for(let i = 0; i < sliceData.length; i++) { let li = document.createElement('li'); li.innerText = JSON.stringify(sliceData[i]) fragment.appendChild(li); } $('.container').insertBefore(fragment, $('.sentinels')); }
若是是基於 Virtual DOM
的框架,直接操做數據便可(僞代碼):
// 父組件 <virtual-list :listData="listData"></virtual-list> // 子組件 <ul class='container'> <li v-for="item in sliceData" :key="item.id" >{{ item }}</li> </ul> ... // js this.sliceData = this.data.slice(start, index)
3. 使用 IntersectionObserver API
建立監聽器:
let count = Math.ceil(document.body.clientHeight / 120); let startIndex = 0; let endIndex = 0; ... let io = new IntersectionObserver(function(entries) { loadData(startIndex, count) // 標誌位元素進入視口 if(entries[0].isIntersecting) { // 更新列表數據起始和結束位置 startIndex = startIndex += count; endIndex = startIndex + count; if(endIndex >= getDataList().length) { // 數據加載完取消觀察 io.unobserve(entries[0].target) } // requestAnimationFrame 由系統決定回調函數的執行時機 requestAnimationFrame(() => { loadData(startIndex, endIndex) let num = Number(getDataList().length - startIndex) let info = ['還有', num , '條數據'] $('.top').innerText = info.join(' ') if(num - count <= 0) { $('.top').classList.add('out') } }) } }); // 開始觀察「標誌位」元素 io.observe($('.sentinels')); })
因爲 IntersectionObserver
沒法監聽動態建立的 dom
,因此咱們設置一個「標誌位」元素 span.sentinels
做爲監聽的目標對象。
<ul class="container"> <span class="sentinels">....</span> </ul>
若是目標元素正處於交叉狀態 entries[0].isIntersecting == true
,則表明 .sentinels
進入了可視區域,從而加載新的列表數據。
if(entries[0].isIntersecting) { ... requestAnimationFrame(() => { loadData(startIndex, endIndex) }) ... }
最後將新的列表 insertBefore
到其前面,進而實現無限加載。
$('.container').insertBefore(fragment, $('.sentinels'));
同系列文章: