✨ 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:
@@ -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'
|
||||
Reference in New Issue
Block a user