Motion
Animate page transitions with Motion (the engine behind Framer Motion), with a single function call per phase.
Motion is a small, MIT-licensed animation library. Its animate function returns something Pagefold can await, so you write each phase as one line.
npm install motion
import Pagefold from 'pagefold'
import PagefoldJs from 'pagefold/js'
import { animate } from 'motion'
new Pagefold({
animationSelector: false,
plugins: [
new PagefoldJs({
animations: [
{
out: () => animate('#pagefold', { opacity: 0 }, { duration: 0.4 }),
in: () => animate('#pagefold', { opacity: [0, 1] }, { duration: 0.4 }),
},
],
}),
],
})
Because the function returns the animation, Pagefold waits until it finishes. There is no done to call.
Springs and several elements
in: () =>
animate(
'#pagefold h1, #pagefold p',
{ opacity: [0, 1], transform: ['translateY(20px)', 'none'] },
{ type: 'spring', stiffness: 200, damping: 20, delay: stagger(0.08) }
)
import { animate, stagger } from 'motion'
A different animation per page
The same entries as any other engine, with patterns for the pages they apply to:
animations: [
{
from: '/',
to: '/work/:slug',
out: () => animate('#pagefold', { opacity: 0, scale: 0.96 }, { duration: 0.3 }),
in: () => animate('#pagefold', { opacity: [0, 1], scale: [1.04, 1] }, { duration: 0.4 }),
},
{ out: () => animate('#pagefold', { opacity: 0 }, { duration: 0.3 }), in: () => animate('#pagefold', { opacity: [0, 1] }, { duration: 0.3 }) },
]
The last entry has no patterns, so it is the fallback for every other visit. See JS for how entries are chosen.
Reduced motion
Check prefers-reduced-motion yourself and shorten the animations when the visitor asked for less motion:
const calm = matchMedia('(prefers-reduced-motion: reduce)').matches
const duration = calm ? 0 : 0.4
The A11y plugin can also skip animation for those visits altogether.
The two phases are tested in the project’s browser tests with the same fade shown at the top.