|
| 1 | +<script setup lang="ts"> |
| 2 | +import { nextTick, onBeforeUnmount, onMounted, watch } from 'vue' |
| 3 | +import { useRoute } from 'vitepress' |
| 4 | +
|
| 5 | +const route = useRoute() |
| 6 | +
|
| 7 | +let teardown: (() => void) | undefined |
| 8 | +
|
| 9 | +function isHomeRoute() { |
| 10 | + return route.path === '/' || route.path === '/index.html' |
| 11 | +} |
| 12 | +
|
| 13 | +async function resetMotion() { |
| 14 | + teardown?.() |
| 15 | + teardown = undefined |
| 16 | +
|
| 17 | + if (!isHomeRoute() || typeof window === 'undefined') { |
| 18 | + return |
| 19 | + } |
| 20 | +
|
| 21 | + await nextTick() |
| 22 | + await new Promise((resolve) => window.requestAnimationFrame(() => resolve(undefined))) |
| 23 | +
|
| 24 | + const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches |
| 25 | + if (reduceMotion) { |
| 26 | + return |
| 27 | + } |
| 28 | +
|
| 29 | + const [{ gsap }, { ScrollTrigger }] = await Promise.all([ |
| 30 | + import('gsap'), |
| 31 | + import('gsap/ScrollTrigger') |
| 32 | + ]) |
| 33 | +
|
| 34 | + gsap.registerPlugin(ScrollTrigger) |
| 35 | +
|
| 36 | + const ctx = gsap.context(() => { |
| 37 | + const heroTargets = [ |
| 38 | + '.VPHomeHero .name', |
| 39 | + '.VPHomeHero .text', |
| 40 | + '.VPHomeHero .tagline', |
| 41 | + '.VPHomeHero .actions', |
| 42 | + '.VPHomeHero .image' |
| 43 | + ].filter((selector) => document.querySelector(selector)) |
| 44 | +
|
| 45 | + gsap |
| 46 | + .timeline({ defaults: { ease: 'power3.out' } }) |
| 47 | + .from(heroTargets, { |
| 48 | + autoAlpha: 0, |
| 49 | + y: 24, |
| 50 | + duration: 0.72, |
| 51 | + stagger: 0.08, |
| 52 | + clearProps: 'transform,visibility,opacity' |
| 53 | + }) |
| 54 | + .from( |
| 55 | + '.VPHome .VPFeature', |
| 56 | + { |
| 57 | + autoAlpha: 0, |
| 58 | + y: 18, |
| 59 | + duration: 0.52, |
| 60 | + stagger: 0.05, |
| 61 | + clearProps: 'transform,visibility,opacity' |
| 62 | + }, |
| 63 | + '-=0.28' |
| 64 | + ) |
| 65 | +
|
| 66 | + gsap.utils.toArray('.nn-home-section').forEach((section, sectionIndex) => { |
| 67 | + const homeSection = section as HTMLElement |
| 68 | + const sectionTargets = homeSection.querySelectorAll( |
| 69 | + '.nn-home-kicker, h2, .nn-section-heading > a, .nn-compare-card, .nn-step-card, .nn-theme-card, .nn-usecase-grid > a, .nn-open-grid > div, .nn-faq-list details' |
| 70 | + ) |
| 71 | +
|
| 72 | + gsap.from(sectionTargets.length ? sectionTargets : homeSection, { |
| 73 | + autoAlpha: 0, |
| 74 | + y: 30, |
| 75 | + duration: 0.62, |
| 76 | + ease: 'power3.out', |
| 77 | + stagger: 0.055, |
| 78 | + clearProps: 'transform,visibility,opacity', |
| 79 | + scrollTrigger: { |
| 80 | + trigger: homeSection, |
| 81 | + start: 'top 82%', |
| 82 | + once: true, |
| 83 | + refreshPriority: sectionIndex |
| 84 | + } |
| 85 | + }) |
| 86 | + }) |
| 87 | +
|
| 88 | + ScrollTrigger.refresh() |
| 89 | + }, document.body) |
| 90 | +
|
| 91 | + teardown = () => { |
| 92 | + ctx.revert() |
| 93 | + } |
| 94 | +} |
| 95 | +
|
| 96 | +onMounted(() => { |
| 97 | + resetMotion() |
| 98 | +}) |
| 99 | +
|
| 100 | +watch( |
| 101 | + () => route.path, |
| 102 | + () => { |
| 103 | + resetMotion() |
| 104 | + } |
| 105 | +) |
| 106 | +
|
| 107 | +onBeforeUnmount(() => { |
| 108 | + teardown?.() |
| 109 | +}) |
| 110 | +</script> |
| 111 | + |
| 112 | +<template></template> |
0 commit comments