✨ 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",
|
||||
|
||||
Generated
-12
@@ -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,
|
||||
options: {
|
||||
children: '.char',
|
||||
stagger: 60,
|
||||
duration: 1600,
|
||||
from: '-105%',
|
||||
opacity: false,
|
||||
delay: 200,
|
||||
},
|
||||
animation: { y: ['-105%', 0] },
|
||||
options: {
|
||||
stagger: 0.06,
|
||||
duration: 1.6,
|
||||
delay: 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,
|
||||
options: {
|
||||
children: '.char',
|
||||
stagger: 40,
|
||||
duration: 1000,
|
||||
from: '110%',
|
||||
opacity: false,
|
||||
delay: DURATION.PAGE_IN
|
||||
},
|
||||
animation: { y: ['105%', 0] },
|
||||
options: {
|
||||
stagger: 0.04,
|
||||
duration: 1,
|
||||
delay: DURATION.PAGE_IN / 1000,
|
||||
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
|
||||
timeline.add({
|
||||
targets: '.shop-page__background',
|
||||
['.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)
|
||||
}, {
|
||||
at: 0.4,
|
||||
duration: 2.4,
|
||||
}],
|
||||
|
||||
// Intro top elements
|
||||
timeline.add({
|
||||
targets: '.shop-page__intro .top > *',
|
||||
translateY: [-100, 0],
|
||||
delay: anime.stagger(250),
|
||||
}, 400)
|
||||
['.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
|
||||
timeline.add({
|
||||
targets: '.shop-page__nav .container > *, .shop-page__intro .button-cart',
|
||||
['.shop-page__nav .container > *, .shop-page__intro .button-cart', {
|
||||
y: [100, 0],
|
||||
opacity: [0, 1],
|
||||
translateY: [100, 0],
|
||||
translateZ: 0,
|
||||
delay: anime.stagger(250),
|
||||
}, 700)
|
||||
}, {
|
||||
at: 0.7,
|
||||
delay: stagger(0.25),
|
||||
}]
|
||||
], {
|
||||
delay: $navigating ? DELAY.PAGE_LOADING / 1000 : 0,
|
||||
defaultOptions: {
|
||||
duration: 1.6,
|
||||
easing: quartOut,
|
||||
},
|
||||
})
|
||||
animation.stop()
|
||||
|
||||
// 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,
|
||||
})
|
||||
|
||||
anime.set('.photo-page__picture', {
|
||||
opacity: 0,
|
||||
})
|
||||
anime.set('.photo-page__picture.is-1', {
|
||||
translateY: 24,
|
||||
})
|
||||
|
||||
// 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]
|
||||
},
|
||||
delay: anime.stagger(55)
|
||||
}, 350)
|
||||
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 },
|
||||
}],
|
||||
|
||||
// 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]
|
||||
},
|
||||
['.photo-page__controls .prev', {
|
||||
x: [-16, 0],
|
||||
opacity: [0, 1],
|
||||
}, 450)
|
||||
|
||||
}, {
|
||||
at: 0.45,
|
||||
}],
|
||||
['.photo-page__controls .next', {
|
||||
x: [16, 0],
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.45,
|
||||
}],
|
||||
|
||||
// Infos
|
||||
timeline.add({
|
||||
targets: '.photo-page__info > *',
|
||||
translateY: [24, 0],
|
||||
['.photo-page__info > *', {
|
||||
y: [24, 0],
|
||||
opacity: [0, 1],
|
||||
delay: anime.stagger(200)
|
||||
}, 400)
|
||||
}, {
|
||||
at: 0.4,
|
||||
delay: stagger(0.3)
|
||||
}],
|
||||
|
||||
|
||||
anime.set('.photo-page__index', {
|
||||
opacity: 0
|
||||
})
|
||||
// Index
|
||||
timeline.add({
|
||||
targets: '.photo-page__index',
|
||||
opacity: 1,
|
||||
duration: 900,
|
||||
}, 600)
|
||||
['.photo-page__index', {
|
||||
opacity: [0, 1],
|
||||
}, {
|
||||
at: 0.6,
|
||||
delay: stagger(0.2),
|
||||
duration: 0.9,
|
||||
}],
|
||||
// Fly each number
|
||||
timeline.add({
|
||||
targets: '.photo-page__index .char',
|
||||
translateY: ['100%', 0],
|
||||
delay: anime.stagger(200),
|
||||
duration: 1000,
|
||||
}, 1100)
|
||||
['.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,
|
||||
},
|
||||
})
|
||||
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