6 Unsuspecting Problems in HTML, CSS, and JavaScript – Part 2

Welcome to part 2 of our series covering six unsuspecting problems and scenarios that one may come across in HTML, CSS, and JavaScript. In part 1 we talked about the Block Formatting Context and Margin Collapsing. In this post, we will be covering DOM reflow and event delegation, and how they affect the performance of your application.

DOM Reflow

DOM reflow is the drawing or redrawing the layout of all or part of the DOM. This is an expensive process, but unfortunately easily triggered. This could cause a noticeable performance degradation of a web app that requires a lot of user interactivity i.e. drag & drop and WYSIWYG editors. If you are developing a highly interactive web app, then you will most likely at some point trigger a DOM reflow.

Eventbrite’s switch to ReactJS is one answer to the poor performing native DOM. ReactJS creates its own virtual DOM, which optimizes which parts of the web page needs to be re-rendered.

Common Causes

  • Inserting DOM elements
  • Removing DOM elements
  • Moving elements
  • Animating elements
  • Resizing the Window

One cool trick of optimization that I use for inserting new DOM elements is to do all of my processing in memory before appending the new element to the DOM. JavaScript has an API class, which explicitly address this problem called Document Fragments; however, this class is not necessary to achieve the optimization. Simply put, whenever you dynamically create DOM nodes via JavaScript, make sure you do all of the manipulations on that node before appending it to the DOM tree.

A real world scenario is creating a popup modal, which contains a list of names. You could dynamically create and append the modal to the DOM, then in a for-loop, iterate over an array of names appending them inside of the modal. This is not an efficient solution because on every iteration of the for-loop you are causing a DOM reflow. It would be better to create the modal dynamically, append the names to the modal node, then append the modal along with its names to the DOM at once resulting in only 1 DOM reflow.

Event Delegation

Event delegation is the process of taking advantage of the DOM’s event propagation process to handle events at the parent (or ancestor) level versus directly on child nodes. Proper use of event delegation can net a highly performant interactive web app. Improper use could cause noticeable lagging for web pages that involve a lot of user interaction such as drag & drop and WYSIWYG editors. The probability of performance degradation increases with the number of DOM nodes on a given page.

I once worked on a project that involved building a WYSIWYG editor from scratch with its main mode of interaction being drag & drop. Users could drag & drop elements like tables, lists, paragraphs, images, etc. in the DOM, and reorder them at will. Before starting on this project, I was unaware of event delegation, event propagation, DOM reflow, and document fragments. Needless to say, my single-page app had a serious lag problem, and my laptop’s fan sounded like a helicopter. After adding event delegation, document fragments, and these other optimization techniques, the page lag and overall user interaction improved to a smooth experience.

Before I continue I will first explain the concept of event propagation. Event Propagation is the process of multiple events firing on DOM nodes and their descendants and ancestors. For instance, tables have several descendants, table-rows and table-cells. Let’s say you want to place a background highlight on the table-cells when they are clicked. If you hover a table-cell the DOM will fire an event not only on the table-cell, but the table-row and table itself.

There are two event propagation models: bubbling and capturing. These two define the order in which the events are fired. In a bubbling model, when an element is clicked, the event firing starts at the child then eventually bubbles up to the top most ancestor by recursively triggering chained click events to the next parent. In a capturing model, the event firing starts firing at the parent then down to its descendants, then bubbles back to the top. In most modern browsers, except for IE9, event capturing is used. Sometimes it may be necessary to stop the event propagation all together, and that can be done with event.stopPropagation.

Let’s say you have a list of blog posts rendered on a web page, and you decide to add event handlers to every list-item. You want to perform some action when a user clicks one of the blog posts.

<ul id="parent">
<li class="blog-item">Post 1</li>
<li class="blog-item">Post 2</li>
<li class="blog-item">Post 3</li>
<li class="blog-item">Post 4</li>
<li class="blog-item">Post 5</li>
</ul> 

Now, let’s say you dynamically add a 6th post to that list without refreshing the page. What happens when you click on the new post that was just added to the list? You would expect the do_something() function to be triggered, but instead nothing happens. Why?

The answer is that even though a new list-item with the class name “blog-post” was added to the list, a new event listener was not explicitly assigned to that new item. Using event delegation, you can simply assign the event listener to the parent element, and allow the event propagation to listen for clicks on any of its descendants including newly added items. Here at Eventbrite we implicitly take advantage of event delegation via React. React utilizes event delegation underneath the hood by attaching an event listener to the top-level document element.

From a performance perspective, this is more efficient as well. Imagine having a list of a 1000 items. You’ll need to invoke 1000 event listeners on the page whereas you can simply invoke one event listener on the parent element, and handle the event during propagation.

Leave a Reply

Your email address will not be published. Required fields are marked *