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

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

相关推荐
神膘护体小月半8 分钟前
css 的 clip-path 属性,绘制气泡
css
code_YuJun8 分钟前
管理系统——应用初始化 Loading 动画
前端
oak隔壁找我9 分钟前
JavaScript 模块化演进历程:问题与解决方案。
前端·javascript·架构
代码改变世界1008615 分钟前
像素塔防游戏:像素守卫者
css·游戏·css3·1024程序员节
Elieal22 分钟前
AJAX 知识
前端·ajax·okhttp
sulikey42 分钟前
Qt 入门简洁笔记:从框架概念到开发环境搭建
开发语言·前端·c++·qt·前端框架·visual studio·qt框架
烛阴1 小时前
循环背后的魔法:Lua 迭代器深度解析
前端·lua
元拓数智2 小时前
现代前端状态管理深度剖析:从单一数据源到分布式状态
前端·1024程序员节
mapbar_front2 小时前
Electron 应用自动更新方案:electron-updater 完整指南
前端·javascript·electron
天一生水water2 小时前
three.js加载三维GLB文件,查看三维模型
前端·1024程序员节