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