全部文章

NOTE 04 / 前端开发

Astro 博客的圆形主题切换动画

用 View Transition API 从点击位置展开新主题,并处理键盘操作和减少动态效果。

主题切换时让新配色从点击位置扩散,可以用浏览器的 View Transition API。浏览器先截取前后两个页面,再把新页面的快照裁剪成一个逐渐变大的圆。在 Astro 中,把交互组件用 client:load 加载即可。

算出能盖住整个视口的半径

从圆心取横向和纵向各自的最大距离,再用 Math.hypot 得到最远角落的距离。鼠标用点击点,键盘用按钮中心。

function toggleTheme(event: MouseEvent) {
  const root = document.documentElement
  const next = root.dataset.theme === 'dark' ? 'light' : 'dark'
  const apply = () => { root.dataset.theme = next }

  if (!document.startViewTransition ||
      matchMedia('(prefers-reduced-motion: reduce)').matches) {
    apply()
    return
  }

  const button = (event.currentTarget as HTMLElement).getBoundingClientRect()
  const x = event.detail ? event.clientX : button.left + button.width / 2
  const y = event.detail ? event.clientY : button.top + button.height / 2
  const radius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y)
  )
  const clip = (r: number) => 'circle(' + r + 'px at ' + x + 'px ' + y + 'px)'
  const transition = document.startViewTransition(apply)
  transition.ready.then(() => {
    root.animate(
      { clipPath: [clip(0), clip(radius)] },
      { duration: 420, easing: 'ease-out', pseudoElement: '::view-transition-new(root)' }
    )
  }).catch(() => {
    // 快照跳过时,主题仍由 apply 更新。
  })
}

关掉默认淡入淡出

::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-new(root) {
  z-index: 1;
}

配色 CSS 要读同一个 data-theme 属性。如果项目用 html.dark,就把 apply 改成增删 class。需要记住选择时,在初始渲染前读取本地设置,避免刷新时先亮再暗。

这个例子只处理当前页面里的主题切换。原笔记中的 ViewTransitions 是 Astro 路由切换的历史写法。需要跨页面导航动画时,再看当前 Astro 的 ClientRouter。

View Transition API · Astro View Transitions

返回文章列表