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)

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)
)