简体   繁体   English

使用RxJ进行单击和双击的单独操作

[英]Separate action for single click and double click with RxJs

I have a problem with RxJs. 我对RxJ有问题。

I need to log a message in console.log when I click once, and different message when I click twice on the button. 单击一次时,我需要在console.log中记录一条消息,而单击两次按钮时,则需要记录不同的消息。 The problems are: 问题是:

  • at the beginning if I click first time - nothing happens - wrong (I should see first message 'one click') 如果我第一次单击,一开始就没有任何反应-错误(我应该看到第一条消息“单击”)

  • then if I click on the button and then (after one second) I click again, for the second click I can see two messages - wrong (I should see only one message per action) 然后,如果我单击按钮,然后(一秒钟后),我再次单击,对于第二次单击,我会看到两条消息-错误(每次操作我只会看到一条消息)

  • when I click on the button, wait a second, click again, wait a second and click again, then for the last click I will see three messages in console.log - wrong (I should see only one message per action) 当我单击按钮时,请稍等片刻,再单击一次,再等一下再单击,然后单击最后一次,我将在console.log中看到三则消息-错误(每次操作我只会看到一条消息)
  • after that if I click twice (doubleclick) I will see five messages for double click, and one for one click - wrong (I should see only one message 'double click') 之后,如果我单击两次(双击),我将看到五则有关双击的消息,以及一则一击的消息-错误(我应该只看到一则消息“双击”)

all I want is: 我想要的是:

  • if I click once, I need to see only one message ('one click') 如果单击一次,则只需要看到一条消息(“一次单击”)
  • if I click twice (doubleclick), still I need to see only one message ('double click') 如果我单击两次(双击),仍然只需要查看一条消息(“双击”)
  • if I click more than twice - nothing happens 如果我点击两次以上-没有任何反应

any help? 有什么帮助吗?

check hear for examples 检查听到的例子

A very simple answer (not using any observables) is to use a setTimeout() and check on each click if the timeout is already set, if so, you know it's a second click within a given time window (double click) and if not, it's the first click. 一个非常简单的答案(不使用任何可观察对象)是使用setTimeout()并检查每次单击是否已设置超时,如果已设置,则知道这是给定时间范围内的第二次单击(双击),如果没有,这是第一次点击。 If the timeout expires, you know it was just a single click, like so: 如果超时到期,则您只需单击一下即可,如下所示:

Updated StackBlitz 更新了StackBlitz

// have a timer that will persist between function calls
private clickTimeout = null;
public test(event): void {
  // if timeout exists, we know it's a second click within the timeout duration
  // AKA double click
  if (this.clickTimeout) {
    // first, clear the timeout to stop it from completing
    clearTimeout(this.clickTimeout);
    // set to null to reset
    this.clickTimeout = null;
    // do whatever you want on double click
    console.log("double!");
  } else {
  // if timeout doesn't exist, we know it's first click
    this.clickTimeout = setTimeout(() => {
      // if timeout expires, we know second click was not handled within time window
      // so we can treat it as a single click
      // first, reset the timeout
      this.clickTimeout = null;
      // do whatever you want on single click
      console.log("one click");
    }, 400);
  }
}

EDIT 编辑

I missed the part about ignoring any more than 2 clicks. 我错过了忽略2次以上点击的部分。 It's not that much more work, but I broke it out a bit more to be able to reuse code, so it looks like a lot more. 它并不需要更多的工作,但是为了使代码可以重用,我做了更多的工作,所以看起来还很多。 Anyway, to ignore 3+ clicks, it would look like the following: 无论如何,要忽略3次以上的点击,它看起来将如下所示:

// count the clicks
private clicks = 0;
private clickTimeout = null;
public test(event): void {
  this.clicks++;
  if (this.clickTimeout) {
    // if is double click, set the timeout to handle double click
    if (this.clicks <= 2) {
      this.setClickTimeout(this.handleDoubleClick);
    } else {
    // otherwise, we are at 3+ clicks, use an empty callback to essentially do a "no op" when completed
      this.setClickTimeout(() => {});
    }
  } else {
    // if timeout doesn't exist, we know it's first click - treat as single click until further notice
    this.setClickTimeout(this.handleSingleClick);
  }
}
// sets the click timeout and takes a callback for what operations you want to complete when the
// click timeout completes
public setClickTimeout(cb) {
  // clear any existing timeout
  clearTimeout(this.clickTimeout);
  this.clickTimeout = setTimeout(() => {
    this.clickTimeout = null;
    this.clicks = 0;
    cb();
  }, 400);
}
public handleSingleClick() {
  console.log("one click");
}
public handleDoubleClick() {
  console.log("double!");
}

@Siddharth Ajmeras answer shows how to do this with events. @Siddharth Ajmeras的答案显示了如何通过事件进行此操作。 I was not aware a dblclick event existed. 我不知道存在dblclick事件。 The more you know. 你懂得越多。 If you are still interested on how to do this with rxjs, here is an example. 如果您仍然对如何使用rxjs进行操作感兴趣,请参考以下示例。

// How fast does the user has to click
// so that it counts as double click
const doubleClickDuration = 100;

// Create a stream out of the mouse click event.
const leftClick$ = fromEvent(window, 'click')
// We are only interested in left clicks, so we filter the result down
  .pipe(filter((event: any) => event.button === 0));

// We have two things to consider in order to detect single or
// or double clicks.

// 1. We debounce the event. The event will only be forwared 
// once enough time has passed to be sure we only have a single click
const debounce$ = leftClick$
  .pipe(debounceTime(doubleClickDuration));

// 2. We also want to abort once two clicks have come in.
const clickLimit$ = leftClick$
  .pipe(
    bufferCount(2),
  );


// Now we combine those two. The gate will emit once we have 
// either waited enough to be sure its a single click or
// two clicks have passed throug
const bufferGate$ = race(debounce$, clickLimit$)
  .pipe(
    // We are only interested in the first event. After that
    // we want to restart.
    first(),
    repeat(),
  );

// Now we can buffer the original click stream until our
// buffer gate triggers.
leftClick$
  .pipe(
    buffer(bufferGate$),
    // Here we map the buffered events into the length of the buffer
    // If the user clicked once, the buffer is 1. If he clicked twice it is 2
    map(clicks => clicks.length),
  ).subscribe(clicks => console.log('CLicks', clicks));

This is either what you want or very close to it: 这是您想要的或非常接近的:

import { Component, ViewChild, ElementRef } from '@angular/core';
import { fromEvent } from 'rxjs';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})

export class AppComponent  {

  @ViewChild('mybutton') button: ElementRef;

  ngAfterViewInit() {
    fromEvent(this.button.nativeElement, 'dblclick')
    .subscribe(e => console.log('double click'));

    fromEvent(this.button.nativeElement, 'click')
    .subscribe((e: MouseEvent) => {
      if (e.detail === 1) console.log('one click') // could use a filter inside a pipe instead of using an if statement
      // if (e.detail === 2) console.log('double click') // an alternative for handling double clicks
    });
  }
}

and the HTML: 和HTML:

<button #mybutton>Test</button>

This solution uses event.detail and is loosely based on this useful post - Prevent click event from firing when dblclick event fires - which not only discusses event.detail but also looks at the timing issues involved. 此解决方案使用event.detail,并且宽松地基于此有用的文章- 防止在dblclick 事件触发时触发click事件 -不仅讨论event.detail,而且着眼于所涉及的计时问题。

One of the big probs with your code is that you are subscribing to the event inside a function that is called multiple times, which means each time the button is clicked you create another subscription. 代码的一个大问题是,您正在多次调用的函数内预订事件,这意味着每次单击该按钮时,您都会创建另一个订阅。 Using ngAfterViewInit (which is only called once as part of the lifecycle) prevents this issue (and ensures the DOM is loaded). 使用ngAfterViewInit (在生命周期中仅被调用一次)可以避免此问题(并确保DOM被加载)。

Here's a stackblitz for you: 这是给你的闪电战:

https://stackblitz.com/edit/angular-t6cvjj https://stackblitz.com/edit/angular-t6cvjj

Forgive me for my sloppy type declarations! 原谅我草率的类型声明!

This satisfies your requirements as follows: 这样可以满足您的要求,如下所示:

  • If I click once, I need to see only one message ('one click') - PASS 如果单击一次,则只需要看到一条消息(“单击”)-通过
  • If I click twice (doubleclick), still I need to see only one message ('double click') - FAIL, you see 'one click' on the 1st click and 'double click' on the 2nd click 如果我单击两次(双击),仍然只需要看到一条消息(“双击”)-失败,您会在第一次单击时看到“一击”,而在第二次单击时看到“双击”
  • If I click more than twice - nothing happens - PASS 如果我单击两次以上-没有任何反应-通过

Due to the timing issues discussed in the SO post given, how you solve this is a matter of preference and thus I decided not to tackle it. 由于SO帖子中讨论的时间安排问题,如何解决此问题是一个优先事项,因此我决定不解决它。 And plus, you are not paying me ;) This answer however should set you well on your way to solving it fully. 而且,您不付我钱;)但是,此答案应该为您提供了完全解决之道的好方法。

PS There is a comment on the SO post above that suggests event.detail may not work on IE11 PS上面的SO帖子上有一条评论表明event.detail可能不适用于IE11

You can create a Directive that listens for dblclick and then do the needful in the directive. 您可以创建一个侦听dblclick的指令,然后在该指令中执行必要的操作。

import { Directive, HostListener } from '@angular/core';

@Directive({
  selector: '[appDoubleClick]'
})
export class DoubleClickDirective {
  constructor() { }

  @HostListener('dblclick') onDoubleClicked() {
    console.log('onDoubleClicked ran!');
  }

}

Template: 模板:

<button appDoubleClick (click)="test($event)">Test</button>

UPDATED STACKBLITZ 更新的堆栈BLITZ

Not sure if this would work in iOS. 不知道这是否可以在iOS中使用。 But please give it a try. 但是请尝试一下。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM