介绍
瀑布流,又称流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。
瀑布流有很多种实现方式,像抖音、小红书的PC首页用的是脱离文档流 transform
定位的模式,这种比较复杂,我们不考虑。
本文采用的是flex布局,通过img标签的load事件,在该事件中获取每栏新的总高度,算出总高度最小的那一栏,并把下一个 item
放进该栏,以此类推,实现瀑布流效果。
在子组件中,为了防止图片在加载显示的过程中出现抖动的情况,我使用了 opacity: 0
先隐藏item,在load事件中,再改为 opacity: 1
,以达到更好的显示效果。
父组件使用
javascript
<template>
<view class="container">
<view v-for="colNum in state.columnNum" :key="colNum" class="columnItem">
<child-item
v-for="(item, i) in state.columnData[colNum]"
:key="i"
:item="item"
@load="imageLoad"
/>
</view>
</view>
<view class="loading">加载中...</view>
</template>
<script setup>
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
import { data } from './data.js'
import { reactive, getCurrentInstance } from 'vue'
import childItem from './child-item.vue'
const { proxy } = getCurrentInstance()
const state = reactive({
columnNum: 2,
minHeightColNum: 1,
columnData: {},
totalList: [],
pages: {
page: 1,
pageSize: 10
}
})
onLoad(() => {
setDefaultData()
getData()
})
// 模拟获取数据
function getData() {
if (state.pages.page == 1) setDefaultData()
return new Promise((resolve) => {
setTimeout(() => {
state.totalList.push(...data)
loadNextItem()
resolve()
}, 500)
})
}
/** @加载更多 **/
onReachBottom(() => {
state.pages.page++
getData()
})
/** @图片加载成功 **/
function imageLoad() {
getMinHeightColumnNum().then(() => loadNextItem())
}
/** @加载下一个元素 **/
function loadNextItem() {
const totalLength = Object.values(state.columnData).flat().length
if (totalLength == state.totalList.length) return
state.columnData[state.minHeightColNum].push(state.totalList[totalLength])
}
/** @获取最小列数 **/
async function getMinHeightColumnNum() {
let minHeight = Infinity // 初始化为无限大
return new Promise((resolve) => {
$('.columnItem', proxy).then((columItems) => {
columItems.forEach((item, index) => {
if (item.height < minHeight) {
minHeight = item.height
state.minHeightColNum = index + 1
}
})
resolve()
})
})
}
/** @设置默认数据 **/
function setDefaultData() {
state.totalList = []
Array(state.columnNum)
.fill(null)
.map((item, i) => {
state.columnData[i + 1] = []
})
}
/** @封装[jvideo](https://v.ixigua.com/iNQLjWJS/)查询DOM **/
function $(selecter, proxy) {
return new Promise((resolve) => {
uni
.createSelectorQuery()
.in(proxy)
.selectAll(selecter)
.boundingClientRect()
.exec((res) => {
resolve(res[0])
})
})
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
padding: 20rpx;
.columnItem {
width: calc(100% / v-bind('state.columnNum'));
height: fit-content;
& + .columnItem {
margin-left: 20rpx;
}
}
}
.loading {
padding: 20rpx;
text-align: center;
font-size: 28rpx;
color: #aaa;
}
</style>
子组件 child-item.vue
javascript
<template>
<view class="child-item" :class="{ isShow }">
<img class="pic" :src="item.imgUrl" mode="widthFix" @load="load" @error="load" />
<view class="info">
<text class="title">{{ item.title }}</text>
<text class="desc">{{ item.desc }}</text>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
defineProps({
item: {
type: Object,
default: () => ({})
}
})
const isShow = ref(false)
const emit = defineEmits(['load', 'error'])
function load(e) {
isShow.value = true
emit('load', e)
}
</script>
<style lang="scss" scoped>
.child-item {
font-size: 0;
border-radius: 10rpx;
margin-bottom: 20rpx;
overflow: hidden;
transition: all ease 0.35s;
opacity: 0;
.pic {
width: 100%;
}
.info {
background: #fff;
padding: 15rpx;
.title,
.desc {
display: block;
font-size: 28rpx;
word-break: break-all; // 允许单词内自动换行,如果一个单词很长的话
text-overflow: ellipsis; // 溢出用省略号显示
overflow: hidden; // 超出的文本隐藏
display: -webkit-box; // 作为弹性伸缩盒子模型显示
-webkit-line-clamp: 2; // 显示的行
-webkit-box-orient: vertical; // 设置伸缩盒子的子元素排列方式--从上到下垂直排列
}
.desc {
margin-top: 10rpx;
color: #666;
font-size: 26rpx;
}
}
&.isShow {
opacity: 1;
}
}
</style>
数据源 data.js
javascript
export const data = [
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679045108_thumb.jpg',
title: '1自动驾驶汽车对交通和城市规划的未来影响与挑战',
desc: '分析自动驾驶汽车对未来交通和城市规划的潜在影响,探讨相关挑战。'
},
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679044667_thumb.jpg',
title: '2人工智能与机器学习:颠覆性技术对未来的巨大影响',
desc: '探讨人工智能和机器学习如何在多个领域引发革命性变革,从工业到医疗,对未来产生深远影响。'
},
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679045190_thumb.jpg',
title: '3消灭传染病:全球卫生领域的挑战与创新',
desc: '探讨在全球范围内消灭传染病的挑战,突出卫生领域的创新方法。'
},
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679044581_thumb.jpg',
title: '4可持续城市发展:构建环保城市的策略和实践',
desc: '分析建设可持续城市的战略和实际方法,强调环保、资源利用和城市规划的重要性。'
},
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679044562_thumb.jpg',
title: '5生命科学的新前沿:基因编辑和生物技术的伦理挑战',
desc: '研究生命科学领域的最新发展,聚焦基因编辑和生物技术的伦理考量,探讨科技前沿的道德挑战。'
},
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679044716_thumb.jpg',
title: '6量子计算与量子技术应用的前沿探索',
desc: '深入研究量子计算和量子技术的前沿,展示这一领域的创新及其应用前景。'
},
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679045057_thumb.jpg',
title: '7加密货币与区块链:重塑全球金融体系的力量',
desc: '解析加密货币和区块链技术对金融体系的颠覆作用,重新定义了全球金融交易方式。'
},
{
imgUrl: 'https://www.logosc.cn/uploads/resources/2023/03/17/1679044779_thumb.jpg',
title: '8气候变化缓解的复杂性:全球视角下的挑战与机遇',
desc: '探讨应对气候变化的复杂性,强调全球合作,突出应对挑战所蕴含的机遇。'
}
]