How to Avoid DOM Blocking in JavaScript

Share this article

How to Avoid DOM Blocking

JavaScript programs run on a single thread in the browser and in runtimes such as Node.js. When code is executing in a browser tab, everything else stops: menu commands, downloads, rendering, DOM updates and even GIF animations.

This is rarely evident to the user because processing occurs quickly in small chunks. For example: a button is clicked which raises an event that runs a function which makes a calculation and updates the DOM. Once complete, the browser is free to handle the next item on the processing queue.

JavaScript code can’t wait for something to occur; imagine the frustration if an app froze every time it made an Ajax request. JavaScript code therefore operates using events and callbacks: a browser or OS-level process is instructed to call a specific function when an operation has completed and the result is ready.

In the following example, a handler function is executed when a button click event occurs which animates an element by applying a CSS class. When that animation completes, an anonymous callback removes the class:

// raise an event when a button is clicked
document.getElementById('clickme').addEventListener('click', handleClick);

// handle button click event
function handleClick(e) {

  // get element to animate
  let sprite = document.getElementById('sprite');
  if (!sprite) return;

  // remove 'animate' class when animation ends
  sprite.addEventListener('animationend', () => {
    sprite.classList.remove('animate');
  });

  // add 'animate' class
  sprite.classList.add('animate');
}

ES2015 provided Promises and ES2017 introduced async/await to make coding easier, but callbacks are still used below the surface. For more information, refer to “Flow Control in Modern JS”.

Blocking Bandits

Unfortunately, some JavaScript operations will always be synchronous, including:

The following pen shows an invader which uses a combination of CSS animation to move and JavaScript to wave the limbs. The image on the right is a basic animated GIF. Hit the write button with the default 100,000 sessionStorage operations:

See the Pen DOM-blocking animation by SitePoint (@SitePoint) on CodePen.

DOM updates are blocked during this operation. The invader will halt or stutter in most browsers. The animated GIF animation will pause in some. Slower devices may show a “script unresponsive” warning.

This is a convoluted example, but it demonstrates how front-end performance can be affected by basic operations.

Web Workers

One solution to long-running processes is web workers. These allow the main browser application to launch a background script and communicate using message events. For example:

// main.js
// are web workers supported?
if (!window.Worker) return;

// start web worker script
let myWorker = new Worker('myworker.js');

// message received from myWorker
myWorker.onmessage = e => {
  console.log('myworker sent:', e.data);
}

// send message to myWorker
myWorker.postMessage('hello');

The web worker script:

// myworker.js
// start when a message is received
onmessage = e => {
  console.log('myworker received:', e.data);
  // ... long-running process ...
  // post message back
  postMessage('result');
};

A worker can even spawn other workers to emulate complex, thread-like operations. However, workers are intentionally limited and a worker cannot directly access the DOM or localStorage (doing so would effectively make JavaScript multi-threaded and break browser stability.) All messages are therefore sent as strings, which permits JSON-encoded objects to be passed but not DOM nodes.

Workers can access some window properties, web sockets, and IndexDB — but they wouldn’t improve the example shown above. In most cases, workers are used for long-running calculations — such as ray tracing, image processing, bitcoin mining and so on.

(Node.js offers child processes which are similar to web workers but have options to run executables written in other languages.)

Hardware-accelerated Animations

Most modern browsers don’t block hardware-accelerated CSS animations which run within their own layer.

By default, the example above moves the invader by changing the left-margin. This and similar properties such as left and width cause the browser to reflow and repaint the whole document at every animation step.

Animation is more efficient when using the transform and/or opacity properties. These effectively place the element into a separate compositing layer so it can be animated in isolation by the GPU.

Click the hardware acceleration checkbox and the animation will immediately become smoother. Now attempt another sessionStorage write; the invader will continue to move even if the animated GIF stops. Note that the limb movement will still pause because that’s controlled by JavaScript.

In-memory Storage

Updating an object in memory is considerably faster than using a storage mechanism which writes to disk. Select the object storage type in the pen above and hit write. Results will vary, but it should be around 10x faster than an equivalent sessionStorage operation.

Memory is volatile: closing the tab or navigating away causes all data to be lost. A good compromise is to use in-memory objects for improved performance, then store data permanently at convenient moments — such as when the page is unloaded:

// get previously-saved data
var store = JSON.parse(localStorage.getItem('store'));

// initialise an empty store
if (!store || !store.initialized) {
  store = {
    initialized: true,
    username: 'anonymous'
    score: 0,
    best: { score: 1000, username: 'Alice' }
  }
};

// save to localStorage on page unload
window.addEventListener('unload', () => {
  localStorage.setItem('store', JSON.stringify(store));
});

Games or single-page applications may require more complex options. For example, data is saved when:

  • there’s no user activity (mouse, touch or keyboard events) for a number of seconds
  • a game is paused or the application tab is in the background (see the Page Visibility API)
  • there’s a natural pause — such as when the player dies, completes a level, moves between primary screens, and so on.

Web Performance

Web performance is a hot topic. Developers are less constrained by browser limits and users expect fast, OS-like application performance.

Do as little processing as infrequently as possible and the DOM will never be noticeably blocked. Fortunately, there are options in situations where long-running tasks can’t be avoided.

Users and clients may never notice your speed optimizations, but they’ll always complain when the application becomes slower!

Frequently Asked Questions (FAQs) about DOM Blocking

What is DOM Blocking and why is it a problem?

DOM Blocking refers to the process where the browser is unable to render a webpage because it is waiting for a script to finish loading. This can significantly slow down the loading speed of a webpage, leading to a poor user experience. The browser has to construct the DOM tree by parsing the HTML markup. During this process, if it encounters a script, it has to stop and execute it before it can continue. This is because the script might alter the DOM tree structure, and the browser needs to ensure it has the most up-to-date view.

How can I avoid DOM Blocking?

There are several ways to avoid DOM Blocking. One of the most effective methods is to use asynchronous loading for your scripts. This means that the script will be loaded in the background while the rest of the page continues to load. Another method is to defer your scripts, which means they will only be executed after the HTML document has been fully parsed. Lastly, you can also move your scripts to the bottom of your HTML document, so they are the last thing to load.

What is the difference between synchronous and asynchronous scripts?

Synchronous scripts block the DOM construction until they are fully loaded and executed. This means that if a script takes a long time to load, it will delay the entire webpage. On the other hand, asynchronous scripts do not block the DOM construction. They are loaded in the background, and they can be executed as soon as they are ready, even if the DOM is not fully constructed yet.

What is the ‘defer’ attribute in script tags?

The ‘defer’ attribute is used in script tags to indicate that the script should be executed after the HTML document has been fully parsed. This means that the script will not block the DOM construction, leading to faster webpage loading times. However, it also means that the script might not be ready when the DOM is constructed, so it should only be used for scripts that do not alter the DOM structure.

How does moving scripts to the bottom of the HTML document help?

Moving scripts to the bottom of the HTML document ensures that they are the last thing to load. This means that the rest of the webpage can be rendered without waiting for the scripts to finish loading. However, this method should only be used for scripts that do not alter the DOM structure, as they might not be ready when the DOM is constructed.

What is the ‘async’ attribute in script tags?

The ‘async’ attribute is used in script tags to indicate that the script should be loaded asynchronously. This means that the script will be loaded in the background while the rest of the webpage continues to load. The script can be executed as soon as it is ready, even if the DOM is not fully constructed yet. This can significantly improve webpage loading times, but it should only be used for scripts that do not alter the DOM structure.

What is the impact of DOM Blocking on SEO?

DOM Blocking can significantly slow down the loading speed of a webpage, which can negatively impact its SEO ranking. Search engines like Google consider webpage loading speed as one of the ranking factors. Therefore, it is important to avoid DOM Blocking to ensure your webpage ranks high in search engine results.

What is the Virtual DOM and how does it help?

The Virtual DOM is a concept used in modern JavaScript frameworks like React. It is a copy of the actual DOM, and changes are first made to the Virtual DOM instead of the actual DOM. Once all changes are made, the Virtual DOM is synced with the actual DOM in a process called reconciliation. This reduces the number of direct manipulations to the actual DOM, leading to faster webpage loading times.

How can I check if my webpage has DOM Blocking issues?

You can use tools like Google’s PageSpeed Insights to check if your webpage has DOM Blocking issues. This tool analyzes your webpage and provides a detailed report on its performance, including any potential issues like DOM Blocking.

Can CSS also cause DOM Blocking?

Yes, CSS can also cause DOM Blocking. When the browser encounters a CSS file, it has to stop and load it before it can continue rendering the webpage. This is because the CSS file might contain styles that alter the appearance of the webpage. To avoid this, you can use methods like inlining critical CSS and deferring non-critical CSS.

Craig BucklerCraig Buckler
View Author

Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.

animationbrowser performancedom blockinghardware accelerationperformancesession storageSPAweb performanceweb workers
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week