✨ Finish to replace Anime with Motion One for page animations
Page intro animation and reveal that has now been simplified as Motion One manages an inView option (that uses IntersectionObserver)
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@studio-freight/lenis": "^0.1.13",
|
||||
"animejs": "^3.2.1",
|
||||
"dayjs": "^1.11.5",
|
||||
"embla-carousel": "^7.0.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
@@ -30,7 +29,6 @@
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.86",
|
||||
"@sveltejs/adapter-vercel": "^1.0.0-next.66",
|
||||
"@sveltejs/kit": "^1.0.0-next.405",
|
||||
"@types/animejs": "^3.1.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"browserslist": "^4.21.3",
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -6,10 +6,8 @@ specifiers:
|
||||
'@sveltejs/adapter-node': ^1.0.0-next.86
|
||||
'@sveltejs/adapter-vercel': ^1.0.0-next.66
|
||||
'@sveltejs/kit': ^1.0.0-next.405
|
||||
'@types/animejs': ^3.1.5
|
||||
'@typescript-eslint/eslint-plugin': ^5.33.0
|
||||
'@typescript-eslint/parser': ^5.33.0
|
||||
animejs: ^3.2.1
|
||||
browserslist: ^4.21.3
|
||||
cssnano: ^5.1.13
|
||||
dayjs: ^1.11.5
|
||||
@@ -37,7 +35,6 @@ specifiers:
|
||||
|
||||
dependencies:
|
||||
'@studio-freight/lenis': 0.1.13
|
||||
animejs: 3.2.1
|
||||
dayjs: 1.11.5
|
||||
embla-carousel: 7.0.0
|
||||
focus-visible: 5.2.0
|
||||
@@ -51,7 +48,6 @@ devDependencies:
|
||||
'@sveltejs/adapter-node': 1.0.0-next.86
|
||||
'@sveltejs/adapter-vercel': 1.0.0-next.66
|
||||
'@sveltejs/kit': 1.0.0-next.405_svelte@3.49.0+vite@3.0.7
|
||||
'@types/animejs': 3.1.5
|
||||
'@typescript-eslint/eslint-plugin': 5.33.0_njno5y7ry2l2lcmiu4tywxkwnq
|
||||
'@typescript-eslint/parser': 5.33.0_qugx7qdu5zevzvxaiqyxfiwquq
|
||||
browserslist: 4.21.3
|
||||
@@ -489,10 +485,6 @@ packages:
|
||||
engines: {node: '>=10.13.0'}
|
||||
dev: true
|
||||
|
||||
/@types/animejs/3.1.5:
|
||||
resolution: {integrity: sha512-4i3i1YuNaNEPoHBJY78uzYu8qKIwyx96G04tnVtNhRMQC9I1Xhg6fY9GeWmZAzudaesKKrPkQgTCthT1zSGYyg==}
|
||||
dev: true
|
||||
|
||||
/@types/json-schema/7.0.11:
|
||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||
dev: true
|
||||
@@ -693,10 +685,6 @@ packages:
|
||||
uri-js: 4.4.1
|
||||
dev: true
|
||||
|
||||
/animejs/3.2.1:
|
||||
resolution: {integrity: sha512-sWno3ugFryK5nhiDm/2BKeFCpZv7vzerWUcUPyAZLDhMek3+S/p418ldZJbJXo5ZUOpfm2kP2XRO4NJcULMy9A==}
|
||||
dev: false
|
||||
|
||||
/ansi-regex/5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
interface AnimationsQueueItem {
|
||||
node: Node
|
||||
animation: Function
|
||||
delay: number
|
||||
}
|
||||
|
||||
export class RevealQueue {
|
||||
items: AnimationsQueueItem[] = []
|
||||
queuedItems: AnimationsQueueItem[] = []
|
||||
timer = null
|
||||
observer = null
|
||||
|
||||
constructor () {
|
||||
if (typeof IntersectionObserver === 'undefined') return
|
||||
|
||||
this.observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
this.observer.unobserve(entry.target)
|
||||
const item = this.findItemFromNode(entry.target)
|
||||
this.queuedItems.push(item)
|
||||
|
||||
if (this.timer === null) {
|
||||
this.run()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Add an animation in queue
|
||||
add (node: Node, animation: Function, delay: number) {
|
||||
this.items.push({
|
||||
node,
|
||||
animation,
|
||||
delay,
|
||||
})
|
||||
this.observer.observe(node)
|
||||
}
|
||||
|
||||
// Remove node from queue and unobserve from IO
|
||||
remove (node: Node) {
|
||||
this.observer.unobserve(node)
|
||||
this.items = this.items.filter(v => v.node !== node)
|
||||
this.queuedItems = this.queuedItems.filter(v => v.node !== node)
|
||||
}
|
||||
|
||||
// Run animation
|
||||
run () {
|
||||
if (this.queuedItems.length === 0) {
|
||||
this.timer = null
|
||||
return
|
||||
}
|
||||
|
||||
const item = this.queuedItems[0]
|
||||
item.animation()
|
||||
this.remove(item.node)
|
||||
this.timer = window.setTimeout(this.run.bind(this), item.delay)
|
||||
}
|
||||
|
||||
// Find item from node
|
||||
findItemFromNode (node: Node) {
|
||||
return this.items.find(i => i.node === node)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export const [send, receive] = crossfade({
|
||||
const style = getComputedStyle(node)
|
||||
const transform = style.transform === 'none' ? '' : style.transform
|
||||
const sd = 1 - start
|
||||
|
||||
return {
|
||||
duration,
|
||||
easing,
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
import anime, { type AnimeParams } from 'animejs'
|
||||
import type { TransitionConfig } from 'svelte/transition'
|
||||
|
||||
|
||||
// Options interface
|
||||
interface TransitionOptions {
|
||||
direct?: boolean
|
||||
children?: string
|
||||
targets?: Element
|
||||
from?: number | string
|
||||
to?: number | string
|
||||
opacity?: boolean
|
||||
rotate?: any
|
||||
rotateX?: number
|
||||
rotateRandom?: boolean
|
||||
duration?: number
|
||||
stagger?: number
|
||||
scale?: number[]
|
||||
delay?: number
|
||||
easing?: string
|
||||
clear?: boolean
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Effect: Fly
|
||||
* @returns Anime.js animation
|
||||
*/
|
||||
export const fly = (
|
||||
node: Element,
|
||||
{
|
||||
direct = false,
|
||||
targets = node,
|
||||
children,
|
||||
from = 16,
|
||||
to = 0,
|
||||
opacity = true,
|
||||
rotate = false,
|
||||
rotateX = 0,
|
||||
rotateRandom = false,
|
||||
duration = 1600,
|
||||
stagger,
|
||||
scale = null,
|
||||
delay = 0,
|
||||
easing = 'easeOutQuart',
|
||||
clear = false
|
||||
}: TransitionOptions
|
||||
): TransitionConfig => {
|
||||
const anim = anime({
|
||||
autoplay: !direct,
|
||||
targets: children ? node.querySelectorAll(children) : targets,
|
||||
...(opacity && { opacity: [0, 1] }),
|
||||
...(scale && { scale }),
|
||||
...(rotate && {
|
||||
rotate:
|
||||
// If Array, use it, otherwise use the value up to 0
|
||||
Array.isArray(rotate)
|
||||
? rotate
|
||||
: [rotateRandom ? anime.random(-rotate, rotate) : rotate, 0]
|
||||
}),
|
||||
...(rotateX && { rotateX: [rotateX, 0] }),
|
||||
translateY: [from, to],
|
||||
translateZ: 0,
|
||||
duration,
|
||||
easing,
|
||||
delay: stagger ? anime.stagger(stagger, { start: delay }) : delay,
|
||||
complete: ({ animatables }) => {
|
||||
// Remove styles on end
|
||||
if (clear) {
|
||||
animatables.forEach((el: AnimeParams) => {
|
||||
el.target.style.transform = ''
|
||||
opacity && (el.target.style.opacity = '')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return direct ? anim : {
|
||||
tick: (t: number, u: number) => anim
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Effect: Fade
|
||||
* @returns Anime.js animation
|
||||
*/
|
||||
export const fade = (
|
||||
node: Element,
|
||||
{
|
||||
direct = false,
|
||||
targets = node,
|
||||
children,
|
||||
from = 0,
|
||||
to = 1,
|
||||
duration = 1600,
|
||||
stagger,
|
||||
delay = 0,
|
||||
easing = 'easeInOutQuart',
|
||||
clear = false
|
||||
}: TransitionOptions
|
||||
): TransitionConfig => {
|
||||
|
||||
const anim = anime({
|
||||
autoplay: !direct,
|
||||
targets: children ? node.querySelectorAll(children) : targets,
|
||||
opacity: [from, to],
|
||||
duration,
|
||||
easing,
|
||||
delay: stagger ? anime.stagger(stagger, { start: delay }) : delay,
|
||||
complete: ({ animatables }) => {
|
||||
// Remove styles on end
|
||||
if (clear) {
|
||||
animatables.forEach((el: AnimeParams) => {
|
||||
el.target.style.opacity = ''
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return direct ? anim : {
|
||||
tick: (t: number, u: number) => anim
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Effect: Scale
|
||||
* @returns Anime.js animation
|
||||
*/
|
||||
export const scale = (
|
||||
node: Element,
|
||||
{
|
||||
direct = false,
|
||||
from = 0,
|
||||
to = 1,
|
||||
duration = 1200,
|
||||
delay = 0,
|
||||
easing = 'easeOutQuart',
|
||||
clear = false
|
||||
}: TransitionOptions
|
||||
): TransitionConfig => {
|
||||
const anim = anime({
|
||||
autoplay: !direct,
|
||||
targets: node,
|
||||
scaleY: [from, to],
|
||||
translateZ: 0,
|
||||
duration,
|
||||
easing,
|
||||
delay,
|
||||
complete: ({ animatables }) => {
|
||||
// Remove styles on end
|
||||
if (clear) {
|
||||
animatables.forEach((el: AnimeParams) => {
|
||||
el.target.style.transform = ''
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return direct ? anim : {
|
||||
tick: (t: number, u: number) => anim
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Effect: Words reveal
|
||||
* @description Anime.js animation
|
||||
*/
|
||||
export const words = (
|
||||
node: Element,
|
||||
{
|
||||
direct = false,
|
||||
children = 'span',
|
||||
from = '45%',
|
||||
to = 0,
|
||||
duration = 1200,
|
||||
stagger = 60,
|
||||
rotate = 0,
|
||||
delay = 0,
|
||||
opacity = true,
|
||||
easing = 'easeOutQuart',
|
||||
clear = false
|
||||
}: TransitionOptions
|
||||
): TransitionConfig => {
|
||||
const anim = anime({
|
||||
autoplay: !direct,
|
||||
targets: node.querySelectorAll(children),
|
||||
...(opacity && { opacity: [0, 1] }),
|
||||
translateY: [from, to],
|
||||
...(rotate && { rotateX: [rotate, 0] }),
|
||||
translateZ: 0,
|
||||
duration,
|
||||
easing,
|
||||
delay: stagger ? anime.stagger(stagger, { start: delay }) : delay,
|
||||
complete: ({ animatables }) => {
|
||||
// Remove styles on end
|
||||
if (clear) {
|
||||
animatables.forEach((el: AnimeParams) => {
|
||||
el.target.style.transform = ''
|
||||
opacity && (el.target.style.opacity = '')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return direct ? anim : {
|
||||
tick: (t: number, u: number) => anim
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Run animation on reveal
|
||||
* @description IntersectionObserver triggering an animation function or a callback
|
||||
*/
|
||||
export const reveal = (
|
||||
node: Element | any,
|
||||
{
|
||||
enable = true,
|
||||
targets = node,
|
||||
animation,
|
||||
options = {},
|
||||
callback,
|
||||
callbackTrigger,
|
||||
once = true,
|
||||
threshold = 0.2,
|
||||
rootMargin = '0px 0px 0px',
|
||||
queue = null,
|
||||
queueDelay = 0,
|
||||
}: revealOptions
|
||||
) => {
|
||||
let observer: IntersectionObserver
|
||||
|
||||
// Kill if IntersectionObserver is not supported
|
||||
if (typeof IntersectionObserver === 'undefined' || !enable) return
|
||||
|
||||
// Use animation with provided node selector
|
||||
if (animation) {
|
||||
const anim = animation(node, {
|
||||
...options,
|
||||
direct: true,
|
||||
autoplay: false
|
||||
})
|
||||
|
||||
// If a queue exists, let it run animations
|
||||
if (queue) {
|
||||
queue.add(node, anim.play, queueDelay)
|
||||
|
||||
return {
|
||||
destroy () {
|
||||
queue.remove(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
anim && anim.play()
|
||||
once && observer.unobserve(entry.target)
|
||||
}
|
||||
})
|
||||
}, { threshold, rootMargin })
|
||||
|
||||
observer.observe(node)
|
||||
}
|
||||
|
||||
// Custom callback
|
||||
else if (callback) {
|
||||
observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
const cb = callback(entry)
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// Run callback
|
||||
callbackTrigger && callbackTrigger(cb)
|
||||
|
||||
// Run IntersectionObserver only once
|
||||
once && observer.unobserve(entry.target)
|
||||
}
|
||||
})
|
||||
}, { threshold })
|
||||
|
||||
const elements = typeof targets === 'string' ? node.querySelectorAll(targets) : targets
|
||||
|
||||
if (elements) {
|
||||
// Observe each element
|
||||
if (elements.length > 0) {
|
||||
elements.forEach((target: Element) => {
|
||||
observer.observe(target)
|
||||
})
|
||||
}
|
||||
// Directly observe
|
||||
else {
|
||||
observer.observe(elements)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
return {
|
||||
// Destroy
|
||||
destroy () {
|
||||
observer && observer.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface revealOptions {
|
||||
animation?: Function
|
||||
options?: TransitionOptions
|
||||
queue?: any
|
||||
callback?: Function
|
||||
callbackTrigger?: any
|
||||
targets?: string | Element
|
||||
enable?: boolean
|
||||
once?: boolean
|
||||
threshold?: number,
|
||||
rootMargin?: string,
|
||||
queueDelay?: number
|
||||
}
|
||||
|
||||
export { RevealQueue } from './RevealQueue'
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { map } from '$utils/functions'
|
||||
import { reveal, fly } from '$animations/index'
|
||||
import reveal from '$animations/reveal'
|
||||
|
||||
export let tag: string
|
||||
export let label: string = undefined
|
||||
@@ -46,17 +46,15 @@
|
||||
|
||||
|
||||
const revealOptions = animate ? {
|
||||
animation: fly,
|
||||
children: '.char',
|
||||
animation: { y: ['-105%', 0] },
|
||||
options: {
|
||||
children: '.char',
|
||||
stagger: 60,
|
||||
duration: 1600,
|
||||
from: '-105%',
|
||||
opacity: false,
|
||||
delay: 200,
|
||||
stagger: 0.06,
|
||||
duration: 1.6,
|
||||
delay: 0.2,
|
||||
threshold: 0.2,
|
||||
},
|
||||
threshold: 0.2,
|
||||
} : {}
|
||||
} : null
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import SplitText from '$components/SplitText.svelte'
|
||||
import { reveal, fly } from '$animations/index'
|
||||
import reveal from '$animations/reveal'
|
||||
import { DURATION } from '$utils/contants'
|
||||
|
||||
export let variant: string = 'lines'
|
||||
@@ -14,16 +14,14 @@
|
||||
{#if tag === 'h1'}
|
||||
<h1 class="site-title site-title--{variant}"
|
||||
use:reveal={{
|
||||
animation: fly,
|
||||
children: '.char',
|
||||
animation: { y: ['105%', 0] },
|
||||
options: {
|
||||
children: '.char',
|
||||
stagger: 40,
|
||||
duration: 1000,
|
||||
from: '110%',
|
||||
opacity: false,
|
||||
delay: DURATION.PAGE_IN
|
||||
stagger: 0.04,
|
||||
duration: 1,
|
||||
delay: DURATION.PAGE_IN / 1000,
|
||||
threshold: 0,
|
||||
},
|
||||
threshold: 0,
|
||||
}}
|
||||
>
|
||||
<SplitText text="Houses" mode="chars" class="pink mask" />
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { getContext } from 'svelte'
|
||||
import { fly, reveal } from '$animations/index'
|
||||
import reveal from '$animations/reveal'
|
||||
// Components
|
||||
import Icon from '$components/atoms/Icon.svelte'
|
||||
|
||||
@@ -42,14 +42,12 @@
|
||||
class:is-open={isOpen}
|
||||
class:is-over={isOver}
|
||||
use:reveal={{
|
||||
animation: fly,
|
||||
animation: { y: [24, 0], opacity: [0, 1] },
|
||||
options: {
|
||||
from: 24,
|
||||
to: 0,
|
||||
duration: 1000,
|
||||
easing: 'easeOutQuart',
|
||||
delay: 600,
|
||||
}
|
||||
duration: 1,
|
||||
delay: 0.6,
|
||||
threshold: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<button class="switcher__button" title="{!isOpen ? 'Open' : 'Close'} menu" tabindex="0"
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { navigating } from '$app/stores'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import anime, { type AnimeTimelineInstance } from 'animejs'
|
||||
import { stagger, timeline } from 'motion'
|
||||
import { cartOpen } from '$utils/stores/shop'
|
||||
import { smoothScroll } from '$utils/functions'
|
||||
import { DELAY } from '$utils/contants'
|
||||
import { quartOut } from '$animations/easings'
|
||||
// Components
|
||||
import Image from '$components/atoms/Image.svelte'
|
||||
import ButtonCart from '$components/atoms/ButtonCart.svelte'
|
||||
@@ -37,46 +40,52 @@
|
||||
/**
|
||||
* Animations
|
||||
*/
|
||||
// Setup animations
|
||||
const timeline: AnimeTimelineInstance = anime.timeline({
|
||||
duration: 1600,
|
||||
easing: 'easeOutQuart',
|
||||
autoplay: false,
|
||||
const animation = timeline([
|
||||
// Hero image
|
||||
['.shop-page__background', {
|
||||
scale: [1.06, 1],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.4,
|
||||
duration: 2.4,
|
||||
}],
|
||||
|
||||
// Intro top elements
|
||||
['.shop-page__intro .top > *', {
|
||||
y: [-100, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.4,
|
||||
delay: stagger(0.25),
|
||||
}],
|
||||
|
||||
// Hero title
|
||||
['.shop-page__title h1', {
|
||||
y: [32, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.5,
|
||||
}],
|
||||
|
||||
// Intro navbar
|
||||
['.shop-page__nav .container > *, .shop-page__intro .button-cart', {
|
||||
y: [100, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.7,
|
||||
delay: stagger(0.25),
|
||||
}]
|
||||
], {
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
},
|
||||
})
|
||||
animation.stop()
|
||||
|
||||
// Hero image
|
||||
timeline.add({
|
||||
targets: '.shop-page__background',
|
||||
scale: [1.06, 1],
|
||||
opacity: [0, 1],
|
||||
duration: 2400,
|
||||
}, 400)
|
||||
|
||||
// Hero title
|
||||
timeline.add({
|
||||
targets: '.shop-page__title h1',
|
||||
translateY: [32, 0],
|
||||
opacity: [0, 1],
|
||||
}, 500)
|
||||
|
||||
// Intro top elements
|
||||
timeline.add({
|
||||
targets: '.shop-page__intro .top > *',
|
||||
translateY: [-100, 0],
|
||||
delay: anime.stagger(250),
|
||||
}, 400)
|
||||
|
||||
// Intro navbar
|
||||
timeline.add({
|
||||
targets: '.shop-page__nav .container > *, .shop-page__intro .button-cart',
|
||||
opacity: [0, 1],
|
||||
translateY: [100, 0],
|
||||
translateZ: 0,
|
||||
delay: anime.stagger(250),
|
||||
}, 700)
|
||||
|
||||
// Transition in
|
||||
requestAnimationFrame(timeline.play)
|
||||
// Run animation
|
||||
requestAnimationFrame(animation.play)
|
||||
|
||||
|
||||
// Destroy
|
||||
|
||||
@@ -4,17 +4,18 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env'
|
||||
import { page } from '$app/stores'
|
||||
import { page, navigating } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
import { onMount, tick } from 'svelte'
|
||||
import { fade, scale } from 'svelte/transition'
|
||||
import { quartOut } from 'svelte/easing'
|
||||
import dayjs from 'dayjs'
|
||||
import { stagger, timeline } from 'motion'
|
||||
import { fetchAPI, getAssetUrlKey } from '$utils/api'
|
||||
import { previousPage } from '$utils/stores'
|
||||
import { DELAY } from '$utils/contants'
|
||||
import { throttle } from '$utils/functions'
|
||||
import { swipe } from '$utils/interactions/swipe'
|
||||
import dayjs from 'dayjs'
|
||||
import anime, { type AnimeTimelineInstance } from 'animejs'
|
||||
// Components
|
||||
import Metas from '$components/Metas.svelte'
|
||||
import SplitText from '$components/SplitText.svelte'
|
||||
@@ -219,76 +220,74 @@
|
||||
/**
|
||||
* Animations
|
||||
*/
|
||||
// Setup animations
|
||||
const timeline: AnimeTimelineInstance = anime.timeline({
|
||||
duration: 1600,
|
||||
easing: 'easeOutQuart',
|
||||
autoplay: false,
|
||||
})
|
||||
const animation = timeline([
|
||||
// First photo
|
||||
['.photo-page__picture.is-1', {
|
||||
y: [24, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
duration: 0.9,
|
||||
}],
|
||||
// Other photos
|
||||
['.photo-page__picture:not(.is-1)', {
|
||||
x: ['-150%', 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.4,
|
||||
delay: stagger(0.1),
|
||||
opacity: { duration: 0.25 },
|
||||
}],
|
||||
|
||||
anime.set('.photo-page__picture', {
|
||||
opacity: 0,
|
||||
})
|
||||
anime.set('.photo-page__picture.is-1', {
|
||||
translateY: 24,
|
||||
})
|
||||
// Prev/Next buttons
|
||||
['.photo-page__controls .prev', {
|
||||
x: [-16, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.45,
|
||||
}],
|
||||
['.photo-page__controls .next', {
|
||||
x: [16, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.45,
|
||||
}],
|
||||
|
||||
// Photos
|
||||
timeline.add({
|
||||
targets: '.photo-page__picture.is-1',
|
||||
opacity: 1,
|
||||
translateY: 0,
|
||||
duration: 900,
|
||||
}, 250)
|
||||
timeline.add({
|
||||
targets: '.photo-page__picture:not(.is-1)',
|
||||
opacity: 1,
|
||||
translateX (element: HTMLElement) {
|
||||
const x = getComputedStyle(element).getPropertyValue('--offset-x').trim()
|
||||
return [`-${x}`, 0]
|
||||
// Infos
|
||||
['.photo-page__info > *', {
|
||||
y: [24, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.4,
|
||||
delay: stagger(0.3)
|
||||
}],
|
||||
|
||||
// Index
|
||||
['.photo-page__index', {
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.6,
|
||||
delay: stagger(0.2),
|
||||
duration: 0.9,
|
||||
}],
|
||||
// Fly each number
|
||||
['.photo-page__index .char', {
|
||||
y: ['300%', 0],
|
||||
}, {
|
||||
at: 1.1,
|
||||
delay: stagger(0.2),
|
||||
duration: 1,
|
||||
}],
|
||||
], {
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
},
|
||||
delay: anime.stagger(55)
|
||||
}, 350)
|
||||
|
||||
// Prev/Next buttons
|
||||
timeline.add({
|
||||
targets: '.photo-page__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: '.photo-page__info > *',
|
||||
translateY: [24, 0],
|
||||
opacity: [0, 1],
|
||||
delay: anime.stagger(200)
|
||||
}, 400)
|
||||
|
||||
|
||||
anime.set('.photo-page__index', {
|
||||
opacity: 0
|
||||
})
|
||||
// Index
|
||||
timeline.add({
|
||||
targets: '.photo-page__index',
|
||||
opacity: 1,
|
||||
duration: 900,
|
||||
}, 600)
|
||||
// Fly each number
|
||||
timeline.add({
|
||||
targets: '.photo-page__index .char',
|
||||
translateY: ['100%', 0],
|
||||
delay: anime.stagger(200),
|
||||
duration: 1000,
|
||||
}, 1100)
|
||||
animation.stop()
|
||||
|
||||
// Transition in
|
||||
requestAnimationFrame(timeline.play)
|
||||
// Run animation
|
||||
requestAnimationFrame(animation.play)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -146,13 +146,12 @@
|
||||
/**
|
||||
* Animations
|
||||
*/
|
||||
const animationDelay = $navigating ? DURATION.PAGE_IN : 0
|
||||
const animation = timeline([
|
||||
// Title word
|
||||
['.location-page__intro .word', {
|
||||
y: ['110%', 0],
|
||||
}, {
|
||||
at: 0.2 + animationDelay,
|
||||
at: 0.2,
|
||||
}],
|
||||
|
||||
// Illustration
|
||||
@@ -160,7 +159,7 @@
|
||||
scale: [1.06, 1],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.4 + animationDelay,
|
||||
at: 0.4,
|
||||
duration: 2.4,
|
||||
}],
|
||||
|
||||
@@ -168,7 +167,7 @@
|
||||
['.location-page__intro .of', {
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.95 + animationDelay,
|
||||
at: 0.95,
|
||||
duration: 1.2,
|
||||
}],
|
||||
|
||||
@@ -177,11 +176,11 @@
|
||||
y: ['10%', 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.9 + animationDelay,
|
||||
at: 0.9,
|
||||
duration: 1.2,
|
||||
}]
|
||||
], {
|
||||
delay: DELAY.PAGE_LOADING / 1000,
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import { navigating } from '$app/stores'
|
||||
import { stagger, timeline } from 'motion'
|
||||
import { DELAY } from '$utils/contants'
|
||||
import { quartOut } from 'svelte/easing'
|
||||
@@ -45,7 +46,7 @@
|
||||
delay: stagger(0.35),
|
||||
}],
|
||||
], {
|
||||
delay: DELAY.PAGE_LOADING / 1000,
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { page, navigating } from '$app/stores'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { timeline, stagger } from 'motion'
|
||||
import { DELAY } from '$utils/contants'
|
||||
import { smoothScroll } from '$utils/functions'
|
||||
import { reveal, fade as animeFade } from '$animations/index'
|
||||
import reveal from '$animations/reveal'
|
||||
import { quartOut } from '$animations/easings'
|
||||
// Components
|
||||
import Metas from '$components/Metas.svelte'
|
||||
@@ -57,7 +57,7 @@
|
||||
delay: stagger(0.075),
|
||||
}]
|
||||
], {
|
||||
delay: DELAY.PAGE_LOADING / 1000,
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
@@ -81,9 +81,9 @@
|
||||
<PageTransition name="homepage">
|
||||
<section class="homepage__intro"
|
||||
use:reveal={{
|
||||
animation: animeFade,
|
||||
animation: { opacity: [0, 1] },
|
||||
options: {
|
||||
duration: 1000,
|
||||
duration: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { page, navigating } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
import dayjs from 'dayjs'
|
||||
import { quartOut as quartOutSvelte } from 'svelte/easing'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime.js'
|
||||
import { stagger, timeline } from 'motion'
|
||||
import { DELAY } from '$utils/contants'
|
||||
@@ -294,11 +294,11 @@
|
||||
y: [16, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.4,
|
||||
delay: stagger(0.25),
|
||||
at: 0.5,
|
||||
delay: stagger(0.3),
|
||||
}]
|
||||
], {
|
||||
delay: DELAY.PAGE_LOADING / 1000,
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { navigating } from '$app/stores'
|
||||
import { onMount } from 'svelte'
|
||||
import { stagger, timeline } from 'motion'
|
||||
import dayjs from 'dayjs'
|
||||
import { stagger, timeline } from 'motion'
|
||||
import { DELAY } from '$utils/contants'
|
||||
import { quartOut } from '$animations/easings'
|
||||
// Components
|
||||
@@ -44,7 +45,7 @@
|
||||
delay: stagger(0.15),
|
||||
}],
|
||||
], {
|
||||
delay: DELAY.PAGE_LOADING / 1000,
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
|
||||
Reference in New Issue
Block a user