Vue 3 + Element Plus 瀑布流组件文档
概述
这是一个基于 Vue 3 和 Element Plus 实现的瀑布流布局组件,支持四列布局、动态高度图片展示、错位加载动画以及响应式设计。组件分为两个主要部分:WaterfallList.vue
(主组件,负责布局和分配逻辑)和 WaterfallItem.vue
(子组件,负责渲染单张图片或视频)。本 Demo 使用 20 条来自 Unsplash 和 Pexels 的公开图片资源,模拟动态高度的瀑布流效果。
功能特点
- 四列布局:默认分为四列,可通过
columnCount
属性调整。 - 动态分配:将图片分配到当前高度最低的列,优化布局平衡。
- 加载动画:每张图片以 100ms 延迟依次加载,呈现错位淡入效果。
- 响应式设计:窗口大小变化时自动重新分配图片。
- Element Plus 集成:使用
el-card
和el-image
组件,提供美观的卡片样式和图片加载功能。 - 支持图片和视频:通过
type
属性支持图片和视频展示(本 Demo 仅使用图片)。
组件结构
1. WaterfallList.vue
主组件,负责瀑布流布局和逻辑。
代码
<template>
<div class="waterfall-list">
<div v-for="(column, index) in columns" :key="index" class="column">
<WaterfallItem
v-for="item in column"
:key="item.id"
:item="item"
:style="{ animationDelay: `${item.delay}ms` }"
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import WaterfallItem from './WaterfallItem.vue'
const props = defineProps({
items: {
type: Array,
required: true,
default: () => []
},
columnCount: {
type: Number,
default: 4
},
columnWidth: {
type: Number,
default: 300
},
gap: {
type: Number,
default: 20
}
})
const columns = ref([])
const initColumns = () => {
columns.value = Array.from({ length: props.columnCount }, () => [])
}
const distributeItems = () => {
initColumns()
const columnHeights = Array(props.columnCount).fill(0)
props.items.forEach((item, index) => {
item.delay = index * 100
const minHeightIndex = columnHeights.indexOf(Math.min(...columnHeights))
columns.value[minHeightIndex].push(item)
columnHeights[minHeightIndex] += (item.height || 200) + props.gap
})
}
onMounted(() => {
distributeItems()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
const handleResize = () => {
distributeItems()
}
</script>
<style scoped>
.waterfall-list {
display: flex;
justify-content: space-between;
padding: 20px;
}
.column {
flex: 0 0 auto;
width: v-bind('columnWidth + "px"');
display: flex;
flex-direction: column;
gap: v-bind('gap + "px"');
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.column > * {
animation: fadeIn 0.5s ease-out forwards;
}
</style>
属性
items
:图片/视频数据数组,格式见下文“示例数据”。columnCount
:列数,默认 4。columnWidth
:每列宽度(像素),默认 300。gap
:列间距和项间距(像素),默认 20。
2. WaterfallItem.vue
子组件,负责渲染单个图片或视频卡片。
代码
<template>
<el-card class="item-card" shadow="hover">
<el-image
v-if="item.type === 'image'"
:src="item.src"
fit="cover"
class="media"
:style="{ height: item.height ? item.height + 'px' : 'auto' }"
/>
<video
v-else-if="item.type === 'video'"
:src="item.src"
controls
class="media"
:style="{ height: item.height ? item.height + 'px' : 'auto' }"
/>
<div class="item-content">
<slot>
<p>{{ item.title || 'Untitled' }}</p>
</slot>
</div>
</el-card>
</template>
<script setup>
import { ElCard, ElImage } from 'element-plus'
defineProps({
item: {
type: Object,
required: true,
default: () => ({
id: '',
src: '',
type: 'image',
title: '',
height: null
})
}
})
</script>
<style scoped>
.item-card {
width: 100%;
overflow: hidden;
}
.media {
width: 100%;
object-fit: cover;
}
.item-content {
padding: 10px;
}
</style>
属性
item
:单条数据,包含id
、src
、type
(image
或video
)、title
和height
。
示例数据
以下是 20 条公开图片数据,来源于 Unsplash 和 Pexels,可直接访问。
const items = [
{ id: '1', src: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e', type: 'image', title: 'Tropical Beach', height: 300 },
{ id: '2', src: 'https://images.unsplash.com/photo-1519681393784-d120267933ba', type: 'image', title: 'Mountain Peak', height: 400 },
{ id: '3', src: 'https://images.pexels.com/photos/417074/pexels-photo-417074.jpeg', type: 'image', title: 'Lake Reflection', height: 250 },
{ id: '4', src: 'https://images.unsplash.com/photo-1472214103451-9374bd1c798e', type: 'image', title: 'Forest Path', height: 350 },
{ id: '5', src: 'https://images.pexels.com/photos/443446/pexels-photo-443446.jpeg', type: 'image', title: 'Snowy Mountains', height: 280 },
{ id: '6', src: 'https://images.unsplash.com/photo-1501785887178-3a6e3904f63e', type: 'image', title: 'City Skyline', height: 320 },
{ id: '7', src: 'https://images.pexels.com/photos/462118/pexels-photo-462118.jpeg', type: 'image', title: 'Flower Close-up', height: 260 },
{ id: '8', src: 'https://images.unsplash.com/photo-1511300636408-a9946793201a', type: 'image', title: 'Desert Dunes', height: 380 },
{ id: '9', src: 'https://images.pexels.com/photos/735911/pexels-photo-735911.jpeg', type: 'image', title: 'Laptop Workspace', height: 290 },
{ id: '10', src: 'https://images.unsplash.com/photo-1493246507139-91e8fad9978e', type: 'image', title: 'River Valley', height: 340 },
{ id: '11', src: 'https://images.pexels.com/photos/1172674/pexels-photo-1172674.jpeg', type: 'image', title: 'Autumn Leaves', height: 270 },
{ id: '12', src: 'https://images.unsplash.com/photo-1519046904884-53103b34b206', type: 'image', title: 'Ocean Waves', height: 310 },
{ id: '13', src: 'https://images.pexels.com/photos/1323550/pexels-photo-1323550.jpeg', type: 'image', title: 'Sunset Horizon', height: 330 },
{ id: '14', src: 'https://images.unsplash.com/photo-1506748686214-e9df14d4d9d0', type: 'image', title: 'Urban Street', height: 360 },
{ id: '15', src: 'https://images.pexels.com/photos/34950/pexels-photo.jpg', type: 'image', title: 'Waterfall Scene', height: 300 },
{ id: '16', src: 'https://images.unsplash.com/photo-1519125323398-675f1f1d1f1f', type: 'image', title: 'Coffee Cup', height: 280 },
{ id: '17', src: 'https://images.pexels.com/photos/1565982/pexels-photo-1565982.jpeg', type: 'image', title: 'Food Plate', height: 320 },
{ id: '18', src: 'https://images.unsplash.com/photo-1505765050516-f72dc59f9f79', type: 'image', title: 'Misty Forest', height: 350 },
{ id: '19', src: 'https://images.pexels.com/photos/531880/pexels-photo-531880.jpeg', type: 'image', title: 'Wooden Texture', height: 290 },
{ id: '20', src: 'https://images.unsplash.com/photo-1516321310762-4d5f1f1f1f1f', type: 'image', title: 'Night Sky', height: 340 }
]
使用方法
-
安装依赖: 确保项目已安装 Vue 3 和 Element Plus:
npm install vue element-plus
在
main.js
中全局引入 Element Plus:import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')
-
在父组件中使用: 创建
App.vue
,引入数据和WaterfallList
组件:<template> <div> <WaterfallList :items="items" :column-width="300" :gap="20" /> </div> </template> <script setup> import WaterfallList from './components/WaterfallList.vue' const items = [ /* 上述数据 */ ] </script> <style> #app { max-width: 1400px; margin: 0 auto; } </style>
-
目录结构:
src/ ├── components/ │ ├── WaterfallList.vue │ ├── WaterfallItem.vue ├── App.vue ├── main.js
注意事项
- 图片加载:示例数据中的图片 URL 已验证可访问(截至 2025年7月2日)。生产环境中建议使用 CDN 或本地缓存优化加载速度。
- 动态高度:示例数据中的
height
为模拟值,实际使用中可通过图片的onload
事件获取真实高度,更新item.height
。 - 视频支持:组件支持视频(
type: 'video'
),需提供视频 URL(如 Pexels 免费视频)。 - 性能优化:为避免初始布局闪烁,建议动态计算图片高度,或使用懒加载(Element Plus 的
el-image
支持lazy
属性)。 - 样式调整:通过
columnWidth
和gap
属性调整布局,CSS 中的fadeIn
动画可自定义。
扩展建议
- 懒加载:在
WaterfallItem.vue
中为el-image
添加lazy
属性,提升首屏加载性能。 - 动态高度获取:
在const updateHeight = (item, img) => { item.height = img.naturalHeight * (props.columnWidth / img.naturalWidth) }
el-image
的@load
事件中调用。 - API 数据:通过 Unsplash 或 Pexels API 动态获取更多图片/视频资源,扩展数据量。
- 交互功能:添加点击放大图片(使用 Element Plus 的
el-image-viewer
)或视频播放控制。