vue+uniapp瀑布流布局多种实现方式

Posted pixle0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue+uniapp瀑布流布局多种实现方式相关的知识,希望对你有一定的参考价值。

文章目录


前言

瀑布流布局是网页设计常见的一种布局,一般用于图片多列展示。列宽固定,图片根据自身高度自适应交错排列。


一、实现原理

通过动态计算哪一列高度最低,就把图片放置该列下显示,直至所有图片分列完毕

计算哪一列高度最低具体实现过程又分2种方式:

方式1:通过计算每一列每张图片渲染后高度进行累加就是该列的高度,记录每列累加高度比较大小
方式2:直接通过图片父级元素高度(列div)来判断哪一列最低

区别:方式1无需等待图片真实渲染完成在比较高度,方式2需要等待图片真实渲染完成在获取高度

二、代码实现

以左右2列为例

<template>
  <div class="page">
    <!-- 左图片列表 -->
    <div class="left" ref="left">
      <img
        class="img"
        v-for="(item, index) in leftList"
        :key="index"
        :src="item"
      />
    </div>
    <!-- 右图片列表 -->
    <div class="right" ref="right">
      <img
        class="img"
        v-for="(item, index) in rightList"
        :key="index"
        :src="item"
      />
    </div>
  </div>
</template>
<style scoped>
.page 
  width: 100%;
  display: flex;
  align-items: flex-start;
  padding: 0 1%;
  box-sizing: border-box;

.left,
.right 
  margin: 0 auto;
  width: 48%;

.img 
  width: 100%;
  height: auto;
  margin-bottom: 10px;

</style>

1.方式1(图片高度累加比较法)

<script>
export default 
  data() 
    return 
      imgList: [
        "https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
        "https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
        "https://img0.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
        "https://img1.baidu.com/it/u=3866290852,3566512524&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500",
        "https://img2.baidu.com/it/u=1114729443,1120710416&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500",
        "https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
        "https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
        "https://img2.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
      ], //所有图片
      leftList: [], //左边列图片
      rightList: [], //右边列图片
      leftHeight: 0, //左边列高度
      rightHeight: 0, //右边列高度
      columnWidth: 0, //列宽度
    ;
  ,
  mounted() 
    this.$nextTick(() => 
      this.columnWidth = this.$refs.left.clientWidth;
      this.setWaterFallLayout();
    );
  ,
  methods: 
    //方法1
    async setWaterFallLayout() 
      for (let item of this.imgList) 
        let img = new Image();
        img.src = item;
        try
          let h = await this.getImgHeight(img);//图片渲染后高度
          if (this.leftHeight <= this.rightHeight) //左边列比右边低,图片放入左边
            this.leftList.push(item);
            this.leftHeight += h;
           else //否则,图片放入右边
            this.rightList.push(item);
            this.rightHeight += h;
          
        catch(e)
          console.log(e)
        
      
    ,
    //获取图片高度
    getImgHeight(img) 
      return new Promise((resolve,reject) => 
      //图片加载完成
        img.onload = () => 
          let h = (img.height / img.width) * this.columnWidth;//计算图片渲染后高度
          resolve(h);
        ;
        //加载出错
        img.onerror=()=>
          reject('error')
        
      );
    ,
  ,
;
</script>

2.方式2(父元素高度比较法)

每次放入图片需要等待渲染后再重新计算父元素高度,关键代码 await this.$nextTick()

<script>
export default 
  data() 
    return 
      imgList: [
        "https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
        "https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
        "https://img0.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
        "https://img1.baidu.com/it/u=3866290852,3566512524&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500",
        "https://img2.baidu.com/it/u=1114729443,1120710416&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500",
        "https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
        "https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
        "https://img2.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
      ], //所有图片
      leftList: [], //左边列表图片
      rightList: [], //右边列表图片
    ;
  ,
  mounted() 
    this.$nextTick(() => 
      this.setWaterFallLayout2();
    );
  ,
  methods: 
    //方法2
    async setWaterFallLayout2() 
      for (let item of this.imgList) 
        if (this.$refs.left.clientHeight <= this.$refs.right.clientHeight) //左边列比右边低,图片放入左边
          this.leftList.push(item);
         else //否则图片放入右边
          this.rightList.push(item);
        
        await this.$nextTick();//等待渲染完成后重新比较左右高度
      
    ,
  ,
;
</script>

三.uniapp实现

由于uniapp获取元素高度和vue有所区别,造成实现瀑布流方式也需要调整。我们知道uniapp不能通过this.$ref.xx.clientHeight获取元素高度,而需要通过uni.createSelectorQuery().in(this).select(‘.xxxx’).boundingClientRect().exec()来获取,且经过实测当图片动态加入列后通过该api计算出父元素真实高度是不准确的,所以uniapp瀑布流布局实现方式只能通过方法1(也即图片高度累加法)进行实现,除了上面方法1通过img.onload来获取图片高度外,uniapp还提供uni.getImageInfo方法来更方便获取图片高度。

代码实现

<template>
	<view class="page">
		<view class="left" ref="left">
			<image class="image" v-for="(item,i) in leftList" :key="i" :src="item" mode="widthFix"></image>
		</view>
		<view class="right" ref="right">
			<image class="image" v-for="(item,i) in rightList" :key="i" :src="item" mode="widthFix"></image>
		</view>
	</view>
</template>
<style lang="scss">
	.page 
		width: 100%;
		display: flex;
		align-items: flex-start;
		padding: 0 1%;
		box-sizing: border-box;
	

	.left,
	.right 
		margin: 0 auto;
		width: 48%;
	

	.image 
		width: 100%;
		height: auto;
		margin-bottom: 10px;
	
</style>

<script>
	export default 
		data() 
			return 
				imageList: [
					"https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
					"https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
					"https://img0.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
					"https://img1.baidu.com/it/u=3866290852,3566512524&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500",
					"https://img2.baidu.com/it/u=1114729443,1120710416&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500",
					"https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
					"https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
					"https://img0.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
				], //所有图片
				leftList: [], //左边列图片
				rightList: [], //右边列图片
				leftHeight: 0, //左边列高度
				rightHeight: 0, //右边列高度
				columnWidth: 0 //列宽度
			
		,
		mounted() 
			this.$nextTick(() => 
				uni.createSelectorQuery().in(this).select('.left').boundingClientRect(res => 
					this.columnWidth = res.width
					//方法1
					this.setWaterFallLayout()
					//方法2
					// this.setWaterFallLayout2()
				).exec()
			)
		,
		methods: 
			//方法1通过img.onload
			async setWaterFallLayout() 
				for (let item of this.imageList) 
					let img = new Image()
					img.src = item
					try 
						let h = await this.getImgHeight(img)
						if (this.leftHeight <= this.rightHeight) 
							this.leftList.push(item)
							this.leftHeight += h
						 else 
							this.rightList.push(item)
							this.rightHeight += h
						
					 catch (e) 
						console.log(e)
					

				

			,
			//获取图片高度
			getImgHeight(img) 
				return new Promise((resolve, reject) => 
					img.onload = () => 
						let h = img.height / img.width * this.columnWidth
						resolve(h)
					
					//加载出错
					img.onerror = () => 
						reject('error')
					
				)
			,
			//方法2通过uni.getImageInfo
			async setWaterFallLayout2() 
				for (let item of this.imageList) 
					uni.getImageInfo(
						src: item,
						success: e => 
							if (this.leftHeight <= this.rightHeight) 
								this.leftList.push(item)
								this.leftHeight += e.height
							 else 
								this.rightList.push(item)
								this.rightHeight += e.height
							

						
					)
				
			

		,

	
</script>


四、多列实现

多列实现和2列一样,动态生成每列图片数据和记录每列高度

代码实现

以最简单的父元素高度比较法(方式2)为例实现,图片高度累加比较法(方式1)自行类比实现

<template>
  <div class="page">
    <div
      class="column"
      ref="column"
      v-for="(item, index) in columnList"
      :key="index"
    >
      <img class="img" v-for="(n, i) in item" :key="i" :src="n" />
    </div>
  </div>
</template>
<style scoped>
.page 
  width: 100%;
  display: flex;
  align-items: flex-start;
  padding: 0 1%;
  box-sizing: border-box;


.column 
  flex: 1;
  padding: 0 10px;
  box-sizing: border-box;
  width: 0;


.img 
  width: 100%;
  height: auto;
  margin-bottom: 10px;

</style>
<script>
export default 
  data() 
    return 
      imgList: [
        "https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
					"https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
					"https://img0.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
					"https://img1.baidu.com/it/u=3866290852,3566512524&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500",
					"https://img2.baidu.com/it/u=1114729443,1120710416&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500",
					"https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
					"https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
					"https://img0.baidu.com/it/u=1088754973,1390499664&fm=253&fmt=auto&app=138&f=JPEG?w=335&h=500",
					"https://img0.baidu.com/it/u=1345303087,1528317222&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=1082",
					"https://img2.baidu.com/it/u=1893470775,4143435497&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
      ], //所有图片
      columnList: [], //分配后的每列图片
      columWidth: 0, //每列宽度
      columnCount: 5, //显示几列
    ;
  ,
  created() 
    //初始化数据
    for (let i = 0; i < this.columnCount; i++) 
      this.columnList.push([]);//生成每列图片数组
    
  ,
  mounted() 
    this.$nextTick(()=>
      this
    参考技术A
            

瀑布流布局,一般指根据内容高度自适应填充到某一列以使整体页面和谐,常见的有图片网站,比如每一行每一列的内容是错开的。

瀑布流布局一般是下面这个样子

css中有这么两个属性:

注意

这也是我推荐大家用第一种方法的最主要原因。

这种方案做出来的效果有点奇葩,特殊的应用场景可能会用到,最终效果是横向瀑布流
flex布局中有一个属性 flex-flow ,指明在哪个方向上在长度不够的情况想拆行,详情大家自己查阅

第一种方法其实是最合适的,也是对开发人员要求最高的,即使有思路与算法在开发过程中也会踩很多坑,比如(1)图片预加载,(2)querySelector取元素居然获取不到,(3)使用box-sizing简化计算过程降低复杂性,等等。 box-sizing 属性用好了非常高效,推荐大家去仔细看看

第二种方法其实也可行,但是不推荐
第三种方法就有点扯了,估计不常用到,大家看看就行

以上是关于vue+uniapp瀑布流布局多种实现方式的主要内容,如果未能解决你的问题,请参考以下文章

H5 图片瀑布流布局 - vue

关于waterfall 瀑布流布局出现布局错乱的问题

前端之瀑布流布局(多种实现方案)

vue瀑布流布局组件vue-waterfall

css 瀑布流布局问题 高手进

h5瀑布流布局会留白