RxJS Buffer,如何将多点击事件组合为流

时间:2016-05-18 21:47:16

标签: rxjs

  

在RxJS 2.2.26版本中

以下代码生成了一个可以发出点击次数的流(双击,三击等)。

var multiClickStream = clickStream
    .buffer(function() { return clickStream.throttle(250); })
    .map(function(list) { return list.length; })
    .filter(function(x) { return x >= 2; });
  

在RxJS版本4.0.6中<​​/ p>

此代码不再产生所需的结果。

如何在RxJS 4.0.6中获得相同的功能?

Working 2.2.26 Example

Broken 4.0.6 Example

8 个答案:

答案 0 :(得分:11)

实际上有更好的方法可以通过RxJs找到双击:

var mouseDowns = Rx.Observable.fromEvent(button, "mousedown");

var doubleClicks = mouseDowns.timeInterval()
                             .scan<number>((acc, val) => val.interval < 250 ? acc + 1 : 0, 0)
                             .filter(val => val == 1);

这样做的好处是您不需要等待整整250毫秒来识别双击 - 如果用户之间只有120毫秒的点击次数,则双击和结果操作之间会有明显的延迟。

请注意,通过计数(使用.scan())并过滤到第一个计数,我们可以将流限制为双击并忽略之后的每次快速点击 - 因此点击 < em>点击 点击不会导致两次双击。

答案 1 :(得分:9)

万一有人想知道如何用RxJS 5(5.0.0-beta.10)做同样的事情,这就是我的工作方式:

const single$ = Rx.Observable.fromEvent(button, 'click');
single$
    .bufferWhen(() => single$.debounceTime(250))
    .map(list => list.length)
    .filter(length => length >= 2)
    .subscribe(totalClicks => {
        console.log(`multi clicks total: ${totalClicks}`);
    });

我花了很多时间试图解决这个问题,因为我也在学习反应式编程(使用RxJS),所以,如果你看到这个实现有问题或知道更好的方法来做同样的事情,我'我很高兴知道!

答案 2 :(得分:6)

检测单击或双击,但不是多次点击

这是我提出的解决方案,它创建了单击或双击流(不再是其他任何内容,因此没有三次点击)。目的是快速连续地检测多次双击,但也接收单击的通知。

let click$ = Observable.fromEvent(theElement, "click");

let trigger$ = click$.exhaustMap(r =>
    click$.merge(Observable.timer(250)).take(1));
let multiclick$ = click$.buffer(trigger$).map(r => r.length);

原因是,我们使用buffer运算符对点击进行分组,并按如下方式设计缓冲区触发事件:每次点击发生时,我们都会在两个可观察对象之间启动种族

  1. 250毫秒计时器
  2. 原始点击流
  3. 一旦这场比赛结束(我们使用take(1),因为我们只对第一个事件感兴趣),我们会发出缓冲区触发事件。这是做什么的?它使缓冲在第二次点击到达时或250毫秒时停止。

    我们使用exhaustMap运算符来抑制另一场比赛的创建。否则,双击对中的第二次点击会发生这种情况。

答案 3 :(得分:5)

这是另一种观点(如果用户快速点击2 * N次,则产生N次双击):

const clicks = Rx.Observable.fromEvent(document, 'click');
const doubleclicks = clicks.exhaustMap(() =>
  clicks.take(1).takeUntil(Rx.Observable.interval(250)),
);

答案 4 :(得分:3)

一个更通用的解决方案:从一个可观察的源开始,当这个observable快速连续两次快速发出完全相同的值时检测并传播:

var scheduler = Rx.Scheduler.default; // replace with TestScheduler for unit tests
var events = // some observable
var doubleClickedEvents = events
  /* Collect the current and previous value */
  .startWith(null) // startWith null is necessary if the double click happens immediately
  .pairwise()
  .map(pair => ({ previous: pair[0], current: pair[1] }) 
  /* Time the interval between selections. */
  .timeInterval(scheduler)
  /* Only accept two events when they happen within 500ms */
  .filter(timedPair => timedPair.interval < 500)
  .map(timedPair => timedPair.value)
  /* Only accept two events when they contain the same value */
  .filter(pair => pair.previous && pair.previous === pair.current)
  .map(pair => pair.current)
  /* Throttle output. This way, triple and quadruple clicks are filtered out */
  .throttle(500, scheduler)

与提供的解决方案的区别在于:

  • 与沃尔夫冈的解决方案一样,这个可观察的内容会立即在双击的时刻立即发出价值&#39;发生,而不是在去抖动时间为500毫秒之后
  • 不必累积点击次数。如果您需要实施&#39;三击&#39;行为,那么沃尔夫冈的解决方案就更好了。但是对于双击行为,可以用startWith(null).pairwise()
  • 替换它
  • 使用油门阻止多次双击。
  • 实际上传播原始事件发出的值,而不只是传播信号&#39;。

答案 5 :(得分:2)

throttle从RxJS 3开始更改为debounce我相信。

由于原始命名方案实际上与Rx的其他实现或实际定义不匹配,因此对这种方式存在很大争议。

https://github.com/Reactive-Extensions/RxJS/issues/284

在RxJS 5的重新实现中,它甚至设法让人们更加困惑: https://github.com/ReactiveX/rxjs/issues/480

答案 6 :(得分:1)

找到一种更适合区分单击/双击的方法。 (所以我可以获得单击或双击,但不能同时获得两者)

对于双击,处理快速双击2 * N次产生N个事件:

Observable.prototype.doubleEvent = function(timing = 150, count = 2, evt = this) {    /// flexibility to handle multiple events > 2
   return this.exhaustMap( () =>                   /// take over tapping event,
       evt.takeUntil( Observable.timer(timing) )   /// until time up,
           .take(count-1)                          /// or until matching count.
   );
}

注意:参数evt = this使doubleEvent与startWith()一起使用,可以正确地使用this Observable。

用法:

this.onTap
    .doubleEvent()
    .subscribe( (result) => console.log('double click!') );

区分单击/双击:

Observable.prototype.singleEvent = function(timing = 150) {
    return this.mergeMap(               /// parallel all single click event
        () => Observable.timer(timing)  /// fire event when time up
    );
}
Observable.prototype.singleOrDoubleEvent = function(timing = 150) {
    return this.exhaustMap( (e) =>                   /// take over tapping event
        Observable.race(                             /// wait until single or double event raise
            this.startWith(e).doubleEvent(timing, 2, this)
                .take(1).mapTo(1),
            this.startWith(e).singleEvent(timing)
                .take(1).mapTo(0),
        )
    );
}

用法:

this.onTap
    .singleOrDoubleEvent()
    .subscribe( (result) => console.log('click result: ', result===1 ? 'double click' : 'single click') );

答案 7 :(得分:1)

  

此答案的灵感来自@Aviad P。

我相信以下代码是正确的答案。满足所有这些要求。

  1. 它区分single-clickdouble-clicktriple-click
  2. 不会传输无法识别的点击。即,如果您快速单击鼠标6次,您将获得2 triple-clicks。如果单击5次,将得到一个triple-click和一个double-click。等等
  3. triple-click将立即被解雇,无需等待整个时间窗口完成。
  4. 没有点击会丢失。
  5. 很容易接受four-clickfive-click ...



const btn = document.querySelector('#btn');
const click$ = Rx.Observable.fromEvent(btn, "click");

const trigger$ =
  click$.exhaustMap(r =>
    click$
    .take(2)
    .last()
    .race(click$
      .startWith(0)
      .debounceTime(500)
      .take(1))
  );


click$
  .buffer(trigger$)
  .map(l => l.length)
  .map(x => ({
    1: 'single',
    2: 'double',
    3: 'tripple'
  }[x]))
  .map(c => c + '-click')
  .subscribe(log);

Rx.Observable.fromEvent(document.querySelector('#reset'), 'click')
  .subscribe(clearLog);

function log(message) {
  document.querySelector('.log-content').innerHTML += ('<div>' + message + '</div>');
}

function clearLog() {
  document.querySelector('.log-content').innerHTML = null;
}
.log {
  padding: 1rem;
  background-color: lightgrey;
  margin: 0.5rem 0;
}

.log-content {
  border-top: 1px solid grey;
  margin-top: 0.5rem;
  min-height: 2rem;
}

.log-header {
  display: flex;
  justify-content: space-between;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.7/Rx.js"></script>

<button id='btn'>Click Me</button>
<button type="button" id='reset'> Reset </button>

<div class='log' id='logpanel'>
  <div class='log-header'>
    Log:
  </div>
  <div class="log-content"></div>
</div>