skip to content
Logo 三七の小站

AVIF导致浏览器卡顿

/ 9 min read

1、问题根源分析:为什么大量 AVIF 动图会导致卡顿?

浏览器卡顿的直接原因是 CPU 占用过高内存(RAM)急剧增加,导致浏览器主线程(Main Thread)被阻塞。主线程负责页面渲染、布局计算、JavaScript 执行和响应用户交互(如滚动、点击)。一旦它被繁忙的解码工作占据,网页就会出现掉帧、滚动不流畅甚至无响应。

具体来说,根本原因有以下几点:

  1. 解码计算密集 (CPU Intensive) AVIF 基于 AV1 视频编码,这是一种极其复杂的编码格式。为了实现超高的压缩率,它使用了复杂的帧间预测、块划分等技术。相应地,将其解码还原为像素图像也需要大量的 CPU 计算。单个 AVIF 解码可能很快,但 几十个动图同时请求解码时,CPU 瞬间不堪重负

  2. 内存消耗巨大 (Memory Pressure) 一个动图是由多帧组成的。解码后的每一帧都是一张原始的位图(Bitmap),其内存占用为 宽度 × 高度 × 4字节 (RGBA)。一个 800x600 的动图,一帧就需要 800 * 600 * 4 ≈ 1.92MB 的内存。如果有20个这样的动图,并且浏览器为了流畅播放需要预加载几帧,内存占用会轻松飙升数百MB,对低内存设备(尤其是移动设备)是致命的。

  3. 同时播放的“资源竞赛” (Resource Contention) 与单个 <video> 标签不同,页面上的多个 <img> 标签会各自独立地尝试播放动画。它们会同时争抢 CPU 和内存资源,没有一个统一的调度中心。这种“野蛮”的播放方式很容易导致系统资源在短时间内被耗尽。


2、优化措施:从策略到代码

优化核心思路:避免同时加载、同时解码、同时播放。按需、分时、分批地处理动图。

措施一:控制播放策略 (最重要)

这是最有效、最应该首先实施的策略。不要让所有动图一进入页面就开始播放。

方案:使用 Intersection Observer API 实现视口内播放

  • 原理:通过 Intersection Observer 监视动图元素是否进入了用户的可视区域(viewport)。只有当用户滚动到能看到它时,才开始加载和播放动画。当它滚出视口时,停止播放以释放资源。

  • 实现步骤

    1. <img> 标签初始不加载动图,而是显示一张静态的封面图(可以是 AVIF 的第一帧,或一个轻量的 WebP/JPG)。将真实的动图地址存储在 data-src 属性中。

    2. 创建 Intersection Observer 实例,观察所有待加载的动图。

    3. 当某个 <img> 元素进入视口时,回调函数触发,此时将 data-src 的值赋给 src 属性,浏览器开始加载并播放动图。

    4. (可选但推荐)当元素离开视口时,可以将 src 重新设置回静态图地址,或将其置空 img.src = '' 来让浏览器停止动画并回收内存。

  • 示例代码

    <img src="poster.jpg" data-src="animation.avif" class="lazy-avif">
    document.addEventListener("DOMContentLoaded", () => {
    const lazyAvifs = document.querySelectorAll('.lazy-avif');
    const observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
    if (entry.isIntersecting) {
    const img = entry.target;
    const src = img.getAttribute('data-src');
    img.setAttribute('src', src);
    observer.unobserve(img); // 开始加载后停止观察
    }
    });
    }, { rootMargin: '200px' }); // 可选:提前200px开始加载
    lazyAvifs.forEach(avif => {
    observer.observe(avif);
    });
    });

措施二:优化资源加载

方案:使用原生懒加载 loading="lazy"

  • 原理:这是浏览器提供的原生能力,实现简单。它能推迟加载屏幕外的图像资源。

  • 优点:无需 JS,一行代码搞定。

  • 缺点:它只控制加载时机,不控制播放时机。一旦加载完成,动画就会开始播放。对于屏幕外的动图,它依然会在后台消耗 CPU 和内存。因此,效果不如 Intersection Observer 精细,但可以作为基础优化。

  • 实现

    <img src="animation.avif" loading="lazy" width="400" height="300">

措施三:优化动图资源本身

在动图制作阶段就进行优化,从源头上减少资源消耗。

  • 降低尺寸:动图的像素尺寸是影响内存占用的最关键因素。非必要不使用超大尺寸的动图。

  • 降低帧率和时长:12-15 fps 的帧率对于很多场景已经足够。缩短不必要的动画时长。

  • 提高压缩率:在导出 AVIF 时,适当降低一些质量(quality),通常肉眼难以分辨,但文件体积和解码复杂度会降低。

措施四:使用 <video> 标签替代 <img> (高级方案)

这是解决此类问题的终极方案之一。因为浏览器对 <video> 标签的优化远胜于 <img> 动图。

  • 原理与优势

    1. 硬件加速解码:浏览器通常会利用 GPU 对视频进行硬件解码,这会把 CPU 从繁重的解码工作中解放出来,极大提升性能。

    2. 高效的流式处理:浏览器以流(stream)的方式处理视频,不会一次性将整个文件加载到内存中,内存管理更高效。

    3. 更精细的控制:拥有 play(), pause() 等丰富的 JS API 和事件。

  • 实现方法:将 AV1 编码的动画内容封装在 MP4 容器中,然后使用 <video> 标签播放。

    <video autoplay loop muted playsinline width="400" height="300">
    <source src="animation.mp4" type="video/mp4">
    </video>

    关键属性解释

    • autoplay: 自动播放。

    • loop: 循环播放。

    • muted: 静音。这是在很多浏览器(尤其是移动端)实现自动播放的必要条件

    • playsinline: 在 iOS 上让视频在当前位置播放,而不是全屏播放。

总结与实施建议

优化措施效果实现复杂度推荐度
1. 视口内播放 (Intersection Observer)⭐⭐⭐⭐⭐强烈推荐
4. 使用 <video> 标签⭐⭐⭐⭐⭐强烈推荐
3. 优化动图资源⭐⭐⭐⭐强烈推荐 (基础)
2. 原生懒加载 loading="lazy"⭐⭐可作为基础,但不足以解决卡顿

Export to Sheets

最佳实践路径

  1. 基础:首先对所有 AVIF 动图资源本身进行优化(尺寸、帧率、压缩率)。

  2. 核心:采用 Intersection Observer API 的方案,实现视口内播放,这是解决卡顿最直接有效的方法。

  3. 进阶:对于性能要求极高或动图数量特别巨大的页面,考虑将 AVIF 动图转换为 MP4 视频,并使用配置好的 <video> 标签进行播放,以利用硬件加速。

通过以上组合拳,可以从根本上解决大量 AVIF 动图导致的浏览器卡顿问题,在享受其高压缩率优势的同时,保证流畅的用户体验。