お待ちください...

白黒切り替えアニメーション

このドキュメントは、言語を選択できます
阿坝藏族羌族自治州2025年2月2日(日) 19時3.83k271見積もり読書時間 7 分
QRコード
お気に入りCtrl + D

最近、白黒切り替えアニメーションを実装したいと思いました。antfuさんのブログでその効果を見て、とても良いと思いました。ソースコードを見て、ちょうどnuxt3を使っているので、そのまま使いました。

antfu

具体的なコードの場所: https://github.com/antfu/antfu.me/blob/main/src/logics/index.ts

完全なコード

DarkToggle.vue
        <script setup lang="ts">
const mode = useColorMode()
const isDark = computed<boolean>({
  get() {
    return mode.value === 'dark'
  },
  set() {
    mode.preference = isDark.value ? 'light' : 'dark'
  },
})

/**
 * クレジット: [@hooray](https://github.com/hooray)
 * @see https://github.com/vuejs/vitepress/pull/2347
 */
function toggle(event?: MouseEvent) {
  const isAppearanceTransition = typeof document !== 'undefined'
  // @ts-expect-error document.startViewTransition can be undefined
    && document.startViewTransition
    && !window.matchMedia('(prefers-reduced-motion: reduce)').matches

  if (!isAppearanceTransition || !event) {
    isDark.value = !isDark.value
    return
  }

  const x = event.clientX
  const y = event.clientY
  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y),
  )
  const transition = document.startViewTransition(async () => {
    isDark.value = !isDark.value
    await nextTick()
  })

  transition.ready.then(() => {
    const clipPath = [
      `circle(0px at ${x}px ${y}px)`,
      `circle(${endRadius}px at ${x}px ${y}px)`,
    ]
    document.documentElement.animate(
      {
        clipPath: isDark.value
          ? clipPath.reverse()
          : clipPath,
      },
      {
        duration: 400,
        easing: 'ease-out',
        pseudoElement: isDark.value
          ? '::view-transition-old(root)'
          : '::view-transition-new(root)',
      },
    )
  })
}

const context = {
  mode,
  isDark,
  toggle,
}
</script>

<template>
  <ClientOnly placeholder-tag="span">
    <slot v-bind="context" />
  </ClientOnly>
</template>

<style>
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
::view-transition-old(root) {
  z-index: 1;
}
::view-transition-new(root) {
  z-index: 9999;
}
.dark::view-transition-old(root) {
  z-index: 9999;
}
.dark::view-transition-new(root) {
  z-index: 1;
}
</style>

    
折りたたむ

コードの説明

modeuseColorMode フックを使用して現在のカラーモードを取得します。

isDark は現在のモードがダークモードかどうかを判断し、モードを切り替える機能を提供する計算プロパティです。

toggle 関数はダークモードとライトモードを切り替えるために使用されます。

isAppearanceTransition はビュー遷移アニメーションがサポートされているかどうか、およびユーザーがモーション効果を減らすことを選択していないかどうかを確認します。 ビュー遷移アニメーションがサポートされていない場合やイベントが渡されていない場合は、直接モードを切り替えます。 ビュー遷移アニメーションがサポートされている場合は、クリック位置と遷移アニメーションの終了半径を計算し、ビュー遷移アニメーションを開始して、アニメーション中にモードを切り替えます。

Math.hypot は直角三角形の斜辺の長さを計算し、アニメーションがビューポート全体をカバーすることを保証します。

innerWidth は読み取り専用プロパティで、ウィンドウのドキュメント表示領域の幅をピクセル単位で返します。スクロールバーの幅も含まれます。アニメーションの拡散半径を計算する際に、innerWidth を使用して、トリガーポイントからビューポートの端までの最遠距離を決定します。

transition.ready.then では、clipPath を使用して円形の拡張アニメーション効果を実現するために、遷移アニメーションの効果を定義します。

ダークモードへの切り替え:clipPath.reverse() は大から小へ拡張します。

ライトモードへの切り替え:clipPath は小から大へ拡張します。

使用方法

TopBar.vue
        <template>
    ...others
    <DarkToggle v-if="isDesktop">
        <template #default="{ toggle }">
        <div cursor="pointer" @click="toggle">
            <Icon size="20" name="material-symbols:light-mode-outline-rounded" dark="hidden" />
            <Icon size="20" name="material-symbols:nightlight-outline-rounded" light="hidden" />
        </div>
        </template>
    </DarkToggle>
    ...others
</template>

    

効果

it-fb

星空