瀑布流的参考实现可以看这篇博客:3种方式实现瀑布流布局

1、利用css实现瀑布流

/**
flex布局实现 
*/

.box {
  display: flex; 
  align-items: center; 
  flex-flow:column wrap; 
  height: 100vh; // 为啥要设置100vh才有效果
}
.item {
    border: 1px solid #5592e5;
    margin: 10px;
    width: calc(100%/3 - 20px);
}

/**
column布局实现 
*/
.box {
  margin: 40px;
  column-count: 3;
  column-gap: 40px;
}
.item {
  box-sizing: border-box;
  border: 1px solid #5592e5;
  margin-bottom: 40px;
  transition: all .5s;

  &:hover{
    transform: scale(1.1,1.1); 
  }
}

css实现有个缺点,它的实现原理是按列来排,先排满第一列,排不满的进入第二页,这个时候最后的呈现效果就可能存在最后一行时,中间有个块,显示不好。

element 图片瀑布流布局_vue


另外还可能出现下面这种情况,该列的最后一个块的部分到了下一列首部。

element 图片瀑布流布局_element 图片瀑布流布局_02

2、利用js实现

可以自定义列数、行间距、列间距,注意margin是用户在对父组件设置了margin的情况下需要填写的,没有设置可以不填。

/**
 * @description: 
 * @param {*} vm : this
 * @param {*} parent : 瀑布流组件
 * @param {*} arg:{cols : 设置列数, margin:父元素设置的margin, vgap:块之间的水平间隙,hgap : 块之间的竖直间隙}
 * @return {*}
 */
function waterFall(vm, parent, arg) {
  const divVGap = arg.vgap || 20
  const divHGap = arg.hgap || 20
  const columns = arg.cols || 3; //默认3列
  const margin = arg.margin || 0; 

  // 1 确定图片的宽度 - 滚动条宽度
  const pageWidth = parent.clientWidth - divVGap * columns - margin * 2 - 8;
  const itemWidth = parseInt(pageWidth / columns); //得到item的宽度
  const list = Array.from(parent.children)
  // 设置到item的宽度
  list.forEach((item, i) => {
    item.style.width = itemWidth + 'px'; //设置到item的宽度
  })
  vm.$nextTick(() => {
    setTimeout(() => {
      const arr = []; // 存储每列当前高度
      list.forEach((item, i) => {
        const height = item.clientHeight || item.offsetHeight;
        if (i < columns) {
          // 2 第一行按序布局 
          item.setAttribute("style", `top:0; left:${(itemWidth) * i + divVGap * i}px; width:${itemWidth}px`)
          //将行高push到数组
          arr.push(height);
        } else {
          // 其他行
          // 3 找到数组中最小高度  和 它的索引
          // 这种写法虽短,但没有下面的效率高
          // const minHeight = Math.min(...arr);
          // const index = arr.findIndex(item => item === minHeight);
          
          let minHeight = arr[0];
          let index = 0;
          for (let j = 0; j < arr.length; j++) {
            if (minHeight > arr[j]) {
              minHeight = arr[j];
              index = j;
            }
          }

          // 4 设置下一行的第一个盒子位置  top值就是最小列的高度
          // 1.8ms:耗时 
          // item.style.top = `${arr[index] + divHGap}px`
          // item.style.left = `${(itemWidth) * index + divVGap * index}px`
          
          // 0.3ms: 多个设置和合并成一个设置,性能更优,注意要加上width设置,此操作会把上面的width覆盖掉
          item.setAttribute("style", `top:${arr[index] + divHGap}px; left:${(itemWidth) * index + divVGap * index}px; width:${itemWidth}px`)

          // 5 修改最小列的高度
          // 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 竖直间隔
          arr[index] = arr[index] + height + divHGap;
        }
      })
    },100)
  })
}

//clientWidth 处理兼容性
function getClient() {
  return {
    width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
    height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
  }
}

export { getClient, waterFall }

这里的vm.$nextTick 和 setTimeout都是为了正确的获取高度,但是这个处理不准确,图片的加载不知道什么时候结束,所以这里可以使用图片的懒加载插件,在图片加载之后进行setTimeOut中间的操作。如vue-lazyload

在vue文件使用

这里监控onsize的变化可以跟随页面变化而调整瀑布流。

<template>
    <div class="box" ref="box" id="box">
      <div class="item" ref="item">
        <img src="@/assets/image/not-exist@2x.png" alt="" />
      </div>
      <div class="item">
        <img src="@/assets/image/1@2x.png" alt="" />
      </div>
    
    </div>
</template>

<script>
import { waterFall } from '@/assets/js/waterfall'
export default {
  mounted() {
    waterFall(this, this.$refs.box, { margin:40, vgap:40, hgap:30})
    
    // 页面尺寸改变时实时触发
    window.onresize = () => {
      //重新定义瀑布流
      waterFall(this, this.$refs.box,{ margin:40, vgap:40, hgap:30})
    }
  },
}
</script>

<style lang="scss" scoped>

img{
  width: 100%;
  height: 100%;
}

.box {
  margin: 40px;
  width: 100%;
  position:relative;
  box-sizing: content-box;
}
.item {
  position: absolute;
  border: 1px solid #5592e5;
  transition: all .3s;
  cursor: pointer;
  &:hover{
    transform: scale(1.05,1.05); 
  }
}
</style>

总结

这里只是简单的实现,后续还可以加上下拉加载等功能。
这里有些推荐瀑布流插件:,
vue-waterfall:这个要传入入瀑布流每项的宽高,这块可以通过上述代码的方式动态设置每个块的宽高。还可以自定义设置一些块位置变换时(页面缩小时)的过渡效果
vue-grid-layout : 提供自由拖拽的功能,官网上还有几个例子