Angular 2 拖放指令极慢
Angular 2 drag and drop directive extremely slow
我正在尝试实现自定义拖放指令。它有效,但速度非常慢,我认为这种缓慢可以追溯到 Angular 2,因为我以前从未遇到过这种缓慢。只有当我将事件侦听器附加到 dragover
或 drag
事件(即频繁发送的事件)时才会出现缓慢,即使我只做 return false
在他们里面。
这是我的指令代码:
import {Directive, ElementRef, Inject, Injectable} from 'angular2/core';
declare var jQuery: any;
declare var document: any;
@Directive({
selector: '.my-log',
host: {
'(dragstart)': 'onDragStart($event)',
'(dragover)': 'onDragOver($event)',
'(dragleave)': 'onDragLeave($event)',
'(dragenter)': 'onDragEnter($event)',
'(drop)': 'onDrop($event)',
}
})
@Injectable()
export class DraggableDirective {
refcount = 0;
jel;
constructor( @Inject(ElementRef) private el: ElementRef) {
el.nativeElement.setAttribute('draggable', 'true');
this.jel = jQuery(el.nativeElement);
}
onDragStart(ev) {
ev.dataTransfer.setData('Text', ev.target.id);
}
onDragOver(ev) {
return false;
}
onDragEnter(ev) {
if (this.refcount === 0) {
this.jel.addClass('my-dragging-over');
}
this.refcount++;
}
onDragLeave(ev) {
this.refcount--;
if (this.refcount === 0) {
this.jel.removeClass('my-dragging-over');
}
}
onDrop(ev) {
this.jel.removeClass('my-dragging-over');
this.refcount = 0;
}
}
这里是相关样式sheet摘录:
.my-log.my-dragging-over {
background-color: yellow;
}
如您所见,我所做的只是用黄色突出显示被拖过的元素。当我不处理 dragover
事件时它工作得很快,但是我 必须 处理它以支持删除。当我处理 dragover
事件时,一切都变慢到无法忍受的程度!!
编辑 我正在使用 angular beta 2.0.0-beta.8
编辑 #2 我尝试使用 chrome 的分析器分析代码,结果如下:
看标记线,奇怪的可疑...
EDIT #3 发现问题:确实是由于 Angular 2 的变化检测。在我的案例中,拖放操作是在一个包含大量绑定和指令的非常密集的页面上完成的。当我注释掉给定列表以外的所有内容时,它再次快速运行......现在我需要你的帮助来找到解决这个问题的方法!
回答我自己的问题(问题已解决)。
缓慢问题是由于我的标记中的数据绑定效率低下,导致 Angular 浪费大量时间在我的视图模型上调用函数。我有很多这样的绑定:
*ngFor="#a of someFunc()"
这导致 Angular 不确定数据是否已更改,函数 someFunc
在 onDragOver
的每个 运行 之后被一次又一次地调用(这大约是每 350 毫秒一次),即使在拖放过程中数据没有改变。我更改了这些绑定以引用我的 class 中的简单属性,并将填充它们的代码移到了它应该在的位置。一切又开始飞速发展了!
刚遇到同样的问题。即使使用高效的 ngFor
代码,如果您有大量可拖动项目,拖放仍然会非常慢。
我的诀窍是用 ngZone
使 Angular 之外的所有拖放事件侦听器 运行,然后使 运行 回到 [=25] =] 掉落时。这使得 Angular 避免检查您移动可拖动项目的每个像素的检测。
注入:
import { Directive, ElementRef, NgZone } from '@angular/core';
constructor(private el: ElementRef, private ngZone: NgZone) {}
正在初始化:
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
el.addEventListener('dragenter', (e) => {
// do stuff with e or el
});
...
掉落时:
el.addEventListener('drop', (e) => {
this.ngZone.run(() => {
console.log("dropped");
})
})
我遇到了类似的问题,当我在 *ngFor
中放置多个拖动区域时,我的拖放也变得非常慢。
我通过将更改检测策略更改为子组件的 OnPush
解决了这个问题。
然后在每次拖动项目时,执行 markForCheck()
。
constructor(private changeDetectorRef: ChangeDetectorRef) {}
// Callback function
public onDrag() {
this.changeDetectorRef.markForCheck();
}
对我来说,问题是即使在生产中也打开了开发模式。当我用 ng build --evn-prod
编译它时,拖放突然变得非常快。
感谢大家的讨论。
最终得到一个非常有效的简单解决方案:
constructor(private cd: ChangeDetectorRef) {
}
drag(event: DragEvent): void {
this.cd.detach();
// Begin the job (use event.dataTransfer)
}
allowDrop(event: DragEvent): void {
event.preventDefault();
}
drop(event: DragEvent): void {
event.preventDefault();
this.cd.reattach();
// Do the job
}
我最近遇到了类似的问题。它在 Angular 6 环境中使用反应形式。这就是我针对我的情况解决的方法:
基本上简单地说,我在拖动发生时关闭了该组件的变化检测。
- 导入 ChangeDetectorRef:
import { ChangeDetectorRef } from '@angular/core';
- 将其注入到构造函数中:
constructor(private chngDetRef: ChangeDetectorRef) { //...
- 在 dragStart 上分离它:
private onDragStart(event, dragSource, dragIndex) {
// ...
this.chngDetRef.detach();
// ...
- 在拖放和拖动结束时重新附加它:
private onDrop(event, dragSource, dragIndex) {
// ...
this.chngDetRef.reattach();
// ...
private onDragEnd(event, dragIndex) {
// ...
this.chngDetRef.reattach();
// ...
如果您有很多父组件或分层组件,您可能还需要对它们的变化检测做一些事情才能看到实质性的改进。
我在 angular 项目中遇到了拖放问题 - detectChanges(reattach(), deTached ..), outSide Angular (ngZone) 无法解决这个问题。
现在我通过使用 jquery 解决了这个问题,我在构造函数中为我的 div 内容绑定了事件。
constructor() {
$(document).delegate('#jsDragZone', 'dragenter', function (e) {
console.log('here your logic')
});
}
通过这种方式,您也可以实现其他事件(dragleave、drop、'dragover')。它对我来说非常好用而且速度很快。
这是对旧 post 的跟进,但拖放“仍然是一个问题。我的特殊问题涉及一个页面,上面有超过 130 个组件,拖放非常糟糕。我试过了在此和其他 post 中提供的各种建议,只有极小的改进。
最后,我决定不使用 ngZone
解决方案,而是尝试将 (dragOver)="function()"
更改为原生 ondragover="event.preventDefault()"
。我让所有其他事件处理程序(即 dragStart
、dragEnter
、dragLeave
、dragDrop
、dragEnd
)根据需要通过 Angular。我的拖放响应从几秒缩短到几毫秒。
如果有人能提供替代的 dragOver
绕过更改检测的事件处理程序,那就太好了。
我正在尝试实现自定义拖放指令。它有效,但速度非常慢,我认为这种缓慢可以追溯到 Angular 2,因为我以前从未遇到过这种缓慢。只有当我将事件侦听器附加到 dragover
或 drag
事件(即频繁发送的事件)时才会出现缓慢,即使我只做 return false
在他们里面。
这是我的指令代码:
import {Directive, ElementRef, Inject, Injectable} from 'angular2/core';
declare var jQuery: any;
declare var document: any;
@Directive({
selector: '.my-log',
host: {
'(dragstart)': 'onDragStart($event)',
'(dragover)': 'onDragOver($event)',
'(dragleave)': 'onDragLeave($event)',
'(dragenter)': 'onDragEnter($event)',
'(drop)': 'onDrop($event)',
}
})
@Injectable()
export class DraggableDirective {
refcount = 0;
jel;
constructor( @Inject(ElementRef) private el: ElementRef) {
el.nativeElement.setAttribute('draggable', 'true');
this.jel = jQuery(el.nativeElement);
}
onDragStart(ev) {
ev.dataTransfer.setData('Text', ev.target.id);
}
onDragOver(ev) {
return false;
}
onDragEnter(ev) {
if (this.refcount === 0) {
this.jel.addClass('my-dragging-over');
}
this.refcount++;
}
onDragLeave(ev) {
this.refcount--;
if (this.refcount === 0) {
this.jel.removeClass('my-dragging-over');
}
}
onDrop(ev) {
this.jel.removeClass('my-dragging-over');
this.refcount = 0;
}
}
这里是相关样式sheet摘录:
.my-log.my-dragging-over {
background-color: yellow;
}
如您所见,我所做的只是用黄色突出显示被拖过的元素。当我不处理 dragover
事件时它工作得很快,但是我 必须 处理它以支持删除。当我处理 dragover
事件时,一切都变慢到无法忍受的程度!!
编辑 我正在使用 angular beta 2.0.0-beta.8
编辑 #2 我尝试使用 chrome 的分析器分析代码,结果如下:
看标记线,奇怪的可疑...
EDIT #3 发现问题:确实是由于 Angular 2 的变化检测。在我的案例中,拖放操作是在一个包含大量绑定和指令的非常密集的页面上完成的。当我注释掉给定列表以外的所有内容时,它再次快速运行......现在我需要你的帮助来找到解决这个问题的方法!
回答我自己的问题(问题已解决)。
缓慢问题是由于我的标记中的数据绑定效率低下,导致 Angular 浪费大量时间在我的视图模型上调用函数。我有很多这样的绑定:
*ngFor="#a of someFunc()"
这导致 Angular 不确定数据是否已更改,函数 someFunc
在 onDragOver
的每个 运行 之后被一次又一次地调用(这大约是每 350 毫秒一次),即使在拖放过程中数据没有改变。我更改了这些绑定以引用我的 class 中的简单属性,并将填充它们的代码移到了它应该在的位置。一切又开始飞速发展了!
刚遇到同样的问题。即使使用高效的 ngFor
代码,如果您有大量可拖动项目,拖放仍然会非常慢。
我的诀窍是用 ngZone
使 Angular 之外的所有拖放事件侦听器 运行,然后使 运行 回到 [=25] =] 掉落时。这使得 Angular 避免检查您移动可拖动项目的每个像素的检测。
注入:
import { Directive, ElementRef, NgZone } from '@angular/core';
constructor(private el: ElementRef, private ngZone: NgZone) {}
正在初始化:
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
el.addEventListener('dragenter', (e) => {
// do stuff with e or el
});
...
掉落时:
el.addEventListener('drop', (e) => {
this.ngZone.run(() => {
console.log("dropped");
})
})
我遇到了类似的问题,当我在 *ngFor
中放置多个拖动区域时,我的拖放也变得非常慢。
我通过将更改检测策略更改为子组件的 OnPush
解决了这个问题。
然后在每次拖动项目时,执行 markForCheck()
。
constructor(private changeDetectorRef: ChangeDetectorRef) {}
// Callback function
public onDrag() {
this.changeDetectorRef.markForCheck();
}
对我来说,问题是即使在生产中也打开了开发模式。当我用 ng build --evn-prod
编译它时,拖放突然变得非常快。
感谢大家的讨论。 最终得到一个非常有效的简单解决方案:
constructor(private cd: ChangeDetectorRef) {
}
drag(event: DragEvent): void {
this.cd.detach();
// Begin the job (use event.dataTransfer)
}
allowDrop(event: DragEvent): void {
event.preventDefault();
}
drop(event: DragEvent): void {
event.preventDefault();
this.cd.reattach();
// Do the job
}
我最近遇到了类似的问题。它在 Angular 6 环境中使用反应形式。这就是我针对我的情况解决的方法:
基本上简单地说,我在拖动发生时关闭了该组件的变化检测。
- 导入 ChangeDetectorRef:
import { ChangeDetectorRef } from '@angular/core';
- 将其注入到构造函数中:
constructor(private chngDetRef: ChangeDetectorRef) { //...
- 在 dragStart 上分离它:
private onDragStart(event, dragSource, dragIndex) {
// ...
this.chngDetRef.detach();
// ...
- 在拖放和拖动结束时重新附加它:
private onDrop(event, dragSource, dragIndex) {
// ...
this.chngDetRef.reattach();
// ...
private onDragEnd(event, dragIndex) {
// ...
this.chngDetRef.reattach();
// ...
如果您有很多父组件或分层组件,您可能还需要对它们的变化检测做一些事情才能看到实质性的改进。
我在 angular 项目中遇到了拖放问题 - detectChanges(reattach(), deTached ..), outSide Angular (ngZone) 无法解决这个问题。 现在我通过使用 jquery 解决了这个问题,我在构造函数中为我的 div 内容绑定了事件。
constructor() {
$(document).delegate('#jsDragZone', 'dragenter', function (e) {
console.log('here your logic')
});
}
通过这种方式,您也可以实现其他事件(dragleave、drop、'dragover')。它对我来说非常好用而且速度很快。
这是对旧 post 的跟进,但拖放“仍然是一个问题。我的特殊问题涉及一个页面,上面有超过 130 个组件,拖放非常糟糕。我试过了在此和其他 post 中提供的各种建议,只有极小的改进。
最后,我决定不使用 ngZone
解决方案,而是尝试将 (dragOver)="function()"
更改为原生 ondragover="event.preventDefault()"
。我让所有其他事件处理程序(即 dragStart
、dragEnter
、dragLeave
、dragDrop
、dragEnd
)根据需要通过 Angular。我的拖放响应从几秒缩短到几毫秒。
如果有人能提供替代的 dragOver
绕过更改检测的事件处理程序,那就太好了。