diff --git a/src/routes/[country]/[location]/[photo].svelte b/src/routes/[country]/[location]/[photo].svelte index 11afbb5..69e769a 100644 --- a/src/routes/[country]/[location]/[photo].svelte +++ b/src/routes/[country]/[location]/[photo].svelte @@ -12,6 +12,7 @@ import dayjs from 'dayjs' import advancedFormat from 'dayjs/plugin/advancedFormat.js' import anime from 'animejs' + import type { AnimeTimelineInstance } from 'animejs' // Components import Metas from '$components/Metas.svelte' import SplitText from '$components/SplitText.svelte' @@ -213,69 +214,88 @@ } + /** + * Transition: Anime timeline + */ + let timeline: AnimeTimelineInstance + + if (browser) { + requestAnimationFrame(() => { + // Setup animations + timeline = anime.timeline({ + duration: 1600, + easing: 'easeOutQuart', + }) + + anime.set('.viewer-photo__picture', { + opacity: 0, + }) + anime.set('.viewer-photo__picture.is-1', { + translateY: 24, + }) + + // Photos + timeline.add({ + targets: '.viewer-photo__picture.is-1', + opacity: 1, + translateY: 0, + duration: 900, + }, 250) + timeline.add({ + targets: '.viewer-photo__picture:not(.is-0):not(.is-1)', + opacity: 1, + translateX (element: HTMLElement, index: number) { + const x = getComputedStyle(element).getPropertyValue('--offset-x').trim() + return [`-${x}`, 0] + }, + delay: anime.stagger(55) + }, 500) + + // Prev/Next buttons + timeline.add({ + targets: '.viewer-photo__controls button', + translateX (item: HTMLElement) { + let direction = item.classList.contains('prev') ? -1 : 1 + return [16 * direction, 0] + }, + opacity: [0, 1], + }, 450) + + + // Infos + timeline.add({ + targets: '.viewer-photo__info > *', + translateY: [24, 0], + opacity: [0, 1], + delay: anime.stagger(200) + }, 400) + + + anime.set('.viewer-photo__index', { + opacity: 0 + }) + // Index + timeline.add({ + targets: '.viewer-photo__index', + opacity: 1, + duration: 900, + }, 600) + // Fly each number + timeline.add({ + targets: '.viewer-photo__index .char', + translateY: ['100%', 0], + delay: anime.stagger(200), + duration: 1000, + }, 700) + }) + } + + onMount(() => { - // Setup animations - const timeline = anime.timeline({ - duration: 1600, - easing: 'easeOutQuart', + // Transition in + requestAnimationFrame(() => { + timeline.play() }) - - anime.set('.viewer-photo__picture', { - opacity: 0, - }) - - // Photos - timeline.add({ - targets: '.viewer-photo__picture.is-1', - opacity: 1, - duration: 900, - }, 200) - timeline.add({ - targets: '.viewer-photo__picture:not(.is-0):not(.is-1)', - opacity: 1, - translateX (element: HTMLElement, index: number) { - const x = getComputedStyle(element).getPropertyValue('--offset-x').trim() - return [`-${x}`, 0] - }, - delay: anime.stagger(55) - }, 400) - - // Prev button - timeline.add({ - targets: '.viewer-photo__controls button:first-child', - translateX: [-16, 0], - opacity: [0, 1], - }, 600) - // Next button - timeline.add({ - targets: '.viewer-photo__controls button:last-child', - translateX: [16, 0], - opacity: [0, 1], - }, 600) - - - // Infos - timeline.add({ - targets: '.viewer-photo__info > *', - translateY: [24, 0], - opacity: [0, 1], - delay: anime.stagger(200) - }, 400) - - - anime.set('.viewer-photo__index', { opacity: 0 }) - // Index - timeline.add({ - targets: '.viewer-photo__index', - opacity: 1, - duration: 900, - }, 600) - timeline.add({ - targets: '.viewer-photo__index .char', - translateY: ['100%', 0], - delay: anime.stagger(200), - duration: 1000, - }, 1100) }) @@ -328,10 +348,10 @@ {/each}
- + - +
diff --git a/src/routes/[country]/[location]/index.svelte b/src/routes/[country]/[location]/index.svelte index cea4cae..0ef8a2d 100644 --- a/src/routes/[country]/[location]/index.svelte +++ b/src/routes/[country]/[location]/index.svelte @@ -1,7 +1,9 @@ - import { getContext, onMount } from 'svelte' + import { browser } from '$app/env' import { page } from '$app/stores' + import { getContext, onMount } from 'svelte' import anime from 'animejs' + import type { AnimeTimelineInstance } from 'animejs' // Components import Metas from '$components/Metas.svelte' import SplitText from '$components/SplitText.svelte' @@ -24,33 +26,54 @@ let scrollY: number, innerHeight: number - onMount(() => { - // Setup animations - const timeline = anime.timeline({ - duration: 1600, - easing: 'easeOutQuart', + /** + * Transition: Anime timeline + */ + let timeline: AnimeTimelineInstance + + if (browser) { + requestAnimationFrame(() => { + // Setup animations + timeline = anime.timeline({ + duration: 1600, + easing: 'easeOutQuart', + autoplay: false, + }) + + // Reveal text + anime.set('.homepage__headline', { + translateY: 16, + opacity: 0, + }) + timeline.add({ + targets: '.homepage__headline', + translateY: 0, + opacity: 1, + }, 900) + + // Animate collage photos + anime.set('.homepage__collage .photo-card', { + opacity: 0, + translateY: '33.33%', + rotate: -4, + }) + timeline.add({ + targets: '.homepage__collage .photo-card', + translateY: 0, + rotate (item: HTMLElement) { + return getComputedStyle(item).getPropertyValue('--rotation') + }, + opacity: 1, + duration: 1200, + delay: anime.stagger(75), + }, 0) }) + } - // Reveal text - timeline.add({ - targets: '.homepage__headline', - translateY: [16, 0], - opacity: [0, 1], - }, 900) - - // Animate collage photos - timeline.add({ - targets: '.homepage__collage .photo-card', - translateY: ['33.333%', 0], - rotate (item: HTMLElement) { - // Get target CSS variable for rotation - const rotateEnd = getComputedStyle(item).getPropertyValue('--rotation') - return [-4, rotateEnd] - }, - opacity: [0, 1], - duration: 1200, - delay: anime.stagger(75), - }, 0) + onMount(() => { + requestAnimationFrame(() => { + timeline.play() + }) }) diff --git a/src/routes/photos.svelte b/src/routes/photos.svelte index 93827be..e4daa46 100644 --- a/src/routes/photos.svelte +++ b/src/routes/photos.svelte @@ -1,4 +1,5 @@
- +