<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 從零開(kāi)始手寫一個(gè)貓咪瀑布流組件

          共 3270字,需瀏覽 7分鐘

           ·

          2021-11-19 03:05

          (給前端大學(xué)加星標(biāo),提升前端技能.)

          貓咪瀑布流

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

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

          waterfall-mao.gif

          瀑布流原理

          mao.jpg

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

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

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

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

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

          seven.jpg

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

          預(yù)加載圖片

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

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

          意味著我們肯定需要知道圖片的寬高比例,因?yàn)槲覀冞@里的一列的寬度需要保持一致,即可以設(shè)置一個(gè)固定值。

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

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

          1.遍歷傳進(jìn)來(lái)的img數(shù)組

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

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

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

          3.無(wú)圖的情況下

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

          4.Image對(duì)象

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

          5.加載完成后,計(jì)算實(shí)際需要渲染圖片的高

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

          6.加載失敗后,標(biāo)識(shí)失敗標(biāo)記

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

          7.全部加載完后,進(jìn)行emit preloaded事件

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

          計(jì)算列數(shù)

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

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

          mao2.jpg
          //當(dāng)加載完以后?頁(yè)面開(kāi)始進(jìn)行渲染?imgsArr_c?為真實(shí)渲染數(shù)組
          this.$emit("preloaded");

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

          使用$nextTick尋找更新時(shí)機(jī)

          mao1.jpg

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

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

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

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

          使用waterfall方法排列(核心)

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

          添加響應(yīng)式

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

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

          添加滾動(dòng)觸底

          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ù)制代碼

          更多細(xì)節(jié)

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

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

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

          具體可以去我的github README.md進(jìn)行查看

          eight.jpg


          關(guān)于本文

          作者:安穩(wěn)

          https://juejin.cn/post/7026253551361851405

          點(diǎn)贊和在看就是最大的支持??

          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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成人电影先锋 A片视频免费播放 | 黄色成人在线免费播放 | 操操操操操操操操操逼 | 日本乱伦三级片 | 久草综合在线视频 |