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

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


1,动画原理

我们可以理解为三层叠加

这里的遮罩不是「画一条线」,而是矩形减去椭圆 (WPF CombinedGeometry.Exclude, SVG fill-rule="evenodd"),再对整个形状做静态高斯模糊 (Radius ≈ 30), 使眼睑边缘与人像产生柔和过渡。动画过程中只改变椭圆的 RadiusY(270 → 10 → 270)。

ini 复制代码
// 共享参数 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)
xml 复制代码
<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 驱动
ini 复制代码
// 通过 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 模糊
javascript 复制代码
// 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 每帧刷新
php 复制代码
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
ini 复制代码
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)
ini 复制代码
// 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
arduino 复制代码
// WinUI 无法像 WPF 那样实时动画 BlurEffect.Radius
// → 换图时用 Win2D 预生成峰值模糊位图,动画只驱动 Opacity
public void SetBlurRadius(double radius) =>
    _image.Opacity = Math.Clamp(radius / BlinkConstants.BlurPeak, 0, 1);
关键代码:Storyboard 动画 RadiusY
csharp 复制代码
return WinUIBlinkStoryboardAnimator.AnimateAsync(
    _blinkMask, nameof(BlinkMaskControl.RadiusY),
    WinUI3BlinkConstants.BlinkRadiusYOpen,  // 270
    BlinkConstants.BlinkRadiusYClosed,      // 10
    HalfDurationMs, useEaseIn: true, autoReverse: true);

WinUI3 实现周折摘要:

  1. 最初用 16ms 定时器 + 共享库后台线程 → UI 不更新;改为 Storyboard
  2. 遮罩无 BlurEffect → 硬边;引入 Win2D BlinkMaskControl
  3. Win2D BlurAmount=30 过重 → 平台常量降为 15
  4. 列表播放白帧 → 图层 Visibility 时序、grid2 先遮挡再换 grid1
  5. 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 管线里完成。

关键代码:单帧合成
ini 复制代码
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迁移到的其他版本。

相关推荐
颜进强1 小时前
前端看后端 13:什么是 Cookie 和 Session?
前端·后端
月月大王的3D日记1 小时前
Three.js 入门系列(7):让甜甜圈围着我转 —— 3D文字生成
前端·javascript
gitboyzcf1 小时前
mpegts.js解决Chrome无法播放7568×142分辨率报错 DOMException问题
前端·后端
yio_yin1 小时前
MyBatis
java·前端·mybatis
棒棒的唐1 小时前
非常棒的向量数据库chroma的前端管理工具及向量模型配置
前端
芭拉拉小魔仙2 小时前
前端文件下载与错误处理实战指南
前端
Yao8063 小时前
MinIO自建对象存储,省OSS费用的完整方案
前端·后端
2501_933923253 小时前
设计网页的时候加载不出来怎么办?认识常见的状态码
前端·spring
only-lucky3 小时前
QML深入学习五(Controls模块)
前端·javascript·学习