如何使用TypeScript:Angular 2或4处理同一HTML DOM元素上的“单击”和“双击”?

59

我的问题是,当我执行“双击”时,两个事件所使用的方法都会被触发。

例如,我需要在特定事件触发时执行特定功能。

<a (click)="method1()" (dblclick)="method2()">

当我执行 "双击" 时,method1()method2() 都会被触发。


2
请参考此plunker - Aravind
@Aravind:你好,Aravind...感谢你提供的plunkr演示,解决了我的问题。我只需要那个解决方案...我已经从PranavKAndro下面得到了答案,它运行良好。 - Durgaachandrakala.E
@Durgaachandrakala 很高兴能帮助你。 - Aravind
13个回答

44

你可以使用超时和布尔标志来解决这个问题。

DOM需要几毫秒才能识别双击。

但是它确定识别了双击,但第一次单击也被识别了。

因此逻辑如下。

isSingleClick: Boolean = true;     

method1CallForClick(){
   this.isSingleClick = true;
        setTimeout(()=>{
            if(this.isSingleClick){
                 doTheStuffHere();
            }
         },250)
}
method2CallForDblClick(){
         this.isSingleClick = false;
         doTheStuffDblClickHere();
}

在元素的点击事件中调用方法一,在另一个元素的点击事件中调用方法二。


7
这不会将“isSingle”重置为true。此外,因为双击也是两次单击,所以会触发两次单击事件。如果在某处重置了标志,则必须忽略第二个单击事件。 - Benny Bottema

31

您可以直接使用纯JavaScript回调dblclick,或者像在Angular 2中使用的一样:(dblclick)="doubleClickFunction()"

如果在同一个元素上同时绑定了(click)和(dblclick),您应该在单击事件回调函数中添加超时以避免在用户双击时被调用。

尝试像这样:

.html:

<button (click)="singleClick()" (dblclick)="doubleClick()">click</button>

.js:

singleClick(): void{
    this.timer = 0;
    this.preventSimpleClick = false;
    let delay = 200;

    this.timer = setTimeout(() => {
      if(!this.preventSimpleClick){
        ...
      }
    }, delay);

  }

  doubleClick(): void{
    this.preventSimpleClick = true;
    clearTimeout(this.timer);
    ...
  }

这可以在Angular/Typescript本身中完成,而不是依靠JavaScript帮助。 - PranavKAndro
1
这些函数是组件的一部分。 - Gili Yaniv

11
clickCount = 0;
click() {
    this.clickCount++;
    setTimeout(() => {
        if (this.clickCount === 1) {
             // single
        } else if (this.clickCount === 2) {
            // double
        }
        this.clickCount = 0;
    }, 250)
}

2
你好,欢迎来到stackoverflow,并感谢您的回答。虽然这段代码可能回答了问题,但您是否可以考虑添加一些解释,说明您解决的问题以及如何解决它?这将有助于未来的读者更好地理解您的答案并从中学习。 - Plutian

9

这更加清晰明了

我找到了一个使用rxjs的解决方案,请尝试一下

我的模板有

<div #divElement></div>

我在我的组件.ts文件中有以下内容:

  @ViewChild('divElement') el: ElementRef;

  ngAfterViewInit(): void {
    this.handleClickAndDoubleClick();
  }

  handleClickAndDoubleClick(){
    const el = this.el.nativeElement;
    const clickEvent = fromEvent<MouseEvent>(el, 'click');
    const dblClickEvent = fromEvent<MouseEvent>(el, 'dblclick');
    const eventsMerged = merge(clickEvent, dblClickEvent).pipe(debounceTime(300));
    eventsMerged.subscribe(
      (event) => {
        if(event.type === 'click'){
          //click
        }

        //dblclick
      }
    );

1
这应该是被接受的答案。谢谢Armando。 - Peter
这看起来很干净,但问题在于订阅只有在'Single'和'Double'两个点击事件都发生后才会触发。我们还可以使用哪些rxjs操作符来解决这个问题? - Harshad Vekariya

4
我没有找到在Angular(或Javascript)中原生实现此功能的方法,但以下是一种通用方法,可使所有单击处理程序正常工作。
虽然概念上与其他答案类似,但其他答案不能很好地处理重复点击,并且不在正确的时间维护(和重置!)它们的标志。
这里的想法是为共享上下文(同一元素)维护一个标志,以决定单击处理程序和双击处理程序之间的区别:
  1. 单击处理程序等待短时间以排除双击
  2. 双击处理程序在该等待期间内阻止单击处理程序
  3. 因为双击意味着两个单击,所以在双击后再次等待短时间,然后切换标志以确保第二个单击也被阻止
以下是如何在示例中使用它:
    <a (click)="method1()" (dblclick)="method2()"></a>

在你的组件内部:

    const clickContext = new ClickContext();

    class YourComponent {
       method1 = clickContext.clickHandler(() => { /* your method1  */ });
       method2 = clickContext.dblClickHandler(() => { /* your method2 */ });
    }

对于每个需要同时具有单击和双击处理程序的上下文(元素),您需要创建一个单独的上下文。幸运的是,这是一个罕见的要求。

现在,通过使用我将在下面介绍的神奇实用类,可以正确处理单击和双击。

这是监视标志并执行处理程序的类:

    export class ClickContext {
        private isSingleClick = true;

        clickHandler(handler: ClickHandler): ClickHandler {
            return function() {
                const args = arguments;
                this.isSingleClick = true;
                setTimeout(() => {
                    this.isSingleClick && handler.apply(undefined, args);
                }, 250);
            }
        }

        dblClickHandler(handler: ClickHandler): ClickHandler {
            return function() {
                const args = arguments;
                this.isSingleClick = false;
                setTimeout(() => {
                    this.isSingleClick = true;
                }, 250);
                handler.apply(undefined, args);
            }
        }
    }

    type ClickHandler = (...any) => void;

注意,需要确保原始参数传递到目标事件处理程序 method1 和 method2 中。

2

我刚刚为此创建了一个简单的解决方案。以下是模板:

最初的回答:

<button (click)="myFunction()">Click me!</button>

最初的回答:
以下是脚本代码:

而这里是脚本:

justClicked = false;
doubleClicked = false;

myFunction() {
  if (this.justClicked === true) {
    this.doubleClicked = true;
    this.doubleClick();
  } else {
    this.justClicked = true;
    setTimeout(() => {
      this.justClicked = false;
      if (this.doubleClicked === false) {
        this.singleClick();
      }
      this.doubleClicked = false;
    }, 500);
  }
}

singleClick() {
  console.log('single');
}

doubleClick() {
  console.log('double');
}

1
It needs simple work-round to block single click then do double click.  Check this example 

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>{{title}}</h2>
        <button (click)="singleClick($event)" (dblclick)="doubleClick( $event)"> Click </button>
    </div>
  `,
})
export class App {
  title:string;
  preventSingleClick = false;
  timer: any;
  delay: Number;
  constructor() {
    this.title = `Angular (click) and (dblclick)`
  
  }
  
  singleClick(event) {
    this.preventSingleClick = false;
     const delay = 200;
      this.timer = setTimeout(() => {
        if (!this.preventSingleClick) {
           alert('Single Click Event');
        }
      }, delay);
  }
  
  doubleClick () {
    this.preventSingleClick = true;
    clearTimeout(this.timer);
    alert('Double Click Event')
  }
}

[plunker][1]

  [1]: http://plnkr.co/edit/pbsB0zYQCEY4xrPKngrF?p=preview

1

更新(Angular 13):我在Angular中创建了一个指令来处理单击和双击事件,希望这有所帮助,这只是一个解决方法。它的工作原理在代码注释中有说明。(在评论部分开放改进)

@Directive({
  // eslint-disable-next-line @angular-eslint/directive-selector
  selector: '[doubleTap]',
})
export class DBLTapGestureDirective implements OnInit {
  el: HTMLElement;
  private lastOnStart: number = 0;
  //double click threshold fires a double click event if there are 2 click within 230ms
  private DOUBLE_CLICK_THRESHOLD: number = 230;
  //single click threshold fires a single click after 250ms
  // during this time is listening for another click to si if it is a double click
  private SINGLE_CLICK_THRESHOLD: number = 250;
  private isSingleClick: boolean = true;

  //Emit Single and double click event
  @Output() doubleTap = new EventEmitter<void>();
  @Output() singleTap = new EventEmitter<void>();

  constructor(el: ElementRef, private gestureCtrl: GestureController) {
    this.el = el.nativeElement;
  }

  ngOnInit(): void {
    //Create gesture
    const gesture = this.gestureCtrl.create({
      el: this.el,
      threshold: 0,
      gestureName: 'my-gesture',
      onStart: () => {
        //Start Listening
        this.onStart();
      },
    });
    gesture.enable();
  }

  private onStart(): void {
    //get current time
    let now = Date.now();
    //check if time is within the double click threshold if not go to single click
    if (Math.abs(now - this.lastOnStart) <= this.DOUBLE_CLICK_THRESHOLD) {
      //emits double click event
      this.doubleTap.emit();
      //set single click false in order to not fire the single click event
      this.isSingleClick = false;
      //reset the last click timer
      this.lastOnStart = 0;
      //after the double click finished reset the single click to true to start listening again          for single click
      setTimeout(() => {
        this.isSingleClick = true;
      }, this.DOUBLE_CLICK_THRESHOLD);
    } else {
      //set a timeout before sending single click event to wait if there is a double click
      setTimeout(() => {
        if (this.isSingleClick) {
          //fire single click event
          this.singleTap.emit();
          //set single click back to true
          this.isSingleClick = true;
        }
      }, this.SINGLE_CLICK_THRESHOLD);
      //set last click time to now to make the compare in the next click
      this.lastOnStart = now;
    }
  }
}
 <div
      (doubleTap)="like()"
      (singleTap)="openImage()"
      class="message-container">Image</div>


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community
@dardan 不要理会机器人说的话,你的回答很棒 :) 谢谢! - user998548

0

https://dev59.com/plYM5IYBdhLWcg3w1S3d#60877770中的Armando类似的方法 - 但在我的情况下,我并不想使用ViewChild

这种方法使用ReplaySubject。本质上,我为单击事件定义了一个接口,并为所有接收到的单击事件和双击事件定义了重放主题。然后,我将真正的单击(没有双击的单击)定义为两者合并的结果,使用防抖动和过滤器仅发出单击:

interface ClickRowEvent<T extends any = any> {
  clickType: "single" | "double";
  data: T;
}

@Component({
  selector: "foo",
  template: `
    <div *ngFor let thing of things>
        <div 
           (click)="handleSingleClick(thing) 
           (dblclick)="handleDoubleClick(thing)"
        >
          {{thing}}
        </div>
    </div>
  `
})
export class MyComponent {

  things = ["foo", "bar", "baz"];

  private _singleClickEvents: ReplaySubject<ClickRowEvent>;
  private singleClickEvents$: Observable<ClickRowEvent>;
  private _doubleClickEvents: ReplaySubject<ClickRowEvent>;
  private doubleClickEvents$: ReplaySubject<ClickRowEvent>;

  constructor() {
    this._singleClickEvents = new ReplaySubject<ClickRowEvent>();
    this._doubleClickEvents = new ReplaySubject<ClickRowEvent>();
    
    this.doubleClickEvents$ = this._doubleClickEvents.asObservable();

    const allClicks$ = this._singleClickEvents.asObservable();

    this.singleClickEvents$ = merge(allClicks$, this.doubleClickEvents$).pipe(
      debounceTime(250),
      filter((e) => e.clickType === "single")
    );
  }

  handleSingleClick(data: T): void {
    this._singleClickEvents.next({ clickType: "single", data });
  }

  handleDoubleClick(data: T): void {
    this._doubleClickEvents.next({ clickType: "double", data });
  }
}

现在,您可以订阅所有singleClickEvents$doubleClickEvents$,无需额外的状态管理。当然,如果您不想要额外的clickType属性,您也可以将其映射回数据/事件。


0

我刚刚偶然发现这个问题,但是我不喜欢使用setTimeout的答案,因为单击的反应时间也会被延迟。我想要立即响应单击事件,但是防止双击时触发两次单击事件。

我的情况比较特殊,我希望同时处理单击和双击事件。所以这可能并没有真正回答问题,因为我同时处理单击和双击事件,唯一的区别是当双击事件触发时,我不再执行第二次单击事件。

我的解决方案很简单,我只需要测量两次点击之间的时间,如果超过500毫秒(默认的双击延迟时间),那么我将执行单击代码。否则,双击事件将触发,第二次单击事件将不会被执行。

clicked = 0;

clickHandler() {
  if (Date.now() - this.clicked > 500) { // change threshold as you need
    this.clicked = Date.now();
    // click logic here
  }
}

dblClickHandler() {
  // dblclick logic here, no extra code needed
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接