Avatar

CaiCai

INITIALIZING SYSTEM

封面

照片墙图片加载优化— 修复总结

2026-06-30 11:03:36
# bug修复
✨ 心情:Emo
👁 次阅读

PhotoImg 组件(6 行)

function PhotoImg({ src, alt, className, imgClassName, showSkeleton: _showSkeleton }) {
  return (
    <div className={`relative overflow-hidden ${className ?? ''}`}>
      <img src={src} alt={alt ?? ''} decoding="async"
           className={`w-full ${imgClassName ?? ''}`} />
    </div>
  );
}

页面级预加载(7 行)

useEffect(() => {
  albums.forEach(album => {
    new window.Image().src = album.cover;
    album.photos.slice(0, 3).forEach(p => {
      new window.Image().src = p.url;
    });
  });
}, []);

改动对比

项目原始版最终版PhotoImg单层 <img>单层 <img decoding="async">模糊层无无淡入动画无无状态管理无无预加载无useEffect 预加载所有封面CSS 冗余animate-img-reveal已删除

技术要点

decoding="async"

告诉浏览器在后台线程解码图片,不阻塞主线程。页面过渡动画不受图片解码干扰。

页面级预加载

PhotoWallClient 挂载时用 new Image().src 预加载所有相册封面和前 3 张照片,后续 PhotoImg 渲染时图片数据大概率已在浏览器缓存中。

架构极简

不引入模糊层、淡入动画、ref 回调、状态切换等任何复杂逻辑。浏览器原生行为处理缓存命中和渐进加载,零额外状态、零额外渲染。

迭代试错总结

尝试结果img.decode() 离屏预解码DOM <img> 仍需重解码双层图片 (模糊 + 清晰)水波纹闪烁、灰底CSS blur-sm 模糊遮罩GPU 负担导致动画卡顿CSS transition-opacity 淡入白底/黑底透出CSS animation animate-img-reveal缓存命中时仍有闪烁useLayoutEffect 缓存检测仍无法消除首帧闪烁useState 初始化缓存检测检测不可靠,未命中时显示灰底backdrop-blur父容器白底挡住无效shimmer 骨架用户认为是灰底CSS 渐变占位用户认为是灰底Canvas 重编码基线 JPEG绕过缓存,动画变卡

核心教训:

  • 缓存命中的首帧渲染无法中途取消,任何基于 ref callback 的缓存检测都会导致首帧闪烁
  • 双层图片方案让模糊层和清晰图同时可见一瞬间,产生水波纹
  • CSS filter:blur() 多图同时渲染时 GPU 负担过重,导致页面过渡动画掉帧
  • 最简方案反而是最鲁棒的——单层 <img> + decoding="async" + 页面预加载

性能特征

缓存命中浏览器原生即时渲染,0ms 延迟未命中渐进式 JPEG 逐通道解码,decoding="async" 保证不阻塞动画页面过渡无额外 GPU 负担(无 blur filter),动画流畅预加载useEffect 在挂载后立即启动,PhotoImg 渲染时数据大概率已缓存