前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >实操图片流页面体验优化

实操图片流页面体验优化

作者头像
前端小鑫同学
发布于 2024-08-21 06:03:22
发布于 2024-08-21 06:03:22
15800
代码可运行
举报
运行总次数:0
代码可运行

“👨🏻‍💻和代码有一个能🏃🏻‍♀️就行”,看似一句玩笑话但可能已经成为了事实。图片优化作为前端应该必须掌握的一项技能,但是你做三年开发也并不会真正的优化一次。

这几天在掘金看到了我将 2K stars 的 《丑丑头像》,用 next.js 重写了 这篇文章,在评论区有几个的人在讨论说遇到了滚动时卡顿的问题,其实整个页面仅展示 10 张随机生成的头像图片,这看起来不是个好的现象,正好可以尝试做一点优化看看效果怎么样。

原因探索

因为不清楚测量哪些指标可以直指卡顿的原因,所以我还是先对页面进行一次分析:

  1. 图片请求:每次刷新页面会同时发起 10 次图片资源请求;
  2. 图片大小:每次响应的图片大小在 100kB ~ 350kB 左右;
  3. 图片格式:预览和下载均为 SVG 格式;
  4. 图片要求:支持调整背景色以及支持i透明背景。

制定方案

通过网络请求这块可以看到,造成这次卡顿的主要原因可能有两个:

  • 同时请求多: 同时发起过多的网络请求势必对浏览器的性能会造成明显影响,这里我选择利用懒加载(Lazy Loading) 的方式处理,保证视图进入页面 1/4 后才开始加载新的图片资源。
  • 图片尺寸大: 每张图片的尺寸偏大,在加载到页面中时同样有卡顿现象,这里我选择将预览和下载分开,保持下载的规则不变,将预览时的图像调整为渐进式 JPEG 格式。

难度升级

目前的页面加载的图片数量为 10,单从数量来看是很少的,所以我选择将图片数量提升到 1000 以上。在图片依次加载完毕后 DOM 中将有大量的不可释放的节点,再次造成卡顿。

解决这个问题的方案我选择虚拟列表,保证 DOM 中不会有大量不可释放的节点。

方案实施

需要编写一个懒加载组件和一个瀑布流布局组件,以及在 Service 端对预览图片动态转换为渐进式 JPEG 格式。

LazyImage 组件:

实现图片懒加载组件的核心是应用 IntersectionObserver API,此提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。

在组件实际编写中我选择直接 react-intersection-observer 代替原生 API,此模块提供了适用于 Reacrt 中用来监控组件状态的钩子 useInView Hoook API,配置可见区域的比例为1/4,当 next/image 组件进去视图1/4后 inView 会切换为 true。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { useState } from 'react';
import { useInView } from 'react-intersection-observer';
import Image from "next/image";
import { placeholder } from './placeholder';export function LazyImage({ src = '' }) {
    const [loaded, setLoaded] = useState(false);const { ref, inView } = useInView({
        triggerOnce: true,
        threshold: 0.25,
    });return (
        <div ref={ref} style={{ width, height }}>
            {inView && <>
                <Image src={src} />
            </>}
        </div>
    );
}

MasonryLayout 组件:

MasonryLayout 组件由 MasonryLayout 容器CardCell 内容项两部分组成:

MasonryLayout 容器: 利用 ResizeObserver API 监听容器尺寸的变化,根据内容项预设的尺寸计算 columnCountrowCount 两个属性,其中容器由 react-window 模块中的 VariableSizeGrid 提供,这个模块的主要特点就是用于高效渲染大量列表和表格数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const columnWidth = 342;
const rowHeight = 400;const MasonryLayout: React.FC<MasonryLayoutProps> = ({ images }: MasonryLayoutProps) => {
    const [containerWidth, setContainerWidth] = useState<number>(1200);
    const [containerHeight, setContainerHeight] = useState<number>(800);
    const containerRef = useRef<HTMLDivElement>(null);useEffect(() => {
        const handleResize = () => {
            if (containerRef.current) {
                setContainerWidth(containerRef.current.offsetWidth);
                setContainerHeight(window.innerHeight - 154);
            }
        };const resizeObserver = new ResizeObserver(handleResize);
        const currentContainer = containerRef.current;
        if (currentContainer) {
            resizeObserver.observe(currentContainer);
        }handleResize();return () => {
            if (currentContainer) {
                resizeObserver.unobserve(currentContainer);
            }
        };
    }, []);const getColumnCount = useCallback(() => {
        return Math.floor(containerWidth / columnWidth);
    }, [containerWidth]);const getRowHeight = useCallback((index: number) => rowHeight, []);const columnCount = getColumnCount();
    const rowCount = Math.ceil(images.length / columnCount);return (
        <div ref={containerRef} style={{ width: '100%' }}>
            <Grid
                columnCount={columnCount}
                columnWidth={() => columnWidth}
                height={containerHeight}
                rowCount={rowCount}
                rowHeight={getRowHeight}
                width={containerWidth}
                itemData={{ images, columnCount, columnWidth }}
            >
                {CardCell}
            </Grid>
        </div>
    );
};

CardCell 内容项: 这个 Card 组件就是源代码中主要的显示区域,直接当做 CardCell 会发现丢失了每行和没列之间的间距,通过网页审查元素可以看到使用 react-window 模块后,每个 Call 区域都是通过定位的方式实现排列,所以我通过判断 CardCell 的位置为每一个 CardCell 添加了合适的 lefttop 属性,实现了每项之间的间隔。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const CardCell: React.FC<CellProps> = ({ columnIndex, rowIndex, style, data }) => {
    const { images, columnCount } = data;
    const imageIndex = rowIndex * columnCount + columnIndex;
    const image = images[imageIndex];if (!image) return null;const rowInIndex = imageIndex % columnCount;return (
        <Card className="w-[342px] h-[400px]" style={{
            ...style,
            boxSizing: 'border-box',
            left: `${(columnWidth + gap) * rowInIndex}px`,
            top: `${(rowHeight + gap) * rowIndex}px`
        }} key={image.url}>
            <CardContent className={'p-5'}>
                <LazyImage
                    src={image.url}
                    alt={'index'}
                    width={300}
                    height={300}
                />
            </CardContent>
            <CardFooter className={'flex justify-around items-center'}>
                <Button onClick={() => onDownload(image.url)} variant="outline"> 下载 </Button>
            </CardFooter>
        </Card>
    );
};
  • 直接迁移无间隔
  • 动态添加间隔

渐进式 JPEG:

渐进式JPEG(Progressive JPEG)一种渐进式 JPEG 压缩格式在呈现图像的方式上类似于 GIF(图形互换格式)。在网页浏览器中呈现时,图像会逐层下载,逐渐显现。直到完全呈现,图像逐渐变得清晰。

支持渐进式 JPEG 需要 Service 端支持,sharp 是用于在 Nodejs 中对图片高效加工的模块,仅通过一个选项就可以支持返回渐进式 JPEG 格式。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 提供渐进式 JPEG 预览, 并降低质量
const jpegBuffer = await sharp(Buffer.from(result))
    .jpeg({ progressive: true, quality: 75 })
    .toBuffer();
return new Response(jpegBuffer, {
    status: 200,
    headers: {
        'Content-Type': `image/jpeg`,
    }
});

遗留问题

每当新的内容项 CardCell 进入视图1/4 时就会发起图片资源的请求,但是由于图片资源加载时间长,你将内容项继续向上滚动移出了视图,新的内容项继续进入视图,继续发起图片资源请求,这样就造成了无法及时加载当前视图中的图片,因为它排到的请求的队尾,我考虑了两种参考方案:

  1. 分页控制:只有当进入视图的图片资源加载完成后才运行继续加载下一分页的数据;
  2. 取消请求:拦截图片资源请求,将被移出视图的内容项对应的图片资源请求终止。

目前这个遗留问题在原项目中不存在,因为原项目要求仅展示 10 张图片。

总结:

通过上述优化措施,不仅解决了原有页面的卡顿问题,还提高了页面在大量图片展示情况下的性能。此外,这些技术方案也为其他类似项目提供了有价值的参考。对于前端开发者而言,了解并掌握这些优化技巧是非常重要的,特别是在现代Web应用中,高性能的图片展示已经成为用户良好体验的关键因素之一。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
图片渐进式加载优化实践指南
沉浸式趣谈
2024/11/21
960
图片渐进式加载优化实践指南
react-grid-layout 之核心代码分析与实践
React Grid Layout 是一个用于构建可拖拽、可调整大小和自适应的网格布局的 React 组件库。通过简单易用的API,在 React 项目中能够快速构建复杂网格布局,轻松地创建可交互的网格布局,适用于构建面向用户的仪表盘、拖拽式页面布局等应用,提供良好的交互体验。通常用于自定义搭建页面中,例如我们公司用到自定义搭建工作台系统等等
政采云前端团队
2023/10/16
2.6K0
react-grid-layout 之核心代码分析与实践
实战:使用 React 实现渐进式加载图片
图片对网站有很大的影响。它们的存在改善了用户体验,提高了用户粘性。然而,加载高质量的图片需要时间,而且会让这种体验更令人沮丧,尤其是在网速较慢的情况下。
前端修罗场
2022/07/29
3.9K0
实战:使用 React 实现渐进式加载图片
【JS】1684- 重学 JavaScript API - Resize Observer API
Resize Observer API[1] 可以帮助我们监听元素尺寸的变化,并在尺寸变化时执行一些操作。例如,我们可以使用 Resize Observer API 来动态调整 UI 布局、加载或卸载图片等。
pingan8787
2023/09/01
6490
【JS】1684- 重学 JavaScript API - Resize Observer API
现代图片性能优化及体验优化指南
图片资源,在我们的业务中可谓是占据了非常大头的一环,尤其是其对带宽的消耗是十分巨大的。
Sb_Coco
2023/04/01
1.6K0
现代图片性能优化及体验优化指南
精读《怎么用 React Hooks 造轮子》
上周的 精读《React Hooks》 已经实现了对 React Hooks 的基本认知,也许你也看了 React Hooks 基本实现剖析(就是数组),但理解实现原理就可以用好了吗?学的是知识,而用的是技能,看别人的用法就像刷抖音一样(哇,饭还可以这样吃?),你总会有新的收获。
黄子毅
2022/03/14
2.5K0
现代图片性能优化及体验优化指南 - 图片类型及 Picture 标签的使用
图片资源,在我们的业务中可谓是占据了非常大头的一环,尤其是其对带宽的消耗是十分巨大的。
Sb_Coco
2023/03/01
1.1K0
现代图片性能优化及体验优化指南 - 图片类型及 Picture 标签的使用
性能:React 实战优化技巧
🌿 性能优化的主要点: 1️⃣ 减少 DOM 的渲染频次 2️⃣ 减少 DOM 的渲染范围 3️⃣ 非必要的内容延后处理
奋飛
2024/05/25
1340
性能:React 实战优化技巧
一篇看懂 React Hooks
React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想尝试的同学安装此版本即可。
前端迷
2019/08/05
3.8K0
逐步拆解React组件—Lazyload懒加载
在平时开发的时候我们总会遇到长列表,因为本身web在长列表的性能并不是特别好;加之web本身受到网络波动影响特别大,在首屏同时加载过多的内容会导致卡顿不流畅响应速度慢等问题。对此我们常用懒加载机制来进行优化。
gary12138
2022/10/05
1.8K0
Flex笔记_MX DataGrid、列表和树
columnCount、columnWidth、dataProvider、iconField、iconFunction、labelField、labelFunction、
LeoXu
2018/08/15
2.8K0
懒加载 React 长页面 - 动态渲染组件
长页面在前端开发中是非常常见的。例如下图中的电商首页,楼层数据来自运营人员在后台的配置,楼层数量是不固定的,同时每个楼层可能会依赖更多翻页数据。在这种情况下,如果一次性将页面全部渲染,可想而知,我们的页面直出效率(fmp, fid)会受到影响。
用户3806669
2021/04/30
3.6K0
懒加载 React 长页面 - 动态渲染组件
React & TDesign | 多尺寸无限瀑布流图库
在刷某些App的时候经常遇到双列Feed流的模式,因为UGC的尺寸不一致,还会有不对齐的方式来展示。
花花Binki
2024/05/16
6490
谈谈Web应用中的图片优化技巧及反思
这篇文章,我们将一起探讨,web应用中能对图片进行什么样的优化,以及反思一些“负优化”手段
桃翁
2019/08/26
2K0
谈谈Web应用中的图片优化技巧及反思
iOS 图片渐进式下载
为了省去制作图片的麻烦,我就直接拿YYWebImage里面的图片了,我个人也是建议使用这个图片框架来做渐进式下载。 先看下YYKit中做的效果图。 渐进式图片 图片加载很美观,用户体验性非常棒。
Raindew
2018/06/14
1.5K0
前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略
在进行非完全标准化数据的可视化展示时,瀑布流是一种经常被采用的展示方法。瀑布流能够有效地将不同大小规格的内容以一种相对规整的方式呈现出来,尤其在处理海量数据时,依然能够保持出色的展示效果,给人一种杂而不乱、乱中有序的积极感受。
watermelo37
2025/03/24
3790
前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略
React Native在Android平台运行gif的解决方法
概述 目前RN在Android平台上不支持gif格式的图片,而在ios平台是支持的,期待以后的版本中系统也是可以默认支持Android的。首先说下在ios平台怎么加载gif呢? <Image source= {require('./img/loading.gif')} style = {styles.loading}/> 完整实例: xport default class Loading extends React.Component { render(){ if (!
xiangzhihong
2018/01/26
1.4K0
React 并发功能体验-前端的并发模式已经到来。
React 是一个开源 JavaScript 库,开发人员使用它来创建基于 Web 和移动的应用程序,并且支持构建交互式用户界面和 UI 组件。React 是由 Facebook 软件工程师 Jordan Walke 创建,React 的第一个版本在七年前问世,现在,Facebook 负责维护。React框架自首次发布以来,React 的受欢迎程度直线飙升,热度不减。 2020 年 10 月,React 17 发布了,但令人惊讶的是——“零新功能”。当然,这并不是真的表示没有任何新添加的功能,让广大程序员使用者兴奋。事实上,这个版本为我们带来了很多重大功能的升级及16版本的bug修复,并推出了:Concurrent Mode 和Suspense。 虽然这两个功能尚未正式发布,这些功能已提供给开发人员进行测试。一旦发布,它们将改变 React 呈现其 UI 的方式,从而达到双倍提高性能和用户体验。
葡萄城控件
2021/07/08
6.4K0
Web 现代应用程序架构下的性能优化,渐进式的极致艺术。
本文是 Rendering on the Web: Performance Implications of Application Architecture (Google I/O ’19) 这篇谷歌工程师带来的现代应用架构体系下的优化相关演讲的总结,演讲介绍了以下优化手段:
ssh_晨曦时梦见兮
2022/03/09
9530
Web 现代应用程序架构下的性能优化,渐进式的极致艺术。
手把手教你如何实现大量图片的自适应图片页面的排列
每一行的总宽度不能超过容器本身的宽度,当前行如果剩余宽度足够,就可以追加新图片。
winty
2020/07/21
1.1K0
手把手教你如何实现大量图片的自适应图片页面的排列
推荐阅读
相关推荐
图片渐进式加载优化实践指南
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验