「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 的封装,代码量也不大。

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

相关推荐
一枚前端小能手几秒前
🔧 半夜被Bug叫醒的痛苦,错误监控帮你早发现
前端
Juchecar2 分钟前
Vue 3 单页应用Router路由跳转示例
前端
liang_jy2 分钟前
数组(Array)
数据结构·面试·trae
这人是玩数学的4 分钟前
在 Cursor 中规范化生成 UI 稿实践
前端·ai编程·cursor
UncleKyrie5 分钟前
🎨 市面上主流 Figma to Code MCP 对比
前端
南半球与北海道#18 分钟前
前端引入vue-super-flow流程图插件
前端·vue.js·流程图
然我24 分钟前
React 16.8:不止 Hooks 那么简单,这才是真正的划时代更新 🚀
前端·react.js·前端框架
小高00738 分钟前
📈前端图片压缩实战:体积直降 80%,LCP 提升 2 倍
前端·javascript·面试
OEC小胖胖41 分钟前
【React Hooks】封装的艺术:如何编写高质量的 React 自-定义 Hooks
前端·react.js·前端框架·web
BillKu1 小时前
vue3+element-plus 输入框el-input设置背景颜色和字体颜色,样式效果等同于不可编辑的效果
前端·javascript·vue.js