<picture> 本身不支持模糊预载,因其仅为声明式资源选择机制,不控制加载时机、不生成占位图、无动画能力;必须配合 LQIP、IntersectionObserver 和 CSS 叠层过渡才能实现“模糊→清晰”效果。

不能直接用 <picture> 实现模糊预载,它只负责资源选择,不控制加载时机或视觉过渡;必须搭配 LQIP(低质量占位图)、IntersectionObserver 和 CSS 过渡才能完成“模糊→清晰”的动态效果。
为什么 <picture> 本身不支持模糊预载
<picture> 是声明式资源分发机制:它告诉浏览器“在什么条件下该用哪张图”,但不会生成占位图、不延迟加载、也不触发任何动画。如果你只写 <source> 和 <img>,浏览器会在解析 HTML 时立即请求匹配的高清图——根本没机会先显示模糊图。
常见错误现象:<picture> 里写了 media="(min-width: 768px)",结果首屏图片还是白屏或硬切;或者所有设备都加载了 desktop 版高清图,LQIP 完全没被用上。
-
<picture>不读取data-src,也不理解“先占位后替换”逻辑 - 它的
<source>必须是真实可加载的 URL,不能是 base64 模糊图 + 高清图组合 - 没有内置的 load 事件监听或状态回调,无法与 JS 动画联动
正确组合:LQIP + <picture> + IntersectionObserver
核心思路是把 LQIP 图作为 <img> 的 src(强制首屏渲染),再用 <picture> 的 <source> 提供多分辨率高清图源,由 JS 在进入视口后手动触发替换。
立即学习“前端免费学习笔记(深入)”;
实操建议:
-
<img>的src必须指向 LQIP 图(如photo-lqip.jpg),且显式设置宽高或aspect-ratio防止布局抖动 -
<source>的srcset存高清图地址,不要放 LQIP;例如:<source media="(min-width: 768px)" srcset="photo-1200.jpg 1x, photo-2400.jpg 2x"> - JS 中用
observer.observe(img)监听,回调里读取第一个匹配的<source>的srcset值,或 fallback 到<img>的dataset.src - 替换前先加
img.classList.add('loading'),替换后等img.onload再加loaded类触发动画
CSS 过渡必须绕开 filter: blur() 动态计算
别用 filter: blur(6px) 初始化再过渡到 blur(0)——这会让浏览器在首帧就解码高清图来模糊,失去预载意义。真正要过渡的是 opacity 或 background-image 叠层。
推荐方案(轻量可靠):
- 初始
<img>设opacity: 1,LQIP 图撑满容器 - 高清图加载完成后,插入一个
<div class="highres-overlay">覆盖其上,背景设为高清图,opacity: 0 - CSS 定义:
.highres-overlay { transition: opacity 0.3s ease; },JS 中设el.style.opacity = 1 - 这样 LQIP 始终可见,高清图纯叠加,无重绘压力,兼容性也比 filter 更稳
容易被忽略的兼容性与性能点
移动端 Safari 对 filter: blur() 在 >10px 时会降级,而 <picture> 在旧 Android WebView 中可能忽略 media 直接加载 <img src>。所以 LQIP 必须是真实小图,不能靠 JS 生成。
- 务必给
<img>加decoding="async",避免阻塞主线程解码 -
IntersectionObserver的rootMargin至少设"100px",否则快速滚动时图片刚进视口就触发,用户仍看到白屏 - 不要在
<picture>外层容器设overflow: hidden,否则 LQIP 和高清图尺寸微差会导致裁剪错位 - 服务端若支持 AVIF,
<source type="image/avif">应放在最前面——但得确认 fallback 的<img src>是 JPEG/PNG,否则不支持的浏览器会空白



















