纯CSS渐变无法实现颗粒感因其本质是平滑数学插值,缺乏随机扰动;唯一纯声明式方案是SVG pattern + feTurbulence噪声滤镜配合background-blend-mode分层混合。

为什么直接用 background: linear-gradient() 画不出颗粒感
因为纯 CSS 渐变本质是数学插值,颜色过渡平滑、精确,没有随机性——而颗粒感依赖微小的、不规则的明暗扰动。浏览器原生渐变函数不支持噪声采样,filter: url(#noise) 或 filter: blur() 单独加在渐变上也无效:滤镜作用于整个元素渲染结果,但渐变背景本身不产生“像素级可扰动内容”,blur 后只会糊成一片灰。
真正可行的方案:SVG <pattern></pattern> + <feturbulence></feturbulence> 噪声生成
利用 SVG 的滤镜能力在图案内部实时生成 Perlin 噪声,再将该图案作为 CSS 背景贴图。这是目前唯一能在不依赖 JS 或图片的前提下,纯声明式实现动态颗粒渐变的方式。
-
<feturbulence type="fractalNoise" basefrequency="0.05" numoctaves="2"></feturbulence>控制颗粒粗细与层次:数值越小颗粒越稀疏;numOctaves超过 3 容易导致边缘闪烁 - 必须用
<pattern patternunits="userSpaceOnUse" width="100" height="100"></pattern>包裹,否则噪声会被拉伸变形 - CSS 中引用需写成
background: url(#noise-pattern);,且 SVG 必须内联(不能外链),否则滤镜不生效
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0"><defs><pattern id="noise-pattern" patternunits="userSpaceOnUse" width="100" height="100"><filter id="noise-filter"><feturbulence type="fractalNoise" basefrequency="0.05" numoctaves="2"></feturbulence></filter><rect width="100" height="100" filter="url(#noise-filter)"></rect></pattern></defs></svg>
叠加渐变的关键:用 background-blend-mode 混合两层背景
单一层无法同时满足“色彩方向性”和“纹理随机性”,必须分层:底层是线性渐变,上层是带透明度的噪声图案。混合模式决定颗粒如何“附着”在颜色上。
- 推荐用
background-blend-mode: overlay;—— 保留渐变色相,同时让噪声在中间调区域凸显颗粒,在高光/阴影处自然弱化 - 若用
multiply,颗粒会偏暗、沉闷;用screen则容易发灰、失真 - 噪声图案自身需设
opacity: 0.12–0.18,过高会盖住渐变,过低则不可见;这个区间值对大多数屏幕亮度最稳妥
兼容性与性能陷阱
Chrome/Firefox/Safari(v16.4+)均支持 <feturbulence></feturbulence> 和 background-blend-mode,但 Safari 旧版(≤15.6)对 fractalNoise 的 numOctaves 支持不稳定,建议降为 1 并微调 baseFrequency 到 0.03。
- 不要把 SVG 写在
底部——某些安卓 WebView 会因渲染顺序导致图案加载延迟或空白 - 避免在动画元素上高频切换该背景,
feTurbulence是 GPU 加速但非零开销,滚动中持续重绘可能掉帧 - 如果设计稿要求颗粒随鼠标移动变化,必须换 JS 方案:
canvas实时绘制噪声纹理,CSS 仅作容器
颗粒感不是加个 filter: noise(2px) 就能出来的效果——它本质是空间频率、混合逻辑与渲染管线的三方博弈。参数差 0.01,视觉反馈就可能从“细腻胶片感”变成“脏污马赛克”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











