<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【W(wǎng)eb技術(shù)】1206- 如何設(shè)計一款支持懶加載的瀑布流組件?

          共 6335字,需瀏覽 13分鐘

           ·

          2022-01-20 04:04

          前言

          瀑布流布局算是一種比較流行的布局,參差不齊的多列結(jié)構(gòu),不僅能節(jié)省空間,還能在視覺展示上錯落有致不拘一格。在一次業(yè)務(wù)需求中,找了幾個開源的瀑布流組件,在使用的過程中總會有點小問題,便開發(fā)了此組件。

          在開始之前你可能需要先了解一下IntersectionObserver[1],核心是這個API監(jiān)聽指定的卡片是否在可視區(qū)域展示,當(dāng)一個被監(jiān)聽卡片出現(xiàn)在可視區(qū)域,就會觸發(fā)回調(diào),執(zhí)行列于列之間對比邏輯,并在高度較小的列添加數(shù)據(jù)。

          基本使用

          這款組件已經(jīng)上傳npm[2]了,有興趣的小伙伴可以下載使用一下。

          1、安裝

          npm?i?waterfall-vue2

          2、使用方式

          import?{?Waterfall?}?from?"waterfall-vue2";
          Vue.use(Waterfall);

          <Waterfall
          ??:pageData="pageData"
          ??:columnCount="2"
          ??:colStyle="{display:'flex',flexDirection:'column',alignItems:'center'}"
          ??query-sign="#cardItem"
          ??@wfLoad="onLoad"
          ??@ObserveDataTotal="ObserveDataTotal"
          >

          ??<template?#default="{?item,?columnIndex,?index}">
          ????
          ????<good-card?:item="item"?id="cardItem"?/>
          ??template>

          Waterfall>

          3、基本參數(shù)和事件

          API

          參數(shù)說明類型默認值版本
          columnCount列數(shù)Number2-
          pageData當(dāng)前 pageIndex 請求的數(shù)據(jù)(非多頁累加數(shù)據(jù))Array[]-
          resetSign重置數(shù)據(jù)(清空每列數(shù)據(jù))Booleanfalse-
          immediateCheck立即檢查Booleantrue-
          offset觸發(fā)加載的距離閾值,單位為pxString|Number300-
          colStyle每列的樣式Object{}-
          querySign內(nèi)容標(biāo)識(querySelectorAll選擇器)String必須項-

          Event

          事件名說明參數(shù)
          wfLoad滾動條與底部距離小于 offset 時觸發(fā)-
          ObserveDataTotal未渲染的數(shù)據(jù)總數(shù)length

          Slot

          名稱說明
          default插槽內(nèi)容
          columnIndex當(dāng)前內(nèi)容所在的列
          item單條數(shù)據(jù)
          index當(dāng)前數(shù)據(jù)所在列的下標(biāo)

          技術(shù)實現(xiàn)

          難點

          圖片大多數(shù)會使用懶加載實現(xiàn),一般情況下節(jié)點內(nèi)容已加載完成,圖片未出現(xiàn)在可視區(qū)域內(nèi)未加載。在圖片高度不確定的情況下,怎么確保列表中列于列的落差小于一個卡片內(nèi)容高度呢?

          原理

          利用IntersectionObserver監(jiān)聽固定的節(jié)點信息,每當(dāng)監(jiān)聽節(jié)點出現(xiàn)在可視區(qū)域中,就會觸發(fā)IntersectionObserver回調(diào),在回調(diào)中執(zhí)行數(shù)據(jù)插入,對比每列的內(nèi)容的高度,在監(jiān)聽數(shù)據(jù)池中取出一個數(shù)據(jù)放在最小列高度的數(shù)據(jù)列表中。每一個數(shù)據(jù)卡片的展示,就會觸發(fā)新數(shù)據(jù)卡片的加載,這就是「懶加載」瀑布流組件的核心思想。

          如下圖,當(dāng)卡片7剛剛展示在可視區(qū)域的時候,就會觸發(fā)IntersectionObserver回調(diào),再回調(diào)邏輯中執(zhí)行插入函數(shù),插入函數(shù)中進行列于列之間的對比,此時對比發(fā)現(xiàn)B列高度較小,然后在監(jiān)聽數(shù)據(jù)池中取出一個數(shù)據(jù),放入B列的數(shù)據(jù)列表中,渲染出卡片8。

          設(shè)計規(guī)劃

          1、一般瀑布流排列方式主要可以分為兩種,一是分欄布局,一是絕對定位布局。不管那種思想難點都在于解決圖片動態(tài)高度的問題。本次采用分欄布局方式,這樣能夠減少因圖片加載而進行大面積卡片位置的計算。

          2、創(chuàng)建一個數(shù)據(jù)監(jiān)聽池,作用一是將所有未渲染的數(shù)據(jù)保存在其中。作用二是當(dāng)數(shù)據(jù)池的數(shù)據(jù)取完之后可以減少很多不必要的執(zhí)行操作。

          3、參數(shù)規(guī)劃

          //?列數(shù)
          ????columnCount:?{
          ??????type:?Number,
          ??????default:?2,
          ????},
          ????//?每頁數(shù)據(jù)
          ????pageData:?{
          ??????type:?Array,
          ??????default:?()?=>?[],
          ????},
          ????//?重置
          ????resetSign:?{
          ??????type:?Boolean,
          ??????default:?false,
          ????},
          ????//?立即檢查
          ????immediateCheck:?{
          ??????type:?Boolean,
          ??????default:?true,
          ????},
          ????//?偏移
          ????offset:?{
          ??????type:?[Number,?String],
          ??????default:?300,
          ????},
          ????//?樣式
          ????colStyle:?{
          ??????type:?Object,
          ??????default:?()?=>?({}),
          ????},
          ????//?查詢標(biāo)識
          ????querySign:?{
          ??????type:?String,
          ??????require:?true,
          ????},

          3、函數(shù)規(guī)劃

          • getMinColSign 返回最小列的標(biāo)識
          • checkObserveDom 檢查當(dāng)前dom是否有未監(jiān)聽,將未監(jiān)聽的節(jié)點放入監(jiān)聽范圍內(nèi)
          • insetData 執(zhí)行取數(shù)據(jù)并插入列數(shù)據(jù)中
          • getScrollParentNode 獲取祖先滾動元素,并綁定滾動事件
          • check 滾動檢查是否觸發(fā)加載閥值

          4、流程圖設(shè)計

          實踐

          pageData實現(xiàn)

          1. 傳入數(shù)據(jù),放入監(jiān)視數(shù)據(jù)池
          2. 如重置標(biāo)識為true,清空監(jiān)視數(shù)據(jù)、列數(shù)據(jù),
          3. 每次新數(shù)據(jù)都會觸發(fā)數(shù)據(jù)插入
          4. 如果不兼容IntersectionObserver,每列均分當(dāng)前的數(shù)據(jù)
          ???pageData(value?=?[])?{
          ??????if?(!value.length)?return
          ??????if?(IntersectionObserver)?{
          ????????//?判斷當(dāng)前是否需要重置
          ????????if?(this.resetSign)?{
          ??????????//?重置斷開當(dāng)前全部監(jiān)控數(shù)據(jù)
          ??????????this.intersectionObserve.disconnect()
          ??????????Object.keys(this.colListData).forEach((key)?=>?{
          ????????????this.colListData[key]?=?[]
          ??????????})
          ??????????this.observeData?=?[...value]
          ??????????this.$nextTick(()?=>?{
          ????????????this.insetData()
          ??????????})
          ????????}?else?{
          ??????????this.observeData?=?[...this.observeData,?...value]
          ??????????//?插入數(shù)據(jù)
          ??????????this.insetData()
          ????????}
          ??????}?else?{
          ????????//?當(dāng)?IntersectionObserver?不支持,每列數(shù)據(jù)均勻分配
          ????????const?val?=?(this.observeData?=?value)
          ????????while?(Array.isArray(val)?&&?val.length)?{
          ??????????let?keys?=?null
          ??????????//?盡量減小數(shù)據(jù)分配不均勻
          ??????????if?(this.averageSign)?{
          ????????????keys?=?Object.keys(this.colListData)
          ??????????}?else?{
          ????????????keys?=?Object.keys(this.colListData).reverse()
          ??????????}
          ??????????keys.forEach((key)?=>?{
          ????????????const?item?=?val.shift()
          ????????????item?&&?this.colListData[key].push(item)
          ??????????})
          ??????????this.averageSign?=?!this.averageSign
          ????????}
          ??????}
          ????}

          insetData實現(xiàn)數(shù)據(jù)插入函數(shù),確保控制數(shù)據(jù)的入口只有一個,避免同一批處理周期內(nèi)執(zhí)行多次。

          //?插入數(shù)據(jù)
          ????insetData()?{
          ??????const?sign?=?this.getMinColSign()
          ??????const?divData?=?this.observeData?&&?this.observeData.shift()
          ??????if?(!divData?||?!sign)?{
          ????????return?null
          ??????}
          ??????this.colListData[sign].push(divData)
          ??????this.checkObserveDom()
          ????},

          getMinColSign實現(xiàn)獲取當(dāng)前所有列中高度最小的列,并返回其標(biāo)識

          //?獲取最小高度最小的標(biāo)識
          ????getMinColSign()?{
          ??????let?minHeight?=?-1
          ??????let?sign?=?null
          ??????Object.keys(this.colListData).forEach((key)?=>?{
          ????????const?div?=?this.$refs[key][0]
          ????????if?(div)?{
          ??????????const?height?=?div.offsetHeight
          ??????????if?(minHeight?===?-1?||?minHeight?>?height)?{
          ????????????minHeight?=?height
          ????????????sign?=?key
          ??????????}
          ????????}
          ??????})
          ??????return?sign
          ????},

          checkObserveDom實現(xiàn)將未加入監(jiān)視的節(jié)點,加入監(jiān)視

          //?檢查dom是否全部被監(jiān)控
          ????checkObserveDom()?{
          ??????const?divs?=?document.querySelectorAll(this.querySign)
          ??????if?(!divs?||?divs.length?===?0)?{
          ????????//?防止數(shù)據(jù)插入dom未渲染,監(jiān)聽函數(shù)無數(shù)據(jù)
          ????????setTimeout(()?=>?{
          ??????????//?每次新數(shù)據(jù)的首個數(shù)據(jù)無法監(jiān)控,需要延遲觸發(fā)
          ??????????this.insetData()
          ????????},?100)
          ??????}
          ??????divs.forEach((div)?=>?{
          ????????if?(!div.getAttribute('data-intersectionobserve'))?{
          ??????????//?避免重復(fù)監(jiān)聽
          ??????????this.intersectionObserve.observe(div)
          ??????????div.setAttribute('data-intersectionobserve',?true)
          ????????}
          ??????})
          ????}

          observeData實現(xiàn)

          1. 每次數(shù)據(jù)池數(shù)據(jù)去空修改觸底標(biāo)識,只要是防止?jié)L動持續(xù)觸底,當(dāng)前數(shù)據(jù)未渲染完
          2. 首次數(shù)據(jù)取空查找祖先滾動元素
          3. 每次數(shù)據(jù)變化,發(fā)布事件,告知當(dāng)前數(shù)據(jù)池剩余數(shù)據(jù)
          ??observeData(val)?{
          ??????if(!val)?return
          ??????if?(val.length?===?0)?{
          ????????if?(this.onceSign)?{
          ??????????//?監(jiān)視數(shù)組數(shù)據(jù)分發(fā)完了,在進行首次的祖先滾動元素的查找
          ??????????this.onceSign?=?false
          ??????????this.scrollTarget?=?this.getScrollParentNode(this.$el)
          ??????????this.scrollTarget.addEventListener('scroll',?this.check)
          ????????}
          ????????//?數(shù)據(jù)更新,修改觸發(fā)觸底標(biāo)識
          ????????this.emitSign?=?true
          ??????}
          ??????this.$emit('ObserveDataTotal',?val.length)
          ????}

          getScrollParentNode實現(xiàn)在內(nèi)容未加載的時候,無法準(zhǔn)確的通過overflow屬性查找到滾動祖先元素,為了能夠更準(zhǔn)確的獲取祖先滾動元素,在首次內(nèi)容全部加載之后才進行祖先滾動元素的查找

          //?獲取滾動的父級元素
          ????getScrollParentNode(el)?{
          ??????let?node?=?el
          ??????while?(node.nodeName?!==?'HTML'?&&?node.nodeName?!==?'BODY'?&&?node.nodeType?===?1)?{
          ????????const?parentNode?=?node.parentNode
          ????????const?{?overflowY?}?=?window.getComputedStyle(parentNode)
          ????????if?(
          ??????????(overflowY?===?'scroll'?||?overflowY?===?'auto')?&&
          ??????????parentNode.clientHeight?!=?parentNode.scrollHeight
          ????????)?{
          ??????????return?parentNode
          ????????}
          ????????node?=?parentNode
          ??????}
          ??????return?window
          ????},

          check實現(xiàn)檢查是否觸發(fā)load

          //?滾動檢查
          ????check()?{
          ??????this.intersectionObserve?&&?this.checkObserveDom()
          ??????//?觸底標(biāo)識為false直接跳過
          ??????if?(!this.emitSign)?{
          ????????return
          ??????}
          ??????const?{?scrollTarget?}?=?this
          ??????let?bounding?=?{
          ????????top:?0,
          ????????bottom:?scrollTarget.innerHeight?||?0,
          ??????}
          ??????if?(this.$refs.bottom.getBoundingClientRect)?{
          ????????bounding?=?this.$refs.bottom.getBoundingClientRect()
          ??????}
          ??????//?元素所在視口容器的高度
          ??????let?height?=?bounding.bottom?-?bounding.top
          ??????if?(!height)?{
          ????????return
          ??????}
          ??????const?container?=?scrollTarget.innerHeight?||?scrollTarget.clientHeight
          ??????const?distance?=?bounding.bottom?-?container?-?this._offset
          ??????if?(distance?????????//?發(fā)布事件
          ????????this.$emit('wfLoad')
          ????????//?發(fā)布事件觸發(fā)修改觸底標(biāo)識
          ????????this.emitSign?=?false
          ??????}
          ????},

          最后

          上面便是整個「懶加載」瀑布流組件的產(chǎn)生過程,感興趣的小伙伴可以下載使用,體驗一下?;蛘吣懈玫南敕ǎ嗷ヌ接?,共同進步。

          源碼地址

          github:https://github.com/zengxiangfu/vue2-waterfall

          參考資料

          [1]

          IntersectionObserver: https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver

          [2]

          npm: https://www.npmjs.com/package/waterfall-vue2

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章

          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧色一级黄色视频 | 久久久一区二区三区四区 | 肏屄在线视频 | 国产精品无码天天爽视频 | 久久精品久久久久 |