filter: blur() 本身不进 GPU 合成层,混进 transition 就必然掉帧;真要过渡模糊,必须强制升层 + 限制值范围 + 避免混用其他滤镜或合成属性。

直接说结论:filter: blur() 本身不进 GPU 合成层,混进 transition 就必然掉帧;真要过渡模糊,必须强制升层 + 限制值范围 + 避免混用其他滤镜或合成属性。
为什么 filter: blur() 一加 transition 就卡
blur() 在绝大多数移动端(尤其是 Android 5–7 WebView、iOS Safari 15.4 之前)默认走 CPU 渲染,每帧都要做高斯卷积重绘——不是“有点慢”,是单个 blur(2px) 就能吃掉 6–9ms 主线程时间。更糟的是,它无法和 transform 或 opacity 共享合成层:哪怕你写了 transition: transform 0.3s, filter 0.3s,整条链也会因 blur() 降级回 CPU。
- 错误写法:
transition: filter 0.3s(没升层,纯 CPU 算) - 错误写法:
transition: transform 0.3s, filter 0.3s(blur()拖垮全部) - 错误写法:
filter: blur(2em)(单位非px,Safari 解析异常跳变)
必须手动触发 GPU 合成层
光写 filter: blur(0) 不代表加速。得让浏览器明确知道“这个元素要动滤镜”,才可能提前建图层。
- 稳妥写法:
transform: translateZ(0)或transform: translate3d(0, 0, 0)(比will-change: filter更兼容,尤其 Android 4.4+) - 加在即将动画的**单个元素**上,比如弹窗背景、轮播图容器,不要加在列表项或全局
* - 动画结束后立刻清掉:
el.style.transform = '',否则图层残留导致滚动卡顿、内存飙升 - 别和
will-change: filter混用——两者叠加不增效,反增开销
模糊值必须严格控制
blur() 的性能开销随像素值非线性增长:从 blur(1px) 到 blur(4px),CPU 耗时可能翻 3 倍。低端机上,超过 blur(2px) 就大概率掉帧。
立即学习“前端免费学习笔记(深入)”;
- 起始值必须显式声明:
filter: blur(0px),不能省略;否则过渡起点不确定,首帧跳变 - 最大值建议 ≤
blur(2px),若需更强虚化,改用预渲染图或 Canvas 模糊 - 禁用
%、em单位,只用px(如blur(1.5px)可接受,但部分旧 WebView 对小数支持差) - 避免与
brightness()、contrast()混写在同一个filter值里——不同函数可能触发不同渲染路径,增加不确定性
替代方案比硬扛更可靠
真要在低端机跑模糊动画,与其调参数挣扎,不如换思路。很多所谓“流畅 blur 过渡”,实际是视觉欺骗。
- 背景虚化不用
filter:用半透明白色遮罩 + 伪元素渐变模拟景深感 - 头像/卡片模糊:服务端生成两版图(清晰 + 预模糊),用
opacity切换,零运行时开销 - 需要动态模糊?Canvas 缩放 + 均值采样比 CSS
blur()快 5–8 倍,且可控降质 - 悬停场景下,改用
filter: contrast(110%)+opacity组合,轻量且可合成
最常被忽略的一点:你以为删掉了 filter 声明就安全了,但只要页面里还存在一个未移除的 will-change: filter 或残留 translateZ(0),图层就还在占着 GPU 内存——验证是否真清掉,唯一办法是 DevTools → Layers 面板看图层树。


















