잠시만 기다려주세요...

흑백 전환 애니메이션

이 문서는 언어를 선택할 수 있습니다
阿坝藏族羌族自治州2025년 2월 2일 일 오후 7시3.83k279예상 읽는 시간 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