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

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

相关推荐
GISer_Jing11 分钟前
Nano Banana:AI图像生成与编辑新标杆
前端·javascript·人工智能
gyx_这个杀手不太冷静24 分钟前
上线前不做 Code Review?你可能正在给团队埋雷!
前端·代码规范·团队管理
全栈老石43 分钟前
从硬编码到 Schema 推断:前端表单开发的工程化转型
前端·vue.js·架构
weixin_462446231 小时前
【原创实践】使用 shell 脚本批量创建 Linux 用户并生成随机密码
linux·服务器·前端
hid711713661 小时前
STM32F0 打造高性能无感 FOC 无刷电机控制方案,挑战 VESC 成本极限
css
软件技术NINI1 小时前
娃娃店html+css 4页
前端·css·html
wordbaby1 小时前
TanStack Router 路径参数(Path Params)速查表
前端
盟接之桥2 小时前
盟接之桥--说制造:从“找缝隙”到“一万米深”——庖丁解牛式的制造业精进之道
大数据·前端·数据库·人工智能·物联网·制造
巴拉巴拉~~2 小时前
Flutter 通用滑块组件 CommonSliderWidget:单值 / 范围 + 刻度 + 标签 + 样式自定义
开发语言·前端·javascript
韭菜炒大葱2 小时前
现代前端开发工程化:Vue3 + Vite 带你从 0 到 1 搭建 Vue3 项目🚀
前端·vue.js·vite