不能直接用当组件封装,因为它是原生容器而非可复用自定义元素;浏览器仅解析真实内的,自定义标签中硬编码的会被静默忽略,只加载兜底;真正复用需动态创建合法结构、严格校验并赋值media/srcset/type/sizes等属性,确保顺序与语法正确。

为什么不能直接用 <picture></picture> 当组件封装?
因为 <picture></picture> 本身不是可复用的自定义元素,它只是原生 HTML 容器;浏览器不解析自定义标签名(如 <responsive-img></responsive-img>)里的 <source></source>,除非你手动接管解析逻辑。常见错误是把 <picture></picture> 套进自定义标签里就以为“封装好了”,结果所有 <source></source> 被静默忽略,只加载兜底 <img src>。
真正能复用的,是你控制 DOM 构建 + 属性映射 + 浏览器资源选择权的组合。核心矛盾在于:浏览器只认原生 <picture></picture> 和 <img> 结构,自定义元素必须最终输出合法结构,且不能破坏 <source></source> 的顺序、media 语法、type 匹配或 <img> 兜底。
- 自定义元素中直接写
<source></source>但未插入到真实<picture></picture>内部 → 浏览器无视 - 用
innerHTML注入字符串但没触发浏览器解析(尤其 SSR 场景)→<source></source>变为文本节点 - 属性名和原生行为不一致(比如用
srcset-sizes而非sizes)→ 无法映射到真实属性
如何让 <responsive-img></responsive-img> 输出合法 <picture></picture> 结构?
必须在 connectedCallback 中动态创建并挂载原生元素,不能靠模板字符串硬编码。关键点是:保证 <source></source> 按顺序 append 到 <picture></picture>,最后 append <img>,且所有属性值经校验后赋给对应 DOM 属性(不是 setAttribute 那种字符串赋值)。
示例片段(精简):
class ResponsiveImg extends HTMLElement {
connectedCallback() {
const picture = document.createElement('picture');
const sources = this.getAttribute('sources'); // 如 '[{"media":"(max-width:768px)","srcset":"a.jpg 480w","sizes":"90vw"}]'
try {
JSON.parse(sources).forEach(({ media, srcset, type, sizes }) => {
const source = document.createElement('source');
if (media) source.media = media; // 不用 setAttribute,避免语法校验失效
if (srcset) source.srcset = srcset;
if (type) source.type = type;
if (sizes) source.sizes = sizes; // 注意:sizes 是 <source> 属性,仅在 <picture> 中生效
picture.appendChild(source);
});
} catch {}
const img = document.createElement('img');
img.src = this.getAttribute('src') || '';
img.alt = this.getAttribute('alt') || '';
picture.appendChild(img);
this.replaceChildren(picture);
}
}</picture></source>
-
source.media = value比setAttribute('media', value)更可靠,避免空格/括号等语法被转义 -
sizes必须设在<source></source>上(<img>的sizes在<picture></picture>中无效) - JSON 属性值要 escape 双引号,或改用 data-* 属性传对象数组
<responsive-img></responsive-img> 怎么处理 WebP/AVIF 格式降级?
格式回退必须靠 <source type="image/webp"></source> + 后续 <source></source> 或最终 <img src>,但浏览器是否跳过取决于服务器返回的 Content-Type 是否匹配。常见坑是:本地开发时 WebP 文件被服务器当成 text/plain,导致 type="image/webp" 失效,直接 fallback 到下一条。
- 验证方式:DevTools → Network → 点开 WebP 请求 → 查看 Response Headers 中
Content-Type: image/webp - 不要只写
type="image/webp"就完事,必须确保后端正确配置 MIME 类型 - AVIF 同理,
type="image/avif"在 Safari 16.4+、Chrome 109+ 才稳定支持,旧版会跳过整条<source></source> - fallback 链必须完整:
<source type="image/avif"></source>→<source type="image/webp"></source>→<img src=".jpg">
为什么 sizes 放在自定义元素上容易失效?
因为 sizes 不是全局属性,它只对当前 <source></source> 或 <img> 生效,且仅当该元素处于有效上下文(即父为 <picture></picture> 或独立 <img>)时才参与资源选择。把 sizes 当成自定义元素的 attribute 存着,却不赋给对应 <source></source>,等于没写。
- 错误写法:
<responsive-img sizes="(max-width:768px) 100vw"></responsive-img>→ 无任何效果 - 正确做法:解析该值,分配给每个匹配的
<source></source>(如移动端<source></source>用"90vw",桌面端用"33vw"),或通过 JS 动态设置source.sizes - 注意:若同一
<picture></picture>里多个<source></source>都设了sizes,浏览器只用第一个匹配项的sizes,其余忽略
最易被忽略的是:自定义元素的 attribute 变化不会自动同步到内部 <source></source>,必须监听 attributeChangedCallback 并手动更新 DOM 属性。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











