新画面从圆形蒙版里“长出来”膨胀铺满全屏
几何映射 · 着色器实现 · 缓动曲线 · 性能优化 · 前沿预判
摘要
圆形物体放大转场(Circular Reveal / Radial Zoom Transition)是移动端与Web动效中极具表现力的一类场景切换手法:一个圆形蒙版从屏幕某点出发,以缓动曲线膨胀至覆盖全屏,新画面如同从圆形“窗口”中生长出来。它看似简单,实则牵涉坐标系变换、蒙版半径插值、抗锯齿、渲染层合成、帧率稳定性等一系列工程问题。
本文以“局部圆形窗口 → 全局画面接管”这一条主线贯穿全文,先建立几何与数学描述,再落到GLSL着色器、CSS/Canvas/Flutter多端实现,随后讨论缓动设计、性能与视觉伪影治理,最后给出前沿预判与可复用代码。全文强调可操作路径,所有数据标注来源,模拟数据单独说明。
笔者认为,圆形放大转场的本质不是“动画”,而是一次受控的可见性接管:在时间轴上,旧画面的可见区域被一个不断扩张的圆逐步吞并,新画面始终“垫”在圆内。理解这一点,才能把转场做得既顺滑又可控。
目录
一、转场本质:一次受控的可见性接管
在讨论任何实现之前,先把“圆形放大转场”从视觉描述翻译成工程语言。用户看到的是“新画面从圆形里长出来”,工程上发生的是:旧画面(A)与新画面(B)同时存在于渲染树中,B位于A下方或上方,一个圆形裁剪区域控制B的可见范围,该圆的半径随时间从0(或一个小值)增长到足以覆盖整个视口。
这里有一个容易被忽略的细节:圆是“裁剪B”还是“裁剪A”?两种做法视觉结果不同。若圆裁剪B(圆内显示B),则圆外仍是A,圆膨胀时B从圆内“涌出”,符合“新画面长出来”的直觉;若圆裁剪A(圆内显示A),则圆外是B,圆膨胀时A被“吞噬”,视觉上是旧画面收缩消失。本文主线采用前者,因为它在叙事上更自然——新内容主动登场。
本文评述:把转场拆成“可见性接管”后,很多问题迎刃而解。例如“圆从哪来”变成“圆心锚定在哪个语义元素上”;“膨胀多快”变成“半径插值曲线”;“边缘为什么毛糙”变成“裁剪边界的抗锯齿策略”。这种拆解方式比直接调CSS参数更可控。
从信息架构角度看,圆形转场还承担“焦点引导”功能。Material Design的动效规范指出,转场应保持空间连续性,让用户理解“我从哪里来、到哪里去”(Material Design 3, Motion, 2024)。圆形蒙版的圆心通常锚定在被点击的按钮、卡片或头像上,膨胀方向与用户注意力方向一致,因此认知负荷低。这一点在移动端尤为关键:屏幕小、手势多,转场若丢失空间锚点,用户会“迷路”。
笔者认为,圆形放大转场之所以流行,还有一个工程上的“便宜”因素:圆形裁剪在GPU上几乎零成本。相比模糊、位移、3D旋转,圆形SDF(有符号距离场)只需一次距离计算和一次step/smoothstep,片元着色器开销极低。这使得它能在低端设备上稳定60fps,而复杂转场往往掉帧。性能红利是它被广泛采用的隐性原因。
二、几何与数学:圆心、半径与坐标系
2.1 基本参数定义
设视口宽高为 W、H(单位像素),圆心为 C=(cx, cy),当前半径为 r(t),t∈[0,1] 为归一化进度。圆形蒙版区域定义为:
mask(x, y, t) = ( (x - cx)^2 + (y - cy)^2 ) <= r(t)^2
当 mask 为真时显示新画面B,否则显示旧画面A。r(t) 从 r0 增长到 rMax,其中 rMax 必须保证圆覆盖整个视口。覆盖条件为:
rMax = max( distance(C, 四个角点) )
= sqrt( max(cx, W-cx)^2 + max(cy, H-cy)^2 )
这个公式是工程实现的关键。很多初学者把 rMax 写成对角线长度 sqrt(W²+H²),虽然也能覆盖,但会导致圆在到达角落前就“超额”膨胀,视觉上后半段速度感丢失。正确做法是取圆心到最远角点的距离,让圆刚好在 t=1 时触达最远角。
2.2 坐标系陷阱:CSS像素 vs 设备像素
在Web与移动端,存在两套坐标系:CSS逻辑像素(layout pixel)与设备物理像素(device pixel)。devicePixelRatio(DPR)是二者比值。若在Canvas中直接用CSS坐标计算圆心,但Canvas的width/height按物理像素设置,圆会偏移或半径错误。正确做法是:统一在物理像素空间计算,或在逻辑空间计算后乘以DPR。
例如,某按钮在CSS中位于(120, 300),DPR=3,则物理圆心为(360, 900)。若Canvas尺寸设为 W*DPR × H*DPR,着色器中的 gl_FragCoord 即为物理像素,圆心必须用物理坐标。这一细节在Retina屏上尤其明显,忽略它会导致圆“跑偏”。
2.3 半径插值与时间轴
r(t) 的插值方式决定转场“手感”。最朴素的是线性:r(t)=r0+(rMax-r0)*t。但线性膨胀在视觉上“前慢后快”或“前快后慢”取决于人眼对面积变化的感知。人眼对面积变化更敏感,而面积正比于 r²,因此若希望面积均匀增长,应对 t 做平方根处理:r(t)=rMax*sqrt(t)。
本文评述:面积均匀并不等于“好看”。实际产品中,多数转场采用先快后慢的缓动(ease-out),让圆迅速铺开、尾部收束,制造“果断登场”的感觉。Google Photos的圆形转场、Android的Activity转场均偏向ease-out。具体曲线选择见第五章。
上表为笔者基于常见动效实践的归纳,非某单一文献数据。缓动曲线的数学定义可参考W3C CSS Easing Functions规范(W3C, 2023)。
三、着色器实现:从SDF到圆形蒙版
3.1 为什么用SDF而不是几何裁剪
有符号距离场(Signed Distance Field, SDF)是图形学中描述形状的经典方法:对任意点p,SDF返回该点到形状边界的最短距离,内部为负、外部为正(或相反约定)。圆的SDF极其简单:
float sdCircle(vec2 p, float r) {
return length(p) - r;
}
相比用 stencil buffer 或 clip path 做几何裁剪,SDF的优势在于:可以在片元着色器内直接做软边缘、发光、噪声扰动等效果,且无需额外的绘制调用。对于圆形转场,SDF让“边缘抗锯齿”和“边缘光晕”变成一行smoothstep的事。
本文评述:SDF的代价是每个片元都要算一次length(),看似浪费,但现代GPU对length()有硬件优化,且转场只持续数百毫秒,总开销可忽略。真正需要警惕的是在低端设备上做大面积全屏SDF叠加多层效果,那才会成为瓶颈。
3.2 基础GLSL实现
下面是一个可直接用于WebGL的全屏转场片元着色器核心片段。假设已有两张纹理:uTexA(旧画面)、uTexB(新画面),uProgress∈[0,1],uCenter为物理像素圆心,uMaxRadius为覆盖半径,uResolution为画布物理尺寸。
precision highp float;
uniform sampler2D uTexA;
uniform sampler2D uTexB;
uniform float uProgress;
uniform vec2 uCenter;
uniform float uMaxRadius;
uniform vec2 uResolution;
// 缓动:ease-out cubic
float easeOutCubic(float t) {
return 1.0 - pow(1.0 - t, 3.0);
}
void main() {
vec2 frag = gl_FragCoord.xy;
vec2 uv = frag / uResolution;
float t = easeOutCubic(clamp(uProgress, 0.0, 1.0));
float r = uMaxRadius * t;
float d = length(frag - uCenter) - r;
// 1.5px 软边缘,抗锯齿
float aa = 1.5;
float m = smoothstep(-aa, aa, d); // 0=圆内, 1=圆外
vec4 colA = texture2D(uTexA, uv);
vec4 colB = texture2D(uTexB, uv);
// 圆内显示B,圆外显示A
gl_FragColor = mix(colB, colA, m);
}
这段代码的关键点有三:其一,用smoothstep而不是step,避免锯齿;其二,aa取值与DPR相关,高DPR屏可适当增大;其三,mix的参数顺序决定“圆内是谁”,务必与设计意图一致。
3.3 边缘光晕与质感增强
基础版转场边缘是“硬切”,若想更有质感,可在圆边界加一圈光晕或色带。做法是在SDF距离d接近0的窄带内叠加颜色:
float ring = 1.0 - smoothstep(0.0, 12.0, abs(d)); vec3 glow = vec3(0.65, 0.35, 0.95) * ring * 0.6; gl_FragColor.rgb += glow;
光晕宽度12px为经验值,实际应随DPR缩放。需要注意的是,光晕会改变边缘亮度,若新旧画面本身对比强烈,光晕可能显得“脏”。建议光晕透明度不超过0.3,且颜色取自新画面主色调。
笔者认为,光晕是“锦上添花”而非“雪中送炭”。在性能敏感场景(如低端安卓机),应优先保证基础转场流畅,光晕可作为可选降级项。这与游戏引擎中“画质分级”的思路一致。
四、多端落地:CSS、Canvas、Flutter与原生
4.1 CSS方案:clip-path + 过渡
Web端最简单的实现是CSS clip-path的circle()函数配合transition。示例:
.reveal {
clip-path: circle(0px at 120px 300px);
transition: clip-path 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
.reveal.active {
clip-path: circle(150vmax at 120px 300px);
}
这里用150vmax作为“足够大”的半径,保证覆盖全屏。cubic-bezier(0.22,1,0.36,1)是常见的ease-out曲线。CSS方案的优点是零JS、合成层友好;缺点是clip-path动画在部分旧版浏览器上会触发重绘,且无法做边缘光晕。
本文评述:CSS方案适合“够用就好”的场景。若产品要求高保真、带光晕、带噪声扰动,则应上WebGL。选择依据不是技术先进与否,而是设计稿的复杂度与目标设备分布。
4.2 Canvas 2D方案:globalCompositeOperation
Canvas 2D可用“先画B,再用destination-in裁剪成圆”或“画A,再用圆形clip画B”两种路径。后者更直观:
ctx.drawImage(imgA, 0, 0, W, H); ctx.save(); ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.clip(); ctx.drawImage(imgB, 0, 0, W, H); ctx.restore();
Canvas 2D的clip边缘默认有抗锯齿,但性能不如WebGL,尤其在4K屏上每帧重绘两张全屏图会吃满CPU。建议仅在低分辨率或静态场景使用。
4.3 Flutter方案:ClipPath + CustomClipper
Flutter中可用ClipPath配合自定义Clipper,或直接用ClipOval配合AnimatedContainer。更灵活的是CustomClipper<Path>:
class CircleRevealClipper extends CustomClipper<Path> {
final Offset center;
final double radius;
CircleRevealClipper({required this.center, required this.radius});
@override
Path getClip(Size size) {
return Path()..addOval(Rect.fromCircle(center: center, radius: radius));
}
@override
bool shouldReclip(CircleRevealClipper old) => old.radius != radius;
}
Flutter的ClipPath在Skia/Impeller后端上会走GPU裁剪,性能良好。注意shouldReclip要正确实现,否则动画不会刷新。Flutter官方文档(Flutter, CustomClipper, 2024)对此有说明。
4.4 原生Android/iOS方案
Android可用ViewAnimationUtils.createCircularReveal(),这是API 21引入的官方API,直接支持圆心、起始半径、结束半径。iOS可用CAShapeLayer配合UIBezierPath的圆形mask,再用CABasicAnimation驱动path变化。两者都是系统级支持,性能与稳定性最佳。
本文评述:多端实现的核心差异不在“能不能做”,而在“边缘质量”和“合成层级”。原生API通常已处理好DPR与抗锯齿,Web端则需要开发者自己兜底。跨端团队应把圆心、半径、时长、曲线抽象成统一配置,各端按平台特性落地。
五、缓动曲线:让“膨胀”符合直觉
5.1 为什么线性不好看
线性半径增长意味着圆面积按t²增长,人眼感知到的“扩张速度”会越来越快,尾部显得突兀。更重要的是,线性转场缺乏“起势”和“收势”,像机器在推,而非物体在生长。
缓动曲线的作用是重新分配时间:让圆在前期快速占据视觉中心,后期缓慢收边。这符合人眼对“物体膨胀”的物理直觉——真实世界中的膨胀(如气球、涟漪)往往先快后慢,因为阻力随面积增大。
5.2 常用曲线对比
表中时长为笔者基于移动端动效实践的推荐区间,非硬性标准。实际应根据内容重要性与设备性能调整。Material Design建议转场时长在300–500ms之间(Material Design 3, 2024),但圆形转场因覆盖面积大,可适当延长至600ms。
5.3 曲线与圆心的协同
圆心位置会影响“感知速度”。若圆心靠近屏幕一角,圆需要膨胀更远才能覆盖对角,此时若用ease-out,后半段会显得“慢吞吞”。一种优化是让缓动强度随圆心到最远角距离动态调整:距离越远,ease-out越强,保证整体时长内视觉速度均匀。
笔者认为,这种“自适应缓动”是产品级转场与Demo级转场的分水岭。Demo只关心“能跑”,产品关心“每个入口都好用”。实现上可预计算rMax,再按rMax归一化缓动参数,代码量不大,体验提升明显。
六、性能与伪影:帧率、抗锯齿与合成层
6.1 帧率稳定性
圆形转场通常持续400–700ms,在60fps下约24–42帧。若中间掉几帧,人眼会感知到“卡顿”。掉帧常见原因有三:其一,每帧重新上传纹理;其二,在主线程做布局/重绘;其三,GPU填充率不足。
对策:纹理只上传一次,转场期间只更新uniform;用requestAnimationFrame驱动,避免setTimeout;若用CSS,确保clip-path动画走合成层(will-change: clip-path)。WebGL方案中,全屏quad的填充率是主要开销,但现代手机GPU处理1080p全屏片元通常无压力。
6.2 抗锯齿策略
圆形边缘的锯齿来自SDF的硬step。解决方法是smoothstep软边缘,宽度约1–2物理像素。若DPR=3,则逻辑1px对应3物理像素,软边宽度应设为3左右。软边过宽会导致边缘“发虚”,过窄则锯齿明显。
另一种方案是MSAA(多重采样抗锯齿),但全屏后处理中MSAA代价高,不推荐。SDF软边是性价比最高的选择。相关讨论可参考Inigo Quilez的SDF抗锯齿文章(Quilez, 2023)。
6.3 合成层与内存
同时持有两张全屏纹理(A和B)会占用显存。以1080×2400、RGBA8888计,单张约10.4MB,两张约20.8MB。对中低端机是可接受但需注意的量级。若转场频繁触发,应复用纹理对象,避免反复创建销毁。
本文评述:性能优化不是“越省越好”,而是“在体验达标的前提下省”。圆形转场本身开销低,真正的风险在于与其它动效叠加。工程上应建立性能预算:转场期间暂停非必要动画,确保主线程空闲。
七、进阶变体:多圆、椭圆与非规则蒙版
7.1 多圆同时膨胀
当页面有多个入口同时跳转(如网格布局),可让多个圆从各自锚点同时膨胀,最终融合覆盖全屏。实现上取多个SDF的最小值:
float d = min(min(sdCircle(p-c1,r), sdCircle(p-c2,r)), sdCircle(p-c3,r));
多圆融合处会形成“凹角”,视觉上像细胞分裂,适合活泼场景。但需注意多圆半径增长不同步时,可能出现“空洞”。建议统一进度,仅圆心不同。
7.2 椭圆与方向性
若希望转场有方向感(如从底部上滑),可将圆改为椭圆,长轴沿运动方向。椭圆SDF需做坐标缩放:
float sdEllipse(vec2 p, vec2 r) {
return (length(p / r) - 1.0) * min(r.x, r.y);
}
注意这是近似SDF,精确椭圆SDF需迭代求解,成本高。对转场而言近似足够。
7.3 噪声扰动边缘
想让转场像“墨水扩散”而非“完美圆”,可在半径上叠加噪声:
float n = noise(frag * 0.01 + uTime); float r = uMaxRadius * t + n * 20.0;
噪声频率与振幅需调参,振幅过大边缘会破碎,过小看不出效果。建议振幅为半径的2%–5%。
八、前沿预判:神经转场与实时渲染趋势
8.1 神经转场(Neural Transition)
近年有研究尝试用轻量神经网络生成转场中间帧,使新旧画面在语义上平滑过渡,而非简单几何裁剪。例如基于光流的帧插值(如RIFE、FILM)可在两帧间生成中间态。但这类方法计算量大,目前难以在移动端实时运行。笔者认为,未来2–3年内,神经转场更可能以“云端预生成+端侧播放”的形式落地,而非端侧实时推理。
8.2 可变刷新率与转场
LTPO屏幕支持1–120Hz可变刷新率。转场期间若帧率波动,缓动曲线的时间采样会不均匀,导致“忽快忽慢”。对策是使用基于时间戳的进度计算,而非基于帧计数。即 t = (now - startTime) / duration,而非 t = frameIndex / totalFrames。这一点在高刷设备上尤为重要。
8.3 WebGPU与计算着色器
WebGPU的普及让浏览器端可以做更复杂的转场效果,如基于计算着色器的实时噪声、光流近似。但圆形转场本身不需要计算着色器,片元着色器足够。WebGPU的价值在于多通道合成与更低CPU开销。相关规范可参考W3C WebGPU标准(W3C, 2024)。
本文评述:前沿技术值得关注,但不应为了“新”而“新”。圆形放大转场的核心体验在于几何与缓动,这两点用现有技术已能做到极致。神经转场是增量创新,不是替代。
九、完整代码与操作路径
9.1 操作路径清单
- 确定圆心:取被点击元素的中心,转换为物理像素坐标。
- 计算rMax:圆心到视口四角的最大距离。
- 准备两张纹理:旧画面截图、新画面截图(或直接渲染两套DOM到纹理)。
- 编写片元着色器:SDF圆 + smoothstep抗锯齿 + mix混合。
- 驱动进度:requestAnimationFrame,t基于时间戳。
- 应用缓动:ease-out cubic或自定义贝塞尔。
- 转场结束:销毁纹理,切换真实DOM,移除Canvas。
9.2 完整HTML+WebGL示例(精简版)
// 顶点着色器
const vs = `
attribute vec2 aPos;
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
`;
// 片元着色器(同上文核心片段)
const fs = `...`;
// 初始化WebGL、编译着色器、创建纹理
// 在点击事件中:
function startReveal(centerX, centerY) {
const dpr = window.devicePixelRatio || 1;
const cx = centerX * dpr;
const cy = centerY * dpr;
const W = canvas.width, H = canvas.height;
const rMax = Math.hypot(Math.max(cx, W-cx), Math.max(cy, H-cy));
const start = performance.now();
const duration = 600;
function frame(now) {
const t = Math.min((now - start) / duration, 1);
gl.uniform1f(uProgress, t);
gl.uniform2f(uCenter, cx, cy);
gl.uniform1f(uMaxRadius, rMax);
gl.drawArrays(gl.TRIANGLES, 0, 6);
if (t < 1) requestAnimationFrame(frame);
else finishReveal();
}
requestAnimationFrame(frame);
}
完整可运行示例可参考MDN WebGL教程与WebGL Fundamentals(WebGL Fundamentals, 2024)。视频讲解可搜索“WebGL circular reveal transition”获取实操演示。
十、参考文献与声明
主要参考文献
- Material Design 3. Motion – Transitions. Google, 2024.
- W3C. CSS Easing Functions Level 2. 2023.
- Quilez, I. Anti-aliasing in SDF. 2023.
- Flutter Documentation. CustomClipper class. 2024.
- W3C. WebGPU Specification. 2024.
- Android Developers. ViewAnimationUtils.createCircularReveal. 2024.
- WebGL Fundamentals. WebGL Image Processing. 2024.
- Apple Developer. CAShapeLayer & CABasicAnimation. 2024.
- RIFE: Real-Time Intermediate Flow Estimation. arXiv, 2022.
注:本文涉及的数据集与模拟数据说明——文中缓动时长区间、光晕宽度、噪声振幅等为笔者基于工程实践的推荐值,属模拟/经验数据,非某单一文献实测值。读者应以自身设备实测为准。
文章声明
本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。
内容仅供学习参考。如需引用,请以原始文献为准。

