本篇內(nèi)容介紹了“Python簡(jiǎn)潔且有趣的無(wú)限下拉的實(shí)現(xiàn)方法是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
不知你是否從上面這張圖中注意到了什么,比如只是渲染了可視區(qū)域的部分 DOM ,滾動(dòng)過(guò)程中只是外層容器的 padding 在改變?
前一點(diǎn)很好理解,我們考慮到性能,不可能將一個(gè)長(zhǎng)列表(甚至是一個(gè)無(wú)限下拉列表)的所有列表元素都進(jìn)行渲染;而后一點(diǎn),則是本文所介紹方案的核心之一!
不賣(mài)關(guān)子,提前告訴你該方案的要素就是兩個(gè):
Intersection Observer
padding
說(shuō)明了要素,也許你可以嘗試著開(kāi)始思考,看你是否能猜到具體的實(shí)現(xiàn)方案。
Intersection Observer
基本概念
一直以來(lái),檢測(cè)元素的可視狀態(tài)或者兩個(gè)元素的相對(duì)可視狀態(tài)都不是件容易事。傳統(tǒng)的各種方案不但復(fù)雜,而且性能成本很高,比如需要監(jiān)聽(tīng)滾動(dòng)事件,然后查詢(xún) DOM , 獲取元素高度、位置,計(jì)算距離視窗高度等等。
這就是 Intersection Observer 要解決的問(wèn)題。它為開(kāi)發(fā)人員提供一種便捷的新方法來(lái)異步查詢(xún)?cè)叵鄬?duì)于其他元素或視窗的位置,消除了昂貴的 DOM 查詢(xún)和樣式讀取成本。
主要在 Safari 上兼容性較差,需要 12.2 及以上才兼容,不過(guò)還好,有 polyfill 可食用。
頁(yè)面滾動(dòng)時(shí)的懶加載實(shí)現(xiàn)。
無(wú)限下拉(本文的實(shí)現(xiàn))。
監(jiān)測(cè)某些廣告元素的曝光情況來(lái)做相關(guān)數(shù)據(jù)統(tǒng)計(jì)。
監(jiān)測(cè)用戶(hù)的滾動(dòng)行為是否到達(dá)了目標(biāo)位置來(lái)實(shí)現(xiàn)一些交互邏輯(比如視頻元素滾動(dòng)到隱藏位置時(shí)暫停播放)。
基本了解 Intersection Observer 之后,接下來(lái)就看下如何用 Intersection Observer + padding 來(lái)實(shí)現(xiàn)無(wú)限下拉。
先概覽下總體思路:
監(jiān)聽(tīng)一個(gè)固定長(zhǎng)度列表的首尾元素是否進(jìn)入視窗;
更新當(dāng)前頁(yè)面內(nèi)渲染的第一個(gè)元素對(duì)應(yīng)的序號(hào);
根據(jù)上述序號(hào),獲取目標(biāo)數(shù)據(jù)元素,列表內(nèi)容重新渲染成對(duì)應(yīng)內(nèi)容;
容器 padding 調(diào)整,模擬滾動(dòng)實(shí)現(xiàn)。
核心:利用父元素的 padding 去填充隨著無(wú)限下拉而本該有的、越來(lái)越多的 DOM 元素,僅僅保留視窗區(qū)域上下一定數(shù)量的 DOM 元素來(lái)進(jìn)行數(shù)據(jù)渲染。
// 觀察者創(chuàng)建
this.observer = new IntersectionObserver(callback, options);
// 觀察列表第一個(gè)以及最后一個(gè)元素
this.observer.observe(this.firstItem);
this.observer.observe(this.lastItem);
我們以在頁(yè)面中渲染固定的 20 個(gè)列表元素為例,我們對(duì)第一個(gè)元素和最后一個(gè)元素,用 Intersection Observer 進(jìn)行觀察,當(dāng)他們其中一個(gè)重新進(jìn)入視窗時(shí),callback 函數(shù)就會(huì)觸發(fā):
const callback = (entries) => {
entries.forEach((entry) => {
if (entry.target.id === firstItemId) {
// 當(dāng)?shù)谝粋€(gè)元素進(jìn)入視窗
} else if (entry.target.id === lastItemId) {
// 當(dāng)最后一個(gè)元素進(jìn)入視窗
}
});
};
拿具體例子來(lái)說(shuō)明,我們用一個(gè)數(shù)組來(lái)維護(hù)需要渲染到頁(yè)面中的數(shù)據(jù)。數(shù)組的長(zhǎng)度會(huì)隨著不斷請(qǐng)求新的數(shù)據(jù)而不斷變大,而渲染的始終是其中一定數(shù)量的元素,比如 20 個(gè)。那么:
1、最開(kāi)始渲染的是數(shù)組中序號(hào)為 0 - 19 的元素,即此時(shí)對(duì)應(yīng)的 firstIndex 為 0;
2、當(dāng)序號(hào)為 19 的元素(即上一步的 lastItem )進(jìn)入視窗時(shí),我們就會(huì)往后渲染 10 個(gè)元素,即渲染序號(hào)為 10 - 29 的元素,那么此時(shí)的 firstIndex 為 10;
3、下一次就是,當(dāng)序號(hào)為 29 的元素進(jìn)入視窗時(shí),繼續(xù)往后渲染 10個(gè)元素,即渲染序號(hào)為 20 - 39 的元素,那么此時(shí)的 firstIndex 為 20,以此類(lèi)推。。。
// 我們對(duì)原先的 firstIndex 做了緩存
const { currentIndex } = this.domDataCache;
// 以全部容器內(nèi)所有元素的一半作為每一次渲染的增量
const increment = Math.floor(this.listSize / 2);
let firstIndex;
if (isScrollDown) {
// 向下滾動(dòng)時(shí)序號(hào)增加
firstIndex = currentIndex + increment;
} else {
// 向上滾動(dòng)時(shí)序號(hào)減少
firstIndex = currentIndex - increment;
}
總體來(lái)說(shuō),更新 firstIndex,是為了根據(jù)頁(yè)面的滾動(dòng)情況,知道接下來(lái)哪些數(shù)據(jù)應(yīng)該被獲取、渲染。
const renderFunction = (firstIndex) => {
// offset = firstIndex, limit = 10 => getData
// getData Done => new dataItems => render DOM
};
這一部分就是根據(jù) firstIndex 查詢(xún)數(shù)據(jù),然后將目標(biāo)數(shù)據(jù)渲染到頁(yè)面上即可。
既然數(shù)據(jù)的更新以及 DOM 元素的更新我們已經(jīng)實(shí)現(xiàn)了,那么無(wú)限下拉的效果以及滾動(dòng)的體驗(yàn),我們要如何實(shí)現(xiàn)呢?
想象一下,拋開(kāi)一切,最原始最直接最粗暴的方式無(wú)非就是我們?cè)儆肢@取了 10 個(gè)新的數(shù)據(jù)元素之后,再塞 10 個(gè)新的 DOM 元素到頁(yè)面中去來(lái)渲染這些數(shù)據(jù)。
但此時(shí),對(duì)比上面這個(gè)粗暴的方案,我們的方案是:這 10個(gè)新的數(shù)據(jù)元素,我們用原來(lái)已有的 DOM 元素去渲染,替換掉已經(jīng)離開(kāi)視窗、不可見(jiàn)的數(shù)據(jù)元素;而本該有更多 DOM 元素進(jìn)一步撐開(kāi)容器高度的部分,我們用 padding 填充來(lái)模擬實(shí)現(xiàn)。
向下滾動(dòng)
// padding的增量 = 每一個(gè)item的高度 x 新的數(shù)據(jù)項(xiàng)的數(shù)目
const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));
if (isScrollDown) {
// paddingTop新增,填充頂部位置
newCurrentPaddingTop = currentPaddingTop + remPaddingsVal;
if (currentPaddingBottom === 0) {
newCurrentPaddingBottom = 0;
} else {
// 如果原來(lái)有paddingBottom則減去,會(huì)有滾動(dòng)到底部的元素進(jìn)行替代
newCurrentPaddingBottom = currentPaddingBottom - remPaddingsVal;
}
}
向上滾動(dòng)
// padding的增量 = 每一個(gè)item的高度 x 新的數(shù)據(jù)項(xiàng)的數(shù)目
const remPaddingsVal = itemHeight * (Math.floor(this.listSize / 2));
if (!isScrollDown) {
// paddingBottom新增,填充底部位置
newCurrentPaddingBottom = currentPaddingBottom + remPaddingsVal;
if (currentPaddingTop === 0) {
newCurrentPaddingTop = 0;
} else {
// 如果原來(lái)有paddingTop則減去,會(huì)有滾動(dòng)到頂部的元素進(jìn)行替代
newCurrentPaddingTop = currentPaddingTop - remPaddingsVal;
}
}
最后是 padding 設(shè)置更新以及相關(guān)緩存數(shù)據(jù)更新
// 容器padding重新設(shè)置
this.updateContainerPadding({
newCurrentPaddingBottom,
newCurrentPaddingTop
})
// DOM元素相關(guān)數(shù)據(jù)緩存更新
this.updateDomDataCache({
currentPaddingTop: newCurrentPaddingTop,
currentPaddingBottom: newCurrentPaddingBottom
});
利用 Intersection Observer 來(lái)監(jiān)測(cè)相關(guān)元素的滾動(dòng)位置,異步監(jiān)聽(tīng),盡可能得減少 DOM 操作,觸發(fā)回調(diào),然后去獲取新的數(shù)據(jù)來(lái)更新頁(yè)面元素,并且用調(diào)整容器 padding 來(lái)替代了本該越來(lái)越多的 DOM 元素,最終實(shí)現(xiàn)列表滾動(dòng)、無(wú)限下拉。
這里和較為有名的庫(kù) - iScroll 實(shí)現(xiàn)的無(wú)限下拉方案進(jìn)行一個(gè)基本的對(duì)比,對(duì)比之前先說(shuō)明下 iScroll infinite 的實(shí)現(xiàn)概要:
iScroll 通過(guò)對(duì)傳統(tǒng)滾動(dòng)事件的監(jiān)聽(tīng),獲取滾動(dòng)距離,然后:
設(shè)置父元素的 translate 來(lái)實(shí)現(xiàn)整體內(nèi)容的上移(下移);
再基于這個(gè)滾動(dòng)距離進(jìn)行相應(yīng)計(jì)算,得知相應(yīng)子元素已經(jīng)被滾動(dòng)到視窗外,并且判斷是否應(yīng)該將這些離開(kāi)視窗的子元素移動(dòng)到末尾,從而再對(duì)它們進(jìn)行 translate 的設(shè)置來(lái)移動(dòng)到末尾。這就像是一個(gè)循環(huán)隊(duì)列一樣,隨著滾動(dòng)的進(jìn)行,頂部元素先出視窗,但又將移動(dòng)到末尾,從而實(shí)現(xiàn)無(wú)限下拉。
相關(guān)對(duì)比:
實(shí)現(xiàn)對(duì)比:一個(gè)是 Intersection Observer 的監(jiān)聽(tīng),來(lái)通知子元素離開(kāi)視窗,只要定量設(shè)置父元素 padding 就行;另一個(gè)是對(duì)傳統(tǒng)滾動(dòng)事件的監(jiān)聽(tīng),滾動(dòng)距離的獲取,再進(jìn)行一系列計(jì)算,去設(shè)置父元素以及子元素的 translate。顯而易見(jiàn),前者看起來(lái)更加簡(jiǎn)潔明了一些。
性能對(duì)比:我知道說(shuō)到對(duì)比,你腦海中肯定一下子會(huì)想到性能問(wèn)題。其實(shí)性能對(duì)比的關(guān)鍵就是 Intersection Observer。因?yàn)閱尉?padding 設(shè)置還是 translate 設(shè)置,性能方面的差距是甚小的,只是個(gè)人感覺(jué) padding 會(huì)簡(jiǎn)潔些?而 Intersection Observer 其實(shí)抽離了所有滾動(dòng)層面的相關(guān)邏輯,你不再需要對(duì)滾動(dòng)距離等相應(yīng) DOM 屬性進(jìn)行獲取,也不再需要進(jìn)行一系列滾動(dòng)距離相關(guān)的復(fù)雜計(jì)算,并且同步的滾動(dòng)事件觸發(fā)變成異步的,你也不再需要另外去做防抖之類(lèi)的邏輯,這在性能方面還是有所提升的。
padding 的計(jì)算依賴(lài)列表項(xiàng)固定的高度。
這是一個(gè)同步渲染的方案,也就是目前容器 padding 的計(jì)算調(diào)整,無(wú)法計(jì)算異步獲取的數(shù)據(jù),只跟用戶(hù)的滾動(dòng)行為有關(guān)。這看起來(lái)與實(shí)際業(yè)務(wù)場(chǎng)景有些不符。解決思路:
思路 1、利用 Skeleton Screen Loading 來(lái)同步渲染數(shù)據(jù)元素,不受數(shù)據(jù)異步獲取的影響。即在數(shù)據(jù)請(qǐng)求還未完成時(shí),先使用一些圖片進(jìn)行占位,待內(nèi)容加載完成之后再進(jìn)行替換。
思路 2、滾動(dòng)到目標(biāo)位置,阻塞容器 padding 的設(shè)置(即無(wú)限下拉的發(fā)生)直至數(shù)據(jù)請(qǐng)求完畢,用 loading gif 提示用戶(hù)加載狀態(tài),但這個(gè)方案相對(duì)復(fù)雜,你需要全面考慮用戶(hù)難以預(yù)測(cè)的滾動(dòng)行為來(lái)設(shè)置容器的 padding。
“Python簡(jiǎn)潔且有趣的無(wú)限下拉的實(shí)現(xiàn)方法是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
新聞標(biāo)題:Python簡(jiǎn)潔且有趣的無(wú)限下拉的實(shí)現(xiàn)方法是什么-創(chuàng)新互聯(lián)
路徑分享:http://jinyejixie.com/article18/dppgdp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護(hù)、服務(wù)器托管、面包屑導(dǎo)航、云服務(wù)器、微信公眾號(hào)、商城網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容
網(wǎng)頁(yè)設(shè)計(jì)公司知識(shí)