<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>

          使用 Vue 從零開始手寫一個貓咪瀑布流組件

          共 3264字,需瀏覽 7分鐘

           ·

          2021-11-22 21:40

          點擊上方?前端Q,關(guān)注公眾號

          回復(fù)加群,加入前端Q技術(shù)交流群


          貓咪瀑布流

          如下動態(tài)圖,一張張不規(guī)則的可愛貓咪照片是否勾起了你的少女心呢?

          瀑布流又稱瀑布流式布局,是比較流行的一種網(wǎng)站頁面布局方式。瀑布流實現(xiàn)的方式有很多種,但是原理都是差不多的,本文我們來詳細介紹下下面這個貓咪瀑布流是如何實現(xiàn)的。

          瀑布流原理

          mao.jpg

          如上圖:第1、2、3、4、5張圖排在容器內(nèi)的第一行,即靠近頂部。

          我們會發(fā)現(xiàn)第6張圖并沒有排在第1張圖的下面,而是排在了第3張圖下面。

          其實這就是瀑布流的關(guān)鍵之處,那么第6張圖片是根據(jù)什么排列的呢?

          其實他會放在當前排列圖片中底部距離頂部最小的圖片下面,這樣做是為了圖片差不會很大,我們可以看到3是高度最小的圖片,然后我們就將第6張圖放在3圖的下面。

          那么同理,第7張圖就應(yīng)該在下圖所示位置。

          seven.jpg

          那么你知道第8張圖應(yīng)該放在哪里嗎,這里我們留個問題讓大家思考,文章結(jié)尾我們會揭曉答案,你要是迫不及待可以滑到文章結(jié)尾看看你猜的對不對。

          預(yù)加載圖片

          實現(xiàn)瀑布流的原理我們大概知道,那么具體的技術(shù)實現(xiàn)是怎么實現(xiàn)呢?

          其實就是根據(jù)圖片寬高等設(shè)置圖片的偏移值即topleft值。

          意味著我們肯定需要知道圖片的寬高比例,因為我們這里的一列的寬度需要保持一致,即可以設(shè)置一個固定值。

          如果我們等渲染完以后再進行高度的獲取,然后再設(shè)置top值和left值,就會導(dǎo)致界面的閃動。

          所以我們需要再一開始就先預(yù)加載圖片并獲取寬高,但是并不進行渲染等時機成熟,也就是所有圖片都加載完成,即所有圖片的高度都算出來以后再進行渲染,說起來柑橘很簡單,但是具體實現(xiàn)應(yīng)該怎么操作呢?

          1.遍歷傳進來的img數(shù)組

          //imgsArr是組件外部傳入的一個圖片數(shù)組?里面有一個src表示圖片的路徑
          this.imgsArr.forEach((imgItem,?imgIndex)?=>?{
          ????//...
          ????//...
          })
          復(fù)制代碼

          2.loadedCount記錄加載數(shù)量

          //聲明loadedCount變量記錄加載完畢的數(shù)量,為了和imgsArr大小作比較,通知加載完畢(包括無圖、加載完畢,加載失敗的情況)
          data(){
          ????return?{
          ????????loadedCount:?0
          ????}
          }
          復(fù)制代碼

          3.無圖的情況下

          //?無圖時?將高度記錄為0
          if?(!imgItem[this.srcKey])?{
          ????this.imgsArr[imgIndex]._height?=?"0";
          ????this.loadedCount++;
          ????//?支持無圖模式
          ????if?(this.loadedCount?==?this.imgsArr.length)?{
          ????????this.$emit("preloaded");
          ????}
          ????return;
          }
          復(fù)制代碼

          4.Image對象

          //使用Image?API?當src屬性改變并完成加載時執(zhí)行
          let?oImg?=?new?Image();
          oImg.src?=?imgItem[this.srcKey];
          oImg.onload?=?oImg.onerror?=?(e)?=>?{
          ????//...
          }
          復(fù)制代碼

          5.加載完成后,計算實際需要渲染圖片的高

          //理論上?預(yù)加載圖片的高度/預(yù)加載圖片的寬度=需要渲染圖片的高度/圖片寬度
          this.imgsArr[imgIndex]._height?=
          ????????????e.type?==?"load"
          ????????????????Math.round(this.imgWidth_c?*?(oImg.height?/?oImg.width))
          ??????????????:?this.imgWidth_c;??
          復(fù)制代碼

          6.加載失敗后,標識失敗標記

          if?(e.type?==?"error")?{
          ????this.imgsArr[imgIndex]._error?=?true;
          ????this.$emit("imgError",?this.imgsArr[imgIndex]);
          }?
          復(fù)制代碼

          7.全部加載完后,進行emit preloaded事件

          if?(this.loadedCount?===?this.imgsArr.length)?{?
          ????this.$emit("preloaded");
          }
          復(fù)制代碼

          計算列數(shù)

          mao4.jpg
          calcuCols()?{
          ????//?需要計算出渲染多少列數(shù)據(jù)
          ????let?waterfallWidth?=?this.width???this.width?:?window.innerWidth;
          ????//最少渲染一列
          ????let?cols?=?Math.max(parseInt(waterfallWidth?/?this.colWidth),1);?
          ????//最大不能超過maxCols列
          ????return?this.isMobile???2?:?Math.min(cols,this.maxCols;
          }
          復(fù)制代碼

          使用on/on/on/emit監(jiān)聽加載完畢

          mao2.jpg
          //當加載完以后?頁面開始進行渲染?imgsArr_c?為真實渲染數(shù)組
          this.$emit("preloaded");

          this.$on("preloaded",?()?=>?{?
          ????this.imgsArr_c?=?this.imgsArr.concat([]);?//?預(yù)加載完成,這時才開始渲染
          ????//?...
          });
          復(fù)制代碼

          使用$nextTick尋找更新時機

          mao1.jpg

          當data中的某個屬性改變的時候,這個值并不是立即渲染到頁面上,而是先放到watcher隊列上(異步),只有當前任務(wù)空閑的時候才會去執(zhí)行watcher隊列上的任務(wù)。所以導(dǎo)致,改變的數(shù)據(jù)掛載到dom上會有一定的延遲,這也就導(dǎo)致了,當我們在改變屬性值的時候,立即通過dom去拿改變的值時發(fā)現(xiàn)拿到的值并不是改變的值,而是之前的值。

          上面的data也就是對應(yīng)了我們的imgsArr_c。

          this.$nextTick作用:在下次dom更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個方法,獲得更新后的dom。

          this.$nextTick(()?=>?{
          ????//表示欲加載結(jié)束
          ????this.isPreloading?=?false;
          ????this.waterfall();
          });
          復(fù)制代碼

          使用waterfall方法排列(核心)

          mao3.jpg
          waterfall()?{
          ????//選擇所有圖片
          ????this.imgBoxEls?=?this.$el.getElementsByClassName("img-box");
          ????//如果一個都沒有則沒有東西可以排列?故直接返回
          ????if?(!this.imgBoxEls)?return;
          ????//聲明top、left、height、colwidth即列的寬度
          ????let?top,
          ????????left,
          ????????height,
          ????????colWidth?=?this.isMobile
          ????????????this.imgBoxEls[0].offsetWidth
          ??????????:?this.colWidth;
          ????//開始排列的坐標大小?如果是從0開始排列?則將colsHeightArr置空,colsHeightArr的作用是用來比較?當前排列圖片中哪個最小
          ????if?(this.beginIndex?==?0)?this.colsHeightArr?=?[];
          ????//從0開始排列
          ????for?(let?i?=?this.beginIndex;?i?this.imgsArr.length;?i++)?{
          ????????if?(!this.imgBoxEls[i])?return;
          ????????//獲取渲染元素的高度
          ????????height?=?this.imgBoxEls[i].offsetHeight;
          ????????if?(i?this.cols)?{
          ????????????//如果小于列數(shù)?則將第一排的幾個元素全部push進數(shù)組里面?將top置為0?left為列坐標乘以列的寬度
          ????????????this.colsHeightArr.push(height);
          ????????????top?=?0;
          ????????????left?=?i?*?colWidth;
          ????????}?else?{
          ????????????//當?shù)谝恍信帕型暌院?算出當前最小的高度
          ????????????let?minHeight?=?Math.min.apply(null,?this.colsHeightArr);?//?最低高低
          ????????????//當?shù)谝恍信帕型暌院?算出當前最小的索引
          ????????????let?minIndex?=?this.colsHeightArr.indexOf(minHeight);?//?最低高度的索
          ????????????//新元素的top值即為數(shù)組中最小的值
          ????????????top?=?minHeight;
          ????????????//左邊的值即為最小索引乘以列寬
          ????????????left?=?minIndex?*?colWidth;
          ????????????//?設(shè)置元素定位的位置
          ????????????//?更新colsHeightArr
          ????????????this.colsHeightArr[minIndex]?=?minHeight?+?height;
          ????????}
          ????????//設(shè)置單個元素的left、top值
          ????????this.imgBoxEls[i].style.left?=?left?+?"px";
          ????????this.imgBoxEls[i].style.top?=?top?+?"px";
          ??????}
          ??????this.beginIndex?=?this.imgsArr.length;?//?排列完之后,新增圖片從這個索引開始預(yù)加載圖片和排列
          }
          復(fù)制代碼

          添加響應(yīng)式

          mao5.jpg
          window.addEventListener("resize",?this.response);

          response:?function?()?{
          ??????let?old?=?this.cols;
          ??????//重新計算列數(shù)
          ??????this.cols?=?this.calcuCols();
          ??????//如果列數(shù)不變?則不需要重新排列
          ??????if?(old?===?this.cols)?return;?//?列數(shù)不變直接退出
          ??????this.beginIndex?=?0;?//?開始排列的元素索引
          ??????this.waterfall();
          }
          復(fù)制代碼

          添加滾動觸底

          mao7.jpg
          this.scroll();

          scroll()?{
          ??????this.$refs.scrollEl.addEventListener("scroll",?this.scrollFn);
          }

          scrollFn()?{
          ??????let?scrollEl?=?this.$refs.scrollEl;
          ??????//如果正在預(yù)加載
          ??????if?(this.isPreloading)?return;
          ??????let?minHeight?=?Math.min.apply(null,?this.colsHeightArr);
          ??????if?(
          ????????scrollEl.scrollTop?+?scrollEl.offsetHeight?>
          ????????minHeight?-?this.reachBottomDistance
          ??????)?{
          ????????this.isPreloading?=?true;
          ????????this.$emit("scrollReachBottom");
          ??????}
          }
          復(fù)制代碼

          更多細節(jié)

          更多細節(jié),源碼盡在github[2]上,歡迎大家踴躍star!

          發(fā)布到npm上供大家使用

          npm?install?@parrotjs/vue-waterfall?-S
          復(fù)制代碼

          具體可以去我的github README.md進行查看

          eight.jpg


          關(guān)于本文

          作者:安穩(wěn)

          https://juejin.cn/post/7026253551361851405


          往期推薦


          2021 TWeb 騰訊前端技術(shù)大會精彩回顧(附PPT)
          面試題:說說事件循環(huán)機制(滿分答案來了)
          專心工作只想搞錢的前端女程序員的2020



          最后


          • 歡迎加我微信,拉你進技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認真學(xué)前端,做個專業(yè)的技術(shù)人...

          點個在看支持我吧
          瀏覽 100
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美日韩极品 | 免费一区在线 | 亚洲AV永久无码精品久久麻豆 | 在线日韩亚洲中文字幕av | 99热在线观看6 |