我不久前为AngularJS(使用Angular Material)构建了一个树表结构。
我的目标是让它只在大屏幕上工作(1280及更高),但现在我想更新它并使其适用于较小的设备(主要是平板电脑)而不限制数据。由于性能的原因,我希望保持HTML尽可能简单(树表可以有1000多行,因此为单行创建更复杂的HTML将延长追加和呈现表行所需的时间(行是动态的,所以它不仅仅是关于初始渲染))。
我想到我将保持“固定”部分与包含名称的第一个单元格并滚动包含所有指标的第二部分并将同步滚动。
当前单行HTML:
<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>
我的想法是在父容器上使用touchmove
事件(包装整个树并绑定为指令)并检查touchmove
何时从metrics部分开始,然后计算我应该移动指标的值。那部分工作正常。当我想在.tb-metrics >
上应用偏移时,问题就出现了。
我的第一次尝试是使用jQuery:
function moveMetrics( offset ) {
var ofx = offset < 0 ? (offset < -getMetricsScrollWidth() ? -getMetricsScrollWidth() : offset) : 0;
$('.tb-metrics').children().css('transform', 'translateX(' + ofx + 'px)');
/*...*/
}
不幸的是,当表包含更多行时,此解决方案非常慢(我无法缓存行,因为它们是动态的)。
在我的第二次尝试中,我试图避免尽可能多的DOM操作。为了达到这个目的,我决定将<script>
标签添加到dom中,其中包含适用于.metrics > .layout-row
的css。
解:
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)}' );
}
/*...*/
}
但是,当表包含大量行时,它似乎不会快得多。所以这不是DOM操作,但渲染/绘制视图似乎是这里的瓶颈。
我尝试创建某种虚拟滚动,但因为树结构对于不同的数据集是不同的,并且可以具有“无限”数量的级别(每行可以包含新的ng-repeat
中的子行),这是一项非常艰巨的任务。
我将非常感谢有关如何在不使用虚拟滚动条的情况下提高性能的任何想法。
编辑:
Chrome时间轴的屏幕截图显示大部分滚动时间都是通过渲染消耗的(我猜这是因为复杂的DOM结构)
编辑2:
我不会说我实现了绝对平滑的滚动,但我发现了一些显着的性能提升(其中一些并不明显,结果比我预期的那么小的变化后更好)。
.tb-header > .tb-metrics > .tb-cell
比.tb-specific-cell
慢得多(似乎需要更多时间来解析更复杂的选择器?)will-change
和/或translateZ(0)
)所以我自己不是专家,但我确实有一些我认为值得一提的想法。如果我理解正确,问题是该项目需要越来越多的HTML元素,这将不断降低性能
以下是我认为可以帮助您的3个js概念
*动态创建html元素:
let div = document.createElement('div');
*动态创建您将用于元素的属性,并动态地分配这些属性
function attributeSetter(atr){
div.setAttribute('class', atr)
}
这样你就可以在任何需要的地方调用函数,并为它提供动态赋予它的任何类名。此外,您可以根据需要多次使用div元素
最终的概念。您也可以动态添加预制类,假设它们已经在css中预先定义,就像这样。
div.classList.add("my-class-name")
另一个可能派上用场的概念是解构
let myClasses = ['flex-10', 'flex-7', 'flex-4', 'flex-9'] //etc
let [flex10, flex7, flex4, flex9] = myClasses;
这会将所有这些字符串转换为您可以轻松迭代的变量,并将它们动态添加到您的attributeSetter函数中,如下所示:
attributeSetter(flex10)
无论你需要它。我不知道这是否有帮助,或者如果这回答了你的问题,但至少有一些想法。祝好运 :)