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,keyandprevPoseKeyproperties. - Transition props. These can be set statefully as
config.propsor via thesetPropsmethod. 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 measured 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.