【发布时间】:2018-09-18 04:10:44
【问题描述】:
我已经实现了一个自定义的 Click Outside 指令,该指令用于关闭模式对话框、通知、弹出窗口等(为简单起见,我们称它们为“弹出窗口”)。但是,我正在努力为通过鼠标单击(即按钮单击)打开的弹出窗口和通过其他操作(即鼠标悬停)打开的弹出窗口实现通用解决方案。
这是我面临的问题的Plunker example:鼠标悬停事件产生的弹出窗口需要单击两次才能关闭,而“单击我”按钮产生的相同弹出窗口只需要单击即可关闭.
这是指令的代码:
@Directive({
selector: '[clickOutside]'
})
export class ClickOutsideDirective {
@Output('clickOutside') clickOutsideEvent = new EventEmitter();
private globalClick: Subscription;
constructor(private elRef: ElementRef) { }
ngOnInit() {
this.globalClick = Observable
.fromEvent(document, 'click')
.skip(1)
.subscribe((event: MouseEvent) => {
this.clicked(event);
});
}
ngOnDestroy() {
this.globalClick.unsubscribe();
}
private clicked(event: MouseEvent) {
let clickedInside = this.elRef.nativeElement.contains(event.target);
if (!clickedInside) {
this.clickOutsideEvent.emit();
}
}
}
在 ngOnInit() 内部,我初始化了一个 observable,它侦听文档上的点击并将事件传递给 clicked() 函数,该函数将检查点击是否在外部进行,如果是则引发事件。 observable 使用跳过运算符来过滤第一次点击事件。这是一种解决方法,因为如果您单击“单击我”按钮,则该原始单击事件是我要跳过的事件,因为它在指令中对我没有用。如果我不跳过它,弹出窗口会自行关闭,因为它会认为外面有点击。但是,此解决方法有一个副作用,即现在需要单击两次才能关闭以其他方式触发的弹出窗口,而不是通过单击(Plunker 中的鼠标悬停示例演示了此问题)。
我正在努力想出一个优雅而通用的解决方案来解决这个问题。我可以想到多种“hacky”解决方案:
黑客解决方案 1: 我可以在“单击我”按钮处停止事件传播,这意味着 ClickOutsideDirective 将不会接收初始单击事件,并且我可以从可观察到的文档中删除 .skip 运算符。理论上应该可行(没有尝试过),但我不喜欢的是指令的使用者现在需要知道他们需要停止事件传播,如果他们不这样做,指令将无法按设计工作.
黑客解决方案 2: 将布尔标志传递给可用于确定是否需要跳过第一次点击的指令。很容易做到,但是 API 的用户现在需要知道他们需要设置这个标志。如果他们忘记了,该指令将无法按预期工作。
黑客解决方案 3: 跳过指令初始化后短时间内(比如说 100 毫秒)发生的第一个事件。这也很容易做到,这个解决方案的优点是 API 的用户不需要向指令提供任何额外的信息(或停止任何事件传播)并且指令将“正常工作”。这里的问题是要找出在所有浏览器和硬件上都可以使用的延迟。延迟必须不小于指令实例化后事件到达所需的时间,但如果用户决定在弹出窗口显示后立即点击离开,则延迟不能大到跳过合法用户点击。
谁能想出一个更好、更简单、更优雅的解决方案来解决这个问题?
【问题讨论】:
标签: javascript angular typescript