From fe91377c3d84dd568b912de385de2ca8597a3a8d Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Sat, 3 Jan 2026 18:11:39 +0100 Subject: [PATCH] feat(gallery): intro and outro animations --- app/pages/gallery.vue | 155 +++++++++++++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 32 deletions(-) diff --git a/app/pages/gallery.vue b/app/pages/gallery.vue index 6ae8c81..82f39f6 100644 --- a/app/pages/gallery.vue +++ b/app/pages/gallery.vue @@ -52,41 +52,127 @@ const getAspectRatio = (image: InternalApi["/api/gallery"]["get"][number]) => { }; const isAnimating = ref(true); +const router = useRouter(); -onMounted(async () => { +const titleText = ref(""); +const descriptionText = ref(""); +const backButtonText = ref(""); +const showBackButtonIcon = ref(false); + +const ANIMATION_SLEEP = 0.25; +const TITLE_DURATION = 1.4; +const DESCRIPTION_DURATION = 1.6; +const BACK_BUTTON_DURATION = 0.6; +const FADE_IN_DELAY = 0.3; +const FADE_IN_DURATION = 1.5; +const FADE_IN_X_FACTOR = 0.15; +const FADE_IN_Y_FACTOR = 0.075; + +const TITLE = "Pihkaal's Gallery"; +const DESCRIPTION = + "Started on March 2025. I love taking photos of plants, insects, and arachnids."; +const BACK_BUTTON = "Back to Home"; + +const preventScroll = (e: Event) => e.preventDefault(); + +const typeText = ( + target: Ref, + text: string, + duration: number, + reverse = false, + cb?: { onStart?: () => void; onComplete?: () => void }, +) => + gsap.to(target, { + duration, + ease: `steps(${text.length})`, + onUpdate() { + const p = this.progress(); + target.value = text.slice( + 0, + reverse ? (1 - p) * text.length : p * text.length, + ); + }, + ...cb, + }); + +const animateIntro = async () => { await nextTick(); const scrollEl = scrollArea.value?.$el; if (!scrollEl) return; - const preventScroll = (e: Event) => { - e.preventDefault(); - e.stopPropagation(); - }; + scrollEl.addEventListener("wheel", preventScroll, { passive: false }); + scrollEl.addEventListener("touchmove", preventScroll, { passive: false }); + + typeText(backButtonText, BACK_BUTTON, BACK_BUTTON_DURATION, false, { + onStart: () => (showBackButtonIcon.value = true), + }).delay(ANIMATION_SLEEP + FADE_IN_DELAY); + + typeText(titleText, TITLE, TITLE_DURATION).delay( + ANIMATION_SLEEP + FADE_IN_DELAY, + ); + typeText(descriptionText, DESCRIPTION, DESCRIPTION_DURATION).delay( + ANIMATION_SLEEP + FADE_IN_DELAY, + ); + + await gsap.fromTo( + ".gallery-item", + { opacity: 0 }, + { + opacity: 1, + duration: FADE_IN_DURATION, + delay: ANIMATION_SLEEP, + stagger: (i) => + (Math.floor(i / lanes.value) * FADE_IN_Y_FACTOR + + (i % lanes.value) * FADE_IN_X_FACTOR) * + FADE_IN_DURATION, + ease: "power2.in", + }, + ); + + isAnimating.value = false; + scrollEl.removeEventListener("wheel", preventScroll); + scrollEl.removeEventListener("touchmove", preventScroll); +}; + +const animateOutro = async () => { + isAnimating.value = true; + + const scrollEl = scrollArea.value?.$el; + if (!scrollEl) return; scrollEl.addEventListener("wheel", preventScroll, { passive: false }); scrollEl.addEventListener("touchmove", preventScroll, { passive: false }); - const items = document.querySelectorAll(".gallery-item"); - gsap.fromTo( - items, - { opacity: 0 }, - { - opacity: 1, - duration: 0.6, - stagger: (index) => { - const line = Math.floor(index / lanes.value); - const column = index % lanes.value; - return line * 0.1 + column * 0.05; - }, - ease: "power2.out", - onComplete: () => { - isAnimating.value = false; - scrollEl.removeEventListener("wheel", preventScroll); - scrollEl.removeEventListener("touchmove", preventScroll); - }, + gsap.to(".gallery-item", { + opacity: 0, + duration: FADE_IN_DURATION, + delay: FADE_IN_DELAY, + stagger: (i) => + (Math.floor(i / lanes.value) * FADE_IN_Y_FACTOR + + (i % lanes.value) * FADE_IN_X_FACTOR) * + FADE_IN_DURATION, + ease: "power2.out", + }); + + typeText(backButtonText, BACK_BUTTON, BACK_BUTTON_DURATION, true, { + onComplete: () => (showBackButtonIcon.value = false), + }); + + typeText(titleText, TITLE, TITLE_DURATION, true); + + typeText(descriptionText, DESCRIPTION, DESCRIPTION_DURATION, true, { + onComplete: async () => { + scrollEl.removeEventListener("wheel", preventScroll); + scrollEl.removeEventListener("touchmove", preventScroll); + await sleep(ANIMATION_SLEEP * 1000); + router.push("/"); }, - ); + }); +}; + +onMounted(() => { + animateIntro(); }); @@ -109,18 +195,23 @@ onMounted(async () => { size="md" color="neutral" variant="link" - to="/" :ui="{ - base: 'px-0 text-neutral-600 hover:text-neutral-400', - leadingIcon: 'size-4', + base: 'px-0 text-neutral-600 hover:text-neutral-400 h-8', + leadingIcon: showBackButtonIcon + ? 'size-4' + : 'size-4 text-[#0a0a0a]', }" - >Back to Home{{ backButtonText }} -

Pihkaal's Gallery

-

- Started on March 2025. I love taking photos of plants, insects, and - arachnids. +

+ + {{ titleText }} +

+

+ + {{ descriptionText }}