专注于与 Angular 中的单击其他元素冲突的元素



我在element1focusout()事件,在element2click()事件,当element1因为element2上执行了点击事件而失焦时,只触发了focusout,没有。

这在 jQuery[1] 上工作正常,但在 Angular 中则不行。

我通过添加一个也适用于角度的window.setTimeout()找到了解决方法。不幸的是,我不能这样做。

另一个建议非常值得赞赏。

请找到带有 setTimeout 的代码:



$('#txtName').on("focusout",function(event) {
//Alternate solution required for `setTimeout`.
window.setTimeout( function() {alert('focus gone');},1000); }); 
$('#button1').on('click',function(){
alert('button click');
}); 
}

这是点击事件的问题。

单击事件由 2 个事件组成:鼠标向下和鼠标向上。

您案例中的事件顺序是这样的

1( 鼠标向下 2(聚焦 3( 鼠标向上

其中 1 和 3 创建点击事件。

当页面上显示其他元素(例如错误消息(并且应该发生单击的按钮从其原始 x 和 y 坐标移动时,可能会发生这种情况。因此,鼠标向上发生在其他地方,而不是鼠标按下发生的地方。

所以基本上我认为你的鼠标向下有效,focusout有效,但鼠标向上不起作用。

此问题的解决方案是使用鼠标按下事件而不是单击。因此,您的单击不应等待鼠标向上工作。

例:

<input type="text" (focusout)="someMethod()">
<button (mousedown)="someMethod()">Click Me!</button> //Changed (click) to (mousedown)

希望这有帮助。

我开发了一个不需要"setTimeout"的解决方案,并且不会强迫您使用"mouseup"事件而不是单击事件。这是用户友好的,因为单击事件"让用户有机会通过在释放鼠标之前将鼠标移离按钮来中止单击"。(辣味评论(

问题

正如维诺德在回答中所说,这是事件年表中的一个问题:

鼠标向下
  1. :按钮注册鼠标向下事件。
  2. 焦点:由于鼠标按下按钮而注册。在此独特方案中,焦点处理程序使按钮移动到另一个位置。
  3. 鼠标向上
  4. :由于按钮的位置已更改,它不会注册鼠标向上事件。因此,单击事件也不会注册,因为这需要鼠标按下,然后在同一元素上鼠标向上。

溶液

我的解决方案是一个指令,它公开了在鼠标按下和鼠标向上事件之后发生的延迟聚焦事件。因此,在 (延迟( focusout 事件的事件处理程序更改按钮的位置之前,将注册 click 事件。

这是由行为主体存储鼠标当前是否关闭来完成的。当鼠标按下时注册焦点事件时,我们不会立即触发延迟聚焦事件(否则我们最终会遇到同样的老问题(。相反,我们等待鼠标再次抬起,然后发出延迟聚焦事件。这将导致以下顺序:

  1. 鼠标向下
  2. 聚焦(忽略此事件(
  3. 鼠标向上
  4. 延迟对焦 + 单击

代码解决方案

该指令的用法如下:

<input appDelayedFocusout (delayedFocusout)="yourLayoutChangingHandler()">

我的指令实现利用 until-destroy 库来防止内存泄漏永无止境的订阅,但可以随意修改。

import {Directive, EventEmitter, HostListener, OnInit, Output} from '@angular/core';
import {BehaviorSubject, fromEvent} from 'rxjs';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {filter, map, take} from 'rxjs/operators';
/**
* This directive exposes a special variant of the 'focusout' event. The regular 'focusout' event has a quirk:
* Imagine the user clicks on some button on the page. This triggers the following events in the following order:
* mousedown, focusout, mouseup. But the focusout event handler might change the layout of the website so that
* the button on which the mousedown event occurred moves around. This leads to no mouseup event registered on
* that button. Therefore a click event is also not registered because a click event consists of
* a mousedown AND a mouseup event on that button. In order to fix that problem, this directive exposes a delayed focusout
* event that is triggered AFTER the mousedown and mouseup events. When the delayed focusout event handler changes
* positions of buttons, click events are still registered as you would expect.
*/
@UntilDestroy()
@Directive({
selector: '[appDelayedFocusout]'
})
export class DelayedFocusoutDirective implements OnInit {
@Output() delayedFocusout = new EventEmitter<boolean>();
isMouseDownSubject = new BehaviorSubject(false);
ngOnInit(): void {
fromEvent(document.body, 'mousedown').pipe(untilDestroyed(this))
.subscribe(() => this.isMouseDownSubject.next(true));
fromEvent(document.body, 'mouseup').pipe(untilDestroyed(this))
.subscribe(() => this.isMouseDownSubject.next(false));
}
@HostListener('focusout') onFocusout() {
// If the mouse is currently down, we subscribe to the the event of
// 'mouse being released' to then trigger the delayed focusout.
// If the mouse is currently not down, we can trigger the delayed focusout immediately.
if (this.isMouseDown()) {
this.mouseRelease().subscribe(() => {
// This code is executed once the mouse has been released.
this.delayedFocusout.emit(true);
});
} else {
this.delayedFocusout.emit(true);
}
}
/**
* Emits the value true once the mouse has been released and then completes.
* Also completes when the mouse is not released but this directive is being destroyed.
*/
mouseRelease() {
return this.isMouseDownSubject.pipe(
untilDestroyed(this),
// Just negate isDown to get the value isReleased.
map(isDown => !isDown),
// Only proceed when the the mouse is released.
filter(isReleased => isReleased),
take(1)
);
}
isMouseDown() {
return this.isMouseDownSubject.value;
}
}

我已经实现了@simon-lammes在此答案中共享的DelayedFocusout指令,而无需使用直到销毁库。我使用共享重播来销毁订阅。

import { Directive, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
import { BehaviorSubject, fromEvent, ReplaySubject } from 'rxjs';
import { filter, map, take, takeUntil } from 'rxjs/operators';
/**
* This directive exposes a special variant of the 'focusout' event. The regular 'focusout' event has a quirk:
* Imagine the user clicks on some button on the page. This triggers the following events in the following order:
* mousedown, focusout, mouseup. But the focusout event handler might change the layout of the website so that
* the button on which the mousedown event occurred moves around. This leads to no mouseup event registered on
* that button. Therefore a click event is also not registered because a click event consists of
* a mousedown AND a mouseup event on that button. In order to fix that problem, this directive exposes a delayed focusout
* event that is triggered AFTER the mousedown and mouseup events. When the delayed focusout event handler changes
* positions of buttons, click events are still registered as you would expect.
*/
@Directive({
selector: '[appDelayedFocusout]'
})
export class DelayedFocusoutDirective implements OnInit, OnDestroy {
@Output() delayedFocusout = new EventEmitter<boolean>();
isMouseDownSubject = new BehaviorSubject(false);
private destroyed$: ReplaySubject<boolean> = new ReplaySubject(1);
ngOnInit(): void {
fromEvent(document.body, 'mousedown').pipe(takeUntil(this.destroyed$))
.subscribe(() => this.isMouseDownSubject.next(true));
fromEvent(document.body, 'mouseup').pipe(takeUntil(this.destroyed$))
.subscribe(() => this.isMouseDownSubject.next(false));
}
@HostListener('focusout') onFocusout() {
// If the mouse is currently down, we subscribe to the the event of
// 'mouse being released' to then trigger the delayed focusout.
// If the mouse is currently not down, we can trigger the delayed focusout immediately.
if (this.isMouseDown()) {
this.mouseRelease().subscribe(() => {
// This code is executed once the mouse has been released.
this.delayedFocusout.emit(true);
});
} else {
this.delayedFocusout.emit(true);
}
}
/**
* Emits the value true once the mouse has been released and then completes.
* Also completes when the mouse is not released but this directive is being destroyed.
*/
mouseRelease() {
return this.isMouseDownSubject.pipe(
// Just negate isDown to get the value isReleased.
takeUntil(this.destroyed$),
map(isDown => !isDown),
// Only proceed when the the mouse is released.
filter(isReleased => isReleased),
take(1)
);
}
isMouseDown() {
return this.isMouseDownSubject.value;
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
}

对我来说,鼠标单击事件将焦点设置在不同的元素上,所以当我尝试捕获focusout事件时,我没有捕获它。

我的方案是一个文本元素,单击文本元素后,该元素就会被替换为输入元素。

解决方案是在使用 setTimeout 单击文本元素后专注于编辑元素

(setTimeout 是必需的,以便在尝试调用 element.focus(( 之前让渲染过程完成(

最新更新