Reputation: 12922
I have a HTML/JS website with several hundreds of div elements. A few dozens of these elements should be updated in a rapid fashion (up to 250 times per second) at one time (i.e. they should all be updated at once, without having the browser to perform unnecessary work by performing the updates one-by-one). What is the most performant way (minimizing the CPU load and memory consumption) to do this supporting modern browsers to achieve this with pure JavaScript or with a simple library (without using React or a similar library requiring me to modify the code beyond the DOM handling)? I'm looking for something like this (where imaginaryLibrary is the library unknown to me, which I'm looking for):
var i, element;
for(i = 0; i < 20; i++){
element = document.getElementById('el' + i);
imaginaryLibrary.collectDomUpdate(element, cssClass, 'updatedCssClass');
}
//executes DOM updates collected in loop before and applies the DOM updates in one reflow
imaginaryLibrary.applyUpdates();
The parent element of the elements to update contains thousands of elements, which should not be updated.
Upvotes: 40
Views: 21261
Reputation: 87
This seems to be similar to the imaginary library you're looking for, except it applies the changes automatically after each rAF rather than the developer handling it manually:
https://github.com/wilsonpage/fastdom
FastDom works as a regulatory layer between your app/library and the DOM. By batching DOM access we avoid unnecessary document reflows and dramatically speed up layout performance.
Each measure/mutate job is added to a corresponding measure/mutate queue. The queues are emptied (reads, then writes) at the turn of the next frame using window.requestAnimationFrame.
The library size is 0.6kb gzipped/minified.
Upvotes: 6
Reputation: 708056
You aren't real specific about exactly what you're doing so about the best we can do here is to give you general advice about a bunch of relevant areas.
DOM Modifications
You don't quite fill in all the details of what you're trying to do, but here are a bunch of things to take into consideration when optimizing how your code works:
Reflows are already queued. The browser already attempts to minimize reflows so if you do four dom modifications in one sequential piece of Javascript, the browser will wait for that piece of Javascript to finish running and then do one reflow and one repaint.
Requesting certain properties can trigger an immediate reflow. There are exceptions to the above rule that you want to make sure you avoid. For example if you request certain DOM properties that require proper layout for the property value to be accurately reported and there is a pending layout from a prior modification, then the browser may relayout the document synchronously before returning the property you requested. These type of properties usually involve things like screen position and other properties that would obviously be affected by the layout of the document. There are many articles on this topic if you want to find further details. In many cases your code won't be using these properties anyway, but if so, the usual workaround is to request all the needed properties first before making any changes to the DOM.
Batch up all your DOM changes at once. The worst thing to do is to make a DOM change, wait a few ms with a timer, make another DOM change, wait a few ms with a timer and so on because you will have DOM change, reflow, repaint, DOM change, reflow, repaint, etc... Instead, make sure you're doing all pending DOM changes at once in one synchronous piece of Javascript. This will then allow the browser to queue the reflow and repaint and do it only once after you've made all your DOM changes. If you want to get even smarter about the batching, you will collapse modifications to the same element so that it is only processed once with the final value. So, if elementA was first given a new value of 3 and then later in the same batch was given a value of 4, when processing the batch of data, you want to skip the 3 and just process the 4.
DOM modifications can sometimes be optimized. You don't give us any specifics on how you are modifying the DOM, but if you are modifying complex objects (like adding many table rows or changing lots of table cells), then it is sometimes better to create a set of DOM elements that are not currently inserted in the DOM, make all your modifications to them and then with one operation, insert them into the DOM. This is because modifying DOM elements that are currently inserted in the DOM forces the browser to figure out which other DOM elements are affected by this change so it can queue the appropriate reflows or repaints. But modifying off-screen DOM elements does not have to do any of that work until the one last step where one larger object is inserted into the DOM. There is no useful generic rules here because how you take advantage of this optimization depends entirely upon what DOM modifications you are doing. We could help you with this, but only when we could see both the HTML you have and exactly what changes you're making to it.
Timing of Updates
Now, as for the timing of things, you mentioned up to 250 times per second. That's kind of fast (4ms per operation) for a user viewable thing. If you do that, your browser is basically going to be constantly reflowing and repainting with only occasional pauses to process other user events. Since no user can actually see something happen at 4ms, 8ms and 12ms from now, there's really no need to update status on screen that often.
So, if you really have changes coming in that fast or often, you may want to batch them by accumulating them into a local data structure and then just updating the screen every 100-500ms or so. You'd have to experiment with what the long interval you could use and not really notice any delay.
There are several implementation strategies for batching the updates. The simplest I can think of if your modifications are always streaming in constantly is to just put the modifications into a local array as they come in and then just have an interval timer set for your update interval that checks the array and if there's anything in it, it process all the items in the array into a DOM update.
Getting Changes From Server
You don't mention how the browser Javascript is getting the new data. There are generally two options, repeated Ajax requests or create a webSocket and the server can directly send you data at any time.
If your updates are regularly and shortly spaced, the webSocket connection is clearly the most efficient. It's one constant connection and the server can just send each client new data whenever the server has new data to send.
If you're going to be polling with Ajax, then I'd strongly suggest you lengthen out the polling interval. A very short polling interval will both really load your server and eat the battery on the client.
Battery
If this app is intended to run for long periods of time on a battery powered device, then getting a steady stream of data real-time (e.g. every few ms like you mentioned) from any server is going to eat battery because the radio (WiFi or Cellular) will be active nearly all the time and the CPU is going to be running a lot too.
Upvotes: 91
Reputation: 401
You'll want to do the updates to the DOM "offline". Depending on your requirements, it may be as using display: None in the CSS of the parent container, doing the updates, then unhiding said container. You can do something like this more manually by removing Nodes from the DOM and reinserting them after manipulation.
If that leads to a perceptible or otherwise unacceptable gap in the display of the elements, then you may want to consider a solution where you're copying the Nodes, manipulating them, and then overwriting the older ones in place. Will probably have to be careful and do a little more research to ensure that doesn't just cause the browser to instantiate a bunch of new elements all the time. Maybe a "pool" of the Nodes
Upvotes: 0
Reputation: 4052
It seems like you already have the basics of what you want to do. For your particular use case, I see no reason why you shouldn't just naively implement those methods.
var imaginaryLibrary = function() {
var toProcess = [];
function collectDomUpdate(el, prop, newValue) {
toProcess.push([el, prop, newValue])
}
function applyUpdates() {
while (toProcess.length) {
var actions = toProcess.pop()
actions[0][actions[1]] = actions[2]
}
}
}
The above code is a skeleton of what you might want to do—it contains no error checking and assigns directly to object instead of potentially using DOM attributes.
Upvotes: 4