「Vue 3 + View Transition 实现炫酷圆形缩放换肤动画」

🚀 Vue 3 + View Transition 打造丝滑的换肤动效

在现代 Web 应用里,「深色模式」已经成了标配。但大多数项目的切换主题还是"闪一下",用户体验不够丝滑。

这篇文章我将结合 Vue 3 + VueUse ,再利用 原生 View Transition API ,实现一个带有 圆形扩散/收缩动画 的换肤功能,让主题切换像系统级动画一样自然。目前有很多的类似功能,但从没用过,所以写一下这个功能到项目中。


复制代码
由于是公司项目还没上线,就不贴图到平台中了,但我实现的结果就是这样,如果有好兄弟搞不定的,大家活儿一起研究研究

✨ 背景与思路

传统的主题切换通常有两种方式:

  1. 直接切 CSS class
    比如在 <html> 上加个 dark class,通过 CSS 变量覆盖样式。这种切换非常快,但肉眼可见闪烁。
  2. 加过渡动画(transition)
    在颜色属性上加 transition,能稍微柔和一点,但仍然会出现明显的"前后两帧不一致"的跳变。

而 View Transition API 带来的思路是:

在切换 DOM 状态时,浏览器会自动生成「旧快照」和「新快照」,我们只需要决定 快照如何过渡

这样就能做到类似 截图切换 → 动画过渡 → 新主题渲染 的效果,极大提升用户感受。


🛠 实现步骤

1. 基础主题切换(VueUse)

我们先用 VueUse 提供的 useDarkuseToggle 来管理主题状态:

php 复制代码
const isDark = useDark({
  storageKey: 'useDarkKey', // 本地存储 key
  valueDark: 'dark',
  valueLight: 'light'
});
const toggleDark = useToggle(isDark);

这里的逻辑很简单:isDark 是响应式变量,toggleDark() 用来切换主题。

对应的 CSS 里,我们只要写好 :roothtml.dark 的变量覆盖即可。


2. 引入 View Transition API

在点击主题切换按钮时,我们触发一个动画。核心代码:

ts 复制代码
async function toggleTheme(e: MouseEvent) {
  const htmlEl = document.documentElement;
  const x = e?.clientX ?? window.innerWidth / 2;
  const y = e?.clientY ?? window.innerHeight / 2;

  const fromDark = isDark.value;
  htmlEl.setAttribute('data-vt-from', fromDark ? 'dark' : 'light');

  if (!('startViewTransition' in document)) {
    toggleDark();
    htmlEl.removeAttribute('data-vt-from');
    return;
  }

  const transition = (document as any).startViewTransition(() => {
    toggleDark();
  });

  await transition.ready;

  const radius = Math.hypot(
    Math.max(x, window.innerWidth - x),
    Math.max(y, window.innerHeight - y)
  );

  const expand = [`circle(0px at ${x}px ${y}px)`, `circle(${radius}px at ${x}px ${y}px)`];
  const collapse = [`circle(${radius}px at ${x}px ${y}px)`, `circle(0px at ${x}px ${y}px)`];

  let anim: Animation;
  if (fromDark) {
    anim = htmlEl.animate({ clipPath: collapse }, {
      duration: 600,
      easing: 'ease-in-out',
      pseudoElement: '::view-transition-old(root)',
      fill: 'both'
    } as any);
  } else {
    anim = htmlEl.animate({ clipPath: expand }, {
      duration: 600,
      easing: 'ease-in-out',
      pseudoElement: '::view-transition-new(root)',
      fill: 'both'
    } as any);
  }

  await anim.finished;
  htmlEl.removeAttribute('data-vt-from');
}

这里有几个关键点:

  • startViewTransition:浏览器快照切换的入口。
  • pseudoElement :通过 ::view-transition-old(root) / ::view-transition-new(root) 区分新旧快照。
  • clipPath 动画:用圆形扩散/收缩的方式做出「聚焦」的感觉。

3. CSS 变量与主题适配

只需在 :roothtml.dark 中定义一组变量:

css 复制代码
:root {
  --dashboard-bg-color: #f5f7f8;
  --dashboard-title-color: #000;
  // ...
}

html.dark {
  --dashboard-bg-color: #002d6d;
  --dashboard-title-color: #fff;
  // ...
}

这样,组件里所有颜色都通过 var(--xxx) 读取,就能随着主题一键切换。

📌


🔧 优化点

  1. 兼容性处理
    View Transition API 目前只在 Chromium 系列浏览器可用。代码里已经做了 fallback(直接切换)。
  2. 动画起点
    动画的圆心取点击坐标 (x, y),这样有"从点击处扩散"的效果。如果用户不是点击,而是快捷键触发,就默认屏幕中心。
  3. 过渡顺序
    通过 data-vt-from + CSS,保证新旧快照的层级控制,避免出现叠放问题。

🎯 最终效果

  • 点击「切换主题」 → 从点击点扩散动画 → 平滑进入暗黑/亮色模式。
  • 不支持 API 的浏览器 → 直接切换,保证功能可用。
  • 颜色管理统一使用 CSS 变量,方便全局维护。

📌


📝 总结

相比于传统的闪切,View Transition API 能让换肤的体验 "无缝 + 丝滑" ,而且配合 VueUse 的封装,代码量也不大。

如果你正在做 后台管理系统 / 数据大屏 / 桌面风格应用,强烈推荐尝试这种换肤方式。

相关推荐
youcans_4 分钟前
【Trae】Trae 插件实战手册(1)PyCharm 安装 Trae
人工智能·python·pycharm·ai编程·trae
卷Java7 分钟前
小程序前端功能更新说明
java·前端·spring boot·微信小程序·小程序·uni-app
FogLetter7 分钟前
前端性能救星:虚拟列表原理与实现,让你的10万条数据流畅如丝!
前端·性能优化
我是天龙_绍8 分钟前
前端驼峰,后端下划线,问:如何统一?
前端
知识分享小能手16 分钟前
微信小程序入门学习教程,从入门到精通,微信小程序常用API(下)——知识点详解 + 案例实战(5)
前端·javascript·学习·微信小程序·小程序·vue·前端开发
code_YuJun16 分钟前
nginx 配置相关
前端·nginx
对不起初见2 小时前
PlantUML 完整教程:从入门到精通
前端·后端
东方掌管牛马的神2 小时前
oh-my-zsh 配置与使用技巧
前端
你的人类朋友2 小时前
HTTP请求结合HMAC增加安全性
前端·后端·安全