必须作用于html元素而非body,因为filter不继承且fixed/absolute元素脱离body层叠上下文,仅html根元素能穿透所有后代(含iframe容器),但video、canvas等需单独重置filter:none。

直接在 html 元素上设 filter: grayscale(100%) 是唯一能覆盖所有内容(包括 position: fixed 导航栏、弹窗、滚动条)的方案,但不加防护会卡顿、表单失灵、视频异常——真机上切一次就可能被用户点退。
为什么必须作用于 html 而非 body
滤镜不继承,且 position: fixed 或 absolute 元素常脱离 body 的层叠上下文,导致灰度“漏掉”。html 是根元素,作用于它才能穿透到所有后代节点,包括 iframe 外部容器(video、canvas 仍需单独处理)。
常见错误:
- 写了
body { filter: grayscale(100%) },但顶部导航栏没变灰——大概率是fixed定位导致 -
html没设min-height: 100vh,页面内容少时视觉上“没生效” - 同时用了
transform或will-change: transform,触发新图层,灰度被隔离失效
grayscale() 参数写法与兼容性陷阱
移动端(iOS 14+/Android 10+)支持 grayscale(100%),但 iOS 12 及更早、部分旧安卓 WebView 会跳过或卡顿。拼写和单位错一点就全失效。
必须用:
-
grayscale(100%):语义清晰,兼容性最好 - 禁用
grayscale(1):虽等价,但某些旧 WebView 解析异常 - 禁用
grayscale(100)(缺 %)、grayscale(true)、greyscale()(英式拼写)
IE 和 Edge ≤17 完全不支持,现代项目应直接放弃支持,用 @supports (filter: grayscale(0)) 做特性检测后启用。
表单、视频、Canvas 等关键元素如何“放行”
原生表单(<input type="date">)、地图瓦片、ECharts 图表、<video></video>、<canvas></canvas> 被灰度后,要么点击区域偏移,要么色彩信息丢失无法识别。
必须显式重置,并带 !important:
input, select, textarea, button, [type="date"], [type="time"] { filter: none !important; }.map-tile, .chart, .captcha-img, video, canvas { filter: none !important; }- 内联 SVG 若有颜色语义(如图标),需额外加
color: #000或fill: currentColor防止灰度映射成不可见色
不要依赖继承——filter 不像 color 那样自然向下传递,父级设了灰度,子级必须自己“申明豁免”。
切换动画不闪、不卡、不抽风的关键操作
用 classList.toggle() 切换 class 比直接操作 style.filter 稳定得多,避免被后续样式覆盖或触发重排。
预定义 CSS:
html.grayscale {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
}
html {
transition: filter 0.3s ease;
}
JS 切换:
document.documentElement.classList.toggle('grayscale');
注意事项:
- 过渡必须声明在默认状态(
html),不能只写在.grayscale里,否则 iOS Safari 13.4 以下版本会跳变 - 禁用
transition: all,性能差且不可控 - 若需兼容 iOS 12,建议降级为
opacity: 0.85+contrast(0.9)模拟灰度,比硬切filter更可靠
真正难的不是加灰度,而是让灰度不干扰用户操作——节日期间上线前,务必在 iPhone 12–15 真机上滑动、点选、唤起键盘、播放视频全流程测一遍,WebKit 渲染残留和闪烁只在实机滚动中才暴露。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











