
28 Jan Supercharged Web Animations
Supercharged Web Animations
Find out how to create high-performance animations from basic state changes to simple physics with these techniques
Animation on the web has always been fairly computationally expensive and while devices have gotten a lot faster overall, we still have to be very careful about how we craft our animations for maximum performance. The bottleneck is often how long it takes to render or ‘paint’ a frame of the animation. This painting process is very expensive so anything else going on, like JavaScript for example, can cause it to spill into the next frame. When lots of frames are ‘dropped’ like this you get janky animation, which no one likes.
It’s for this reason that a lot of jQuery $(element).animate animations get janky. It’s very easy to interact with the DOM more than the ideal amount, leaving less time for the painting process to In this tutorial we cover basic and complex animations using CSS to do the heavy lifting and JavaScript to retain the state. We also cover how you can use JavaScript to craft more advanced web animations that are interaction driven, without impacting performance.
It’s important to craft animations for your target devices. It’s no good achieving 60FPS on a powerful desktop if you’re building for mobile. For that reason, it’s important to use powerful remote debugging tools, for example you can use those provided by ghostlab (vanamco.com/ghostlab).
TECHNIQUE 1
Basic animation
1. Animation between states
The source files for this are under the ‘basic animation demo’ folder on FileSilo. Please look there to see the full source for this technique and for these tutorial pages. First, let’s create a simple box that will animate between two states when clicked.
<div class="box"> Click me </div>
2. The two states
There are two states here: the box and then the box when it’s ‘toggled’. Notice that the box will transition all CSS properties that change over half a second with ease-in easing. We’re expecting the box to change colour and opacity when clicked. The CSS is doing all of the heavy lifting here.
.box { background: red; width: 100px; height: 100px; transition: all 0.5s ease-in; } .box.toggle { background: orange; opacity: .8; }
3. JavaScript to change state
We want to interact with the DOM as little as we possibly can. The JavaScript isn’t actually doing a lot here – what it does though is that it interacts once with the DOM on page load and then it interacts again subsequently every time that the box has been clicked. This leaves the CSS to figure out everything else, which is a more efficient option for the browser.
function bindToggle() { var box = document.querySelector('.box'); box.addEventListener('mousedown', toggle, false); } function toggle(e) { e.target.classList.toggle('toggle'); }
4. Timeline of animation
The associated tutorial files on FileSilo include examples of a timeline that shows the animation that occurs when the box has been clicked once. We have given the browser as much time as possible to render each animation frame by using as little JavaScript as possible here. This has meant that we have been able to achieve frame rates of 60FPS on our device.
5. Use easing curves
Chrome DevTools have a handy curves pallet for adjusting easing on CSS animations. Here we’re using it to make most of the animation happen in the first half. The element feels like it responds immediately, but still has a satisfying transition.
6. Add easing to our transition
The cubic-bezier function shares its basic premise with the curves tool in Photoshop. The ease-in property we had before is essentially a preset and is still a cubic-bezier behind the scenes.
.box { transition: all 0.5s cubic-bezier (0, 1, 1, 1); }
TECHNIQUE 2
Complex state transitions
1. Transitions for animations
We can run multiple transitions from one change in state, and this will enable us to build more intricate UI animations. The full demo code is under the ‘complex state transitions’ folder on FileSilo and is required for reference in this section. Here’s the HTML we’ll be working with.
<article class="form-transform"> <button class="trigger">Login</button> <form class="form"> <input type="email" name="email" placeholder="email@example.com"> <input type="password" name="password" placeholder="password"> </form> </article>
2. Initial state
Now we move on to the initial state of our form component. The full styles are in the demo folder and we’ll highlight some of the key ones in the next steps.
3. Initial CSS states
We want the log-in form to transition in when the button is toggled. To make space for that the width and height of the container will change and the form will need to fade in. These are the initial states required:
.form-transform { width: 150px; height: 60px; transition: all 1s; } .form { position: absolute; opacity: 0; transition: all 1s; }
4. Second states
When the widget gets expanded the container will now grow to accommodate the form. The form itself will also grow, move in from the top, and fade in – all at the same time. If this were JavaScript animation we may expect to see a performance hit with simultaneous animations.
.form-transform.expanded { width: 250px; height: 130px; } .form-transform.expanded .form { width: 200px; top: 50px; opacity: 1; }
5. Add interaction
With the CSS set up we will now need to make the state change. It’s similar to last time, but with a subtle difference. Instead of toggling a class on the element that has been clicked, we toggle a class on the container element instead – allowing us to structure the CSS in the way we did.
function bindToggle() { document.querySelector('.trigger').addEventListener('mousedown', toggle, false); } function toggle() { document.querySelector('.form-transform').classList.toggle('expanded'); }
6. Button in mid-transition
The animation had to be slowed down to get the button in mid-transition. It’s worth considering what your UI states will look like while animating and perhaps designing with it in mind.
TECHNIQUE 3
Breaking the rules!
1. Leave the DOM alone
So long as we stay away from the DOM, we can still use JavaScript to aid us in creating animations on the web. The rest of the tutorial will be focused on adding physics to the x axis of this circle.
2. The variables
The full source for this technique is under ‘drag demo’ on FileSilo. These are the main variables we’ll work with to implement simple physics. It’s worth taking time to understand these because they can get complex.
3. Render and update
The render function is expensive because it interacts with the DOM. UpdatePosition calculates the position of the item every frame based on the velocity, acceleration, friction, and current position of the item.
function render() { element.style.transform = 'translateX(' + positionX + 'px)'; // update DOM }; function updatePosition() { velocityX += accelX; velocityX *= friction; // friction always present positionX += velocityX; // where the item will be rendered this frame accelX = 0; // the further acceleration is from 0 the faster the item is };
4. Apply force
When the item is dragged we want to apply a force to it. To do this we calculate how far the item has been dragged (drag velocity) and then how it should be applied as acceleration. We do this by taking into account the current velocity.
5. The animate function
We have to create a loop for animation to exist. This function will call itself as quickly as possible applying any forces, updating the position of the item, and then rendering to the DOM. All code inside this loop needs to be fairly efficient for smooth animation.
function animate() { applyDragForce(); // apply any forces updatePosition(); // calculate position of item for this frame render(); // update the DOM requestAnimationFrame(animate) // do it all again };
6. Mouse bindings!
This is our mousedown handler. When the user starts dragging we will update some variables to be able to later calculate the ‘drag force’. We also bind a mousemove listener to keep the ‘drag force’ updated and a mouseup listener to clean everything up once the drag is over.
function onMousedown(e) { dragStartX = e.pageX; elementDragStartX = positionX; isDragging = true; setDragPositionX(e); window.addEventListener('mousemove', setDragPositionX, false); window.addEventListener('mouseup', onMouseup, false); };
7. Mousemove and mouseup
When the drag has ended we unbind the listeners we set at the beginning of the drag, lest we have the item we’re animating stuck to our cursor. We also update dragPositionX, which is used in the drag force calculation in the animation loop.
function onMouseup() { isDragging = false; // finish drag and clean up event listeners window.removeEventListener('mousemove', setDragPositionX, false); window.removeEventListener('mouseup', onMouseup, false); }; function setDragPositionX(e) { var moveX = e.pageX – dragStartX; // how far mouse has been dragged dragPositionX = elementDragStartX + moveX; // how far element has been dragged };
8. Kicking it off
When the document is ready for interaction set the element we’re applying physics to. Then set the animation loop going. Lastly, bind onMousedown to mousedown on the element we’re applying physics to.
document.onreadystatechange = function() { if(document.readyState == "interactive") { element = document.querySelector('.drag-me'); animate(); // start animating element.addEventListener('mousedown', onMousedown, false); } };
9. The affect of friction
Multiplying the velocity of the item by a number less than 1 will make it slower every frame. This mock friction allows our item to slowly glide to a halt – interesting things happen when you make the friction variable greater than one.