Navigate back to the homepage
BLOG
Try NowLogin
Back

How Browser Rendering Works and Why You Should Care

Aleksandr Kaminskii
August 7th, 2020 · 3 min read

Within the last decades web technologies have evolved in a very rapid way. Nowadays, with things like WebAssembly, PWA, constantly progressing browser API and other surrounding technologies, web has a great potential to become a universal platform for a wide range of tasks and applications.

With web app’s area of responsibility expanding, more attention is paid to app performance. User experience became an important thing to care about, especially when you develop an app that runs on multiple devices.

To be able to manage performance appropriately, one need to know how the rendering pipeline of the browser works and where potential bottlenecks can be found.

Page Rendering

Web page load simply starts from entering the URL in the address bar. In fact, a bunch of things happen right after that and even before HTML request is done. Some of them can influence the performance, but we’ll leave this part for another dedicated article. Now we are interested in things happening when the HTML file has been loaded:

  1. Parse HTML HTML document parsed and the DOM tree as we know it has been built.
  2. Recalculate Styles All kind of stylesheets are parsed. Render tree is built. Render tree contains DOM tree nodes that are going to be displayed. Thus, head tag is excluded as well as style and script. Elements with CSS display property set to none are also ignored. On the other hand, pseudo elements, such as :after and :before, are included. Moreover, every text line becomes a single block.
  3. Layout (also called reflow) A collection of blocks generated from the render tree. All the block dimensions, that are dependent on each other, are calculated.
  4. Paint Rasterization of the blocks and texts. Images decoding and resize if necessary also happen here.
  5. Layer Composition Composition of the visual layers that can be processed and painted independently.

All the phases can be found in the Chrome DevTools’ Performance tab. In reality some of the steps above happen in parallel, e.g. CSS and HTML parsing since HTML document contains styles and stylesheet links in it, so it makes sense to perform DOM and CSSOM processing in parallel.

Any time you want to change a view on the page using javascript (or CSS animation) this pipeline gets fired. Although it is not necessary to parse HTML file and rebuild the whole render tree (usually only part of it), a proper processing for the visual update is required. Depending on requested changes, browser skips some of the steps, but in general, the pipeline would look like this:

Image of Frame Pipeline

Problem

You might not need to know all of this when you’re just adding a couple of paragraphs, or modifying background color once every blue moon. Everything changes when it comes to a complex animation and continuous DOM updates. Normally contemporary screens have frequency 60Hz, which means that new frame ought to be rendered 60 times per second (60 FPS). Therefore, browser have 1000/60 ≈ 16ms for doing his job on every frame. If it takes more time, the FPS decreases, animation might become jerky and user gets disappointed.

In the old browsers animation was usually implemented with setTimeout (or setInterval) function, which wasn’t initially designed for this purpose. setTimeout (or setInterval) function knows nothing about the browser reflow, thus visual changes might be requested at the middle of the pipeline, cancelling the last steps of the work on a previous frame and causing recalculations. Therefore we have missing frames and possible worthless work done, which might be critical in the scale of 16 ms and leads to janky animations.

Solution

Fortunately, modern browsers have a proper method to deal with it. requestAnimationFrame is the browser API that provides a sufficient way to perform animations. The call for this function (with a callback as a first argument) asks the browser to call it right before the next styles calculation, so it won’t get interrupted in the middle of the next frame processing.

1requestAnimationFrame(function() {
2 changeTheDOM();
3})

There is a common way to implement continuous javascript animation:

1let prevAnimationTime;
2function handleFrame(currentAnimationTime) {
3 if (!prevAnimationTime) {
4 prevAnimationTime = currentAnimationTime;
5}
6 const diffTime = currentAnimationTime - prevAnimationTime;
7 changeDOM(diffTime);
8 requestAnimationFrame(handleFrame);
9}
10requestAnimationFrame(handleFrame);

The first argument that is passed to the callback is the time in milliseconds passed from the navigation start (from the time origin). We can use it to know the time difference with the last frame and adapt our animation properly. We can use requestAnimationFrame return value to stop an animation with cancelAnimationFrame:

1let requestID;
2function handleFrame() {
3 changeDOM();
4 requestID = requestAnimationFrame(handleFrame);
5}
6
7function startAnimation() {
8 requestID = requestAnimationFrame(handleFrame);
9}
10
11function stopAnimation() {
12 cancelAnimationFrame(requestID);
13}

Forced Reflow (Layout)

However, there are underwater rocks. There is a set of methods and attributes that require browser to know a layout state, e.g. offsetWidth, getComputedStyle() and others. In other words, if you do changes to the layout and then request such a property a few lines after, browser will have to recalculate all the size relations and perform layout step twice. Which, again, on the scale of 16 ms has consequences on performance.

You can find the full list of methods and properties that trigger forced layout here.

Also I would recommend this course from Paul Lewis and Cameron Pittman which talks in details about rendering pipeline and optimizations.

Frontend Monitoring

Asayer is a frontend monitoring tool that replays everything your users do and shows how your web app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder.

Asayer Frontend Monitoring

Asayer lets you track the Framerate for each of your user sessions and monitor your web app’s performance. Happy debugging, for modern frontend teams - Start monitoring your web app for free.

More articles from Asayer Blog

How to Write Better TypeScript

Learn how to make better use of Typescript's strengths and features while involving React.

July 31st, 2020 · 8 min read

Incorporating CSS to a Page via HTTP Headers

A quick solution to use CSS via HTTP headers. Tips & Techniques on how to add CSS to pages on personal projects.

July 29th, 2020 · 5 min read
© 2020 Asayer Blog
Link to $https://twitter.com/asayerioLink to $https://github.com/asayerioLink to $https://www.linkedin.com/company/18257552