几何遮罩与揭幕过渡:让界面切换像一层光被慢慢打开
有些过渡动画,并不是为了让元素“动起来”。
它更像是把界面之间的关系说清楚:旧画面怎样退场,新画面怎样出现,用户的注意力应该被带到哪里。普通的 opacity 和 translateY 很实用,但当你想表达“打开”“揭幕”“扫描”“聚焦”“切换场景”时,几何遮罩会更贴近这种叙事。
最近我在做一个应用启动 Loading 的视觉优化。最开始,画面中心放了一个很大的百分比数字,后来又试过圆环、数字、横向进度条一起出现。结果很快发现一个问题:进度被表达了太多次,视觉变得吵了。
最后稳定下来的方案反而更简单:只保留一个小号品牌字标和百分比,让真正的过渡交给中心扩张的几何圆形。也正是这个过程,让我重新整理了一套通用方法:把几何图形当作过渡的主角,而不是把它当作又一个装饰元素。
一、核心思想:内容不动,遮罩动
几何揭幕过渡最重要的结构不是“圆形”本身,而是分层:
实现时,真正的 UI 尽量保持稳定。我们不要在复杂 DOM 树上做大范围 layout 动画,而是把一个很小、很独立的遮罩层放在上面。这个遮罩层可以是:
- 一个不断扩张的圆形
- 一个斜向扫过屏幕的多边形
- 一个从左到右推进的柔边渐变
- 一个 SVG path 形成的窗口
- 一张 canvas/WebGL matte 纹理
换句话说,过渡动的是“可见性边界”,不是整个界面。
这种思路的好处是:
- 底层内容可以提前渲染,减少首帧闪烁
- 动画只集中在一个遮罩子树里,性能更容易控制
- 视觉叙事清晰,不需要叠加很多进度控件
- 深浅色主题、品牌色、背景内容可以更独立地处理
二、先选隐喻,再选技术
很多失败的过渡效果,不是技术错了,而是隐喻没想清楚。
在写代码前,我会先问一句:这次切换到底像什么?
| 过渡隐喻 | 适合场景 | 常见技术 |
|---|---|---|
| 打开 / 揭幕 | 启动页、欢迎页、页面进入 | clip-path: circle()、radial mask-image |
| 聚焦 / 探照 | 搜索、指引、局部强调 | 圆形 aperture、SVG mask |
| 扫描 / 扫光 | 数据面板、检测状态、科技感场景 | linear gradient mask、斜向 overlay |
| 折页 / 切片 | 编辑器、作品集、游戏 UI | clip-path: polygon()、SVG path |
| 柔化消失 | 图片、背景、卡片边缘 | mask-image: linear-gradient() |
| 纹理显影 | 影像、品牌视觉、实验性界面 | canvas/WebGL matte、noise mask |
这里有个很重要的取舍:能用 CSS 表达清楚,就先不要上 canvas/WebGL。 只有当过渡依赖粒子、噪声、流体、视频或大量独立图形时,canvas 才真的值得引入。
三、最小结构:三层就够
先看一个非常小的结构。它不依赖具体业务,只表达“目标内容已经在下面,遮罩层在上面慢慢打开”:
几个细节值得固定成习惯:
- 外层用
relative isolate overflow-hidden,把层叠上下文和裁切范围收住 - 遮罩层
pointer-events-none,不抢点击 - 遮罩层
aria-hidden="true",不参与语义 - 目标内容先挂载在底下,遮罩只负责“露出来”
CSS 也很短:
这段代码表达的是“在遮罩层中挖出一个透明圆孔”。圆孔越大,底下内容露出来越多。
如果你想做的是“一个实心圆逐渐覆盖屏幕”,那就反过来:让圆形 overlay 本身扩张,而不是在遮罩上挖孔。
四、用 CSS 变量驱动几何参数
遮罩过渡最舒服的写法,是把图形参数变成 CSS 变量。
React 或 Motion 不直接关心 mask-image 的具体语法,只负责把 --reveal-radius、--wipe-x、--edge 这些变量从 A 推到 B。
这种拆法有几个好处:
- 动画库可以替换,Motion、GSAP、Web Animations API、
requestAnimationFrame都行 - CSS 里保留了图形语义,调试时更直观
- JS 只更新少数变量,不需要频繁改 className 或重排布局
没有 Motion 的项目,也可以用 requestAnimationFrame:
五、覆盖半径不要靠猜
如果是圆形或椭圆揭幕,经常会遇到一个小坑:中心圆看起来已经很大了,但宽屏或高屏的角落还没被覆盖到。
正确做法是根据视口或容器尺寸计算“到最远角的距离”:
如果揭幕原点不在中心,例如从按钮位置扩散,就要算这个点到四个角的最大距离:
这类几何计算非常值得写清楚。它比“把 radius 设成 200vw”更可靠,也更容易维护。
六、几种常见变体
1. 圆形 aperture:适合启动和聚焦
圆形 aperture 最适合表达“从一点打开”。它可以从视口中心开始,也可以从用户点击的按钮位置开始。
如果只是简单裁切,clip-path 很直接;如果需要软边、反向挖孔或更强的 Safari 兼容控制,mask-image 往往更灵活。
2. 斜向 wipe:适合场景切换
斜切过渡可以用一个旋转后的 overlay 做,也可以直接用 polygon。
--wipe 从 -30% 推到 130%,就能形成一条斜向扫过屏幕的几何边界。
3. 柔边渐变:适合媒体和背景
当你不想要很硬的切割感时,可以让边界带一点渐变:
这类效果很适合图片、视频、背景纹理。它不会强行宣布“我在转场”,而是安静地让内容退出。
4. SVG mask:适合复杂形状
当你需要的是品牌图形、手绘路径、非规则开口时,SVG mask 更合适。
SVG 的优势是路径表达能力强,代价是你需要更认真地处理尺寸、坐标系和浏览器差异。
七、时序模型:不要在最后一帧硬切
一个完整的揭幕过渡,通常不是“动画结束,马上删掉”。更稳的时序是:
几个经验值:
- 常规 UI 过渡:
0.4s - 0.6s - 启动 / 入场揭幕:
0.7s - 1s - 完成停留:
80ms - 180ms - 退出辅助文字:比主过渡短一些,通常
120ms - 300ms
如果是 Loading,建议把“合成进度”停在 90% - 95%,等应用真的 ready 后再收束到 100%。这样不会向用户展示虚假的完成状态。
八、一个容易忽略的设计原则:进度只说一遍
这次启动 Loading 优化里,最有价值的结论其实不是某段 CSS,而是一个减法原则:
如果几何图形已经在表达进度,就不要再同时放圆环、进度条、大号百分比。
进度可以有很多表现方式:
- 形状扩张
- 数字增长
- 进度条推进
- 圆环描边
- 文案阶段变化
但一个画面里最好只保留一个主表达。其他信息要么是辅助读数,要么就应该删掉。
我最后更喜欢的结构是:
这比“圆环 + 数字 + 进度条 + 大圆揭幕”更安静,也更像一个真正的启动画面。
九、可访问性与 reduced motion
遮罩过渡经常是纯视觉层,不应该干扰语义。
最低限度要做到:
- 装饰遮罩使用
aria-hidden="true" - 遮罩层使用
pointer-events: none - 过渡结束后移除遮罩 DOM
- 尊重
prefers-reduced-motion - 如果是真进度,用
role="progressbar"和aria-valuenow - 如果是假进度,不要把它伪装成真实进度暴露给辅助技术
一个 reduced motion 分支可以很简单:
在偏工具型产品里,reduced motion 不只是“照顾少数人”,也是对重复工作场景的尊重。动画可以有品味,但不能让用户每次操作都被迫看完表演。
十、性能与验收清单
几何遮罩看起来像视觉问题,最后经常会变成工程问题。下面这份清单我会在实现后逐项扫一遍:
性能
- 只动画遮罩层,不动画复杂内容树
- 优先驱动 CSS 变量,而不是频繁改 DOM 结构
- 遮罩层使用
isolate/contain限制影响范围 will-change只加在真正动画的元素上- 过渡完成后清理 DOM、定时器、RAF、订阅
- resize 或容器变化后重新计算覆盖半径
视觉
- 第一帧没有白屏、黑屏或硬闪
- 完成帧不会突然跳切
- 所有视口尺寸下都能覆盖到角落
- 深色 / 浅色主题对比度稳定
- 过渡时文字不会因为 blend mode 变脏
- 截图验证时要确认全局遮罩已经完全移除
交互
- 遮罩不拦截点击
- 过渡结束后焦点仍在合理位置
- 路由切换不要阻塞输入太久
- 高频操作场景不要滥用大过渡
收束:把遮罩当作“叙事层”
几何遮罩过渡最迷人的地方,是它能把技术和视觉语言接起来。
它不是简单的 CSS 技巧,也不只是 Motion 的某个用法。更准确地说,它是一种分层思维:
- 内容层负责真实信息
- 遮罩层负责可见性叙事
- 动画层负责时间和节奏
- 清理阶段负责工程稳定性
当这几层各自只做一件事时,过渡效果就会变得轻、稳、可复用。它可以是一个圆形揭幕,也可以是一道斜切光、一层柔边雾、一块 SVG 品牌图形,甚至是一张带噪声的动态 matte。
真正重要的不是形状本身,而是那个判断:这个界面切换,是否值得被温柔地打开。