旅游项目中的图片展示如何做到丝滑流畅?关键在于懒加载与无限滚动的默契配合!
今天在开发旅游类项目的瀑布流页面时,我实现了一套高性能的图片展示方案。这个方案核心在于两点:无限滚动加载和图片懒加载,配合React Hooks和CSS模块化,让用户体验达到极致。可以看下面的具体效果
下面我来详细拆解实现过程。
无限滚动加载:滚动即加载的魔法
核心文件:Waterfall.jsx和Collection.jsx
无限滚动的基本原理是:当用户滚动到页面底部时,自动触发加载更多数据的操作。我们使用IntersectionObserver API来实现这个效果:
// Waterfall.jsx 关键代码
useEffect(() => {
const observer = new IntersectionObserver(([entry], obs) => {
if (entry.isIntersecting) {
fetchMore() // 触发加载更多
}
})
if (loader.current) observer.observe(loader.current)
return () => observer.disconnect()
}, [])
这里的关键元素是位于瀑布流底部的加载指示器:
<div ref={loader} className={styles.loader}>加载中</div>
当这个元素进入视口时,观察者会触发fetchMore函数请求更多数据。配合父组件Collection.jsx的使用:
// Collection.jsx
const Collection = () => {
const { loading, images, fetchMore } = useImageStore()
useEffect(() => {
fetchMore() // 初始化加载
}, [])
return <Waterfall images={images} fetchMore={fetchMore} loading={loading} />
}
这种设计实现了加载与展示分离:瀑布流组件只负责展示和触发加载,实际的数据获取逻辑由父组件通过状态管理注入。
双列瀑布流布局:CSS的巧妙设计
核心文件:waterfall.module.css
要实现美观的瀑布流布局,CSS的编写至关重要:
.wrapper {
display: flex;
justify-content: space-between;
padding: 16px;
flex-wrap: nowrap;
position: relative;
}
.column {
width: 48%;
margin: 0 1%;
display: flex;
flex-direction: column;
}
.loader {
position: absolute;
bottom: 0;
right: 0;
left: 0;
height: 80px;
text-align: center;
}
这里使用Flex布局创建两个等宽列(各占48%+1%边距)。特别值得注意的是加载器的定位:
position: absolute使其脱离文档流bottom: 0定位在容器底部left:0和right:0保证全宽
在JSX中,图片被分配到两列中:
<div className={styles.column}>
{images.filter((_, i) => i % 2 === 0).map(img => (
<ImageCard key={img.id} {...img} />
))}
</div>
<div className={styles.column}>
{images.filter((_, i) => i % 2 !== 0).map(img => (
<ImageCard key={img.id} {...img} />
))}
</div>
通过简单的奇偶过滤,实现图片在双列中的均匀分布。
图片懒加载:性能优化的关键
核心文件:ImageCard.jsx和card.module.css
当页面包含大量图片时,直接加载所有图片会导致性能问题。解决方案是懒加载——只有当图片进入视口时才加载真实资源。
实现方案:
// ImageCard.jsx
useEffect(() => {
const observer = new IntersectionObserver(([entry], obs) => {
if (entry.isIntersecting) {
const img = entry.target
const oImg = document.createElement('img')
oImg.src = img.dataset.src
oImg.onload = () => {
img.src = img.dataset.src // 图片加载完成后设置真实src
}
obs.unobserve(entry.target) // 加载后停止观察
}
})
observer.observe(imgRef.current)
}, [])
在渲染部分,我们使用data-src存储真实URL:
<div style={{ height }} className={styles.card}>
<img ref={imgRef} data-src={url} className={styles.img} />
</div>
配合精心设计的CSS保证图片展示效果:
.card {
width: 100%;
margin: 1%;
background-color: #eee; /* 占位背景色 */
overflow: hidden;
border-radius: 16px;
}
.img {
width: 100%;
height: 100%;
object-fit: cover; /* 关键属性:保持宽高比并裁剪 */
display: block;
}
object-fit: cover是这里的神来之笔,它确保不同尺寸的图片都能完美填充容器,保持视觉一致性。
数据模拟:Mock.js的妙用
核心文件:data.js
在开发阶段,我们使用Mock.js模拟后端API:
// 图片数据生成
const getImages = (page, pageSize = 10) => {
return Array.from({ length: pageSize }, (_, i) => ({
id: `${page}-${i}`,
height: Mock.Random.integer(300, 500),
url: Mock.Random.image('300x400', Mock.Random.color(), '#fff', 'img')
}))
}
// API路由
{
url: '/api/images',
method: 'get',
response: ({ query }) => {
const page = Number(query.page) || 1;
return {
code: 0,
data: getImages(page)
}
}
}
Mock.js的Random.image()方法生成占位图片URL,格式为:
'https://dummyimage.com/300x400/ff0000/ffffff'
其中参数依次为:尺寸、背景色、文字颜色和文字内容。这种模拟方式让我们无需等待真实API就能开发前端功能。
数据获取:axios封装
核心文件:home.js和config.js
我们对axios进行了统一封装,简化API调用:
// config.js
axios.defaults.baseURL = 'http://localhost:5173/api'
// 响应拦截
axios.interceptors.response.use((data) => {
return data.data // 直接返回核心数据
})
// home.js
export const getImages = (page) => {
return axios.get('/images', { params: { page } })
}
这种封装带来两大好处:
- 统一管理基础URL
- 响应拦截器自动剥离外层结构,直接返回核心数据
性能优化技巧总结
-
观察器资源释放
在useEffect清理函数中调用observer.disconnect(),防止内存泄漏return () => observer.disconnect() -
图片加载优化
使用临时Image对象预加载,避免阻塞渲染:const oImg = document.createElement('img') oImg.src = img.dataset.src -
虚拟滚动替代方案
虽然项目未实现,但思路值得提及:- 仅渲染可视区域内图片
- 动态计算图片位置
- 使用空白占位保持滚动条高度
-
请求防抖
可在fetchMore函数中加入防抖逻辑,避免滚动事件频繁触发请求
遇到的坑与解决方案
- 图片闪烁问题
初始:直接设置src导致加载过程中的空白
解决:添加背景色占位 + 加载完成后再显示 - 重复加载
初始:未取消观察导致进入视口多次触发
解决:加载后调用obs.unobserve(entry.target) - 图片尺寸跳跃
现象:懒加载导致布局跳动
解决:在卡片div上设置固定高度style={{ height }}
总结
今天的开发让我深刻理解了现代前端性能优化的核心思路:按需加载。通过Intersection Observer API,我们实现了:
- 滚动到页面底部自动加载(无限滚动)
- 图片进入视口才加载(懒加载)
- 组件化设计保证可维护性
技术栈组合:
- React Hooks(useEffect, useRef)
- CSS Modules样式隔离
- Axios网络请求
- Mock.js数据模拟
最终效果:用户无限滚动浏览图片,系统按需加载资源,完美平衡体验与性能。这种模式特别适合旅游类项目的图片展示,后续可扩展为三列布局、加入分类过滤等功能。