<picture> 标签不支持 prefers-color-scheme,因其 media 属性仅识别视口尺寸和像素密度,忽略颜色方案;应改用 CSS @media (prefers-color-scheme: dark) 控制 background-image,或用 JS 监听 matchMedia().addEventListener('change') 动态切换 <img src>。

直接用 <picture> 标签无法根据系统暗黑模式自动切换图片——它根本不识别 prefers-color-scheme。 想让背景图或内容图随深色/浅色主题变化,得换思路:用 CSS 媒体查询控制 background-image,或者用 JS 监听 matchMedia 手动换 <img src>。前者更轻量、无 JS、兼容性好;后者只在极少数需要精确控制图片 DOM 的场景下才值得上。
为什么 <picture> 的 media 属性对暗黑模式无效
<picture> 的 <source media> 只响应视口尺寸(如 (max-width: 768px))和像素密度(如 (min-resolution: 2dppx)),不支持颜色方案媒体特性。浏览器遇到 media="(prefers-color-scheme: dark)" 会直接忽略该 <source>,继续往下找,最终 fallback 到 <img src>。
- 即使你写了
<source media="(prefers-color-scheme: dark)" srcset="logo-dark.png">,Chrome/Firefox/Safari 都不会加载它 - DevTools 的 Network 面板里看不到任何 dark 模式下的额外请求,证明浏览器压根没解析这条规则
- 这不是 bug,是规范明确限定的行为:
<picture>是为艺术方向(art direction)和设备能力(DPR/width)设计的,不是为 UI 主题服务的
真正可行的方案:用 CSS @media (prefers-color-scheme) 控制背景图
如果你的图片是作为容器背景(比如 banner、header、card),这是最推荐的做法。它零 JS、可缓存、语义清晰,且所有现代浏览器(Chrome 81+、Firefox 95+、Safari 14+)都支持。
- 容器必须有明确高度或
aspect-ratio,否则背景不可见(常见坑:只写background-image却忘了min-height: 400px) - 两套图片路径需真实存在,且建议保持相同宽高比,避免拉伸或裁剪错位
-
background-size: cover和background-position: center应作为标配,防止变形
.hero-banner {
min-height: 100vh;
background-size: cover;
background-position: center;
background-image: url('hero-light.jpg');
}
@media (prefers-color-scheme: dark) {
.hero-banner {
background-image: url('hero-dark.jpg');
}
}
如果非要用 <img> 标签显示主题相关图片,JS 是唯一可靠路径
当图片需要语义化(如带 alt)、要参与无障碍读取、或需配合懒加载/交互动画时,<img> 不可替代。这时只能用 JS 监听系统主题变化,并手动更新 src。
立即学习“前端免费学习笔记(深入)”;
- 别用
window.matchMedia('(prefers-color-scheme: dark)').matches一次性判断——它只反映初始状态,不响应后续切换 - 必须用
addEventListener('change', ...)绑定监听器,否则用户手动切主题后图片不会更新 - 记得在页面加载时也执行一次初始化,避免首次渲染错图
- 不要直接操作
<picture>内部结构——改<img src>就够了,<picture>本身无需动
const img = document.querySelector('#theme-logo');
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
function updateImage() {
img.src = darkModeMediaQuery.matches ? 'logo-dark.svg' : 'logo-light.svg';
}
darkModeMediaQuery.addEventListener('change', updateImage);
updateImage(); // 初始化
最容易被忽略的是:很多人以为“写了 prefers-color-scheme 就万事大吉”,但忘了检查图片路径是否 404、容器有没有高度、或者 JS 是否在 DOM 加载前就执行了。实际部署前,务必在 DevTools 里手动切换系统主题并观察 Network 和 Elements 面板的变化。



















