在 Angular 2 中应用频繁 CSS 更改的最有效方法

Posted

技术标签:

【中文标题】在 Angular 2 中应用频繁 CSS 更改的最有效方法【英文标题】:Most effective way to apply frequent CSS changes in Angular 2 【发布时间】:2017-10-17 23:54:38 【问题描述】:

我们有一个 Angular 2 站点,其中有一个 websocket 将数据从后端泵入我们的网格。为了指示最近的更新,我们使用 CSS 为受影响的单元格设置行的背景颜色和粗体字体。

指示应该只持续很短的时间。

1) 我们的第一次尝试是在下一批从服务器到达时重置所有指标。这运行良好,但在某些视图中很少更新,这意味着指标可以保持很长时间,这有点令人困惑。

如果更新指示器在固定间隔后消失,例如 4 秒,会更加一致。

2) 我们的下一个尝试是使用 CSS 动画。但过了一段时间,它就滞后了很多。给人的印象是,运行过多的动画会使浏览器过载,无法处理请求的时间。也许每个动画在后台都有自己的计时器?

3) 第三种尝试是让一个计时器以固定的时间间隔运行,然后检查要重置的记录。我们创建了一个 TimerService,它会定期检查到期项目。向计时器池添加项目时,可以配置任意等待时间。

这可行,但在日志窗口中经常出现违规警告:

[Violation] 'setInterval' handler took 56ms
[Violation] 'setInterval' handler took 74ms
[Violation] 'setInterval' handler took 63ms
[Violation] 'setInterval' handler took 88ms
...

但是当我们计算 checkItems 方法中发生的事情时,它只需要 0.03 毫秒!

我们都有 C# 背景,并且刚刚使用 Angular 工作了几个月。也许我们正在实施一种后端方法?

是否有我们错过的上下文切换?

还有其他更前端友好的方法吗?

我们可以对代码进行一些关键的优化吗?

感谢所有建议!

这是导致所有警告的建议 TimerService:

import  Injectable, OnInit  from "@angular/core";
import  Observable  from "rxjs/Rx";
import  Subject  from "rxjs/Subject";

@Injectable()
export class TimerService 
    private timerItems: TimerItem[] = [];
    private dueTimeReachedSubject: Subject<string> = new Subject<string>();
    public dueTimeReached: Observable<string> = this.dueTimeReachedSubject.asObservable();

    constructor() 
        setInterval(() => this.checkItems(), 1000);
    

    private checkItems() 
        let now = Date.now();
        let removeKeys: string[] = [];
        this.timerItems.filter(t => t.dueTime <= now).forEach(t => 
            this.dueTimeReachedSubject.next(t.key);
            removeKeys.push(t.key);
        );
        this.timerItems = this.timerItems.filter(t => removeKeys.indexOf(t.key) < 0);
    

    public add(key: string, delayInSeconds: number) 
        let dueTime = Date.now() + delayInSeconds * 1000;
        let timerItem = this.timerItems.find(t => t.key === key);
        if (timerItem) 
            timerItem.dueTime = dueTime;
        
        else 
            this.timerItems.push(new TimerItem(key, dueTime));
        
       

    public remove(key: string) 
        this.timerItems = this.timerItems.filter(t => t.key !== key);
    



class TimerItem 
    constructor(public key: string, public dueTime: number)  

编辑

我尝试使用 Observable.interval:相同的结果和完全相同的警告消息:“[Violation] 'setInterval' 处理程序花费了 xx 毫秒”

我尝试将 setTimeout 用于重复调用:结果相同,但警告消息已修改:“[Violation] 'setTimeout' 处理程序花费了 xx 毫秒”

我什至尝试清空每一行的 checkItems,但仍然收到警告。

警告是从 zone.js 中抛出的,似乎是 Angular 内部建议。我知道我可以在 Chrome 开发人员工具中关闭详细日志记录,但我经常将 console.debug 用于开发目的,这意味着它们也会消失。

我猜对于慢速函数来说,警告是可以的,但在这种情况下,它只是触发了一个 setInterval 函数。为什么会很慢?

【问题讨论】:

相当盲目的建议:如果你时间:setInterval( () =&gt;  start; this.checkItems(); end; , 1000); 会发生什么? 0.03 ms 是一致的度量,当 Chrome 记录 'setInterval' handler took 74ms 时您也会观察到? 你能在没有 @Injectable 装饰器的情况下测试你的类吗? 这不是一个通用的解决方案,但是您可以将一个类应用于一批行并使其一起淡出。它会节省很多计算 在这种情况下,如果进程由具有许多子组件的智能组件处理,您还应该使用ChangeDetectionStrategy.OnPush Array.filter 导致此问题,尝试基本的for 循环,看看它是否持续 【参考方案1】:

你确定你在从 checkItems 清除代码后重新构建了你的项目。

据我所知,它抱怨 checkItems 函数花费的时间太长。

也以防万一, 我确实设置了StackBlitz here

使用您的代码并且无法重新创建它..

如果您仍然遇到问题,也许您可​​以分叉 StackBlitz 并尝试在那里重现问题?

【讨论】:

【参考方案2】:

我会放弃 setInterval 并仅在必要时使用 setTimeout。

import  Injectable, OnInit  from "@angular/core";
import  Observable  from "rxjs/Rx";
import  Subject  from "rxjs/Subject";

let timer = null

@Injectable()
export class TimerService 
    private timerItems: TimerItem[] = [];
    private dueTimeReachedSubject: Subject<string> = new Subject<string>();
    public dueTimeReached: Observable<string> = this.dueTimeReachedSubject.asObservable();

    constructor() 
        this.checkItems();
    

    private checkItems() 
        // clear the timeout
        clearTimeout(timer)
        let now = Date.now();
        let removeKeys: string[] = [];
        this.timerItems.filter(t => t.dueTime <= now).forEach(t => 
            this.dueTimeReachedSubject.next(t.key);
            removeKeys.push(t.key);
        );
        this.timerItems = this.timerItems.filter(t => removeKeys.indexOf(t.key) < 0);
        // only use the timer if there are timerItems
        if(this.timerItems.length) 
            timer = setTimeout(() => this.checkItems(), 1000)
        
    

    public add(key: string, delayInSeconds: number) 
        let dueTime = Date.now() + delayInSeconds * 1000;
        let timerItem = this.timerItems.find(t => t.key === key);
        if(timerItem) 
            timerItem.dueTime = dueTime;
        
        else 
            this.timerItems.push(new TimerItem(key, dueTime));
        
        // check for items and engage the timer if necessary
        this.checkItems()
       

    public remove(key: string) 
        this.timerItems = this.timerItems.filter(t => t.key !== key);
    



class TimerItem 
    constructor(public key: string, public dueTime: number)  

【讨论】:

【参考方案3】:

据我了解,您希望 css 更改在一定间隔后消失。

    具有正常的 css 类,并更新单元格和行的状态。例如

.row-normal
//css-styling goes here

.row-updates
//css- here

---接近1--- 在更新监听器上设置 在 html 中或在 javascript 代码中使用 document.getElementByTagName 为您的视图定义 onChange 属性。

function onUpdate()
  let v = this;
  v.className = "row-updated";
  setTimeOut(function()v.className = "row-normal", interval_in_millis)

希望这会有所帮助。

【讨论】:

【参考方案4】:

一种解决方案是使用Angular's animations。

例如,添加到列表中的元素是:enter transition 的情况(这是void =&gt; * 转换的别名)。来自链接的文档:

HTML:

<div @myInsertRemoveTrigger *ngIf="isShown" class="insert-remove-container">
  <p>The box is inserted</p>
</div>

TS:

trigger('myInsertRemoveTrigger', [
  transition(':enter', [
    style( opacity: 0 ),
    animate('5s', style( opacity: 1 )),
  ]),
  transition(':leave', [
    animate('5s', style( opacity: 0 ))
  ])
]),

当带有myInsertRemoveTrigger 触发器的元素“从虚空中出来”(:enter) 时,它最初会获得样式opacity: 0,然后在5 秒内转换为opacity: 1

animate() 的第一个参数(例如'5s')也可以用来定义delay and easing。您提到“新项目”样式可以应用 4 秒,因此转换规则可以是例如:

  transition(':enter', [
    style( backgroundColor: 'green' ),
    animate('1s 4s ease-in', style( backgroundColor: 'white' )),
  ])

这将立即在新项目上应用绿色背景,然后在 4 秒后为它们提供白色背景颜色(过渡长度为 1 秒)。

【讨论】:

【参考方案5】:

在这样的车站可以做很多事情。

第一个是将 ChangeDetectionStrategy 更改为 ChangeDetectionStrategy.onPush ,然后仅在需要时激活检测,在您的情况下,在 checkItems 末尾添加和删除.这将大大减少脚本编写,因为 Angular 仅在被询问时才需要评估您的 html

您可以做的第二件事是检查您的寺庙是否在 *ngIf 中有函数调用 或 *ngFor,如果你这样做,angular 将无法缓存此函数返回的值,并且必须为每次检查处理它们

您应该考虑的第三件事是优化 checkItems,checkItems 运行时间为 O(n^2) 并执行许多不必要的循环。你可以减少它

checkItems() 
  this.timerItems = this.timerItems.filter(t => 
      if(t.dueTime <= now)
          this.dueTimeReachedSubject.next(t.key);
          return false;
      
      return true;
  );        

对于小型阵列,它不会有太大帮助,但越大,此更改将开始生效..

我猜想可以做更多的事情,但这 3 件事可以帮助我解决类似的性能问题

【讨论】:

以上是关于在 Angular 2 中应用频繁 CSS 更改的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Chrome 开发人员工具中将 CSS 样式更改保存到 ANGULAR 2.0 组件表单?

当值频繁更改时 CSS3 转换过渡口吃(与性能无关)

如何在 Angular 2 中动态更改 :host 中的 CSS?

使用不同的 CSS 样式表动态更改 Angular 组件的样式

Angular Material ng-tns 类更改了我表中项目的边距,可以使用 CSS 来更改边距,但随后应用了 ng-tns 的另一个变体?

如何使用 Chrome 工作区保存使用 Angular2 + webpack 在本地更改的 CSS?