Poser
A poser is used to animate an element and its poser children.
Note: For React, use posed components.
Import
import pose from 'popmotion-pose'
Usage
Create
Posers are created with the pose
function.
pose
accepts two arguments: A HTML or SVG element, and an object of pose config.
const poser = pose(element, config)
Animate
Animating to a pose defined in the pose config is a matter of calling the poser’s set
method:
poser.set('nameOfPose')
Sequence
set
returns a Promise, which resolves once all animations, and animations of any child posers, are complete.
// Promise
poser.set('nameOfPose').then(() => /* Do other thing */)
// Async
await poser.set('nameOfPose')
/* Do other thing */
Multiple animations
When a pose is set on a poser, it is only set on the values defined in that pose. Which means, if we have two poses with different sets of properties, we can animate both at the same time:
const poser = pose(element, {
flash: {
backgroundColor: '#f00',
transition: ({ from, to }) => tween({ from, to, yoyo: Infinity })
},
shake: {
x: true, // We're not using `to` in the transition but want to animate x
transition: ({ from, velocity }) => spring({
from, velocity,
stiffness: 1000,
damping: 100
})
}
})
poser.set('flash')
poser.set('shake')
Transitions
By default, Pose will choose a transition depending on the property being animated:
Note: In a future release, these default animations will be configurable via personality
settings.
Custom transitions
Every pose has an optional transition
property that allows you to define a custom transition:
const config = {
attention: {
scale: 1.2,
transition: ({ from, to }) => tween({ from, to, yoyo: Infinity })
},
rest: { scale: 1 }
}
This function is run once for each animating property and must return a Popmotion animation (or false
for no animation).
The transition
function receives a single argument, an object containing:
- Information about the current transition:
from
,to
,velocity
,key
andprevPoseKey
properties. - Transition props. These can be set statefully as
config.props
or via thesetProps
method. Or they can be set temporarily, as the second argument provided toset
. - React Pose: All props set on the posed component.
You can use these props to create different animations for different values.
Dynamic values
Values on a pose can be set as a function that returns the to
property for a transition.
This function is passed all the same properties as the transition
function except for to
, which this function is responsible for returning.
const config = {
open: { x: 0 },
closed: {
x: ({ i }) => Math.sin(i * 0.5) * 100
}
}
// Vanilla
// --------------------
itemPosers = items.map((item, i) => pose(item, {
...config,
props: { i }
}))
// React
// --------------------
const Item = posed.li(config)
// In component render
{items.map((item, i) => <Item pose="closed" i={i} />}
Draggable
Any element can be made draggable by passing the draggable
property to config
:
const config = { draggable: true };
Dragging can be locked to a single axis by passing the name of that axis instead:
const config = { draggable: 'x' };
dragEnd pose
When an element is draggable and a user stops dragging, a special pose called dragEnd
is automatically set.
You can decide what animation fires by using the transition
property:
const config = {
draggable: 'x',
dragEnd: {
transition: ({ from }) => // return custom animation
}
};
Drag lifecycle events
onDragStart
and onDragEnd
functions can be defined to fire when a user starts and stops dragging:
const config {
draggable: true,
onDragStart: (e) => // not our business!
}
Bound drag movement
We can limit pointer-driven movement with the dragBounds
object.
It can restrict movement in both dimensions with optional left
, right
, top
, and bottom
properties:
const config = {
draggable: 'x',
dragBounds: { left: 0, right: 500 }
}
onChange events
We can append onChange
callbacks to any value with the onChange
map:
const config = {
draggable: true,
onChange: {
x: v => // Do your thing!
}
}
Children
With a poser’s addChild
method, we can spawn a new poser as a child.
When we call set
on the parent poser, the same set
will be passed down to its children. Like this, we can orchestrate multiple animations with a single call.
Add children
addChild
is called exactly like pose
:
const childPoser = parentPoser.addChild(element, childConfig)
Now, all set
calls on parentPoser
will be passed down to all of its children. It doesn’t even need a pose with that label of its own to do so.
parentPoser.set('open')
We can still call set on the child posers without affecting the parent or its siblings:
childPoser.set('hover')
Delay and stagger children
We can delay the propagation of a set call delayChildren
on the parent pose:
const parentConfig = {
initialPose: 'close',
open: {
x: '0%',
delayChildren: 200
},
close: { x: '100%' }
}
Or if we wanted to stagger over the children, we can do so with staggerChildren
, and optionally staggerDirection
:
const sidebarConfig = {
initialPose: 'close',
open: {
x: '0%',
delayChildren: 200,
staggerChildren: 50,
staggerDirection: -1 // stagger from the last child
},
close: { x: '100%' }
}
Passive values
Not all values have to be actively animated via a pose. The passive
property can be used to define values that simply subscribe to actively animated values and update when they do.
For instance, we could update y
when x
updates like so:
const config = {
draggable: 'x',
passive: {
y: ['x', x => x]
}
}
Each passive value is defined as a tuple:
[subscribeKey: string, transform: v => v, fromParent?: boolean]
subscribeKey
: The key of the value we’ll subscribe to.transform
: Receives the latest subscribed value and returns the passive value.fromParent
: Iftrue
, will subscribe to this value in the direct parent instead of the current poser.
FLIP
Animating positional and dimensional properties like width
and top
is tasking for browsers and can cause stuttering in animations.
The FLIP technique was developed to animate these performantly by replacing them with transforms.
When animating these properties, flip: true
can be set to use FLIP:
// Pose will automatically measure the difference
// in element size and animate `scaleX` instead:
const config = {
open: { width: 200, flip: true }, // Will FLIP
closed: { width: 0 } // Will not FLIP
}
Explicit FLIP methods
Alternatively, we might want to transition to a new state where we don’t know the new position or size.
For instance, if we change the children of the element, we might change the height. We can smoothly transition to the new height with the measure
and flip
methods:
const poser = pose(element, config)
// Measure the current bounding box
poser.measure()
// Do stuff, like swap the element's children
doStuff()
// FLIP!
poser.flip()
Alternatively, we can just pass a callback to flip
:
poser.flip(doStuff)
Methods
set
set(poseName: string, props?: Object): Promise
Sets the current pose to poseName
. If Poser
has children, this will get set on those, too. Returns a Promise
.
If props
is defined, these will be passed through to the selected pose’s transition
function.
setProps
setProps(props: Object)
Sets props on the poser that will be passed to any functions set on a pose.
measure
measure()
Measures the current bounding box. Use this before making a change to the element that will affect physical dimensions (like adding new children, or moving it in the DOM), and then use flip
to animate it to the new size.
flip
flip()
Performs a FLIP animation between the previously measure
d bounding box and the latest one.
You can add flip
as a custom pose use a custom transition for this:
const config = {
flip: {
transition: () => // your custom transition
}
};
addChild
addChild(element: HTMLElement | SVGElement, config: PoseConfig): Poser
Creates and returns a new Poser
as a child.
removeChild
removeChild(poser: Poser)
Removes a child.
clearChildren
clearChildren()
Removes all child posers and destroys them.
destroy
destroy()
Stops all active transitions of this Poser
and its children.