The most efficient way to transform / move a large number of DOM elements

I built a tree structure for AngularJS (with Angular Material) a while ago.

My goal was to make it work only on large screens (1280 and above), but now I want to update it and make it work on small devices (mainly tablets) without data limitation. Due to performance, I want to keep HTML as simple as possible (a tree table can have 1000+ lines, so creating more complex HTML for one line will increase the time it takes to add and display a table row (the lines are dynamic, so not only the rendering)).

It occurred to me that I would save the "fixed" part with the first cell that contains the name, and scroll through the second part, which contains all the metrics and will scroll synchronously.

Current HTML single line:

<div class="tb-header layout-row"> <div class="tb-title"><span>Name</span></div> <div class="tb-metrics"> <div class="layout-row"> <div class="tb-cell flex-10">812</div> <div class="tb-cell flex-7">621</div> <div class="tb-cell flex-4">76.5</div> <div class="tb-cell flex-7">289</div> <div class="tb-cell flex-4">46.5</div> <div class="tb-cell flex-7">308</div> <div class="tb-cell flex-4">49.6</div> <div class="tb-cell flex-7">390</div> <div class="tb-cell flex-4">48.0</div> <div class="tb-cell flex-7">190</div> <div class="tb-cell flex-4">23.4</div> <div class="tb-cell flex-7">0</div> <div class="tb-cell flex-4">0.0</div> <div class="tb-cell flex-8">6.4</div> <div class="tb-cell flex-8">0.0</div> <div class="tb-cell flex-8"></div> </div> </div> 

My idea was to use the touchmove event in the parent container (wrapping the whole tree and linking as a directive) and checking when touchmove starts over the metrics section, then calculate the value that I have to move the metrics. And this part works fine. The problem starts when I want to apply the offset to .tb-metrics > .

My first attempt was to use jQuery:

 function moveMetrics( offset ) { var ofx = offset < 0 ? (offset < -getMetricsScrollWidth() ? -getMetricsScrollWidth() : offset) : 0; $('.tb-metrics').children().css('transform', 'translateX(' + ofx + 'px)'); /*...*/ } 

Unfortunately, this solution is rather slow when the table contains more rows (I cannot cache rows because they are dynamic).

In the second attempt, I tried to avoid as many DOM manipulations as possible. To achieve this, I decided to add <script> to the dom, which contains css, which applies to .metrics >.layout-row .

Decision:

 function moveMetrics( offset ) { var ofx = offset < 0 ? (offset < -getMetricsScrollWidth() ? -getMetricsScrollWidth() : offset) : 0 , style = $( '#tbMetricsVirtualScroll' ) ; if ( !style.length ) { body.append( '<style id="tbMetricsVirtualScroll">.tb-metrics > * {transform: translateX(' + ofx + 'px)}</style>' ); style = $( '#tbMetricsVirtualScroll' ); } else { style.text( '.tb-metrics > * {transform: translateX(' + ofx + 'px)}' ); } /*...*/ } 

However, it seems that it is not much faster when the table contains a large number of rows. Thus, the bottleneck here is not the DOM manipulation, but the rendering / drawing view.

I tried to create some kind of virtual scrolling, but since the tree structure is different for different data sets and can have an β€œinfinite” number of levels (each line can contain child lines in the new ng-repeat ), this is really a difficult task.

I would appreciate any ideas on how I can improve performance in this situation without using virtual scrolling.

EDIT:

The Chrome timeline screenshot shows that most of the scroll time is rendered (I suppose this is due to the complex structure of the DOM)

enter image description here

EDIT 2:

I won’t say that I achieved absolutely smooth scrolling, but I found several things to significantly improve performance (some of them were not obvious, and the result was better than I expected after such small changes).

  • Simplify class selectors: .tb-header >.tb-metrics >.tb-cell much slower than .tb-specific-cell (does it seem to take more time to analyze more complex selectors?)
  • remove opacity and shadow from converted elements
  • try spreading the transformed element to a new layer (use css will-change and / or translateZ(0) )
+10
source share
1 answer

So I'm not an expert myself, but I have some ideas that I think are worth mentioning. If I understand correctly, the problem is that this project needs more and more HTML elements, which will constantly reduce productivity

Here are 3 JS concepts that I think will help you

* Dynamically creating an HTML element:

 let div = document.createElement('div'); 

* Dynamically creating attributes that you intend to use for elements, and the ability to dynamically assign such attributes.

 function attributeSetter(atr){ div.setAttribute('class', atr) } 

Thus, you can call the function where you need it, and give it any class name that you want dynamically. Alternatively, you can use the div element as many times as you like.

The final concept. You can also dynamically add ready-made classes, assuming they are already predefined in CSS like this.

 div.classList.add("my-class-name") 

Another concept that may come in handy is restructuring.

 let myClasses = ['flex-10', 'flex-7', 'flex-4', 'flex-9'] //etc let [flex10, flex7, flex4, flex9] = myClasses; 

This will turn all of these strings into variables that you can easily iterate over and dynamically add them to your attributeSetter function, like so:

 attributeSetter(flex10) 

Wherever you need it. I do not know if this will help, or if it answered your question, but at least some ideas. Good luck :)

0
source

Source: https://habr.com/ru/post/1245478/


All Articles