【瀑布流大全】分析原理及实现方式(微信小程序和网页都适用)

前言

前端的同学在开发过程中,会遇到瀑布流这种布局方式,特别是商城类、社交类的项目,会很经常遇到。

先明确需求,再选择适合的方法,去渲染页面。

文章目录

  • 前言
    • 一、什么是瀑布流?
    • [二、实现方式: 通过 css 样式绘制瀑布流](#二、实现方式: 通过 css 样式绘制瀑布流)
        • [1.column-count 多列布局](#1.column-count 多列布局)
        • [2. grid 网格布局 (网页版)](#2. grid 网格布局 (网页版))
    • [三、 flex 分栏 + JS](#三、 flex 分栏 + JS)
    • [四、scroll-view 结合 grid-builder](#四、scroll-view 结合 grid-builder)

一、什么是瀑布流?

瀑布流 是一种非对称的纵向内容布局方式,核心特点是内容按列排列,每列高度随自身内容自动调整,新内容会不断加载到当前最短的列下方,视觉上像瀑布一样自然向下流动,无需传统分页操作。

这里举一个很经典的例子------小红书:

如图所示,从用户实际体验来看,它有两个最直观的感受:

  • "无限滑" 的浏览:不用点击 "下一页",只要向下滚动屏幕,新内容就会自动加载出来,适合碎片化、沉浸式浏览(比如刷小红书笔记、淘宝商品、图片社区内容)。
  • 内容不被 "挤压":图片、卡片等内容会按原始比例显示(比如竖版长图、方形图各展所长),不会被强行拉成统一尺寸,视觉上错落有致,重点更突出内容本身。

简单说,它就像把内容 "自然堆叠" 在多列里,列的高度随内容 "长",用户下滑就能不断看到新内容,是现在很多内容 / 电商类平台常用的布局形式。

二、实现方式: 通过 css 样式绘制瀑布流

1.column-count 多列布局

利用 CSS 多列布局(column-count)自动将内容分配到指定列数,内容会按 "从上到下、从左到右" 的顺序填充,实现基础瀑布流效果。

核心原理:

  • column-count: N:指定布局为 N 列。
  • column-gap: Xpx:设置列之间的间距。
  • 子元素(如卡片、图片)会自动按顺序填充到各列,列高随内容自然撑开。

效果图:

可以直接复制在本地运行看一下效果哦~

typescript 复制代码
<template>
  <view class="demo-container">
    <view class="waterfall">
      <view class="item" v-for="item in items" :key="item.id" @click="navigateToDetail(item)">
        <image :src="item.image" mode="widthFix" />
        <text class="title">{{ item.title }}</text>
      </view>
    </view>
  </view>
</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue'

// Mock数据
const items = ref([
  { id: 1, title: 'Item 1', image: 'https://picsum.photos/200/300' },
  { id: 2, title: 'Item 2', image: 'https://picsum.photos/200/250' },
  { id: 3, title: 'Item 3', image: 'https://picsum.photos/200/350' },
  { id: 4, title: 'Item 4', image: 'https://picsum.photos/200/280' },
  { id: 5, title: 'Item 5', image: 'https://picsum.photos/200/320' },
  { id: 6, title: 'Item 6', image: 'https://picsum.photos/200/260' },
  { id: 7, title: 'Item 7', image: 'https://picsum.photos/200/300' },
  { id: 8, title: 'Item 8', image: 'https://picsum.photos/200/290' },
])

onMounted(() => {
  // 可以在这里添加更多数据或进行其他初始化操作
})

const navigateToDetail = (item: any) => {
  console.log('navigateToDetail', item)
}
</script>

<style lang="scss">
.demo-container {
  padding: 10px;
}

.waterfall {
  column-count: 2;
  column-gap: 10px;
}

.item {
  break-inside: avoid;
  margin-bottom: 10px;
  background: #fff;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  image {
    width: 100%;
    display: block;
  }

  .title {
    padding: 8px;
    font-size: 14px;
    color: #333;
  }
}
</style>

特点:

  • 优点:实现简单,纯 CSS 无需 JS,自动适应列数,响应式友好。
  • 缺点:内容排列顺序是 "从上到下、从左到右"(先填满第一列再填第二列),而非 "优先填充最短列",视觉上列高差异可能较大;动态加载新内容时,新内容会从最后一列继续填充,可能破坏布局平衡。
2. grid 网格布局 (网页版)

利用 CSS Grid 布局,结合 grid-template-rows: masonrymasonry 网格)实现 "优先填充最短列" 的瀑布流,更符合传统瀑布流的视觉效果(需注意浏览器兼容性)。

核心原理:

  • display: grid:启用网格布局。
  • grid-template-columns: repeat(N, 1fr):设置 N 列,每列宽度平均分配。
  • grid-template-rows: masonry:关键属性,启用 "masonry 布局",让每行高度随内容动态调整,优先填充最短列(类似瀑布流)。

这里的图片是在小程序上写了展示的,大家看一下就行。

特点:

  • 优点:内容会优先填充最短列,列高更平衡,视觉效果更接近理想瀑布流;支持动态添加内容时自动平衡布局。
  • 缺点:grid-template-rows: masonry 是较新的 CSS 特性(2023 年起主流浏览器逐步支持),低版本浏览器(如 Safari 16 及以下)可能不兼容,需谨慎使用(可通过 caniuse 查看兼容性)。

三、 flex 分栏 + JS

1.根据index奇偶分为两列

这个就简单说一下,一般很少会用这样的方法,除非有特殊需求。

将列表分为两列,js中根据index,遍历出left列和right列。

typescript 复制代码
// 样式代码
.waterfall-container {
  display: flex;
  padding: 16rpx;
  gap: 16rpx; /* 列间距 */
}

.waterfall-column {
  flex: 1; /* 两列宽度相等 */
}

// js方法
/**
 * 将数组按索引奇偶性分为两列
 * @param arr 源数组(可传入任意类型的数组)
 * @returns 包含左列(偶数索引)和右列(奇数索引)的对象
 */
const splitArrayByIndex = () => {
  // 初始化左右两列数组
  let left = [];  // 存放偶数索引元素(index % 2 === 0)
  let right = []; // 存放奇数索引元素(index % 2 === 1)

  // 遍历数组,根据索引奇偶性分配元素
  arr.forEach((item, index) => {
    if (index % 2 === 0) {
      left.push(item);
    } else {
      right.push(item);
    }
  });

  return { left, right };
}

特点:

  • 优点:emm 简单?
  • 缺点:只能奇偶列出,不够灵活。
2. 估算item的高度,将item放入更短的列

直接来代码,样式都倒差不差。

typescript 复制代码
// html 代码
  <view class="waterfall-layout">
     <view class="column">
       <YourCard v-for="card in list.left" :key="card.id" :item="card" />
     </view>
     <view class="column">
       <YourCard v-for="card in list.left" :key="card.id" :item="card" />
     </view>
   </view>
   
// css代码
.waterfall-layout {
   width: fit-content;
   overflow-y: auto;
   display: grid;
   grid-template-columns: repeat(2, 1fr);
   gap: 12px;
   align-items: start;

   .column {
     width: fit-content;
   }
 }
  
// js代码
const splitArrayByHeight = (arr: any[]) => {
  let leftHeight = 0
  let rightHeight = 0
  const left = []
  const right = []
  
  arr.forEach(item => {
    // 根据内容估算高度(需根据实际内容调整系数)
    const itemHeight = item.resource ? 320 : 160 // 假设有图片的卡片更高
    
    if (leftHeight <= rightHeight) {
      left.push(item)
      leftHeight += itemHeight
    } else {
      right.push(item)
      rightHeight += itemHeight
    }
  })
  
  return [left, right]
}

特点:

  • 优点:更加灵活,能满足大部分的需求场景;
  • 缺点:对于不同高度的图片,适应性没那么好,有待优化,优化之后估计会更好用一些。

四、scroll-view 结合 grid-builder

文档直通车:

瀑布流

typescript 复制代码
<template>
  <view class="demo-container">
    <scroll-view 
      class="scroll-container"
      scroll-y="true"
      @scrolltolower="loadMore"
    >
      <view class="waterfall-container">
        <view class="column" v-for="(column, columnIndex) in columns" :key="columnIndex">
          <view @click="navigateToDetail(item)" class="item" 
            v-for="(item, index) in column"
            :key="index"
            :style="{ height: item.height + 'px' }"
          >
            <image :src="item.image" mode="widthFix" />
            <text class="title">{{ item.title }}</text>
          </view>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue'

// Mock数据生成函数
const generateMockData = (count: number) => {
  return Array.from({ length: count }, (_, i) => ({
    id: i,
    title: `Item ${i}`,
    image: `https://picsum.photos/200/${200 + Math.floor(Math.random() * 100)}`,
    height: 200 + Math.floor(Math.random() * 100)
  }))
}

const columns = ref([[], []]) // 两列瀑布流
const loading = ref(false)

// 分配数据到列
const distributeItems = (items: any[]) => {
  items.forEach(item => {
    // 找到当前高度较小的列
    const minHeightColumn = columns.value.reduce((prev, curr, index) => {
      const prevHeight = prev.reduce((sum, item) => sum + item.height, 0)
      const currHeight = curr.reduce((sum, item) => sum + item.height, 0)
      return currHeight < prevHeight ? curr : prev
    })
    minHeightColumn.push(item)
  })
}

const navigateToDetail = (item: any) => {
  console.log('navigateToDetail', item)
}

// 加载更多数据
const loadMore = () => {
  if (loading.value) return
  loading.value = true
  setTimeout(() => {
    const newData = generateMockData(10)
    distributeItems(newData)
    loading.value = false
  }, 1000)
}

onMounted(() => {
  // 初始加载数据
  const initialData = generateMockData(20)
  distributeItems(initialData)
})
</script>

<style lang="scss">
.demo-container {
  width: 100%;
  height: 100vh;
}

.scroll-container {
  height: 100%;
}

.waterfall-container {
  display: flex;
  padding: 10px;
  gap: 10px;
}

.column {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.item {
  background: #fff;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  
  image {
    width: 100%;
    display: block;
  }
  
  .title {
    padding: 8px;
    font-size: 14px;
    color: #333;
  }
}
</style>

特点:

  • 优点:组件很直接就能使用;
  • 缺点:可能不太稳定,具体看微信开发者社区,有些同学使用起来会出现没有正确渲染的问题。

总结

大家选择其中一个适合自己需求的来写就行,有更好的方法请分享!!!

相关推荐
专注前端30年6 小时前
2025 最新 Vue2/Vue3 高频面试题(10月最新版)
前端·javascript·vue.js·面试
代码改变世界100866 小时前
像素策略游戏:资源战争
css·游戏·css3
文火冰糖的硅基工坊6 小时前
[嵌入式系统-146]:五次工业革命对应的机器人形态的演进、主要功能的演进以及操作系统的演进
前端·网络·人工智能·嵌入式硬件·机器人
2401_837088507 小时前
ResponseEntity - Spring框架的“标准回复模板“
java·前端·spring
yaoganjili7 小时前
用 Tinymce 打造智能写作
前端
angelQ7 小时前
Vue 3 中 ref 获取 scrollHeight 属性为 undefined 问题定位
前端·javascript
Dontla7 小时前
(临时解决)Chrome调试避免跳入第三方源码(设置Blackbox Scripts、将目录添加到忽略列表、向忽略列表添加脚本)
前端·chrome
我的div丢了肿么办7 小时前
js函数声明和函数表达式的理解
前端·javascript·vue.js
云中雾丽7 小时前
React.forwardRef 实战代码示例
前端