Supercharged Web Animations

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.