经典布局-瀑布流

瀑布流又称瀑布流式布局,以视觉表现为参差不齐的多栏布局为主,随着页面的滚动条不断向下滚动,这种布局会不断加载数据块并附加至当前尾部。实现这种布局有很多不同的方法:如flex布局,column-count 多栏布局,grid布局,float浮动布局。

flex实现瀑布流

先创建一个包住整个瀑布流的盒子,如果是2列,给该盒子设置display:flex;flex-wrap:wrap;完成这一步我们就可以有一个flex布局,超出该盒子宽度自动换行,然后在这个大盒子内部设置2个div容器,一列取奇数,一列取偶数即可简单进行实现

<view class="box">   
  <view  v-for="(item,idx) in  dataList"  :key="idx">
    <view v-if="idx%2==1">  {{item.img}} </view>
  </view>
  <view  v-for="(item,idx) in  dataList"  :key="idx">
    <view v-if="idx%2!=1"> {{item.img}} </view>
  </view>
</view>

column-count 多栏布局

使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>column多行布局实现</title>
    <style>
      .box {
        margin: 10px;
        column-count: 3;  /* 是控制屏幕分为多少列 */
        column-gap: 10px; /* 控制列与列之间的距离 */
      }
      .item {
        margin-bottom: 10px;
      }
      img {
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item">
        <img src="https://img2.baidu.com/it/u=1337068678,3064275007&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=750" alt="">
      </div>
      <div class="item">
        <img src="https://img2.baidu.com/it/u=2814429148,2262424695&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="http://img2.baidu.com/it/u=3139110441,2189129638&fm=253&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="http://img2.baidu.com/it/u=3444890671,796356424&fm=253&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="http://img1.baidu.com/it/u=1286854971,3553024375&fm=253&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="https://inews.gtimg.com/om_bt/O6SG7dHjdG0kWNyWz6WPo2_3v6A6eAC9ThTazwlKPO1qMAA/641" alt="">
      </div>
      <div class="item">
        <img src="https://inews.gtimg.com/om_bt/O0e2a37GGF5CDfNgK8GU29rF_2eJlHLDsa17LABXns7V4AA/641" alt="">
      </div>
      <div class="item">
        <img src="https://inews.gtimg.com/om_bt/Os3eJ8u3SgB3Kd-zrRRhgfR5hUvdwcVPKUTNO6O7sZfUwAA/641" alt="">
      </div>
      <div class="item">
        <img src="https://img0.baidu.com/it/u=1799694557,1475747482&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750" alt="">
      </div>
      <div class="item">
        <img src="https://pic.rmb.bdstatic.com/bjh/news/57e572cd41520408ebbbe5e3a6fb5b6d.jpeg" alt="">
      </div>
    </div>

    <script>
    </script>
  </body>
</html>

grid布局

先通过grid布局设置列数以及列的宽度,并通过js代码设置元素上边距和元素所需占行数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>column多行布局实现瀑布流</title>
    <style>
      body {
        margin: 0;
      }
      .box {
        display: grid;
        grid-template-columns: repeat(1, 1fr); /* 声明列的宽度 fr实现等分响应式*/
        grid-gap: 0 20px; /* 声明行间距和列间距 */
        grid-auto-rows: 2px; /*  */
        align-items: end;
      }
      .item {
        display: flex;
        justify-content: center;
        align-items: center;
      }
      img {
        width: 100%;
        height: 100%;
      }
      /* 媒体查询 */
      @media (min-width: 1280px) and (max-width: 1920px) {
        .box {
          grid-template-columns: repeat(3, 1fr);
        }
      }
      @media (min-width: 768px) and (max-width: 1280px) {
        .box {
          grid-template-columns: repeat(2, 1fr);
        }
      }
      @media (max-width: 768px) {
        .box {
          grid-template-columns: repeat(1,1fr);
        }
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item">
        <img src="https://img2.baidu.com/it/u=1337068678,3064275007&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=750" alt="">
      </div>
      <div class="item">
        <img src="https://img2.baidu.com/it/u=2814429148,2262424695&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="http://img2.baidu.com/it/u=3139110441,2189129638&fm=253&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="http://img2.baidu.com/it/u=3444890671,796356424&fm=253&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="http://img1.baidu.com/it/u=1286854971,3553024375&fm=253&app=138&f=JPEG?w=800&h=1422" alt="">
      </div>
      <div class="item">
        <img src="https://inews.gtimg.com/om_bt/O6SG7dHjdG0kWNyWz6WPo2_3v6A6eAC9ThTazwlKPO1qMAA/641" alt="">
      </div>
      <div class="item">
        <img src="https://inews.gtimg.com/om_bt/O0e2a37GGF5CDfNgK8GU29rF_2eJlHLDsa17LABXns7V4AA/641" alt="">
      </div>
      <div class="item">
        <img src="https://inews.gtimg.com/om_bt/Os3eJ8u3SgB3Kd-zrRRhgfR5hUvdwcVPKUTNO6O7sZfUwAA/641" alt="">
      </div>
      <div class="item">
        <img src="https://img0.baidu.com/it/u=1799694557,1475747482&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750" alt="">
      </div>
      <div class="item">
        <img src="https://pic.rmb.bdstatic.com/bjh/news/57e572cd41520408ebbbe5e3a6fb5b6d.jpeg" alt="">
      </div>
    </div>

    <script>
      // 给每个元素模拟随机高度
      window.addEventListener('load', () => {
        document.querySelectorAll('.box > .item').forEach(item => {
          item.style.height = `${Math.floor(Math.random() * 200) + 100}px`
        })
      })

      const calcRows = () => {
        const box = document.querySelector('.box')
        const items = document.querySelectorAll('.item')
          // 获取当前列数
          const cols = getComputedStyle(box).gridTemplateColumns.split(" ").length
        
        items.forEach((item, index) => {
          // 给需要上下间隔的元素增加上间隔(每列第一个元素无需上间距)
          const gapRows = index >= cols ? 8 : 0;
          
          // 根据元素高度设置元素的需占行数
          const rows = Math.ceil(item.clientHeight / 2) + gapRows;
          // grid-row-end 属性:下边框所在的水平网格线
          item.style.gridRowEnd = `span ${rows}`;
        })
      }

      window.addEventListener('resize', calcRows)
      window.addEventListener('load', calcRows)
    </script>
  </body>
</html>

float布局

uni-app实现移动端瀑布流布局时需要先设置几列,在进行循环创建几个div,在进行浮动,然后在每个div里面放置图片,使用js代码计算每个图片高度

<template>
	<view class="container">
		<Header title="瀑布流" alignAway="center" class="sticky-pos"/>
		
		<!-- 浮动实现 -->
		<view class="waterfalls-flow">
			<view v-for="(item,index) in data.column" :key="index" class="waterfalls-flow-column"
				:style="{'width':w,'margin-left':index==0?0:m}" :id="`waterfalls_flow_column_${index+1}`">
				<view class="column-value" v-for="(item2,index2) in data[`column_${index+1}`]" :key="index2">
					<image :src="item2.image" mode="widthFix" @load="imgLoad(item2)" @error="imgError(item2)" class="imgsty">
					</image>
					<text>{{item2.title}}</text>
				</view>
			</view>
		</view>
	</view>
</template>

<script setup>
	import Header from '../components/header.vue';
	import {
		ref,
		reactive,
		watch,
		computed,
		getCurrentInstance,
		onMounted
	} from 'vue';
	const _this = getCurrentInstance();
	const data = reactive({
		list: [],
		column: 2,
		columnSpace: 1,
	});
	// 数据赋值
	data.list = [{
			image: 'https://via.placeholder.com/200x500.png/ff0000',
			title: '我是标题1',
			desc: '描述描述描述描述描述描述描述描述1'
		},
		{
			image: 'https://via.placeholder.com/200x200.png/2878ff',
			title: '我是标题2',
			desc: '描述描述描述描述描述描述描述描述2'
		},
		{
			image: 'https://via.placeholder.com/200x100.png/FFB6C1',
			title: '我是标题3',
			desc: '描述描述描述描述描述描述描述描述3'
		},
		{
			image: 'https://via.placeholder.com/200x300.png/9400D3',
			title: '我是标题4',
			desc: '描述描述描述描述描述描述描述描述4'
		},
		{
			image: 'https://via.placeholder.com/100x240.png/B0E0E6',
			title: '我是标题5',
			desc: '描述描述描述描述描述描述描述描述5'
		},
		{
			image: 'https://via.placeholder.com/140x280.png/7FFFAA',
			title: '我是标题6',
			desc: '描述描述描述描述描述描述描述描述6'
		},
		{
			image: 'https://via.placeholder.com/40x60.png/EEE8AA',
			title: '我是标题7',
			desc: '描述描述描述描述描述描述描述描述7'
		},
		{
			image: 'https://via.placeholder.com/200x500.png/ff0000',
			title: '我是标题1',
			desc: '描述描述描述描述描述描述描述描述1'
		},
		{
			image: 'https://via.placeholder.com/200x200.png/2878ff',
			title: '我是标题2',
			desc: '描述描述描述描述描述描述描述描述2'
		},
		{
			image: 'https://via.placeholder.com/200x100.png/FFB6C1',
			title: '我是标题3',
			desc: '描述描述描述描述描述描述描述描述3'
		},
		{
			image: 'https://via.placeholder.com/200x300.png/9400D3',
			title: '我是标题4',
			desc: '描述描述描述描述描述描述描述描述4'
		},
		{
			image: 'https://via.placeholder.com/100x240.png/B0E0E6',
			title: '我是标题5',
			desc: '描述描述描述描述描述描述描述描述5'
		},
		{
			image: 'https://via.placeholder.com/140x280.png/7FFFAA',
			title: '我是标题6',
			desc: '描述描述描述描述描述描述描述描述6'
		},
		{
			image: 'https://via.placeholder.com/40x60.png/EEE8AA',
			title: '我是标题7',
			desc: '描述描述描述描述描述描述描述描述7'
		},
	];
	// 计算列宽
	const w = computed(() => {
		const column_rate = `${100 / data.column - (+data.columnSpace)}%`;
		return column_rate;
	})
	// 计算margin
	const m = computed(() => {
		const column_margin =
			`${(100-(100 / data.column - (+data.columnSpace)).toFixed(5)*data.column)/(data.column-1)}%`;
		return column_margin;
	})
	// 每列的数据初始化
	for (let i = 1; i <= data.column; i++) {
		data[`column_${i}`] = [];
	}
	// 获取最小值的对象
	const getMin = (a, s) => {
		let m = a[0][s];
		let mo = a[0];
		for (var i = a.length - 1; i >= 0; i--) {
			if (a[i][s] < m) {
				m = a[i][s];
			}
		}
		mo = a.filter(i => i[s] == m);
		return mo[0];
	}
	// 计算每列的高度
	function getMinColumnHeight() {
		return new Promise(resolve => {
			const heightArr = [];
			for (let i = 1; i <= data.column; i++) {
				const query = uni.createSelectorQuery().in(_this);
				query.select(`#waterfalls_flow_column_${i}`).boundingClientRect(data => {
					heightArr.push({
						column: i,
						height: data.height
					});
				}).exec(() => {
					if (data.column <= heightArr.length) {
						resolve(getMin(heightArr, 'height'));
					}
				});
			}
		})
	};
	async function initValue(i) {
		if (i >= data.list.length) return false;
		const minHeightRes = await getMinColumnHeight();
		data[`column_${minHeightRes.column}`].push({
			...data.list[i],
			index: i
		});
	}
	onMounted(() => {
		initValue(0);
	})
	// 图片加载完成
	function imgLoad(item) {
		const i = item.index;
		initValue(i + 1);
	}
	// 图片加载失败
	function imgError(item) {
		const i = item.index;
		initValue(i + 1);
	}
</script>

<style scoped lang="scss">
	.container {
		background-color: rgb(237, 239, 246);
		height: 100%;
	}
	.sticky-pos {
		position: sticky;
		top: 0;
		z-index: 9;
		background-color: rgb(237, 239, 246);
		padding-bottom: 30rpx;
	}
	.waterfalls-flow {
		padding-top: 50rpx;

		&-column {
			float: left;
			padding: 0 0 200rpx;
		}
	}

	.column-value {
		width: 100%;
	}

	.imgsty {
		width: 100%
	}
</style>

使用瀑布流插件

  1. vue-waterfall-easy
  2. vue-masonry-css
  3. vue-waterfall
### 使用 CSSJavaScript 实现瀑布布局 #### 1. HTML 结构 为了创建一个简单的瀑布布局,HTML 需要有一个容器来容纳所有的项目。 ```html <div id="waterfall-container"> <!-- 图片项会在这里动态添加 --> </div> ``` #### 2. CSS 样式 CSS 负责设定基础样式以及确保每一列具有相同的宽度并能够垂直堆叠。这里采用浮动属性让元素自然地排列成多列形式[^1]。 ```css #waterfall-container { column-count: 3; /* 设置三栏 */ column-gap: 1em; } .item { display: inline-block; width: 100%; margin-bottom: 1em; } ``` 对于不同设备尺寸下的响应性调整,可以通过媒体查询改变 `column-count` 属性值: ```css @media (max-width: 768px) { #waterfall-container { column-count: 2; } } @media (max-width: 480px) { #waterfall-container { column-count: 1; } } ``` #### 3. JavaScript 动态加载内容 JavaScript 可用于计算最佳插入点并将新条目追加到文档对象模型(DOM)[^2] 中。以下是简化版的实现逻辑: ```javascript function createWaterfallItem(imageUrl, description) { const itemElement = document.createElement(&#39;div&#39;); itemElement.className = &#39;item&#39;; // 创建内部结构 const imgTag = `<img src="${imageUrl}" alt="">`; const descTag = `<p>${description}</p>`; itemElement.innerHTML = `${imgTag}${descTag}`; return itemElement; } // 假设有一组图片URLs和描述文字的数据源 const itemsData = [ { url: &#39;image-url-1.jpg&#39;, text: &#39;Description 1&#39; }, { url: &#39;image-url-2.jpg&#39;, text: &#39;Description 2&#39; }, // 更多项... ]; itemsData.forEach(item => { const newItem = createWaterfallItem(item.url, item.text); waterfallContainer.appendChild(newItem); // 添加至页面中 }); ``` 此段脚本遍历给定的数据集,并为每一个数据项构建对应的 DOM 元素,随后将其附加到指定的目标节点之下。这种方法允许开发者轻松管理大量异步获取的内容片段,在实际应用中还可以加入懒加载机制优化性能表现[^4]。 #### 4. 进一步改进 为了避免初次渲染时可能出现的闪烁现象,可以在初始化阶段隐藏所有卡片直到完成全部DOM操作之后再显示出来。这可通过设置初始状态为 `visibility:hidden` 并在适当时候切换回 `visibility:visible` 来达成。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值