#perfmatters

March 1st, 2015 | javascript, performance |

Goal : be jank-free at 60fps

Jank is when missed frame appears when navigating a website, you don’t want that. http://jankfree.org/

You can find a lot of talks and articles from Paul Lewis, Tom Wiltzius and Nat Duca, who are great speakers btw, that explain clearly how to avoid jank, how to diagnose it, and how to fix it. Check the resources at the end of this post to have some pointers.

I decided to resume quickly everything I learned from a lot of different sources in order to not do those mistakes again and help you and me to understand what can cause this jank. I don’t explain in details everything, every term I use, check out the resources of the post to have a better understanding if needed.

I’ll start with general javascript concepts and then I’ll continue with deeper concepts such as the layout/painting/layers processes the browser do and how to optimize them, then I’ll finish with some network advices.

Some of the items are maybe not relevant anymore nowadays for certain browsers because it was optimized since. I tried to filter them but still.

Feel free to correct me if I’m wrong.

Object instances

If you have a bunch of object to create, forget about module pattern, prototypes are faster to create, or create plain simple object. http://jsperf.com/prototypal-performance/55

Holey array

Don’t do holey array (it can be converted to a dictionary internally thus being way slower). Try to not use the delete keyword. It can change on the fly the internal class being used for the object you just deleted from (for instance, an array with a delete in the middle can transform it into a map). http://jsperf.com/packed-vs-holey-arrays

Objects pooling

If you are creating and destructing a lot of objects/references, consider using an object pool to avoid triggering the Garbage Collector (which would make you lose some precious ms at random times). Check http://www.html5rocks.com/en/tutorials/speed/static-mem-pools/ for more details.

Image resizing

Resize your image exactly as the size you are using in your page (we are not talking about multi-support here) to avoid the browser to resize the image itself (cpu consuming). Consider using srcset on or use if you want to handle high DPI devices.

Background-size

Beware of background-size, keep the image at the original size instead of asking the browser to resize on the fly. Create multiple version of the same image if needed. You can also create a spritesheet image of your images to do only one HTTP request (and maybe have a faster memory access but don’t take this into consideration, that’s way too insignificant to be considered).

CSS gradients

Beware of repeating gradients. That slows down a lot the browser rendering. For instance, if you put it on the background of your page in which you can scroll. Prefer to use a tiled background image.

Custom fonts instead of image

Don’t forget that you can use nice custom fonts instead of images to draw symbolish things. Such as FontAwesome. You will save bandwidth and designer time. http://fortawesome.github.io/Font-Awesome/icons/

Or you can use unicode characters (it has a lot of icons, not only characters) or svg (light and scalable).

Image lazy loading

Consider using an image lazy loading plugin to load images only if the user can see them of if everything else has been loaded first. If by default the image is not displayed in the first view at loading time, you can defer its request when the user is scrolling into for instance.

If you have a carousel of images, consider to not load every images of the carousel at the beginning. Either wait the user to hover it or load them after everything else is loaded. If the user does not even use the carousel, it’s useless to load the other images.

Event listeners

Try to not bind event listeners on the whole document just to handle the event on a deep child. Try to bind listeners on the element you need or a nearest parent. Otherwise the compositor (a thread dedicated to handle inputs and scrolling) will be triggered for nothing each time the event will happen on the page, that could have no link with your element.

Memory management / variable scoping

Try to not use global variables. Release what you can when you can. With ES6, use only let, no more var. Remove the event listeners you don’t need. Be especially careful with closures, they can contain a lot of references that will never be released if you are careless.

Events throttling

Throttle the events that can be called numerous times : onscroll, onmousemove, onresize, ondragover. They will be called only a few times and that will be enough.

requestAnimationFrame() is your best friend

Do not use setTimeout() or setInterval() to throttle event handlers such as onscroll or onresize. Use requestAnimationFrame() to only change the visual state when a new frame is going to be rendered, to not compute things that won’t be render in-between 2 frames. Check http://www.html5rocks.com/en/tutorials/speed/animations/ for more details.

Event handlers logic

Don’t do any complex logic in the event handlers. Just save somewhere the values which you care of the current state (scrollTop, mouse position…), and let the browser pursues its events pipeline. If you are doing something heavy in a handlers, you will delay every other events to be handle and will delay the rendering (then you call a requestAnimationFrame(fn) to use those saved values). Try to bind global events only once, such as onscroll or mousemove. Just save their positions in those handlers and when you need them, just call functions in requestAnimationFrame() using those saved values.

:hover and scrolling

Be careful with :hover effect on elements in the middle of a scrollable page. If the user scrolls using its mousewheel on top of those elements, they will trigger the :hover at the same time the scrolling is occurring. That can slow down dramatically the fps and you end with jank (missed frames during scrolling becausee of a lot of restyle/paint/composite layers operations). You can for instance add a class on the container when it’s scrolling to not trigger the :hover effect while it’s present.

Dynamic class adds

Don’t add classes on top of your document if that only change an item deep in the DOM (that could cause a full repainting from the parent). Try to target specifically the element or a near parent.

Avoid Layout Trashing

When you need to read some positions or sizes from the DOM (.offsetTop, .offsetWidth …), don’t alternate them with writes of positions or sizes on the DOM (element.style.width = width). That would cause what is called “Layout Trashing” because each time you change a size or position on the DOM, that triggers a forced synchronous layout (painting). Do all your readings at the beginning, store them into variables, then do your writings. You can call a requestAnimationFrame() to read those values, then inside, call another requestAnimationFrame() to write your transformations.
To know if have to kind of cycles, check your timeline in the debugger and look for cycles of :
– recalculate style
– layout
– recalculate style
– layout
You can know what property triggers what on http://csstriggers.com/ (only Chrome for now)

transformZ(0) aka the null transform hack

There is a trick called the “null transform hack” to force the browser to create a distinct layer containing a DOM element that will be painted using the hardware acceleration (and independently of the other layers) using transform: translateZ(0) or transform: translate3d(0,0,0). Those statements changes nothing visually but on a desktop computer, that can really boost your fps. If you combine that on elements that have a shadow (like, a lot on your page), you clearly see the difference when playing with. But be careful, mobiles do not have the same capability than desktops, and the performances could be worse.

Will-change

Instead of this hack, consider implementing the fairly new css property will-change: [transform|scroll-position|contents| to hint the browser to create a distinct composite layer for this element. You just notify it that the particular property will probably change for this element. The browser will try to optimize what it can (ie: create a new layer for it). But don’t abuse it to not create another problem (it’s useless to do * { will-change: * } the browser is already trying hard).

If you know you are going to change the text of your item, you can set will-change: contents. Check https://dev.opera.com/articles/css-will-change-property/ for more details.

Remove this property when you are done with the change. Do not apply it on a global rule, just apply it when just before it’s needed. For instance, listen to the :hover state of its container to set it on the child using css : no hover on parent = no will-change on child. Or set it using javascript and reset it to auto when you’re done.

Composite layer

What creates a new composite layer is the when you use translationZ/3d, and  (they are automatically embedded in a new layer), animations with opacity and transform, and some css filters that use hardware acceleration. It has nothing to do with z-index layers. Check http://www.html5rocks.com/en/tutorials/speed/layers/ for more details.

Animation/Transitions

Do not animate with javascript. Use css animations/transitions. The animations will be processed by another thread than the main one. Even if your js does complex calculations and slows down the main thread, the css animation will still be smooth.

Transform to animate position

When you have element which position is dynamic, do not use css top/left, use transform: translate. top/left should be reserved for initial positioning only, just to organize the layout. css transform is especially done for transformations, it does not reflow/redraw whereas top/left does. In DevTools, enable Show Paint Rectangle (Timeline > Rendering) to see what is repainting on your page.

Moreover, the translate transform animation is using subpixel rendering instead of pixel per pixel for top/left animation. The rendering is smoother.

FLIP

It’s a technique used to animate an element the cheapest way possible when the positions/dimensions are unknown/dynamic. Paul Lewis explains it:

Basically, it stands for: First, Last, Invert, Play. From the initial state, you set directly its last state, read some values from it (position, size), apply an inverted transform style to put it back where it was, and clear the transform style to trigger the animation.

Fixed elements

If you have a top bar in fixed position, consider to put it in its own layer. Otherwise, scrolling the content underneath cause the element to be repainted each time. You can check with the Show Paint Rectangle toggle.

Chrome DevTools <3

In Chrome DevTools, you can press h on an element to toggle its visibility. That avoids you to add a display: none manually on its style. Life saver. Of course, their performance tools is uberly useful and you can use about:tracing if you are desperate (and you will probably be more desperate with that ;-).

To check if you have some weird or expansive painting behavior, enable the continuous painting mode and toggle elements visibility to see which one(s) is(are) causing the problem : Timeline > Rendering (press to toggle the panel visibility if not there) > Show paint rectangles + Enable continuous page repainting. You can enable the fps meter too. Now, scroll into your page, do some actions, hide some elements to see if you can find the culprit (if you notice a boost).

Multiple adds to the DOM / DocumentFragment

When you want to append several items to the DOM, use document.createFragment(). It’s available in all browsers since a while (jQuery internally use it when you call append() with an array for instance). You can use traditional DOM operations on it (appendChild, style etc.) without the browser to render it while you create it. Then when you are done, you just append it to the real DOM in one shot.

Avoid to read value from CSS

Try to not use getComputedStyle() (or .css() getter from jquery, it’s using that internally) to read value from CSS directly, that’s calling the “Recalculate Style” process. Save the values you need before somewhere if you can and use them, or if it’s for animate something, use css transition/animation.

getBoundingClientRect()

This magic function returns in one call the position and the dimension of the item it’s called on : height, width, top, left, bottom, right. Use it once when you need to read those differents values.

User reaction

When a user clicks on a button, you have about 100ms before doing any animation. The user won’t notice anything before, it’s its reaction time. Therefore, you can prepare anything you need (get sizes, positions…) and have an ugly 100ms frame if you need, he won’t notice. ;-)

If you want to deal with mobiles, add in your . That could enable the GPU rasterization on the phone (Androids). But basically, add that even if it’s not an Android, that won’t do harm on the contrary.

DOM element count

Try to not have too many DOM nodes on your page. For instance, 1000 for a mobile app is a good number. (desktop version : 4000 twitter.com, 2500 for amazon or facebook, mobile version: 1000 for m.twitter.com, 1700 for m.facebook)

CSS stylesheets

CSS are blocking resources for the browser. It waits for them to be loaded and parsed before rendering anything to avoid a flickering effect. So, keep it light, avoid to load unnecessary css rules. You can split your stylesheet by media for instance. The browser won’t wait for them if their rule does not apply :



Put your CSS in the for the browser to request them as soon as possible.

Avoid to use @import in stylesheets to not block again the browser (because it will request again a stylesheet).

Async JS scripts

Inline blocks the DOM construction when the browser encounters them, which will therefore delay the initial render. Generally, you put your