
SVG 滤镜里有一组效果刚接触的人往往觉得它“玄学”——feTurbulence就是其中最典型的一个。你写下一行标签屏幕上就冒出一片云雾、木纹、大理石或者水面波纹参数稍微动一动纹理的疏密、走向、颗粒感全变了但你说不清它到底按什么规律在变。我第一次用它的时候纯粹是瞎调baseFrequency和numOctaves调到看着顺眼就收手结果换一个尺寸的画布纹理立刻崩掉完全没法复用。这篇内容就是把我这些年折腾feTurbulence的经验摊开讲清楚。它适合已经会用 SVG 基本图形、想进一步做纹理和质感的前端开发者也适合做视觉设计、想把噪声纹理做成可参数化组件的朋友。核心关键词是feTurbulence、湍流滤镜、噪声纹理、baseFrequency、numOctaves、feDisplacementMap。读完之后你应该能做到不再靠试参数碰运气而是知道每个参数在控制什么能稳定地做出云、雾、木纹、水面、磨砂玻璃这几类常见效果并且知道它在性能和兼容性上有哪些坑。1. 先搞清楚 feTurbulence 到底在生成什么很多人把feTurbulence当成一个“纹理生成器”这个理解不算错但太模糊导致调参时没有方向。更准确的说法是它在为画布上的每一个像素计算一个伪随机的数值然后把这个数值映射成颜色。你看到的云雾和木纹本质上是“一张由算法算出来的噪声图”。1.1 从“每个像素一个随机数”说起如果只是给每个像素一个完全独立的随机数你得到的是电视雪花那种纯白噪声颗粒感极强没有任何结构。feTurbulence高明的地方在于它生成的随机数是有空间相关性的——相邻像素的数值彼此接近距离越远差异越大。这种“平滑过渡的随机”就是所谓的噪声noise它才是云雾、木纹这些自然纹理的数学基础。理解这一点非常关键你调的所有参数本质上都在控制“相邻像素之间的相关性有多强、在几个尺度上叠加”。相关性强的区域看起来就是大块的云相关性弱的区域看起来就是细密的沙。所以后面讲baseFrequency时你会发现它控制的就是这个相关性的尺度。1.2 两种噪声类型fractalNoise 与 turbulencefeTurbulence的type属性只有两个取值但它们的视觉差异非常大这是新手最容易忽略的一个分叉点。type 取值数学特征视觉表现典型用途fractalNoise多个倍频噪声直接叠加值域集中在中间柔和、连续、对比度低云雾、烟雾、磨砂质感、纸张纹理turbulence叠加时对绝对值做处理产生尖锐的“脊线”对比强烈、有沟壑感、边缘锐利木纹、大理石、火焰、水面波纹、闪电我一开始默认用turbulence做云雾效果怎么调都显得脏、显得硬后来换成fractalNoise立刻就柔和了。反过来做木纹时用fractalNoise纹理糊成一团没有年轮的锐利边界。这个选择基本决定了你后续调参的方向选错了再怎么调都是事倍功半。提示如果你不确定该用哪个先做云雾类效果就用fractalNoise做有“纹路边界”的效果就用turbulence这个经验判断在绝大多数场景下都成立。1.3 噪声图是怎么变成颜色的feTurbulence输出的其实是一个 RGBA 四通道的图像每个通道都是独立的噪声值。默认情况下R、G、B 三个通道的噪声是各自独立计算的所以直接看输出会是一张彩色的噪点图。这也是为什么很多人第一次单独用feTurbulence时看到的是花花绿绿的杂色而不是想象中的灰度纹理。要把它变成可用的灰度纹理通常要配合feColorMatrix把彩色压成灰度或者用feComponentTransfer调整通道。这个组合套路后面会专门讲。先记住一个结论feTurbulence单独用几乎没意义它永远是滤镜链里的一环前后通常要接颜色处理或位移处理。2. baseFrequency控制纹理“颗粒有多大”如果只能记住一个参数那就是baseFrequency。它决定了噪声的基础频率频率越高单位距离内变化越快纹理越细密频率越低纹理越舒展、块面越大。这是调参时第一个要动的旋钮。2.1 单值和双值的区别baseFrequency可以写一个值也可以写两个值用空格分隔。这个细节很多人没注意但它直接决定了纹理是不是“各向同性”。写一个值比如baseFrequency0.05X 和 Y 方向频率相同纹理在各个方向均匀分布适合云雾、磨砂这类无方向性的效果。写两个值比如baseFrequency0.01 0.2X 方向频率低、Y 方向频率高纹理在水平方向被拉长形成横向的条纹感。做木纹、拉丝金属、水波纹时这个双值写法是核心技巧。我做过一个横向木纹的效果一开始用单值怎么调都是均匀颗粒后来把 X 设成 0.008、Y 设成 0.15纹理立刻被拉成横向的细长纹路再配合turbulence类型的脊线年轮的感觉就出来了。这个“方向性靠双值频率制造”的思路是feTurbulence里最实用的技巧之一。2.2 频率和画布尺寸的耦合关系这里有个大坑我踩过不止一次baseFrequency是相对于滤镜坐标系的而滤镜默认使用objectBoundingBox作为primitiveUnits的参考。这意味着同一个频率值作用在 100×100 的小图上和 1000×1000 的大图上视觉颗粒大小完全不同——大图上的纹理会被“放大”看起来更粗。解决办法有两个我一般按场景选把filterUnits和primitiveUnits都设成userSpaceOnUse这样频率就对应绝对像素坐标纹理密度和画布尺寸解耦换尺寸不会崩。保持默认的objectBoundingBox但把频率值按画布尺寸做换算比如目标是在 800px 宽上得到合适颗粒就按比例调整。filter idgrain filterUnitsuserSpaceOnUse x0 y0 width800 height800 feTurbulence typefractalNoise baseFrequency0.9 numOctaves3 resultnoise/ /filter上面这段用userSpaceOnUse后baseFrequency0.9就稳定对应“每像素级别的高频颗粒”做磨砂质感时非常好复用。这个设置是我现在做任何纹理组件的默认起点。2.3 频率取值的经验区间调了这么多效果我大致总结出一个频率区间参考能帮你少走很多弯路效果目标baseFrequency 参考type 建议大块云雾、烟雾0.005 ~ 0.02fractalNoise中等云层、纸张纹理0.02 ~ 0.08fractalNoise木纹、大理石纹路0.005 ~ 0.03配合双值turbulence水面波纹0.01 ~ 0.05双值拉方向turbulence细密磨砂、颗粒噪点0.5 ~ 1.2fractalNoise这些数值不是绝对标准但作为起点非常靠谱。我的习惯是先按目标效果选一个区间中值再微调而不是从 0 开始瞎试。3. numOctaves 与 seed叠加层数与随机起点baseFrequency决定了“基础颗粒”但自然纹理很少是单一尺度的——云有大团也有细边木纹有粗纹也有细丝。numOctaves就是用来叠加多个尺度的而seed决定这堆随机数的起点。3.1 numOctaves 是“倍频叠加”的层数numOctaves的每个“八度”octave会把频率翻倍、振幅减半然后叠加到结果上。层数越多纹理的细节越丰富但同时计算量也线性增长。这个机制和音频里的泛音叠加是一个道理基频决定音高泛音决定音色。实际调参时我的经验是numOctaves1只有基础层纹理干净但单调适合做纯色块上的轻微质感。numOctaves2到3最常用的区间既有细节又不至于太脏云雾和木纹的甜点区。numOctaves4以上细节非常丰富但容易显得“噪”而且性能开销明显上升移动端要谨慎。我做过对比测试在同一个 800×800 画布上numOctaves从 1 加到 5渲染耗时大概翻了三倍多。所以除非你确实需要极细腻的纹理否则 3 层基本够用。这个“够用就好”的判断比盲目堆参数重要得多。3.2 seed 让同一套参数产出不同纹理seed是个很实用的参数它改变随机数的起始值从而在参数完全不变的情况下得到不同的纹理分布。做卡片背景、头像底纹这类需要“同风格但不同图案”的场景时seed就是你的批量生成开关。filter idcloud-a feTurbulence typefractalNoise baseFrequency0.02 numOctaves3 seed7/ /filter filter idcloud-b feTurbulence typefractalNoise baseFrequency0.02 numOctaves3 seed42/ /filter两个滤镜参数一模一样只有seed不同出来的云纹分布就完全不一样。我一般会准备一组 seed 值比如 3、7、13、42配合 CSS 变量切换就能低成本地做出“每张卡片纹理都不同”的效果而不需要准备多张图片。注意seed的取值在不同渲染引擎下的具体分布可能有细微差异所以不要指望“seed42 在所有浏览器里长得一模一样”。它适合做“同风格随机”不适合做“像素级精确复现”。3.3 stitchTiles平铺时的接缝处理如果你打算把噪声纹理做成可平铺的背景比如background-repeatstitchTilesstitch这个属性就派上用场了。它会让噪声在瓦片边界处做特殊处理使得相邻瓦片拼接时接缝不明显。默认值是noStitch平铺时接缝处会有明显的断裂感。做无缝纹理背景时记得加上stitchTilesstitch。不过它也不是万能的频率特别低、块面特别大的时候接缝依然可能看得出来这时候要么提高频率要么干脆用一张足够大的纹理不做平铺。4. 把噪声变成可用纹理颜色与位移的组合拳前面说过feTurbulence单独输出是彩色噪点没法直接用。真正让它发挥价值的是它和feColorMatrix、feComponentTransfer、feDisplacementMap的组合。这一节讲几套我反复用、稳定可靠的组合套路。4.1 用 feColorMatrix 压成灰度并调对比最基础的组合是把彩色噪声转成灰度并拉开对比度。feColorMatrix的typesaturate设成 0 就能去色再用typematrix调整对比和亮度。filter idpaper feTurbulence typefractalNoise baseFrequency0.04 numOctaves4 resultnoise/ feColorMatrix innoise typesaturate values0 resultgray/ feComponentTransfer ingray feFuncR typelinear slope0.6 intercept0.2/ feFuncG typelinear slope0.6 intercept0.2/ feFuncB typelinear slope0.6 intercept0.2/ /feComponentTransfer /filter这里feComponentTransfer的slope控制对比度、intercept控制整体亮度偏移。slope大于 1 拉大对比小于 1 压缩对比。做纸张纹理时我一般用 0.5~0.7 的 slope让纹理若隐若现不会喧宾夺主。这个“压低对比”的思路很关键——很多新手做出来的纹理太抢眼盖过了主体内容就是因为没做这一步。4.2 feDisplacementMap让噪声去“扭曲”别的图形这是feTurbulence最有价值的用法也是它从“纹理生成”升级为“质感工具”的关键。feDisplacementMap会读取噪声图的通道值用它来偏移另一张图的像素位置从而产生扭曲、波动、毛边效果。它的核心参数是scale偏移强度和xChannelSelector/yChannelSelector用噪声的哪个通道控制 X/Y 方向偏移。做水面倒影、旗帜飘动、毛玻璃边缘时这套组合几乎是标配。filter idwater feTurbulence typeturbulence baseFrequency0.01 0.06 numOctaves2 resultnoise/ feDisplacementMap inSourceGraphic in2noise scale18 xChannelSelectorR yChannelSelectorG/ /filterscale18表示最大偏移 18 像素这个值直接决定扭曲的剧烈程度。我调这个参数的经验是做轻微的水面波动用 5~15做强烈的扭曲变形用 30 以上超过 50 基本就面目全非了。baseFrequency的双值写法在这里制造了“横向拉长的波纹”配合位移就有了水面的感觉。4.3 组合顺序为什么不能随便换滤镜链是有顺序的in和result把各个原语串起来。顺序错了效果完全不对。比如上面水面那个例子如果先做feDisplacementMap再生成噪声位移就没有噪声可读直接失效。我总结的通用顺序是先生成噪声 → 再处理噪声去色/调对比→ 最后用噪声去影响目标图形。这个顺序符合数据流的方向也最好调试。调试时我习惯给每个原语加result命名这样在浏览器开发者工具里能逐段检查中间输出定位问题快很多。5. 五类高频效果的完整参数配方光讲原理不够这一节直接给配方。下面每个效果都是我在实际项目里用过、验证过可复用的参数组合你可以直接抄再按需微调。5.1 云雾与烟雾filter idfog x-20% y-20% width140% height140% feTurbulence typefractalNoise baseFrequency0.012 numOctaves4 seed5 resultnoise/ feColorMatrix innoise typesaturate values0/ feComponentTransfer feFuncA typelinear slope0.7 intercept0/ /feComponentTransfer /filter要点是低频率0.01 左右加fractalNoise再通过feFuncA控制透明度让云雾有浓淡层次。x/y/width/height扩大滤镜区域是为了避免云雾被裁切——这是新手常犯的错滤镜默认区域只有图形包围盒云雾超出部分会被切掉。5.2 木纹filter idwood feTurbulence typeturbulence baseFrequency0.006 0.14 numOctaves3 seed11 resultnoise/ feColorMatrix innoise typesaturate values0/ feComponentTransfer feFuncR typelinear slope1.4 intercept-0.2/ feFuncG typelinear slope1.4 intercept-0.2/ feFuncB typelinear slope1.4 intercept-0.2/ /feComponentTransfer /filter双值频率0.006 0.14是灵魂X 方向极低、Y 方向较高纹理被拉成横向细纹。turbulence类型提供锐利的年轮边界slope拉到 1.4 增强对比让纹路更清晰。5.3 大理石大理石和木纹思路类似但频率更均匀、对比更柔和且常配合位移做出“脉络”感。我一般用fractalNoise打底再用一层turbulence做细脉络叠加两层不同 seed 混合出来的纹路更自然。5.4 水面波纹前面 4.2 已经给了核心配方补充一点做静态水面时scale小一点8~12做动态波动时可以配合 SMIL 或 CSS 动画去改baseFrequency或seed但要注意改seed会导致纹理跳变动画不连续所以动态效果更适合改baseFrequency或直接对feDisplacementMap的scale做动画。5.5 磨砂玻璃磨砂玻璃的关键是“高频细颗粒 轻微位移”。用baseFrequency0.8左右的高频fractalNoise配合feDisplacementMap的scale2到4让背后的内容产生细微模糊扭曲再叠一层半透明白色磨砂感就出来了。这个效果在卡片 UI 上很出彩但性能开销要留意后面会讲。6. 性能、兼容与调试那些文档不会告诉你的坑feTurbulence是计算密集型滤镜用不好会直接拖垮页面。这一节讲我踩过的性能和兼容坑以及一套实用的调试方法。6.1 性能开销的真实来源噪声计算是逐像素的画布越大、numOctaves越高、滤镜区域越大开销越大。我实测过一个全屏1920×1080的feTurbulence滤镜numOctaves4时在某些集成显卡上会掉到 30fps 以下。降低开销的几个实操手段把滤镜应用在小尺寸元素上再用 CSStransform: scale()放大比直接在大元素上算噪声便宜得多。控制numOctaves不超过 3除非确实需要。用x/y/width/height精确限定滤镜区域别用默认的 120% 包围盒去算一大片空白。静态纹理考虑“算一次存成图片”而不是每次渲染都实时计算。注意feTurbulence的结果在页面滚动、重绘时可能被反复计算如果发现滚动卡顿优先怀疑它。6.2 浏览器差异与降级思路主流现代浏览器对feTurbulence的支持都不错但不同引擎在seed的具体分布、stitchTiles的接缝处理上会有细微差异。我的做法是不追求跨浏览器像素级一致只保证“风格一致”。如果某个环境完全不支持 SVG 滤镜就降级成一张预渲染的纹理图片用supports或特性检测来切换。6.3 一套可复现的调试流程调feTurbulence最怕瞎试。我固定用这套流程先只保留feTurbulence把type、baseFrequency、numOctaves定下来看灰度噪声形态对不对。加feColorMatrix去色确认对比度合适。需要扭曲时再加feDisplacementMap从scale0慢慢往上加观察变化。最后调滤镜区域x/y/width/height确保不被裁切。每一步都给result命名用开发者工具的滤镜检查器逐段看输出。这套流程能让你在几分钟内定位到是哪个参数出了问题而不是对着最终效果猜。7. 我个人的几条实战心得最后分享几条纯经验的东西都是文档里不会写的。第一先定 type 再调频率。很多人一上来就调baseFrequency但 type 选错了方向就错了。我现在的习惯是先花十秒判断这个效果该用fractalNoise还是turbulence定下来再动频率效率高很多。第二双值频率是被低估的技巧。大部分教程只讲单值但木纹、水波、拉丝这些有方向性的纹理双值频率才是关键。记住“X 低 Y 高是横向纹理X 高 Y 低是纵向纹理”这个口诀。第三滤镜区域一定要显式设置。默认区域会裁掉超出包围盒的纹理云雾类效果尤其明显。养成写x-20% y-20% width140% height140%的习惯能省掉大量“为什么纹理被切了”的困惑。第四静态纹理优先预渲染。如果一个纹理在页面上是固定的别让它每次渲染都实时算。用一次性的 canvas 或离屏渲染生成图片性能收益非常明显尤其是在列表、卡片这种重复出现的场景里。第五seed 是批量生成的利器但别依赖它的精确值。做同风格不同图案时用 seed 切换但不要写死“seed42 必须是这个图案”这种假设跨引擎会翻车。feTurbulence这个滤镜入门时觉得它不可控用熟了会发现它其实是 SVG 里少数能“用代码生成自然质感”的工具。参数不多但每个都值得吃透。把 type、baseFrequency、numOctaves、seed 这四个搞明白再配上颜色和位移的组合你能做出的效果远比想象中多。