【Vue/React】Vue中的瀑布流(逻辑清晰,可拓展性)

需要考虑的问题

  • Vue中获取DOM节点元素
  • 涉及请求数据如何确认数据到达再去渲染DOM节点
  • 涉及图片加载渲染的,如何监听DOM渲染完毕再去计算元素高度(Bug:如果涉及图片加载的,在网络不快的情况下,Vue中拿到的当前含有图片的元素的高度是不正确的,会影响后续的瀑布流布局)
  • 如何实现根据设备屏幕大小来自动计算当前瀑布流卡片的宽度,并且可自定义距离屏幕边距以及卡片与卡片元素间的距离(自定义拓展)
  • 由于是通过卡片的绝对定位(top,left属性)进行布局,那么瀑布流的高度如何随着瀑布流卡片的增加而自动撑起(高度计算)

这就是使用Vue开发瀑布流时遇到的主要问题,这里大概罗列了出来,不多BB上代码一一讲解解决方法

分析与解决

首先我们如何布局(HTML)

定义:
正确的布局(也就是HTML的编写)是实现瀑布流的前提,根据自己项目进行对号入座即可

<template>
	<!-- 瀑布流容器 -->
	<div class="waterfull-container" :style="{height:waterFullBoxH + 'px'}">
		<!-- 瀑布流卡片列表 -->
		<div class="waterfull-card-list" v-for="item in waterFullList" :key="item.id" ref="waterFullItem">
			<!-- 瀑布流卡片单体 -->
			<div class="waterfull-card-item">
				...
			</div>
		</div>
	</div>
</template>

分析:

从上面的HTML不难看出就三部曲

  • 定义一个存放瀑布流的整个容器 waterfull-container

    细心的小伙伴会发现这里还定义了一个动态的style属性,里面就是放着之后会用到的根据瀑布流卡片的最大高度计算整体容器的高度,后续会讲到

  • 循环体 waterfull-card-list 这就是根据 waterFullList 数据进行循环

    可以看到这里有一个 ref="waterFullItem" 这就是上面罗列的问题中:【Vue获取元素节点】 → 通过this.refs.waterFullItem 可以获取你想要的元素信息

  • 瀑布流卡片 waterfull-card-item 整体

    这里就自行根据卡片的布局展示进行编写即可,但要知道你卡片所有的内容应该都放在这里,才不会影响后续的卡片元素高度计算

然后我们如何写样式(CSS)

定义:
根据设计的HTML,进行对应的样式编写,这里除了 waterfull-card-item 是自己设计之外,其他都是固定写就行

扫描二维码关注公众号,回复: 9895554 查看本文章
.waterfull-container{
  position: relative;
  background: #fafafa;  /* 瀑布流容器背景色(自定) */
}
.waterfull-card-list{
  position: absolute;
}

分析:
没错就是这么简单,我们可以看出就主容器的相对定位 relative ,以及瀑布流列表的绝对定位 absolute,有小伙伴就不解了,我瀑布流卡片 waterfull-card-item 这个怎么没有设置宽高??

首先我们要知道,我们定义的瀑布流宽高并不是写死的,宽是根据设备的宽度和你需要定义多少列瀑布流进行计算得到的,而高就是瀑布流的精髓,这个不用说,如果高定死了,那还写啥瀑布流;而这些计算都是归功于后面的Javascript了…

最后我们如何进行计算(JavaScript)

定义:
我们先把计算的逻辑写出来,然后我们慢慢解决之后遇到的问题,比如何时知道数据到达再进行渲染,其次就是何时调用计算函数等一系列的问题

获取当前设备的宽高函数【通用】:

如前面所说,要根据设备宽度来计算出我们单个瀑布流卡片的宽度

_sizeWidth () {
      return {
        width:
          window.innerWidth ||
          document.documentElement.clientWidth ||
          document.body.clientWidth,
        height:
          window.innerHeight ||
          document.documentElement.clientHeight ||
          document.body.clientHeight
      }
    }

计算用到的自定义数据【自定可拓展】:

  data () {
    return {
      imgCount: 20, // 瀑布流图片加载的临界值,默认20个
      imgFinishCount: 0, // 当前已经完成加载的图片个数
      row: 2, // 瀑布流列数,默认为2 (自定)
      gap: 10, // 瀑布流间距,默认10px (自定)
      initDistance: 10, // 瀑布流距离屏幕边缘的距离,默认10px (自定)
      screenWidth: '', // 当前屏幕的宽度
      waterFullBoxH: '', // 瀑布流盒子总高度
      waterFullItemW: '' // 根据屏幕宽度计算的瀑布流卡片宽度
    }
  }

计算逻辑【重点】:

  created () {
  	// 根据数据返回的条数计算出临界值
    if (this.postList.length <= 20) {
      this.imgCount = this.postList.length
    } else {
      this.imgCount =
      this.postList.length % 20 === 0 ? 20 : this.postList.length % 20
    }

	// 获取屏幕宽度并且计算每一个瀑布流卡片的宽度
    this.screenWidth = this._sizeWidth().width
    this.waterFullItemW =
       (this.screenWidth - this.gap * (this.row - 1) - this.initDistance * 2) /
      this.row
  },

分析1(关于图片的临界值):
瀑布流一般都是包含图片的,有图片就会影响瀑布流卡片的渲染,因此我们要知道如何设定一个临界值作为图片加载完成的节点,当到达这个节点时再去获取元素宽高;话说回来,20 其实是一页可以存放多少条数据,这个问后端就行;而临界值 imgCount 其实就是你需要渲染的瀑布流展示个数,也就是请求返回的 waterFullList 里新增的数据条数,因为涉及下拉请求,那 waterFullList 就会有新的数据加入,因此做个判断来知道我们等会要加载多少个图片,这个或许比较绕,有不懂的可以私聊

分析2(关于瀑布流卡片的宽度计算):
首先我们在DOM渲染前对基础数据的计算,那我们使用 created 生命钩子即可获取屏幕的可视宽度 screenWidth 并且 计算出根据自定定义的瀑布流列数 row 、瀑布流卡片的间距 gap 以及瀑布流距离屏幕两边的距离 initDistance 得到瀑布流卡片的宽度


methods:{
	_doSort (nodeList) {
      let arr = []
      for (let i = 0; i < nodeList.length; i++) {
        if (i < this.row) {
          // 1- 确定第一行
          nodeList[i].style.top = this.initDistance + 'px'
          nodeList[i].style.left =
            (this.waterFullItemW + this.gap) * i + this.initDistance + 'px'
          arr.push(nodeList[i].offsetHeight)
			
		  // 2- 当瀑布流卡片个数为2以下的话,计算容器高度
          if (nodeList.length === 2) {
            let maxHeight = arr[0] > arr[1] ? arr[0] : arr[1]
            this.waterFallBoxH = maxHeight + this.initDistance * 2
          } else if (nodeList.length === 1) {
            this.waterFallBoxH = arr[0] + this.initDistance * 2
          }
        } else {
          // 3- 找到数组中最小高度  和 它的索引
          var minHeight = arr[0]
          var index = 0
          for (var j = 0; j < arr.length; j++) {
            if (minHeight > arr[j]) {
              minHeight = arr[j]
              index = j
            }
          }
          // 4- 设置下一行的第一个盒子位置
          nodeList[i].style.top =
            arr[index] + this.gap + this.initDistance + 'px'
          // left值就是最小列距离左边的距离
          nodeList[i].style.left = nodeList[index].offsetLeft + 'px'

          // 5- 修改最小列的高度
          arr[index] = arr[index] + nodeList[i].offsetHeight + this.gap
          this.waterFallBoxH = arr[index] + this.initDistance * 2
        }
      }
    },
}

分析:
这里的逻辑就不多说了,就是如何计算每个瀑布流卡片的top和left;这里可以看到 nodeList 是啥??

这就是我们通过 this.refs.waterFullItem 获取当前元素的信息,这里可以拿到瀑布流下的所有卡片节点,那话说回来我们什么时候获取这些节点及其何时调用 _doSort方法呢?

methods:{
 _loadImage () {
   this.imgFinishCount = this.imgFinishCount + 1
   if (this.imgFinishCount >= this.imgCount) {
     const nodeList = this.$refs.waterFullItem
     this._doSort(nodeList)
     this.imgFinishCount = 0
   	}
  }
}

分析:
这里就说说怎么监听图片的渲染完成 → 使用Vue中的 @load="_loadImage" ,只要图片加载完成就会进入 _loadImage ,此时配合临界值实现确保需要展示的瀑布流卡片的图片加载完后在执行计算韩式 _doSort()

有数据再渲染

上面说那么多,其实关于请求回来的数据 waterFullList 才是我们的数据主体,如果数据还没有请求到就开始渲染,那瀑布流也不会出现;

所以这里简要说说怎么知道什么时候有数据,这里不具体写了,想具体了解的小伙伴可自行google,百度

  • 请求数据时使用promise…then(或者async…await)回调中处理数据
  • 或者直接通过判断 waterFullList是否为空,为空就不渲染即可

完结

到这里,关于Vue中的瀑布流实现就完成了

发布了134 篇原创文章 · 获赞 80 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Umbrella_Um/article/details/103726422