「眨眼」UI 特效 - 在 Winform/WPF/WinUI3/Avalonia/Web 的实现


1


这么可爱的【眨眼】转场动画如何实现呢?

1,动画原理
我们可以理解为三层叠加:
image

这里的遮罩不是「画一条线」,而是矩形减去椭圆(WPF CombinedGeometry.Exclude, SVG fill-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 — 眼睑遮罩

image

1

2.1. 动画2 — 双层模糊换图
image

2

2.1. 动画3 — 整体缩放
ImageScaleGrid 以中心为原点,Scale 从 1.1 缓动到 1.0 再 AutoReverse 回到 1.1,营造轻微「呼吸感」。

59d7dd42168a4e9085a4bc2fcdce9cc7

2.1. 动画合并
上述三路并行执行(Task.WhenAll / Promise.all),总时长仍为每半段 1200ms × 2。

59d7dd42168a4e9085a4bc2fcdce9cc7


3,五种UI框架 实现
3.1. WPF 实现
WPF 是本项目的参考实现:声明式 XAML + BlurEffect + Storyboard, 与设计师直觉最接近,代码量最少,效果也最「正」。

技术栈要点
遮罩:Path + CombinedGeometry.Exclude + BlurEffect Radius=30
图片模糊:双层 Image,上层 BlurEffect.Radius 可动画
动画驱动:DoubleAnimation + 自定义 DependencyProperty
缩放:ScaleTransform 绑在 ImageScaleGrid
image

关键代码:遮罩结构(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 驱动
效果:
59d7dd42168a4e9085a4bc2fcdce9cc7


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
image

关键代码:遮罩路径 + 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  先硬切椭圆再 blur,边缘永远是硬边。 必须像 WPF 一样——先构造 even-odd 实心形状,再整体高斯模糊。

优势
零安装、易分享、与设计稿联调快
动画逻辑透明,rAF 时间轴完全可控
跨平台(任何现代浏览器)
劣势
每帧 render() 全量更新,大图 + 多 blur 时 CPU 压力明显
CSS/SVG blur 数值与 WPF 不是 1:1,需单独调参(当前 blurPeak/maskBlur=10)
无原生「图层合成」概念,靠 DOM 顺序模拟
效果:

59d7dd42168a4e9085a4bc2fcdce9cc7


3.3. Avalonia 实现
结构几乎复制 WPF:Path + BlurEffect + 双层 Grid。 差异主要在布局裁切、UniformToFill 留边、以及动画用自研 AvaloniaBlinkAnimator(Dispatcher 定时器)而非 WPF Storyboard。
image

关键代码:遮罩每帧重建 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 省电
效果:
59d7dd42168a4e9085a4bc2fcdce9cc7


3.4. WinUI 3 实现
WinUI 3 没有 WPF 那种可直接挂在 Path 上的 BlurEffect。 遮罩模糊改用 Win2D CanvasControl 每帧 GPU 绘制;图片模糊改用 预渲染模糊位图 + Opacity 交叉淡入;缩放走 Composition API。
image

关键代码: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)
效果:
59d7dd42168a4e9085a4bc2fcdce9cc7


3.5. WinForms 实现
没有 XAML 树:一个 SKControl / PictureBox,每帧把图片层 + 遮罩层 绘制到同一张位图。所有效果都在 CPU/GPU 混合的 Skia 管线里完成。
image

关键代码:单帧合成

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
效果:
59d7dd42168a4e9085a4bc2fcdce9cc7


4,五种UI框架 对比
image

差异:同一效果用不同技术
image


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迁移到的其他版本。

59d7dd42168a4e9085a4bc2fcdce9cc7

posted @ 2026-08-03 08:02  行人--  阅读(6)  评论(0)    收藏  举报