Popmotion is a functional JavaScript animation library. Compared to other libraries like GreenSock or Anime.js, Popmotion is low-level and unopinionated.
It packs a ton of features, like spring physics and pointer tracking, into a very small filesize (11.5kb).
It allows developers to write their own features using simple functions, rather than waiting for the library author to add them.
It also means it's just as easy to animate 3D objects, charts or React components as it is to animate DOM or SVG elements.
This flexibility can make the initial learning curve steeper than for other libraries. So, in this tutorial series, we'll learn the basics of Popmotion's powerful animations. We'll start with the workhorse of the animation world, the tween.
Install
Popmotion supports a variety of installation methods. In production, I recommend installing via npm, as this allows you to import only the bits you need, saving space even further.
However, for this tutorial, you can follow along with this CodePen, which has been set up with the latest version of Popmotion.
Tween
For those unfamiliar, a tween transitions between one number and another over a predetermined length of time. If you've used a CSS transition, Popmotion's tween
function works exactly the same.
We can import tween
like so:
const { tween } = popmotion;
By default, tween
animates between 0
and 1
over a duration of 300
milliseconds. If you open your console, you can test this yourself:
tween().start({ update: v => console.log(v), complete: () => console.log('complete!') });
But we don't want to animate the console—we want to animate the ball. For this, Popmotion includes another function, styler
.
Note: In this first example, we defined both the update
and complete
functions. But if you provide start
with only a single function, it will automatically assign it to update
.
Styler
styler
is used to create get/set interfaces for HTML and SVG styles optimised for use with animations (from any library!).
In the above example, tween
is outputting a number, so we could of course set the ball's opacity like this (try it):
const ball = document.querySelector('.ball'); tween().start(v => ball.style.opacity = v);
However, styler
has the following benefits:
- Batches renders to prevent layout thrashing.
- Renders, at most, once per frame.
- Allows
transform
props to be set individually, allowing the independent animation of props likescale
andtranslateX
. - Unifies the CSS and SVG transform coordinate models.
- Understands default value types, so you can set
translateX
(for instance) without appending'px'
.
You're also not limited to using it inside an animation. You could manually set an element's style while others are animating, and the change will be automatically scheduled and batched along with the others.
So let's import it:
const { tween, styler } = popmotion;
Create the ball styler:
const ballStyler = styler(ball);
Now we can use ballStyler
to set and animate any of the ball's properties. ballStyler.set
is flexible. It can set a single property:
ballStyler.set('background', '#f00');
Or multiple properties:
ballStyler.set({ x: 100, y: 100 });
We want to animate opacity
for now, so let's change our animation:
tween().start(v => ballStyler.set('opacity', v));
set
can also be curried. By providing it just a property name, it will return a setter function for that prop. So we can neaten the above by writing:
tween().start(ballStyler.set('opacity'));
So far, we've only animated the ball using the default tween
properties. Let's take a look at how versatile a tween
can be.
Tween Props
tween
accepts one optional argument, an object of tween properties. Let's take a look at some of the more commonly used props:
from
/to
A tween
can be between any two states. We define these with from
and to
.
Let's animate translateX
by rewriting 'opacity'
to 'x'
. Then, pass from
and to
props:
tween({ from: 0, to: 300 })
Your ball now moves from left to right by 300px.
However, I said that a tween
can be between two states, not just numbers. If we provide from
and to
objects of numbers and/or colors, we can animate multiple properties at once.
Try this:
tween({ from: { x: 0, background: '#198FE3' }, to: { x: 300, background: '#FF1C68' } }).start(ballStyler.set);
This is an easy way to animate multiple props simultaneously.
Duration
duration
is defined in milliseconds. By default, a tween will take 300ms, but if we set duration
to 1000
, it'll take a second:
tween({ duration: 1000, from: 0, to: 300 }).start(ballStyler.set('x'));
Easing
Easing functions are used in tweening to change the rate of movement throughout the animation.
In real life, objects don't start or stop at their target velocity. Depending on the object, they gradually speed up, or gradually slow down, or both.
An easing function simply works by taking the tween's progress, defined as a number between 0
and 1
, and returning a new one.
You don't need to know how to make these functions because Popmotion provides a bunch for you.
Import them:
const { easing, tween, styler } = popmotion;
By default, ease
is set to easing.easeOut
. When a function eases out, it means it starts fast and ends slow.
This was chosen as default because it's my belief that most animation in user interfaces should initiate as a result of a user's action. By starting fast and ending slow, the user will feel as if they imparted their energy, via their tap or click, directly into the interface. It feels snappy, alive, and responsive.
For many animations away from the user's input, or on their own, it can feel a little less jarring to use an animation that eases in, like easing.easeInOut
or easing.anticipate
, which does a playful tug before animating.
Finally, there's the easing.cubicBezier
function, which creates a new easing function based on an easing curve, just like CSS transitions. This provides a massive degree of control and flexibility over your motion.
Try applying some of these to your animation while playing around with duration
to see how it affects the feel and character of it.
Repeating
Animations can be repeated in three different ways: loop
, yoyo
, and flip
.
Loop starts the animation from the start. Yoyo mirrors the tween by running it backwards. And flip runs it backwards and flips the easing function.
One of these can be set per tween, and each is set as a number that denotes the number of times to repeat the animation. To repeat forever, simply pass Infinity
:
tween({ yoyo: Infinity, from: 0, to: 300 }).start(ballStyler.set('x'));
Playback
When a tween is started, it returns playback controls that we can use to control that animation.
const controls = tween().start(console.log);
In the above example, controls
will have access to all of these playback methods, like stop
, pause
, and resume
:
const controls = tween({ duration: 1000, from: 0, to: 300 }).start(ballStyler.set('x')); setTimeout(() => controls.stop(), 500);
We can use these playback controls to pause
and then seek
through the tween:
const controls = tween({ duration: 1000, from: 0, to: 300 }).start(ballStyler.set('x')); controls.pause(); controls.seek(0.5);
With this, we can create a scrubbable animation! In a later tutorial, we'll explore how to use Popmotion's pointer
function to create a scrub bar, but for now you can scrub one tween
with a second tween, to see this in action:
const controls = tween({ from: 0, to: 300 }).start(ballStyler.set('x')); controls.pause(); tween({ duration: 1000 }) .start(controls.seek);
Keyframes
For simple, a-to-b transitions, tween
is excellent. For more complicated sequences of tweens, Popmotion provides another function called keyframes
.
Let's import it now:
const { keyframes, easing, tween, styler } = popmotion;
keyframes
tweens through a linear series of states. We provide these states to its values
property:
keyframes({ values: [0, -150, 150, 0], duration: 2000 }).start(ballStyler.set('x'));
Like tween
, we can also define these states as objects. So to move the ball around in a square, we can write:
keyframes({ values: [ { x: 0, y: 0 }, { x: -150, y: -150 }, { x: -150, y: 150 }, { x: 150, y: 150 }, { x: 150, y: -150 }, { x: 0, y: 0 } ], duration: 2000 }).start(ballStyler.set);
By default, keyframes
will allocate each of these tweens an equal share of the overall duration
.
By providing a times
array, we can mark each of these states with a number between 0
and 1
. 0
represents the start of the animation, and 1
represents the end:
keyframes({ values: [0, -150, 150, 0], times: [0, 0.1, 0.9, 1], duration: 2000 }).start(ballStyler.set('x'));
This way, we can adjust the length of the animation without having to remark each individual segment.
It also allows each animation to be given an individual easing with the easings
property:
keyframes({ values: [0, -150, 150, 0], times: [0, 0.1, 0.9, 1], easings: [easing.easeIn, easing.linear, easing.easeOut], duration: 2000 }).start(ballStyler.set('x'));
Because keyframes
is just a tween
, we can adjust its overall playback with all the same properties like ease
and loop
, and control it with all the same methods that we learned earlier.
Conclusion
The tween
and keyframes
functions allow you to create both simple and complex animations.
styler
brings its own benefits, like usage outside of animations, standardisation of CSS and SVG transform models, and render batching for high animation performance.
In this tutorial, we've covered just a couple of the animations that Popmotion offers. In the next installment, we're going to explore pointer tracking and velocity-based animations like physics
and spring
.
Velocity-based animations can be used to create natural-feeling UIs that react realistically to a user's input. See you there!
No comments:
Post a Comment