照片墙图片加载优化— 修复总结
```
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 渲染时数据大概率已缓存
#bug修复