HTML媒体加载失败,真的没有“万能解法”
说实话,这个问题在技术圈里被问过无数次了:有没有一套“万能备用方案”,能让图片、视频在加载失败时自动优雅降级?答案很干脆——没有。正确的做法,应该是分层应对。图片要靠onerror配合data-loaded标记来切断事件链,否则备用图一旦404,页面就会陷入无限循环;视频则需要根据video.error.code的不同值,分别处理网络失败、解码失败、格式不支持等情形,再通过多source降级来兜底。而所有这一切,都首先得绕过file://协议的限制——否则,90%的“失败”根本不是代码的问题,而是浏览器主动帮你拦截了。

图片的onerror,为什么一写就卡死?
问题在于,备用图要是也404了,onerror就会无限循环——内存暴涨,页面直接崩溃。所以,它绝不仅仅是“换一张图”那么简单,而是要彻底切断事件链。
onerror="if (!this.dataset.loaded) { this.dataset.loaded = '1'; this.src = 'fallback.png'; }"——必须用dataset做个标记,不能只靠this.onerror = null,后者在某些浏览器下并不可靠。- 如果是动态创建的
img元素,onerror必须在appendChild()之前就绑定好,否则第一次加载失败时,根本来不及捕获。 - 备用图最稳妥的选择,是直接用
data:image/png;base64,...内联base64,彻底跟路径问题说拜拜。 - 别忘了,
alt属性不是视觉上的替补,它只服务读屏器。想让页面空白区域“看起来正常”,还得靠onerror或者CSS占位。
视频的error事件,怎么区分真实失败?
这里必须明确一点:不是所有报错都该去重试。比如MEDIA_ERR_DECODE(解码失败),重试一千次也没用。关键信号,藏在video.error对象的code值里。
video.error.code === 2(MEDIA_ERR_NETWORK):网络问题,可以尝试用fetch(video.currentSrc, { method: 'HEAD' })验证一下URL是否真的可达。video.error.code === 4(MEDIA_ERR_SRC_NOT_SUPPORTED):这说明所有source全都不被支持。这时候应该检查canPlayType()的返回值,而不是盲目地换源。video.error.code === 3(MEDIA_ERR_DECODE):大概率是编码不兼容,比如Safari不支持A V1。正确的做法是降级到H.264 MP4,而不是换个CDN地址。- 需要注意的是,
error事件只在加载阶段触发。播放中间出现的卡顿、花屏、音画不同步,它完全不管。
为什么本地双击打开HTML文件,总是失败?
很多开发者以为是路径写错了,其实根本不是。真正的原因是浏览器对file://协议的主动封锁——JS、CSS、图片请求都会被拦截,控制台会报net::ERR_FILE_NOT_FOUND或者Access to script at 'file:///xxx.js' from origin 'null'。
- 最可靠的开发方式,还是在VS Code里装上
ritwickdey.LiveServer插件,右键选择Open with Live Server。 - Chrome的
--allow-file-access-from-files参数已经逐步废弃了,别指望它。 - 路径写法上,
./video.mp4比video.mp4更稳妥,尤其是在Webpack或Vite构建后,路径可能会被重写。 - 如果遇到跨域图片,控制台报
Blocked by CORS policy,onerror虽然会触发,但换源是无效的。这得靠后端配合调整托管方式,前端无能为力。
全局监听error事件,真能兜底吗?
能,但必须精准过滤,否则会误伤脚本、样式表甚至XHR请求。
document.addEventListener('error', e => { if (e.target.tagName === 'IMG' && !e.target.dataset.handled) { ... } }, true)——注意第三个参数true,表示在捕获阶段处理,确保命中。- 务必用
e.target.tagName判断类型。script和link的onerror行为跟img完全不同,不能混用逻辑。 - 如果
src为空、undefined或者纯空格,onerror根本不触发。这类问题只能靠构建工具来校验,比如webpack的html-loader会报Must be non-empty。 - 对于
srcset中的单个候选源失败,onerror同样不响应。这种情况下,只能靠服务端统一返回适配格式。
最容易被忽略的一点是:错误分类决定了应对动作,不是所有失败都该“换源”。解码失败,换CDN没用;网络失败,重试三次就够了;格式不支持,硬塞WebM只会让iOS用户看到白屏。真正的鲁棒性,来自对每个video.error.code和img加载上下文的精确响应。
