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:
2022-08-14 20:24:28 +02:00
parent fa74e5bf7f
commit fd6fc20b13
15 changed files with 157 additions and 558 deletions

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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'