现代 CSS 做完排版,还能继续处理元素已经画出来的样子。浏览器会把元素的渲染结果看成一张“输入图像”,再对它应用滤镜、图层混合、裁剪或遮罩。
这一类属性看起来很多,理解时却只需要盯住三个对象:元素盒子负责布局占位,像素结果是我们最终看见的内容,背板是参与混合的后方画面。下面我们沿着浏览器的处理顺序,把 filter、混合模式、形状、裁剪、遮罩和 object-fit 串起来。遇到边界条件时,也会顺手说明性能和可访问性问题。
视觉上被裁掉、变透明或移出可见区域,并不等于布局盒子消失。除非属性明确参与布局,否则周围元素仍按原来的盒子尺寸排版。
我们先把一个元素的显示过程简化成几步。浏览器画好元素及其子树,再执行滤镜;元素与背板重叠时,接着计算混合结果;最后由裁剪或遮罩决定哪些像素还能看见。浏览器内部会做不少优化,但用这条管线判断属性顺序,已经足够可靠。
这里有个很实用的语法区别。filter 的函数之间用空格分隔,浏览器按书写顺序逐个执行。多个背景层和遮罩层通常用逗号分隔,它们组成的是图层栈。一个强调先后处理,一个强调上下叠放,写复杂效果时别把两种模型混在一起。

filter:基础滤镜与颜色滤镜filter 可以用在普通 HTML 元素上,也适用于多数 SVG 图形元素。它不会继承,初始值是 none,多数滤镜函数可以做插值动画。你可以把处理过程理解成接力:浏览器先拿到元素完整的渲染快照,每个函数再接着处理上一个函数的结果。
.card {
filter: blur(2px) brightness(1.12) contrast(1.08);
}先看三个处理轮廓和透明度的函数:
blur(<length>) 使用高斯模糊。0 保持原样,负长度无效。模糊会向元素边界外扩散,周围空间不够时,边缘看起来可能被切掉。opacity(<number-percentage>) 在滤镜链内部改变透明度。0 表示完全透明,1 或 100% 表示不变。它和 opacity 属性是两套机制,同时使用时透明效果会叠加。drop-shadow(x y blur color) 根据最终像素的 alpha 轮廓生成投影。它不支持 spread 和 inset;要做多重投影,需要连续写多个函数,不能用逗号分隔。.sticker {
filter:
drop-shadow(0 2px 2px rgb(0 0 0 / .22))
drop-shadow(8px 10px 18px rgb(30 50 90 / .28));
}选 drop-shadow() 还是 box-shadow,先看你希望阴影跟着什么走。前者追随透明轮廓,后者主要以盒子为基准。如果 JPEG 没有透明通道,即使画面里的主体是不规则形状,drop-shadow() 看到的仍是一整块不透明矩形。
颜色滤镜可以按用途分成两组。下面这组直接改变颜色性质:
grayscale(1) 得到完全灰度的结果,0 保持原色。sepia(1) 把画面完全转成棕褐色调,0 保持不变。invert(1) 完全反相;invert(.5) 会把各颜色分量推向中间灰。hue-rotate(90deg) 沿色相环旋转。角度可以是负数,也可以超过一圈;这个函数不会直接修改饱和度和明度。另一组调节画面强度。brightness(0) 得到黑色,brightness(1) 保持不变;contrast(0) 把颜色压向中灰,contrast(1) 保持不变;saturate(0) 完全去除饱和度,saturate(1) 保持不变。saturate() 还允许使用大于 1 的值,让颜色进入过饱和状态。
不要把滤镜当作保证文字对比度的工具。背景、图片与用户色彩模式变化后,滤镜结果可能失去可读性;正文颜色仍应按实际前景与背景做对比度检查。
CSS 内置函数足以应付常见处理。效果涉及噪声、位移或多个图像步骤时,可以用 SVG <filter> 描述,再通过 url() 引用。引用目标既可以是当前文档中的节点,也可以是外部 SVG 中带标识的片段。
<svg width="0" height="0" aria-hidden="true">
<filter id="rough-paper">
<feTurbulence baseFrequency="0.04" numOctaves="2" result="noise" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="8" />
</filter
.poster { filter: url(#rough-paper) saturate(1.15); }别把 url() 当成独立于滤镜链的特殊步骤,它仍然只是链条中的一环。上例会先执行 SVG 滤镜,再提高饱和度;交换两者的位置,就会先改色再位移。如果 URL 不存在,或者片段没有指向 <filter>,整个 filter 声明都可能失效。所以页面的基础内容应该在没有这层特效时仍然清楚可读。
引用外部资源时,还要检查同源策略、加载失败和缓存。可复用的滤镜可以集中放进一个 SVG 文件,再用不同 id 区分效果。关键内容不要依赖远程滤镜才能辨认。
mix-blend-mode:元素如何与背板混合设置 mix-blend-mode 后,当前元素是前景,它会和背后已经画好的像素混合。默认值 normal 只做普通的 alpha 合成,其他模式则会按像素分量或颜色特征重新计算结果。调试混合效果时,第一步永远是先确认背板究竟是谁。

这些模式不必逐个死记,可以按结果来理解:
darken 逐分量取较小值,lighten 取较大值。difference 取差的绝对值,exclusion 的差值效果更柔和。multiply 类似颜料叠加,结果通常变暗;screen 类似光线叠加,结果通常变亮;overlay 会压暗暗部并提亮亮部。hard-light 的判断基准与 overlay 相反,效果更强;soft-light 更柔和。color-dodge 会强烈提亮背板,color-burn 会强烈压暗背板。颜色接近极值时,两者都容易丢失细节。hue 取前景色相,保留背板的饱和度与明度;saturation 取前景饱和度;color 取前景的色相和饱和度;luminosity 取前景明度。.ink { mix-blend-mode: multiply; }
.light { mix-blend-mode: screen; }
.colorize { mix-blend-mode: color; }有些模式交换前景与背板后,结果相同或非常接近,例如 multiply、screen、darken、lighten、difference 和 exclusion。overlay 与 hard-light 不满足这个规律。元素的 opacity 会在合成过程中调整前景贡献,因此把不透明度从 1 改成 .8,也不能理解成在最终截图上简单盖一层白色。
background-blend-mode 的作用范围比 mix-blend-mode 更窄:它只混合同一个元素内部的背景图层和背景色。背景图像列表从最前层写到最后层,浏览器逐层算出结果。混合模式也用逗号与图层对应;数量不足时,模式列表会循环匹配。
.hero {
background-image:
url('/texture.png'),
radial-gradient(circle at 30% 20%, #fff8, transparent 45%),
linear-gradient(135deg, #4aa8d8, #8357c5);
background-blend-mode: multiply, screen, normal;
}这些背景层会先在元素内部完成混合,不会直接碰到元素外的页面背景。不过,整个元素仍然可以再通过 mix-blend-mode 与外部背板混合。
如果一组子元素需要彼此混合,但结果不该影响组外背景,就把 isolation: isolate 写在它们的共同祖先上:
.artwork { isolation: isolate; }
.artwork > .shape { mix-blend-mode: difference; }不少创建层叠上下文的条件也会带来隔离边界,包括非 1 的 opacity、非 none 的 transform、filter、perspective、遮罩、非 normal 的混合模式,以及包含 layout 或 paint 的 contain。这也解释了一个常见怪现象:动画中途出现 opacity: .999,混合结果可能突然改变,即使肉眼几乎看不出透明度差异。
contain 与 content-visibility:明确渲染边界contain 是开发者给浏览器的一组边界承诺:这个子树在指定方面不会影响外界。浏览器因此可以缩小布局、样式或绘制的工作范围。不过,承诺和实际内容不一致时,页面行为也会跟着改变,所以不能把它当成随手开启的性能选项。
paint 表示后代不会画到容器的绘制边界之外,浏览器可以跳过不可见区域。它的效果和 overflow: hidden 有相似之处,但表达的约束更强。style 把计数器、引号嵌套等样式影响限制在子树内。它并不是通用的 CSS 样式作用域。size 让浏览器在计算容器尺寸时忽略后代和固有比例。容器没有显式尺寸时,高度可能像没有内容一样。layout 隔开内部与外部布局,并建立独立的格式化和层叠环境。content 等于 layout paint style;strict 等于 size layout paint style。.feed-card {
contain: layout paint style;
contain-intrinsic-size: auto 320px;
content-visibility: auto;
}content-visibility: visible 按常规方式渲染。hidden 会抑制内容渲染和尺寸贡献,也可能影响页面查找、文本选择、焦点与可访问树。auto 会启用内容型包含,让浏览器跳过视口外的渲染,同时内容仍应参与页面查找和键盘导航。实际使用 auto 时,通常再用 contain-intrinsic-size 预留一个接近真实值的高度,滚动时就不容易发生明显跳动。
并非所有元素都能接受全部包含类型。不生成盒子的元素、部分内部表格盒、Ruby 内部盒和非原子行内盒都有适用限制。涉及核心交互、焦点管理或动态尺寸测量时,最好在真实目标浏览器里验证,而不是只看静态页面。
shape-outside:让文字沿浮动形状绕排shape-outside 只改变浮动元素旁边的文字绕排边界。它不会裁剪元素,也不会改变元素本身的外观。这个属性目前要求目标元素参与浮动;没有 float 时,即使写了形状,通常也看不到文字绕排效果。

.leaf {
float: inline-start;
width: 16rem;
shape-outside: url('/leaf-with-alpha.png');
shape-image-threshold: .45;
shape-margin: 1rem;
margin: 0 1rem 1rem 0;
}用图片定义形状时,浏览器读取的是真实透明度。JPEG 或没有 alpha 通道的 PNG 只会得到矩形边界。shape-image-threshold 用 0 到 1 之间的阈值决定哪些像素算进绕排形状:默认值 0 会纳入所有不完全透明的像素;阈值越高,像素需要越不透明才会被纳入;值为 1 时,形状会趋于消失。跨源图片还会受到 CORS 限制。
除了图片,shape-outside 还支持基本形状和参考框:
.a { shape-outside: inset(8% 12% round 1.2rem) padding-box; }
.b { shape-outside: circle(closest-side at 45% 40%); }
.c { shape-outside: ellipse(46% 35% at center); }
.d { shape-outside参考框可以是 margin-box、border-box、padding-box 或 content-box,默认使用外边距框。inset() 的一到四个偏移值按上、右、下、左的规则复制,round 后面的圆角写法与 border-radius 相似。circle() 的半径可以使用长度、百分比、closest-side 或 farthest-side。圆形的百分比半径由宽高共同计算的参考尺度决定;椭圆则不同,ellipse() 的水平百分比看宽度,垂直百分比看高度。
polygon() 接收一组用逗号分隔的坐标点,并自动闭合路径。填充规则可以选 nonzero 或 evenodd。所有基本形状都受参考框限制,越过参考框的部分不能继续把文字推开。
shape-margin 会沿形状向外扩张绕排距离,但扩张结果仍会被参考框裁住。如果尖角附近也要留出足够空隙,可以再给浮动元素设置普通外边距,数值通常不小于形状外边距。
clip-path:用硬边界决定哪些像素可见clip-path 划出一块可见区域。区域内的像素照常显示,区域外的像素完全不画。这里改变的仍然只是绘制结果:布局中的占位不会缩小,元素本身也没有真的变成那个几何形状。
.avatar { clip-path: circle(45% at 50% 44%); }
.ticket { clip-path: inset(4px round 18px); }
.diamond { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 它可以使用 inset()、circle()、ellipse() 和 polygon(),也能与 margin-box、border-box、padding-box、content-box 组合。SVG 图形还支持 fill-box、stroke-box 和 view-box。例如,clip-path: ellipse(50% 50%) padding-box 会以内边距框为基准计算椭圆。
通过 URL 可以引用内嵌 SVG 中的 <clipPath>;外部 SVG 的支持情况需要在目标浏览器中实测。path("...") 能直接写 SVG 路径数据,不过传统路径坐标多为绝对单位。元素尺寸需要响应式变化时,百分比 polygon() 往往更容易控制。
.badge { clip-path: path("M 0 40 L 80 0 L 160 40 L 80 80 Z"); }不要裁掉可聚焦控件的焦点轮廓。outline 常画在边框外,若参考框截断它,键盘用户会失去位置提示。裁剪也不会自动修正点击区域与语义结构。
mask:用透明度或亮度控制显隐裁剪只区分显示和隐藏,遮罩则能做出连续的半透明过渡。浏览器会把遮罩图像的每个像素换算成可见度:alpha 模式读取透明通道,luminance 模式读取明暗。

mask-image 可以接收 URL、SVG <mask> 和渐变等图像值,也支持用逗号叠放多层遮罩。简单的渐隐效果直接用渐变即可,不需要额外准备图片:
.fade {
mask-image: linear-gradient(to bottom, #000 72%, transparent);
mask-mode: alpha;
}mask-mode: alpha 读取透明度。luminance 读取亮度,此时白色显示、黑色隐藏、灰色半透明;透明像素的亮度也按零处理。默认值 match-source 会根据来源选择模式:普通图像和渐变通常按 alpha 处理,SVG <mask> 通常按 luminance 处理。你也可以在 SVG <mask> 上用 mask-type: alpha | luminance 声明默认类型,但元素上显式设置的 mask-mode 优先级更高。
遮罩在尺寸、重复和位置方面的写法与背景图很接近:
.stamp {
mask-image: url('/dots.svg');
mask-size: 3rem 3rem;
mask-repeat: round space;
mask-position: center;
mask-origin: padding-box;
mask-clip: border-box;
}mask-size 接受一到两个尺寸,也支持 auto、cover 和 contain。mask-repeat 可选 repeat、repeat-x/y、no-repeat、round 或 space。mask-position 沿用背景定位的规则,默认值是 0% 0%。mask-origin 决定定位参考框,默认是 border-box,也支持其他 CSS 盒与 SVG 盒。mask-clip 把最终遮罩限制在指定盒内,另外还支持 no-clip。多层遮罩通过 mask-composite 合成。add 求并集,intersect 求交集,exclude 保留不重叠的部分,subtract 则用源层减去下方已经合成的目标层。浏览器从后向前处理这些图层,因此减法的结果会受到顺序影响。
mask 简写可以在每一层同时写图像、位置、尺寸、重复、裁切、起点、合成与模式。尺寸有一个固定要求:必须紧跟在位置后面,并用 / 分隔。
.reveal {
mask:
url('/circle.svg') top right / 5rem repeat-y subtract,
linear-gradient(#000, transparent) center / cover no-repeat add;
}边框遮罩会把一张遮罩图像切成九宫格,处理方式与 border-image 很接近。使用时先确保元素确实有边框,再设置图像来源和切片;否则边缘区域可能没有宽度来显示遮罩。
.ornament {
border: 16px solid transparent;
mask-border-source: url('/ink-frame.png');
mask-border-slice: 30 fill;
mask-border-width: 16px;
mask-border-repeat: round;
}mask-border-source 指定 URL、渐变等图像来源。mask-border-slice 用一到四个数字或百分比定义上、右、下、左四条切线。无单位数字按源图坐标解释;加上 fill 后,中心切片也会应用到内容和内边距区域。mask-border-width 控制四条边实际使用的宽度,值可以是长度、百分比、数字或 auto。mask-border-outset 把遮罩边缘向边框盒外推出。mask-border-repeat 决定边片使用 stretch、repeat、round 还是 space,水平和垂直方向可以分别设置。mask-border-mode 在 alpha 与 luminance 之间选择;mask-border 是这一组属性的总简写。边框遮罩的标准属性在不同浏览器中的支持并不整齐。项目里常常还要补写 -webkit-mask-box-image-* 前缀版本,并在目标浏览器中做截图测试。如果这层装饰不影响内容理解,准备一条普通边框作为降级会更稳妥。
object-fit 与 object-position:控制替换元素如何装进盒子object-fit 用于图片、视频等替换元素,决定内容怎样放进一个尺寸已经确定的元素盒。它本身不是裁剪路径,不过元素盒会像视窗一样,只显示落在盒内的部分。
fill 是默认值,会分别沿横向和纵向拉伸,图片可能因此变形。contain 保持比例并让内容完整进入盒子,剩余空间会留白。cover 保持比例并铺满盒子,超出的边缘会被截去。none 保留内容的固有尺寸,不主动缩放。scale-down 会比较 none 与 contain 的结果并选择较小者。它不会放大内容,但空间不足时可以缩小。.thumb {
width: 18rem;
aspect-ratio: 16 / 9;
object-fit: cover;
object-position: 65% 30%;
}object-position 沿用 background-position 的定位语法,默认值是 50% 50%。例如,cover 裁掉人物头部时,可以把画面焦点向上移动。使用 none 保留固有尺寸时,还能用超出 0% 到 100% 的值,让对象的一部分移出盒子。
object-fit 改的是盒内对象,不会修改 <img> 盒本身的宽高。完成适配后,我们仍然可以对整个盒子应用 filter、clip-path 或 mask。
复杂视觉效果出错时,真正难找的往往不是语法,而是我们认错了参考对象。可以按下面的顺序逐步缩小范围:
clip-path 或遮罩临时换成简单的 circle(45%) 或线性渐变,确认问题来自几何、来源图,还是参考框。性能开销通常会随着模糊半径、绘制面积和动画频率增长。尽量让效果只作用在尺寸受控的小区域,也不要用 will-change 长期保留大量合成图层。contain 与 content-visibility 是否值得使用,应由真实测量决定。动画里出现 filter、clip-path 或遮罩时,除了帧率,还要留意显存占用和移动端发热。
排查时可以反复核对几件事。滤镜链的顺序会改变结果;drop-shadow 读取透明轮廓,box-shadow 主要看盒子;混合模式必须先找准背板。shape-outside 只控制浮动绕排,裁剪使用硬边,遮罩可以渐变。这些视觉变化通常都不会改变原来的布局占位。
稳妥的生产策略是渐进增强:先交付没有特效也完整可读的结构,再叠加滤镜、混合、裁剪和遮罩;最后用目标浏览器、键盘和低性能设备验证。
回到开头的处理管线,这些属性各自回答了不同的问题。滤镜决定像素怎样变化,混合计算前景与背板,隔离和包含划出影响边界。shape-outside 处理文字绕排,裁剪与遮罩控制像素显隐,object-fit 则安排替换内容怎样进入既定盒子。
写效果之前先确认自己正在处理布局、像素还是背板,再按浏览器的执行顺序组合属性。这样遇到异常时,我们能很快判断该检查哪个环节,也不必靠不断叠加新属性来碰运气。