Skip to content

Instantly share code, notes, and snippets.

@joshblack
Last active August 29, 2015 14:12
Show Gist options
  • Save joshblack/09a3458a46dcb09bb621 to your computer and use it in GitHub Desktop.
Save joshblack/09a3458a46dcb09bb621 to your computer and use it in GitHub Desktop.
Useful Front-End Stuff

Animation Performance Notes

The Rules (learn them so you can break them!)

  1. No frame should exceed 16ms
  • Caveat: really only the duration between when an animation starts and ends do we need 60fps
  • Do expensive operations before and after an animation
  • Only Need to Worry About Animation Performance in terms of FPS
  1. Never trigger a forced sync layout (layout thrashing)
  • Case Study
  • In the above image, the highlighted methods are deferred to the end of the frame due to browser optimization steps. As a result, the second method now overrides the first method and the resulting effect is that the first method never happened. Net effect: no animation.
  • In this case, we need to force the browser to recognize that we've set some styles before we start changing them again.
  • In order to resolve this, we need to access a property of the element that forces a layout (such as width or height) in order for the browser to register the change.
  • Caveats: this technique should be done before animations begin and should be performed only once!
  1. Never Sniff the User Agent String
  • Do UA sniffing only as a way to provide the best experience to your users on every platform

Performance matters!

"Users really feel it, they may not be able to say what it is but it matters to them. ~ Paul Lewis

On Animating properties that trigger layout, paint, composite...

In order to render elements to screen the browser makes use of two threads called the main thread and the compositor thread.

The main thread is tasked primary with:

  • Running your JavaScript.
  • Calculating your HTML elements’ CSS styles.
  • Laying out your page.
  • Painting your elements into one or more bitmaps.
  • Handing these bitmaps over to the compositor thread.

The compositor thread is used primary for:

  • Drawing bitmaps to the screen via the GPU.
  • Asking the main thread to update bitmaps for visible or soon-to-be-visible parts of the page.
  • Figuring out which parts of the page are visible.
  • Figuring out which parts are soon-to-be-visible when you’re scrolling.
  • Moving parts of the page when you scroll.

When we animate properties such as height, we get the following communication pattern between the two threads. Note: Expensive operations are in orange.

Thread Communication When Animating Height

When we use transforms or transitions, properties easily mainupalted by the GPU, we get the following pattern:

Thread Communcication When Animating Transform and Transition

Source

Subpixel Rendering

Changing width / height vs Changing Transforms

When animating height and width on an element, you will still get visual "jitter" due to the fact that the element is being animated pixel by pixel. Constrastly, you get subpixel rendering when you use a transform.

Magic functions

// Through the use of getBoundingClientRect on an element, we get an object that looks similar to this:
{
  top: 178,
  left: 201,
  width: 234,
  height: 240,
  bottom: 418,
  right: 435
}

Gotchas

Animations

  • Transitions' forced hardware acceleration taxes GPU's. TODO: add Adobe link
  • Transitions do not work below IE10, which causes accessibility problems for desktop sites with IE8 and IE9 still remaining popular
  • Transitions are not natively controlled by JavaScript (instead, they are simply triggered). As a result, the browser does not know how to optimize transitions in sync with the JS code that manipulates them.
// Add the pixel unit to a value with an optional separator value
function px(value, separator = '') {
return value + 'px' + separator;
}
// USAGE:
// ES5
element.style.transform = 'scaleY(' + px(25) + ')';
// ES6
const scaler = px(25);
element.style.transform = `scaleY(${ scaler })`;
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());

Terminology

  • Animations
    • Layout Thrashing: causes stuttering at the start of animations
    • Garbage Collection: causes stuttering during animations
// Useful for determining the type of a variable.
/* Copyright Julian Shapiro. https://github.com/julianshapiro/velocity/blob/master/velocity.js */
var Type = {
isString: function (variable) {
return (typeof variable === "string");
},
isArray: Array.isArray || function (variable) {
return Object.prototype.toString.call(variable) === "[object Array]";
},
isFunction: function (variable) {
return Object.prototype.toString.call(variable) === "[object Function]";
},
isNode: function (variable) {
return variable && variable.nodeType;
},
/* Copyright Martin Bohm. MIT License: https://gist.github.com/Tomalak/818a78a226a0738eaade */
isNodeList: function (variable) {
return typeof variable === "object" &&
/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(variable)) &&
variable.length !== undefined &&
(variable.length === 0 || (typeof variable[0] === "object" && variable[0].nodeType > 0));
},
/* Determine if variable is a wrapped jQuery or Zepto element. */
isWrapped: function (variable) {
return variable && (variable.jquery || (window.Zepto && window.Zepto.zepto.isZ(variable)));
},
isSVG: function (variable) {
return window.SVGElement && (variable instanceof window.SVGElement);
},
isEmptyObject: function (variable) {
for (var name in variable) {
return false;
}
return true;
}
};
function isIOS() {
return (/(iPhone|iPad|iPod)/gi).test(navigator.platform);
}
function isSafari() {
var userAgent = navigator.userAgent;
return (/Safari/gi).test(userAgent) &&
!(/Chrome/gi).test(userAgent);
}
function isIE() {
var userAgent = navigator.userAgent;
return (/Trident/gi).test(userAgent);
}
function getWindowScrollPosition() {
if (typeof window.scrollY === 'undefined')
return document.documentElement.scrollTop;
else
return window.scrollY;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment