I built up a tree table structure for AngularJS (with Angular Material) some time ago.
My target was to make it work on large screens only (1280 and higher) but right now I want to update it and make it work on smaller devices (mostly tablets) without limiting data. Because of performance, I want to keep HTML as simple as possible (tree table can have 1000+ rows so creating more complicated HTML for the single row will elongate the time needed to append and render table row (rows are dynamic so it's not only about initial rendering)).
I came up with idea that I will keep the "fixed" part with the first cell which contains a name and scroll the second part which contains all metrics and will be scrolled synchronically.
Current HTML of single row:
<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 touchmove event on parent container (wrapping the whole tree and bind as a directive) and check when touchmove starts over the metrics section then calculate the value which I should move metrics. And that part works fine.
The problem starts when I want to apply the offset on the .tb-metrics > .
My first try 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 quite slow when the table contains more rows (I cannot cache rows because they are dynamic).
In my second attempt, a tried to avoid as much DOM manipulation as I can.
To achieve that I decided to add <script> tag to dom which contains css which applies to .metrics > .layout-row.
Solution:
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 doesn't seem to be much faster when the table contains a large number of rows. So it's not DOM manipulation but rendering/painting view seems to be the bottleneck here.
I tried to create some kind of virtual scroll but because tree structure is different for different sets of data and can have an "infinite" number of levels (each row can contain children rows in new ng-repeat) it's a really hard task.
I will appreciate any ideas about how I can improve performance in that situation without using the virtual scroll.
EDIT:
Screenshot of the Chrome timeline shows that most time of scrolling is consumed by rendering (I guess that it is because of complicated DOM structure)
EDIT 2:
I won't say that I achieved absolutely smooth scrolling, but I found a couple of things for significant performance improvement (some of them weren't obvious and the result is better than I expected after such small changes).
- Simplify class selectors :
.tb-header > .tb-metrics > .tb-cellis much slower than.tb-specific-cell(it seems that it take more time to parse more complicated selectors?) - remove opacity and box shadows from transformed elements
- try to distribute transformed element to new layer (use css
will-changeand/ortranslateZ(0))
