zoukankan      html  css  js  c++  java
  • 自己实现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="{(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(let 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;
    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;
    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 {
    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;
    /*25%;*/
    margin: 0 5px;
    color: #444;
    overflow:hidden;
    .goodsA:hover {
    color: darkorange;
    background-color: rgba(223,234,200,.1);
    }
    .goodsA{
    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 {
    100%;
    }
    .goodsInfo {
    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)地址:
    把兴趣变为职业是很酷的事
  • 相关阅读:
    iOS UILable 自定义高度 用masony适配
    iOS上架所需图片大小明细
    GCD倒计时
    iOS 小知识汇总
    七、Swift 枚举 Enumerations
    C语言深度剖析笔记
    六、闭包 Closures
    经济学常识
    Mac小技巧
    五、函数 Functions
  • 原文地址:https://www.cnblogs.com/hhyf/p/11452287.html
Copyright © 2011-2022 走看看