阴影怎么写才像设计稿:Material 与 iOS 的两种系统

· 约 4 分钟 🪟 CSS 阴影

写 CSS 阴影最容易翻车的不是参数,是”系统”。一个项目里同时混用 Material 风和 iOS 风,立刻显得业余。先选一套,再调参数,是出活的顺序。

单层阴影为什么像贴纸

.card { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.25); }

这是大多数人第一次写的样子,看上去就是”PPT 阴影”。问题:

  1. 不透明度 25% 太高,物理世界里阴影边缘是从透明到半透明渐变,不是均匀深色
  2. 没有环境遮蔽,元素和背景之间缺一层贴近、低对比的暗化
  3. 方向单一,仿佛被一束 1° 直径的激光从正上方照射

修法是多层叠加

.card {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.06),    /* ambient: 紧贴元素的暗化 */
    0 4px 8px rgba(0, 0, 0, 0.08),    /* mid: 中等距离的方向影 */
    0 16px 24px rgba(0, 0, 0, 0.06);  /* far: 远距离柔和影 */
}

三层加起来不到 20% 总暗度,但层次远比 25% 单层好看。

Material elevation 系统

Material Design 把阴影量化成 dp(density-independent pixel)的”高度”,每个高度对应两层投影:一层 ambient(环境光遮蔽),一层 key light(来自上方 45°)。

常用档位(Material 3 简化版):

:root {
  --elevation-1: 0 1px 2px rgba(0,0,0,.06), 0 1px 3px rgba(0,0,0,.10);
  --elevation-2: 0 1px 2px rgba(0,0,0,.06), 0 2px 6px rgba(0,0,0,.12);
  --elevation-3: 0 1px 3px rgba(0,0,0,.08), 0 4px 12px rgba(0,0,0,.14);
  --elevation-4: 0 2px 4px rgba(0,0,0,.10), 0 8px 24px rgba(0,0,0,.16);
  --elevation-5: 0 4px 8px rgba(0,0,0,.10), 0 16px 32px rgba(0,0,0,.20);
}

.card        { box-shadow: var(--elevation-1); }
.card:hover  { box-shadow: var(--elevation-3); }
.modal       { box-shadow: var(--elevation-5); }

层级靠高度差表达:卡片是 1,按钮悬停升到 3,弹窗是 5。同一页面里超过 3 个不同高度就会乱,控制在 2-3 档。

iOS / Apple HIG 风格

iOS 不用 elevation。它的层级靠:

  1. 半透明 + 模糊backdrop-filter: blur(20px); background: rgba(255,255,255,0.7);
  2. 单层柔和阴影:仅在卡片”飘起来”时用一次
  3. 细边框border: 0.5px solid rgba(0, 0, 0, 0.08);

iOS 风的卡片:

.ios-card {
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(40px) saturate(180%);
  border: 0.5px solid rgba(0, 0, 0, 0.06);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.06);
  border-radius: 16px;
}

阴影只一层、模糊半径很大、不透明度极低。整体感觉”轻”——和 Material 的”重”形成对比。

拟物 / Neumorphism

2020 年流行过的”软 UI”靠 inset shadow + 反向高光做凹凸:

.neuro-button {
  background: #e0e5ec;
  border-radius: 16px;
  box-shadow:
    -6px -6px 12px rgba(255, 255, 255, 0.9),  /* 左上高光 */
     6px  6px 12px rgba(163, 177, 198, 0.6);  /* 右下暗影 */
}

.neuro-button:active {
  box-shadow:
    inset -4px -4px 8px rgba(255, 255, 255, 0.9),
    inset  4px  4px 8px rgba(163, 177, 198, 0.6);
}

只能在接近背景色的元素上用,背景必须是中等灰。对比度差,可访问性差,慎用。

暗色模式策略

黑底加黑阴影 = 几乎看不见。三种应对:

方案 A — 放弃阴影,用边框

[data-theme="dark"] .card {
  box-shadow: none;
  border: 1px solid rgba(255, 255, 255, 0.08);
}

方案 B — 用背景色阶

[data-theme="dark"] {
  --bg: #0a0a0a;
  --card: #1a1a1a;       /* 比背景亮 */
  --card-elevated: #242424; /* 比卡片更亮 */
}

方案 C — 加大不透明度 + 加白色 inset 高光

[data-theme="dark"] .card {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 8px 24px rgba(0, 0, 0, 0.4),
    inset 0 0 0 1px rgba(255, 255, 255, 0.04);  /* 顶边一根高光 */
}

方案 B 最稳,A 配合主题色边框很现代,C 用在不能改背景色的地方(比如卡片要透明)。

text-shadow / drop-shadow / box-shadow

三个名字接近但完全不同:

属性作用对象是否跟随 alpha 边缘GPU 合成
box-shadow元素的 border-box 矩形✗(永远是矩形阴影)
text-shadow文字本身的字形
filter: drop-shadow()元素实际像素(含透明)

带圆角的 PNG 图片用 box-shadow 会出现”矩形阴影”——必须用 filter: drop-shadow()

.cutout-img { filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.2)); }

drop-shadow 不支持 spread,多层阴影只能 drop-shadow() drop-shadow() 串起来,性能比 box-shadow 差。能用 box-shadow 不要用 drop-shadow。

性能

阴影是合成层操作,正常情况零成本。出问题的场景:

  • 大模糊 + 大元素 + 滚动:阴影边缘要跟随重绘
  • 频繁动画 box-shadow:每帧重算阴影,GPU 也吃不消

修法:

/* 把阴影挪到伪元素,只动 opacity */
.card { position: relative; }
.card::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  opacity: 0;
  transition: opacity .2s;
  pointer-events: none;
}
.card:hover::after { opacity: 1; }

动画 opacity 永远比动画 shadow 流畅。

一句话总结

先选系统(Material elevation 或 iOS 毛玻璃),不要混;暗色模式优先换层级而不是叠阴影。

❓ 常见问题

为什么我写的阴影看起来"飘",像贴纸?

90% 的 PPT 阴影是单层、纯黑、不透明度太高。真实物体在自然光下至少有两层影:一层是大范围、低对比的环境遮蔽(ambient occlusion),一层是定向光投出的方向阴影(key light)。两层叠加才像"放在桌面上",而不是"贴在桌面上"。

Material 和 iOS 的阴影系统到底差在哪?

Material 用 elevation(高度,单位 dp)量化阴影,每个高度档位预定义两到三层 box-shadow,组件之间靠"高度差"暗示层级。iOS / Apple HIG 不规定阴影曲线,倾向于单层柔和阴影 + 半透明背景模糊(毛玻璃),层级靠材质而非投影。两个系统不能混用,混用必丑。

暗色模式下阴影看不见怎么办?

暗色背景上黑阴影几乎看不见——这是物理正确,所以暗色模式应该减少甚至放弃用阴影分层,改用边框、背景色阶 (--card--bg 亮 4-8%)、毛玻璃来表达层级。如果一定要阴影,把颜色从 rgba(0,0,0,.2) 换成 rgba(0,0,0,.5) 加大模糊半径,并配合一层 inset 0 0 0 1px rgba(255,255,255,.04) 高光线,才会有立体感。

box-shadow 影响性能吗?

绝大多数场景不会——阴影是合成层操作。但大模糊半径 + 大尺寸元素 + 滚动会变卡,因为每次滚动浏览器要重绘阴影边缘。卡顿时把阴影放进 ::before 伪元素并 will-change: transform,或者用 filter: drop-shadow() 让 GPU 接管。drop-shadow 还能跟随元素的透明像素(比如带圆角的 PNG)。

🪟 打开 CSS 阴影 box/text/drop-shadow·多层叠加·拖拽偏移·Material/Neumorphism 预设