目录
1,动画原理
2,动画拆解
2.1. 动画1 — 眼睑遮罩
2.1. 动画2 — 双层模糊换图
2.1. 动画3 — 整体缩放
2.1. 动画合并
3,五种UI框架 实现
3.1. WPF 实现
技术栈要点
关键代码:遮罩结构(XAML)
关键代码:动画1 驱动
✅ 优势
⚠️ 劣势 / 坑
3.2. Web实现
技术栈要点
关键代码:遮罩路径 + SVG 模糊
关键代码:动画1 每帧刷新
✅ 优势
⚠️ 劣势
3.3. Avalonia 实现
关键代码:遮罩每帧重建 Geometry
平台补偿
✅ 优势
⚠️ 劣势 / 周折
3.4. WinUI 3 实现
关键代码:Win2D 遮罩(替代 Path.BlurEffect)
关键代码:图片模糊 workaround
关键代码:Storyboard 动画 RadiusY
✅ 优势
⚠️ 劣势
3.5. WinForms 实现
关键代码:单帧合成
✅ 优势
⚠️ 劣势
4,五种UI框架 对比
差异:同一效果用不同技术
5,总结
5.1 遮罩:形状与模糊的先后顺序
5.2 动画2:图层时序 > 动画曲线
5.3 平台常量:共享语义 vs 共享数值
5.4 框架能力边界清单
6. 源码下载
这么可爱的【眨眼】转场动画如何实现呢?
1,动画原理
我们可以理解为三层叠加:
这里的遮罩不是「画一条线」,而是矩形减去椭圆(WPFCombinedGeometry.Exclude, SVGfill-rule="evenodd"),再对整个形状做静态高斯模糊(Radius ≈ 30), 使眼睑边缘与人像产生柔和过渡。动画过程中只改变椭圆的RadiusY(270 → 10 → 270)。
// 共享参数 BlinkConstants(节选) ViewportWidth = 400, ViewportHeight = 600 EllipseCenter = (200, 300), EllipseRadiusX = 500 BlinkRadiusY: Open → Closed → Open // 各平台 Open 值略有平台补偿 MaskBlur = 30, MaskScale = 1.2 ImageScale: 1.1 ↔ 1.0 BlurPeak = 30 // 动画2 峰值模糊 DefaultHalfDurationMs = 1200 // 每半段时长不同平台 Open 半径不同,因为同一数值在不同渲染管线(WPF BlurEffect、Win2D GaussianBlur、UniformToFill 裁切)下视觉覆盖范围不一致。 因此 WPF 用 270、Avalonia 用 310、WinUI3 用 270,是在不改共享语义的前提下做平台校准。
2,动画拆解
2.1. 动画1 — 眼睑遮罩
2.1. 动画2 — 双层模糊换图
2.1. 动画3 — 整体缩放
ImageScaleGrid以中心为原点,Scale 从 1.1 缓动到 1.0 再 AutoReverse 回到 1.1,营造轻微「呼吸感」。
2.1. 动画合并
上述三路并行执行(Task.WhenAll/Promise.all),总时长仍为每半段 1200ms × 2。
3,五种UI框架 实现
3.1. WPF 实现
WPF 是本项目的参考实现:声明式 XAML +BlurEffect+Storyboard, 与设计师直觉最接近,代码量最少,效果也最「正」。
技术栈要点
- 遮罩:
Path+CombinedGeometry.Exclude+BlurEffect Radius=30 - 图片模糊:双层
Image,上层BlurEffect.Radius可动画 - 动画驱动:
DoubleAnimation+ 自定义DependencyProperty - 缩放:
ScaleTransform绑在ImageScaleGrid
关键代码:遮罩结构(XAML)
<Path x:Name="MaskPath" Fill="Black" RenderTransformOrigin="0.5,0.5"> <Path.RenderTransform> <ScaleTransform ScaleX="1.2" ScaleY="1.2"/> </Path.RenderTransform> <Path.Effect> <BlurEffect Radius="30" /> </Path.Effect> </Path>关键代码:动画1 驱动
// 通过 DP 驱动 EllipseGeometry.RadiusY,Storyboard 动画 DP 而非直接动画 Geometry BlinkRadiusY = 270; var animation = new DoubleAnimation { From = 270, To = 10, Duration = TimeSpan.FromMilliseconds(1200), AutoReverse = true, EasingFunction = BlinkEasing.Accelerate }; Storyboard.SetTargetProperty(animation, new PropertyPath(BlinkRadiusYProperty));✅ 优势
BlurEffect一行声明,边缘羽化自然- Storyboard 与 UI 线程模型成熟
- 实现最短,维护成本最低
⚠️ 劣势 / 坑
- XAML 中
BlurEffect会被 Freeze,需代码克隆 - 仅 Windows 桌面,无法跨 Web / 移动
- 复杂场景下 Blur 性能依赖 GPU 驱动
效果:
3.2. Web实现
纯前端实现:DOM 叠层 + SVG 遮罩 + CSS Filter / SVG Filter。
技术栈要点
- 遮罩:SVG
path+fill-rule="evenodd"+feGaussianBlur - 图片模糊:叠加 Blur 层,
filter: blur(Npx)+ opacity - 动画:
requestAnimationFrame+ 手写 easing(BlinkEngine.animateValue) - 合并:
Promise.all
关键代码:遮罩路径 + SVG 模糊
// even-odd:外矩形 + 内椭圆 → 与 WPF Exclude 等价 function buildMaskPathD(ry) { return 'M 0 0 H 400 V 600 H 0 Z' + ' M 200 300 m -500 0 a 500 ' + ry + ' 0 1 0 1000 0 a 500 ' + ry + ' 0 1 0 -1000 0 Z'; } // SVG filter 模糊整个 path(注意:必须先有完整形状再 blur) <filter id="maskBlur"> <feGaussianBlur in="SourceGraphic" stdDeviation="10" /> </filter>关键代码:动画1 每帧刷新
return animateValue({ from: blinkOpen, to: blinkClosed, durationMs: halfDurationMs, ease: easeIn, autoReverse: true, onUpdate: function (v) { blinkRadiusY = v; render(); } }); // render() 同步更新 scale、mask path d、blur opacity曾踩的坑:若用 SVG<mask>先硬切椭圆再 blur,边缘永远是硬边。 必须像 WPF 一样——先构造 even-odd 实心形状,再整体高斯模糊。
✅ 优势
- 零安装、易分享、与设计稿联调快
- 动画逻辑透明,rAF 时间轴完全可控
- 跨平台(任何现代浏览器)
⚠️ 劣势
- 每帧
render()全量更新,大图 + 多 blur 时 CPU 压力明显 - CSS/SVG blur 数值与 WPF 不是 1:1,需单独调参(当前 blurPeak/maskBlur=10)
- 无原生「图层合成」概念,靠 DOM 顺序模拟
效果:
3.3. Avalonia 实现
结构几乎复制 WPF:Path+BlurEffect+ 双层 Grid。 差异主要在布局裁切、UniformToFill留边、以及动画用自研AvaloniaBlinkAnimator(Dispatcher 定时器)而非 WPF Storyboard。
关键代码:遮罩每帧重建 Geometry
private void SetBlinkRadiusY(double value) { _blinkRadiusY = value; MaskPath.Data = BlinkMaskHelper.CreateMaskGeometry(value); MaskPath.InvalidateVisual(); } // 图片模糊:BlurEffect.Radius + 上层 Image Opacity 联动 public static void ApplyBlurRadius(BlurEffect effect, Image blurImage, double radius) { effect.Radius = radius; blurImage.Opacity = radius > 0.01 ? 1 : 0; }平台补偿
AvaloniaBlinkConstants.BlinkRadiusYOpen = 310— 比 WPF 270 更大,补偿 UniformToFill 在 Avalonia 下的贴边差异。
✅ 优势
- API 与 WPF 高度相似,迁移成本低
- 原生
BlurEffect遮罩效果接近 WPF - 真正跨 Windows / macOS / Linux
⚠️ 劣势 / 周折
- 多层 Clip / 固定尺寸会导致图片铺不满 — 需删冗余 Border、只保留最外层 Clip
- 动画2 曾出现错帧闪烁 — Grid 可见性与 blur 重置顺序必须对齐 WPF
- 定时器驱动不如 Storyboard 省电
效果:
3.4. WinUI 3 实现
WinUI 3没有WPF 那种可直接挂在Path上的BlurEffect。 遮罩模糊改用Win2D CanvasControl每帧 GPU 绘制;图片模糊改用预渲染模糊位图 + Opacity 交叉淡入;缩放走Composition API。
关键代码:Win2D 遮罩(替代 Path.BlurEffect)
// BlinkMaskControl.OnCanvasDraw — 每帧重绘 using var maskGeometry = rectGeometry.CombineWith( ellipseGeometry, Matrix3x2.Identity, CanvasGeometryCombine.Exclude); var blurredMask = new GaussianBlurEffect { Source = commandList, BlurAmount = (float)WinUI3BlinkConstants.MaskBlur, // 15,比 WPF 30 轻 BorderMode = EffectBorderMode.Soft }; args.DrawingSession.DrawImage(blurredMask);关键代码:图片模糊 workaround
// WinUI 无法像 WPF 那样实时动画 BlurEffect.Radius // → 换图时用 Win2D 预生成峰值模糊位图,动画只驱动 Opacity public void SetBlurRadius(double radius) => _image.Opacity = Math.Clamp(radius / BlinkConstants.BlurPeak, 0, 1);关键代码:Storyboard 动画 RadiusY
return WinUIBlinkStoryboardAnimator.AnimateAsync( _blinkMask, nameof(BlinkMaskControl.RadiusY), WinUI3BlinkConstants.BlinkRadiusYOpen, // 270 BlinkConstants.BlinkRadiusYClosed, // 10 HalfDurationMs, useEaseIn: true, autoReverse: true);WinUI3 实现周折摘要:
- 最初用 16ms 定时器 + 共享库后台线程 → UI 不更新;改为 Storyboard
- 遮罩无 BlurEffect → 硬边;引入 Win2D BlinkMaskControl
- Win2D BlurAmount=30 过重 → 平台常量降为 15
- 列表播放白帧 → 图层 Visibility 时序、grid2 先遮挡再换 grid1
- ScaleTransform + 双 Storyboard 曾崩溃 → 缩放改 Composition GPU 路径
✅ 优势
- Composition 缩放性能优秀
- Win2D 遮罩模糊可控、GPU 加速
- 适合现代 Windows 商店 / WinAppSDK 生态
⚠️ 劣势
- 缺失 WPF 级声明式 Effect,实现复杂度最高
- 图片 blur 只能「预烘焙 + opacity」,换图有异步加载窗口期
- 遮罩每帧 Canvas 重绘,需注意 Invalidate 时机
- 平台特化常量多(BlinkRadiusYOpen、MaskBlur)
效果:
3.5. WinForms 实现
没有 XAML 树:一个SKControl/PictureBox,每帧把图片层 + 遮罩层绘制到同一张位图。所有效果都在 CPU/GPU 混合的 Skia 管线里完成。
关键代码:单帧合成
private Bitmap RenderFrame() { using var surface = SKSurface.Create(new SKImageInfo(400, 600)); var canvas = surface.Canvas; canvas.Clear(SKColors.White); DrawImageLayers(canvas, 400, 600); // 先图片 DrawMask(canvas, 400, 600); // 后遮罩 return SKBitmap.FromImage(surface.Snapshot()).ToBitmap(); } // 遮罩:EvenOdd 路径 + ImageFilter 模糊 using var maskPaint = new SKPaint { ImageFilter = SKImageFilter.CreateBlur(MaskBlur/2f, MaskBlur/2f) }; canvas.DrawPath(maskPath, maskPaint);✅ 优势
- 渲染模型简单直观,调试=看一张 bitmap
- Skia 滤镜能力完整,不依赖 XAML Effect
- Legacy 项目嵌入成本最低
⚠️ 劣势
- 每帧全量重绘+ GDI+ 互操作,合并动画 CPU 占用最高
- 无声明式 UI,布局全靠手工计算 UniformToFill
- PictureBox 刷新可能闪烁,需手动 Dispose 旧 Bitmap
效果:
4,五种UI框架 对比
| 维度 | WPF | HTML | Avalonia | WinUI 3 | WinForms |
|---|---|---|---|---|---|
| 遮罩模糊 | Path.BlurEffect | SVG feGaussianBlur | Path.BlurEffect | Win2D 每帧绘制 | Skia ImageFilter |
| 图片模糊 | BlurEffect 实时 | CSS filter | BlurEffect 实时 | 预渲染+Opacity | Skia 实时 |
| 动画驱动 | Storyboard | rAF + 手写插值 | Dispatcher 定时器 | Storyboard + Composition | 定时器 + 全帧重绘 |
| 实现复杂度 | 低 | 中 | 中 | 高 | 中 |
| 合并动画性能 | 优 | 中 | 优 | 优 | 偏低 |
| 视觉还原度 | 基准 100% | ~90%(参数量) | ~95% | ~95%(校准后) | ~92% |
| 跨平台 | 仅 Windows | 全平台 | 桌面全平台 | 仅 Windows | 仅 Windows |
WPF真的是很全能!
差异:同一效果用不同技术
5,总结
5.1 遮罩:形状与模糊的先后顺序
眼睑要「糊到人像里」,必须先 even-odd 实心形,再 blur 整形状。 这在 WPF 是一行 Effect;在 HTML 曾因为 SVG mask 顺序错误导致硬边; 在 WinUI3 则完全要用 Win2D 自建管线。
5.2 动画2:图层时序 > 动画曲线
换图闪白不是 easing 问题,而是某一帧两层都不可见。 正确顺序:grid2 先盖住视口 → 在遮挡下更新 grid1 → 再跑 blur 动画; 隐藏 grid2 只用Visibility.Collapsed,不要额外Opacity=0整层。
5.3 平台常量:共享语义 vs 共享数值
BlinkAnimation.Core共享的是动画语义(时长、easing 指数、闭合半径 10), 不是每个像素级参数。Open 半径、MaskBlur 强度允许、也应该分平台校准。
5.4 框架能力边界清单
- WinUI 3:无 WPF 式 UI Effect;复杂模糊要 Win2D / Composition
- HTML:无真正图层合成;多 blur 同时运行吃 CPU
- WPF:Effect 冻结、Dispatcher 绑定;但桌面特效最省心
- WinForms:最简单 mental model,最贵的是每帧 bitmap
给 UI 体验设计者的结论:先 WPF/HTML 快速验证视觉语法(遮罩几何 + blur + 分层),再按目标平台替换「特效实现层」,而不是指望一份 XAML 到处跑。 「眨眼2」列表播放只是在「眨眼」四层逻辑上叠加换图状态机——读懂本文,就读懂了 80% 的跨平台 UI 动效迁移工作。
WPF版编写起来最容易,Web版给人一种非常柔和的感觉,Winform版也能胜任,但是像素点略微闪烁的感觉,WinUI3的模糊效果很有特色,Avalonia表现比较均衡。我先用WPF控制精准后,再用AI迁移到的其他版本。
6. 源码下载
以上五种效果的完整源码:
https://download.csdn.net/download/LateFrames/93113960