平稳降级的核心是让浏览器始终有图可加载,且失败路径可控、不闪白、不循环、不依赖JS执行时机;关键在资源链路每一步设防,如用<picture>实现格式协商,严格匹配type与响应头,内联onerror并置空防止死循环,配合decode()+超时监听提升可靠性。

平稳降级的核心是:让浏览器始终有图可加载,且失败路径可控、不闪白、不循环、不依赖 JS 执行时机。光写 onerror 或堆 <source> 不够,关键在资源链路的每一步都设防。
为什么 <img src="x.webp"> 在 Safari 里直接留白
旧版 Safari(≤13.1)、IE、Android WebView 4.4 前压根不识别 image/webp MIME 类型。它看到 src 后不做解析、不报错、不触发 onerror,直接跳过请求——页面就空了。
这不是加载慢,是根本没发请求。所以:
-
<picture>是唯一原生支持格式协商的方案,<img>单独用 WebP/AVIF 就是放弃降级 -
<source type="image/webp">的type必须严格等于image/webp,拼错、大小写错、漏image/前缀都会被跳过 - 服务器返回的
Content-Type响应头必须和type完全一致;Nginx 没配types { image/webp webp; },.webp就会被当成application/octet-stream
<picture> 结构写错一个字符就失效
<picture> 不是“写了就能 fallback”,它是按顺序逐条匹配的执行器。结构错、路径错、兜底缺,结果都是静默空白。
立即学习“前端免费学习笔记(深入)”;
-
<img>是强制子元素,不是可选后备——漏掉它,整个<picture>渲染为空 -
<img src="fallback.jpg">的src必须是静态、可直访路径(比如/img/logo.jpg),不能带查询参数(?format=webp)或指向 404 地址 -
<source>顺序决定加载优先级:avif → webp → img;把jpeg写成<source type="image/jpeg">放在前面,现代浏览器可能永远不加载 WebP - 所有格式图片的宽高、裁剪比例、构图必须一致,否则切换时视觉跳变(CLIP)
onerror 必须内联且立即切断事件链
动态绑定 addEventListener('error', ...) 在 Safari 和旧 Edge 中大概率不触发;document.addEventListener('error') 也捕获不到,因为错误事件不冒泡。
- 唯一可靠写法是内联:
onerror="this.src='/img/fallback.jpg'; this.onerror=null;" - 必须加
this.onerror=null,否则 fallback 图再失败会无限循环重试 - 更稳妥一点可加判断:
onerror="if (!this.src.includes('fallback')) this.src='/img/fallback.jpg'; this.onerror=null;" - 如果连 fallback 图也不放心,直接内联 SVG:
this.src='data:image/svg+xml;base64,PHN2ZyB3...'
JS 批量接管时,别等 error,改用 decode() + 超时
监听 load/error 事件不可靠:已缓存图片不触发,网络卡住时 error 可能几秒才来,甚至不触发。
- 用
img.decode()显式等待解码完成,配合Promise.race()+setTimeout控制超时(推荐 3–5 秒) - 多级 fallback 建议最多 2~3 层(如
webp → jpg → placeholder.svg),再多首屏性能明显下降 - 懒加载场景下,
loading="lazy"不支持时只读src,所以src必须永远指向一个可用占位图(不能是空、"#"或data-src) - CSS 隐藏风险:
display: none会让IntersectionObserver完全跳过该元素;改用opacity: 0或预留aspect-ratio
真正难的不是写出正确标签,而是让每种格式的图片在服务端真实存在、MIME 正确、路径稳定、尺寸一致——这些环节一断,前端再精巧的降级逻辑也会失效。



















