跳到主要内容
前端实战:基于 Vue3 实现无限滚动、懒加载与瀑布流模块及优化策略 | 极客日志
JavaScript AI 大前端 算法
前端实战:基于 Vue3 实现无限滚动、懒加载与瀑布流模块及优化策略 综述由AI生成 基于 Vue3 实现无限滚动、懒加载及瀑布流模块的前端实战方案。文章分析了瀑布流布局的特点,阐述了处理海量数据时的核心难点,包括新元素加载时机判断以及防止无限滚动导致的内存泄漏问题。通过虚拟列表管理、布局容器初始化、动态渲染与销毁机制、无限滚动实现及内存优化策略等步骤,提供了完整的底层逻辑与代码实现。最终展示了可复用的 Masonry 组件代码及其用法,旨在帮助开发者构建高性能、动态且美观的内容展示界面。
忘忧 发布于 2026/4/6 更新于 2026/5/21 28 浏览前端实战:基于 Vue3 实现无限滚动、懒加载与瀑布流模块及优化策略
一、前言
在进行非完全标准化数据的可视化展示时,瀑布流是一种经常被采用的展示方法。瀑布流能够有效地将不同大小规格的内容以一种相对规整的方式呈现出来,尤其在处理海量数据时,依然能够保持出色的展示效果,给人一种杂而不乱、乱中有序的积极感受。
像小红书、淘宝、京东、千图网等平台,都采用了这种布局方式。固定列数、大量元素、每个元素高度各不相同的情况,就是我们所说的瀑布流布局。
实际在开发中,瀑布流离不开的一个情况就是海量数据,那么应对海量数据最好的设计模式是加入懒加载和无限滚动,但是做无限滚动还要同时做好页面的优化(即 DOM 的产生、销毁与复现策略),否则在滚动的过程中页面 DOM 不断堆砌,越来越多,会导致内存泄漏,严重的时候会导致页面崩溃。
二、无限滚动 + 懒加载 + 瀑布流模块的底层逻辑
在正式编码之前,我们要先做好顶层设计。AI 工具只能当做顾问,不能当做专家。当你无法理解 AI,无法驾驭 AI 的时候,请先慢下来,专注于自身思维的提升。
首先,我们要搞清楚这个模块实现的难点在哪:
新元素的加载时机 :新元素什么时候加载?那肯定是某一列上一个元素尾部到达某个界限的时候,这个界限可以是视口的最底部,也可以是视口底部再往下一个固定数值(比如视口底部往下 1000px,这样是为了提前加载图片内容优化用户体验)。
无限滚动带来的内存泄漏问题 :在元素不断加载的过程中,页面中累积的元素会越来越多,造成的内存泄露问题也越来越大,直至页面崩溃,所以很多网站为了避免这个问题,在做了瀑布流的同时拒绝实现无限滚动。那要怎么避免内存泄漏的问题呢?我们可以记录一个位置,比如视口上方 1000px,当元素的顶部随着滚动到达视口上方 1000px 位置的时候,就记录该元素的元数据信息,然后销毁该 DOM。这样就能避免 DOM 元素无限堆砌,同时记录的元数据信息(id、url、标题等重要数据)可以在用户回滚的时候重现这些元素。
也就是说,我们要:基于 DOM 元素与视口的空间关系来判断新元素添加、销毁与重建的时机,同时监测元素与视口顶部的距离,超过预定阈值,就记录元素元数据并销毁 DOM(这是为了在用户回滚的时候依然能按顺序生成销毁的 DOM 元素),以避免内存泄漏。
三、人机交互策略与实现过程
1. 虚拟列表管理
维护两个数据池:
virtualPool :存储所有已加载的元数据(包括尺寸、位置等信息)
visibleItems :当前视口内实际渲染的 DOM 元素集合
interface VirtualItem { id : string ; data : any ; height : number ; top : number ; column : number }
const virtualPool = ref<VirtualItem []>([])
const visibleItems = ref<Set <string >>(new Set ())
2. 布局容器初始化
使用 CSS Grid 创建自适应列布局,通过 ResizeObserver 动态调整列数:
<template >
<div ref ="container" >
<div v-for ="col in columns" :key ="col" />
</div >
</template >
<script setup >
const container = ref<HTMLElement >()
const columns = ref (3 )
onMounted (() => {
const observer = new ResizeObserver (entries => {
columns.value = Math .floor (entries[0 ].contentRect .width / 300 )
})
observer.observe (container.value )
})
</script >
3. 动态渲染与销毁机制 Intersection Observer 配置,创建双阈值观察器(顶部/底部):
const observer = new IntersectionObserver ((entries ) => {
entries.forEach (entry => {
const id = entry.target .dataset .id
if (entry.isIntersecting ) {
visibleItems.value .add (id)
} else {
if (entry.boundingClientRect .top < entry.rootBounds !.top ) {
recordPosition (id)
visibleItems.value .delete (id)
}
}
})
}, { root : null , rootMargin : '200px 0px' , threshold : 0.1 })
<template v-for ="col in columns" :key ="col" >
<div v-for ="item in getColumnItems(col)" :key ="item.id" >
<component :is ="visibleItems.has(item.id) ? 'RealComponent' : 'Placeholder'" :data-id ="item.id" :data ="item.data" />
</div >
</template >
4. 无限滚动实现 滚动事件节流处理使用 requestAnimationFrame 优化性能:
const checkScroll = ( ) => {
const { scrollTop, scrollHeight, clientHeight } = document .documentElement
if (scrollHeight - (scrollTop + clientHeight) < 500 ) {
loadMoreItems ()
}
}
window .addEventListener ('scroll' , () => {
requestAnimationFrame (checkScroll)
})
const loadMoreItems = async ( ) => {
const lastItem = virtualPool.value [virtualPool.value .length - 1 ]
const newItems = await fetchItems ({ offset : virtualPool.value .length , position : lastItem?.top || 0 })
newItems.forEach (item => {
virtualPool.value .push ({ ...item, height : 0 ,
})
}
5. 内存优化策略 const positionCache = new Map <string, { top : number, column : number }>()
const recordPosition = (id: string ) => {
const element = document .querySelector (`[data-id="${id} "]` )
if (element) {
positionCache.set (id, { top : element.getBoundingClientRect ().top + window .scrollY , column : parseInt (element.parentElement !.dataset .column !) })
}
}
const predictHeight = (dataType: string ) => {
const history = virtualPool.value
.filter (item => item.data .type === dataType)
.map (item => item.height )
return history.length > 0 ? Math .ceil (history.reduce ((a,b ) => a+b)/history.length ) : 200
}
如果还有更多需求,继续人机交互完善代码即可,核心是我提供的思路 + 强力的 AI 工具,剩下的就是时间问题。
四、最终代码呈现
1. 组件代码 <template>
<div ref="container">
<div v-for="(col, index) in columns" :key="index" :style="{ width: columnWidth + 'px', marginRight: gutter + 'px' }">
<div v-for="item in columnItems[index]" :key="item._uid" :class="itemClass" :style="{ transition: `all ${transitionDuration}ms ease`, transform: `translateY(${item.y}px)` }" @transitionend="handleTransitionEnd(item)">
<slot name="item" :item="item.data"></slot>
</div>
</div>
</div>
</template>
<script>
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
export default {
props: {
items: { type: Array, required: true },
columns: { type: Number, default: 2 },
gutter: { type: Number, default: 8 },
breakpoints: { type: Object, default: () => ({}) },
resizeObserver: { type: Boolean, default: true },
useImageLoader: { type: Boolean, default: true },
itemClass: { type: String, default: 'masonry-item' },
transitionDuration: { type: Number, default: 300 }
},
emits: ['layout-complete', 'item-positioned'],
setup(props, { emit }) {
const container = ref(null)
const columnHeights = ref([])
const columnItems = ref([])
const activeColumns = ref(props.columns)
const observer = ref(null)
// 计算列宽
const columnWidth = computed(() => {
if (!container.value) return 0
const totalGutter = (activeColumns.value - 1) * props.gutter
return (container.value.offsetWidth - totalGutter) / activeColumns.value
})
// 响应式断点处理
const handleBreakpoints = () => {
const breakpoints = Object.entries(props.breakpoints).sort((a, b) => b[0] - a[0])
const width = window.innerWidth
for (const [bp, cols] of breakpoints) {
if (width >= bp) {
activeColumns.value = cols
return
}
}
activeColumns.value = props.columns
}
// 布局核心算法
const layoutItems = () => {
columnHeights.value = new Array(activeColumns.value).fill(0)
columnItems.value = new Array(activeColumns.value).fill([])
props.items.forEach(item => {
const minHeight = Math.min(...columnHeights.value)
const columnIndex = columnHeights.value.indexOf(minHeight)
const newItem = { ...item, y: minHeight, _uid: Math.random().toString(36).substr(2, 9) }
columnItems.value[columnIndex] = [...columnItems.value[columnIndex], newItem]
// 触发单个元素定位事件
emit('item-positioned', { element: newItem, position: { x: columnIndex * (columnWidth.value + props.gutter), y: minHeight } })
// 更新列高度(假设已获取元素高度)
columnHeights.value[columnIndex] += item._height + props.gutter
})
// 触发布局完成事件
emit('layout-complete', { columns: activeColumns.value, containerHeight: Math.max(...columnHeights.value) })
}
// 图片加载处理
const loadImages = () => {
if (!props.useImageLoader) return
props.items.forEach(item => {
const img = new Image()
img.src = item.image
img.onload = () => {
item._height = (columnWidth.value * img.height) / img.width
layoutItems()
}
})
}
// 初始化
onMounted(() => {
handleBreakpoints()
loadImages()
layoutItems()
if (props.resizeObserver) {
observer.value = new ResizeObserver(() => {
handleBreakpoints()
layoutItems()
})
observer.value.observe(container.value)
}
window.addEventListener('resize', handleBreakpoints)
})
onBeforeUnmount(() => {
if (observer.value) observer.value.disconnect()
window.removeEventListener('resize', handleBreakpoints)
})
// 监听相关变化
watch(() => props.items, layoutItems)
watch(activeColumns, layoutItems)
return { container, columnWidth, columnItems }
}
}
</script>
<style>
.masonry-container {
position: relative;
display: flex;
justify-content: flex-start;
}
.masonry-column {
transition: all 0.3s ease;
}
.masonry-item {
position: absolute;
width: 100%;
will-change: transform;
}
</style>
2. 组件用法 <template>
<MasonryLayout :items="items" @layout-complete="handleLayoutComplete" />
</template>
<script setup>
import { onMounted } from 'vue'
const handleLayoutComplete = ({ columns, containerHeight }) => {
console.log(`当前列数:${columns},容器高度:${containerHeight}px`)
}
// 滚动加载更多
window.addEventListener('scroll', () => {
if (nearBottom()) {
items.value = [...items.value, ...newItems]
}
})
</script>
五、结语 瀑布流 + 无限滚动 + 懒加载的结合能够提升用户体验和页面性能:瀑布流以错落有致的布局呈现内容,增加视觉吸引力;无限滚动让用户无需翻页即可持续浏览,提高交互流畅性;懒加载则延迟加载非可视区域的内容,减少初始加载时间与资源消耗,从而实现高效、动态且美观的内容展示。
只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online