The title

How to render an array of 10W records to the page at once without freezing the UI?

specific

There is an empty unordered list node ul on the page, whose ID is list-with-big-data. Now 10W Li need to be inserted into the list. The text content of each list item can be defined by oneself, and the text content in the list item should be displayed through alert when each LI is clicked.


      
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>The page loads massive data</title>
</head>

<body>
  <ul id="list-with-big-data">100000 data</ul>
  <script>
    // Add your code logic here
  </script>
</body>

</html>
Copy the code

Analysis of the

At first glance, we might think of a solution: get the UL element, create a new LI element, set the li text content and listener binding, and append ul in a loop, which might be the following code implementation.

(function() {
  const ulContainer = document.getElementById("list-with-big-data");

  // Defensive programming
  if(! ulContainer) {return;
  }

  for (let i = 0; i < 100000; i++) {
    const liItem = document.createElement("li");

    liItem.innerText = i + 1;
    // This of the EventListener callback points to the current node by default. Use caution when using arrow functions
    liItem.addEventListener("click".function() {
      alert(this.innerText);
    });
    ulContainer.appendChild(liItem);
  }
})();
Copy the code

By putting the above code into practice, we found that the interface experience was very unfriendly and sluggish. The main reason for the stutter is that the DOM structure is changed in each loop, and the loop takes too long due to the large amount of data, resulting in the browser rendering frame rate is too low.

In fact, with a long list of 100,000 Li’s, the user will not see all of them at once, but only a few. Therefore, for most of li’s rendering work, we can delay it.

We can reduce the main thread blocking time both by reducing DOM operations and shortening loop time.

DocumentFragment

The DocumentFragment interface represents a minimal document object that has no parent. It is used as a lightweight version of Document that stores a segment of a document structure comprised of nodes just like a standard document. The key difference is that because the document fragment isn’t part of the active document tree structure, changes made to the fragment don’t affect the document, cause reflow, or incur any performance impact that can occur when changes are made.

In the introduction of MDN, we know that the use of DocumentFragment can reduce the number of DOM operations and the impact of backflow on performance.

requestAniminationFrame

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.

In terms of shortening the loop time, we can insert 100,000 Li in batches into the page using divide-and-conquer, and we insert new nodes before the page is redrawn using requestAniminationFrame.

event

If we want to listen to a large number of elements, the recommended approach is to use JavaScript’s event mechanism and implement event delegates, which can significantly reduce the number of DOM event registrations.

The solution

After the above discussion, we came up with the following solution.

(function() {
  const ulContainer = document.getElementById("list-with-big-data");

  // Defensive programming
  if(! ulContainer) {return;
  }

  const total = 100000; // The total number of inserted data
  const batchSize = 4; // The number of nodes to be inserted in each batch. The more nodes are inserted, the more the interface becomes slow
  const batchCount = total / batchSize; // The number of batches processed
  let batchDone = 0; // Number of completed batches

  function appendItems() {
    // Use The DocumentFragment to reduce the number of DOM operations
    const fragment = document.createDocumentFragment();

    for (let i = 0; i < batchSize; i++) {
      const liItem = document.createElement("li");
      liItem.innerText = batchDone * batchSize + i + 1;
      fragment.appendChild(liItem);
    }

    // DOM is modified only once per batch
    ulContainer.appendChild(fragment);
    batchDone++;
    doAppendBatch();
  }

  function doAppendBatch() {
    if (batchDone < batchCount) {
      // Insert new nodes in batches before redrawing
      window.requestAnimationFrame(appendItems); }}// kickoff
  doAppendBatch();

  // Using event delegate, using JavaScript event mechanism, to realize the monitoring of massive elements, effectively reduce the number of event registration
  ulContainer.addEventListener("click".function(e) {
    const target = e.target;

    if (target.tagName === "LI") {
      alert(target.innerText);
    }
  });
})();
Copy the code