【问题标题】:Separate action for single click and double click with RxJs使用 RxJs 进行单击和双击的单独操作
【发布时间】:2018-09-05 20:45:54
【问题描述】:

我对 RxJs 有疑问。

单击一次时,我需要在 console.log 中记录一条消息,单击两次按钮时需要记录不同的消息。问题是:

  • 如果我第一次点击开始 - 没有任何反应 - 错误 (我应该会看到第一条消息“单击”)

  • 然后,如果我单击按钮,然后(一秒钟后)再次单击,第二次单击时我可以看到两条消息 - 错误(每个操作我应该只看到一条消息)

  • 当我单击按钮时,稍等片刻,再次单击,稍等片刻,然后再次单击,最后一次单击时,我将在 console.log 中看到三条消息 - 错误(每个操作我应该只看到一条消息)
  • 之后,如果我单击两次(双击),我将看到 5 条消息用于双击,1 条用于单击 - 错误(我应该只看到一条消息“双击”)

我想要的是:

  • 如果我点击一次,我只需要看到一条消息('一次点击')
  • 如果我单击两次(双击),我仍然只需要看到一条消息('双击')
  • 如果我点击两次以上 - 没有任何反应

有什么帮助吗?

check hear for examples

【问题讨论】:

  • 请将您的代码也放入您的问题中
  • 是的,我忘记了,谢谢
  • 您是否尝试过 angulars (dblclick) 绑定而不是复杂的 observable?
  • 当然可以,但我需要另一种解决方案来解决 dblclick 无法正常工作的 ios
  • 仅供参考,您收到每条消息的倍数的原因是因为您每次调用 test() 函数时都在重新订阅 observable。您应该在可观察的订阅回调触发后取消订阅。这就引出了一个问题,你为什么首先使用 observables?看起来像一个普通的点击处理程序应该做的,是吗?

标签: javascript angular rxjs


【解决方案1】:

一个非常简单的答案(不使用任何可观察对象)是使用setTimeout() 并检查每次点击是否已设置超时,如果是,您知道这是在给定时间窗口内的第二次点击(双击)如果没有,这是第一次点击。如果超时到期,您知道这只是一次单击,如下所示:

Updated 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);
  }
}

编辑

我错过了忽略超过 2 次点击的部分。这不是更多的工作,但我把它分解了一点,以便能够重用代码,所以看起来更多。无论如何,要忽略 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!");
}

【讨论】:

  • @Przemo 您是否有特定原因需要使用 Rxjs?我没有从最初的问题中理解这一点。我以为您正在尝试使用 Rxjs 解决问题,而不是要求它。您能否编辑您的问题以进一步说明您的要求?
  • 我决定使用您的解决方案。谢谢!
  • @Przemo Great =) 很高兴它对你有用。请务必将答案标记为已接受,以供未来的观众使用。谢谢!
【解决方案2】:

@Siddharth Ajmeras 的回答显示了如何处理事件。我不知道存在 dblclick 事件。你懂得越多。如果您仍然对如何使用 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));

【讨论】:

  • 这应该是公认的答案:反应式、纯粹、可组合到其他可观察的管道中。干得好!
【解决方案3】:

这要么是你想要的,要么非常接近:

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
    });
  }
}

和 HTML:

<button #mybutton>Test</button>

此解决方案使用 event.detail 并大致基于这篇有用的帖子 - Prevent click event from firing when dblclick event fires - 该帖子不仅讨论了 event.detail,还着眼于所涉及的时间问题。

您的代码的一个大问题是您订阅了多次调用的函数内的事件,这意味着每次单击按钮时,您都会创建另一个订阅。使用 ngAfterViewInit(在生命周期中只调用一次)可以防止这个问题(并确保 DOM 被加载)。

这是一个适合你的堆栈闪电战:

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

请原谅我草率的类型声明!

这满足您的要求如下:

  • 如果我点击一次,我只需要看到一条消息(“一次点击”) - PASS
  • 如果我单击两次(双击),我仍然只需要看到一条消息(“双击”) - 失败,您在第一次单击时看到“单击”,在第二次单击时看到“双击”
  • 如果我点击两次以上 - 没有任何反应 - PASS

由于给出的 SO 帖子中讨论了时间问题,您如何解决这个问题是一个偏好问题,因此我决定不解决它。另外,你没有付钱给我;)然而,这个答案应该能让你很好地完全解决它。

PS 上面的 SO 帖子有一条评论表明 event.detail 可能不适用于 IE11

【讨论】:

  • OP 表示 dblclick 事件在 IOS 上无法正常工作。不确定这是否可行
【解决方案4】:

您可以创建一个监听dblclick 的指令,然后在指令中执行必要的操作。

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

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

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

}

模板:

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

UPDATED STACKBLITZ

不确定这是否适用于 iOS。但请试一试。

【讨论】:

  • 谢谢,但我需要使用 rxjs。 ios上的dblclick有一些问题
猜你喜欢
  • 1970-01-01
  • 2012-02-11
  • 1970-01-01
  • 2015-04-09
  • 1970-01-01
  • 1970-01-01
  • 2013-04-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多