跳到主要内容

CSS 投影实战:box-shadow 五个参数、内阴影与多层叠加的立体做法

讲清 box-shadow 的 x/y 偏移、模糊、扩展、颜色五个参数,内阴影 inset 的坑,以及多层叠加做拟物立体的方法,附一段真实生成的阴影代码和性能注意点。

发布于 作者 李雷
#CSS #box-shadow #前端 #UI

CSS 投影实战:box-shadow 五个参数、内阴影与多层叠加的立体做法

很多人写 box-shadow 全靠在 DevTools 里反复拖,拖到看着顺眼为止。问题是,顺眼之后你说不清为什么顺眼,下次换个元素又得从头试。其实这个属性的逻辑很清楚,把五个参数和叠加规则摸透,你就能想要什么效果直接写出来,而不是碰运气。

五个参数的顺序不能记错

按 MDN 的定义,完整的单层语法是 box-shadow: <offset-x> <offset-y> <blur-radius> <spread-radius> <color>,前两个长度值是必填的偏移量,后两个长度值和颜色可选。这个顺序是规范规定的,写错位置浏览器不会报错,只会给你一个不是你想要的阴影。

逐个说:

  • offset-x:水平偏移。正值阴影往右,负值往左。
  • offset-y:垂直偏移。正值往下,负值往上。绝大多数 UI 阴影 y 是正的,因为现实里光从上方来,影子落在下面。
  • blur-radius:模糊半径,必须非负。值越大边缘越虚越柔和,0 就是一条硬边。这是廉价感和讲究感的分水岭。
  • spread-radius:扩展半径。正值让阴影整体向四周膨胀,负值让它收缩。做"贴边描边"或者收紧光晕时用得上。
  • color:阴影颜色。强烈建议用带透明度的 rgba,而不是纯色。

颜色一定要带透明度

新手最常见的错误,是图省事写 box-shadow: 0 10px 20px #000。结果是一道又黑又硬的色块,像贴了块黑纸。现实世界里的影子都是半透明的,光透过去会衰减。所以正确写法是 rgba(0,0,0,0.15) 这类低透明度的值,让阴影若有若无地压在背景上。

记住一个判断标准:如果你的阴影边缘能看出一条明确的"颜色边界",那 alpha 八成给高了。

内阴影 inset:凹进去的那种

在最前面加一个 inset 关键字,阴影就从"浮起"变成"凹陷"。按钮按下态、输入框内陷、开关凹槽,都靠它。

但 inset 有两个跟着规范走的"怪现象",不是浏览器 bug:

第一,inset 阴影会跟着 border-radius 走,而且正的 spread 会向内收缩可见边缘。圆角大的元素如果 inset 给了较大的正 spread,阴影会侵蚀到内容区,把文字盖住。做内阴影时先把 spread 设成 0 或者一点点负值试。

第二,inset 阴影的裁剪区是 padding box 而不是 border box。元素 border 一粗,内阴影看起来就从 border 里面开始,而不是齐平边缘。要齐平,加 background-clip: padding-box,或者干脆套一层 wrapper。

多层叠加:立体感的真正来源

单层阴影永远做不出有质感的立体。秘诀是把"暗度"拆开:一层柔和的环境光(透明度低、blur 大、扩散开),叠一层紧凑的关键光(透明度略高、blur 小、贴近元素)。这正是 Material Design 的 elevation 做法,一条环境光加一条关键光。

我自己接需求时,最省事的就是直接在 /zh/t/box-shadow-generator/ 里挑一个 Material 预设当起点,再微调。比如要一个浮起的卡片,工具实时给出的复合阴影是这样:

box-shadow:
  0 1px 3px rgba(0, 0, 0, 0.12),
  0 8px 24px rgba(0, 0, 0, 0.14);

第一行是贴着卡片下沿的接触阴影,第二行是散开的环境光。两层叠起来,卡片才像真的离开了平面。多个阴影之间用逗号隔开,书写顺序就是绘制顺序,先写的画在上面。inset 层和外层可以混着写在同一个声明里,这就是按钮按下态又凹又有边的实现方式。

性能:层数不是问题,blur 才是

很多人怕叠多层会卡。实际上现代浏览器对 box-shadow 走 GPU 合成,绘制成本主要取决于最大的那个 blur 半径,而不是层数。三层 12px blur 的复合阴影,跟单层 12px 的成本差不多,静态卡片叠 3 到 5 层都没有可感知的损耗。

真正要小心的是两种情况:一是 blur 加 spread 加起来很大(比如 200px 以上)的光晕,挂在每帧都做 transform 动画的元素上,会爆 paint 预算;二是频繁重排的元素,比如每次渲染都改尺寸的列表,在上面挂阴影代价偏高。规避方法就一句话:凡是每帧都动的东西,blur 给小一点。

阴影调好之后,如果你还要配渐变背景或者异形裁剪,可以顺手用 /zh/t/gradient-generator/ 一起把整块视觉收口,省得在多个工具间来回切。

把这五个参数、inset 的两个坑、多层叠加的环境光加关键光结构记住,box-shadow 这件事就从"凭感觉拖"变成"想要什么直接写"了。


Made by Toolora · Updated 2026-06-13