自己實現vue瀑布流組件,含詳細注釋


我知道vue有瀑布流插件vue-waterfall-easy,但是使用的時候與我的預期有部分別,所以就自己動手寫了這個組件

人和動物的根本區別是是否會使用工具,我們不僅要會使用,還要會創造工具,別人提供的工具不一定能滿足自己的需求。

 

先來張效果圖:

 

 

 

使用示例:


 

html:

    <waterfall :col="4" :onReachbottom="onReachBottom">

      //插槽內容,根據個人需求對數據進行渲染,數據為goods,建議使用組件,方便設置樣式

      //這里根據我自己的需求,使用自己的goodsInfo組件對數據goods進行了渲染

      <goodsInfo slot-scope="{goods}" :goods="goods"/>

    </waterfall>

  


JS:

  methods:{

     onReachbottom(){

        //此方法用於數據請求,返回一個Promise,需要以屬性方式傳入組件,

        //示例:

        return Axios.post('http://xxx.xxx.xx:8088/getData',qs.stringify({

          pageSize:8,

          page:1

        })

      }

    } 

  參數:

    col:必傳,瀑布流分為多少列

    onReachBottom: 必傳,新數據得獲取方式


 

 

    

思想:

   1. 在組件掛載時,根據用戶傳入的 col(多少列),初始化渲染列表為一個二維數組

  2. 組件掛載時發送首次數據請求,並進行首次渲染(如果你喜歡,也可以在created時發送請求)

  3.  需要根據列的高度來判斷每條數據填充至哪一列(此處有坑),每次填充都需要先更新視圖再進入下一條數據得判斷,否則每次獲取到的高度都是一致的,這樣會導致所有數據填充至一列(由於使用二維數組,導致這個坑被我踩爛了)

  4. 添加滾動事件,判斷觸底,並再次拉取數據

 


 

實現代碼:

 HTML:

    

<template>
  <div class="myWaterfall" v-show="imgsArr" ref="fallbox">
  <ul>
    <!--列的寬度應由計算的來-->
    <li v-for="(it,index) in col" :style="{width:(100/col-1)+'%'}" :ref="'col'+index" :key="it">
    <div v-for="goods in renderList[index]" :key="goods.ID">
       //插槽,用戶如何對數據渲染
      <slot :goods="goods" />
    </div>
    </li>
  </ul>
  <div class="loading" v-show="rendering">
  <div class="wait">
    <span>L</span>
    <span>o</span>
    <span>a</span>
    <span>d</span>
    <span>i</span>
    <span>n</span>
    <span>g</span>
    ...
  </div>
  </div>
  <p class="nomore" v-if="over">沒有更多了>>></p>
</div>
</template>
 

Script:
  
export default {
   props: [" col"," onReachBottom" ],
   data(){
     return{
      loading :  false ,      //是否處於加載狀態,用於觸底事件的節流
      nowPage :  1,
      imgsArr : [],       //數據列表
      over : false,       //是否已經到底了
      lock : false,       //請求鎖
      rendering : false,    //渲染中,防止渲染未完成高度獲取不准確而導致可連續觸發請求
      renderList :  [],      //渲染列表,根據imgsArr+col初始化為二維數組
      }
    },

   methods: {
     computedOffset( obj, prop){    //工具函數,計算元素到body的絕對位置,獲取obj元素的prop值(prop為offset中的某一項)
       if( obj== document. body || obj. offsetParent == document. body){
         return parseInt( obj[ prop])
      }
       return parseInt( obj[ prop]) +  this. computedOffset( obj. offsetParent, prop)     //遞歸
      },
 
     getDataList() {                  //數據加載
                           //節流處理
       if( ! this. loading && ! this.over){       //不處於加載狀態且有新數據
        let self = this;
        this.loading = true;
                           //頁數增加
         this.onReachBottom(self.nowPage).then(res=>{
                           //拼接
          self.rendering = true;       //渲染狀態中
           if(res.data.list.length>0){
            self.nowPage++;
             let len = self.imgsArr.length;
            self.imgsArr= self.imgsArr. concat(res.data.list);
            self.fullData(len);       //僅對新的數據做渲染,需要從原數組的終點開始
            self.lock= false
          }else{
             //沒有新數據
            self.over = true;
            self.rendering = false;
            }
         self.loading = false;
        })
      }else{
                  //處於請求狀態,節流,或已出現無數據(over),忽略請求
         return false;
      }
    },

    
    fullData( index){         //比較列的高度來判斷向哪一個列中添加數據
     if( index < this.imgsArr. length){
     let self = this;

     let newImg = new Image();
    newImg.src = self.imgsArr[ index].img;
    /********防止錯誤********/
    /*
     未防止圖片長時間加載不成功,可設置超時時間,超時默認圖片出錯並替換為默認圖片:
    let loadTimer = setTimeout(_=>{
       newImg.onerror();    //5秒主動觸發失敗
    
      },5000)
    newImg.onerror=function(){
      //如果圖片加載失敗,替換為默認圖片
      newImg.src="你的默認圖片地址"
    }
    */
    newImg. onload=()=>{      //需等待圖片加載,否則高度不准確   
    
    //加載成功,清除超時定時器
    // clearTimeout(loadTimer);
 
     let colHeightList = [];       //所有列的高度表
     for( let i = 0 ; i < self.col ; i++){
    colHeightList[i] = self.$refs['col' + i][0]. offsetHeight;
   }

           //獲取最小列
     let min = colHeightList.indexOf(Math.min.apply(Math, colHeightList));
     // self.renderList[min].push(self.imgsArr[index]);    踩坑
     let tar = self.renderList[min].concat(self.imgsArr[ index])
           //需要更新視圖,上面的使用push不會更新視圖(操作的第二維),使用set
    self.$set(self.renderList,min,tar)
    self. fullData( index+1)
   }
  } else{
     this.rendering = false;
  }
  
 }
},
   mounted(){
     let self =this;
 
    //渲染列 列表,根據如的col生成對應列數,並置為空的二維數組
     for(l et i=0;i<this.col;i++){
       this.renderList[i] = []
    }
 
    //請求首次數據:
     this.getDataList();
 
 
 
    //監聽滾動事件
     window. onscroll= function(e) {
       //監測觸底
      //瀑布流高度 + 瀑布流的相對top < 可視區高度+滾動距離 ==觸底
      //獲取到瀑布流盒子
       let box = self.$refs.fallbox;
         //獲取到盒子相對於文檔的位置
       let top = self.computedOffset( box, ' offsetTop');
       let height = box. offsetHeight;
         //可視區高度
       let clientHeight = document. documentElement. clientHeight;
         //滾動距離
       let scrollTop = document. documentElement. scrollTop;
       if (top + height < clientHeight + scrollTop + 50 && !self.lock && !self.rendering) {
         //觸底判斷,50用於提前觸發,不用完全到底才觸發
         //觸底成功
        self.lock= true;
        self.getDataList();
       }
      }
     this.fullData(0);
  },

   beforeDestroy(){
    //取消滾動事件,重要,否則路由跳轉后執行scroll事件將會有一堆的undefined
     window. onscroll=null;
    //滾動條置頂,否則路由跳轉后滾動條的位置沒有變化
     document. documentElement. scrollTop=0;
  }
}
 

Css:使用了less語法
  
<style lang="less" >
@keyframes jump{
0%{
top:-10px;
}
100%{
top:10px;
}
}
.loading{
position:fixed;
width:100%;
height:100%;
background:rgba(0,0,0,.2);
top:0;
left:0;
.wait{
font-size:14px;
background:rgba(0,0,0,.8);
border-radius:10px;
line-height:50px;
font-weight:900;
width:200px;
height:50px;
position:absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
letter-spacing: 2px;
span{
font-size:20px;
position:relative;
}
span:first-of-type{
color:red;
animation: jump 0.8s linear alternate-reverse infinite
}
span:nth-of-type(2){
color:orange;
animation: jump .8s linear 0.3s alternate-reverse infinite
}
span:nth-of-type(3){
color:yellow;
animation: jump .8s linear .6s alternate-reverse infinite
}
span:nth-of-type(4){
color:green;
animation: jump .8s linear .9s alternate-reverse infinite
}
span:nth-of-type(5){
color:cyan;
animation: jump .8s linear 1.2s alternate-reverse infinite
}
span:nth-of-type(6){
color:blue;
animation: jump .8s linear 1.5s alternate-reverse infinite
}
span:nth-of-type(7){
color:purple;
animation: jump .8s linear 1.8s alternate-reverse infinite
}
}
}
.myWaterfall {
width: 100%;
height: 100%;
.nomore{
color:grey;
height:30px;
line-height:30px;
}
ul {
/*display: flex;*/
overflow: hidden;
padding:10px;
background:whitesmoke;
border-radius:10px;
li {
/*overflow: hidden;*/
/*flex: 1;*/
float:left;
/*width:25%;*/
margin: 0 5px;
color: #444;
overflow:hidden;
.goodsA:hover {
color: darkorange;
background-color: rgba(223,234,200,.1);
}
.goodsA{
width:100%;
cursor: pointer;
box-sizing:border-box;
border:1px solid #ddd;
box-shadow:3px 1px 3px 0 grey;
background:red;
margin-bottom: 20px;
background-color: #fff;
img {
width: 100%;
}
.goodsInfo {
width: 100%;
.goodsName {
font-weight: 900;
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height:40px;
line-height:40px;
text-indent:10px;
}
.description {
font-size: 12px;
text-align: left;
text-indent: 10px;
/*height:25px;*/
line-height:25px;
}
.price{
height:30px;
line-height:30px;
font-size:10px;
text-align: left;
text-indent:10px;
position: relative;
.nowPrice{
color:rgb(253, 132, 18);
font-weight:900;
font-size:18px;
margin-right:10px;
em{
font-size:24px;
}
}
.originPrice{
font-size:14px;
text-decoration: line-through;
color:#999;
}
.sale{
position: absolute;
right:5px;
top:5px;
color:#444;
}
}
}
}
}
}
}
</style>
 
 
以上代碼就是我用來實現瀑布流組件的,並沒有什么高深的用法, 只是提供這樣的思路,希望對你自己實現瀑布流有所啟發!
copyRight by 埃爾本  2019-09-03 
 
組件(copy)地址:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM