你如何在 rxjs 中组合 Scheduler.animationFrame 和 fromEvent(window, 'scroll') 流?

How do would you combine Scheduler.animationFrame and fromEvent(window, 'scroll') streams in rxjs?

我有两个可观察值 Observable.of(0, animationFrame)Observable.fromEvent(window, 'scroll')。我想将它们结合起来,以便我的 renderScrollBar(event) 仅在 animationFrame 的滴答声中被调用。

  Observable.fromEvent(window, 'scroll')
      .map((event: any) => event.currentTarget)
      .subscribe((event) => {
        this._renderScrollBar(event);
      });
  let x = 0;
  Observable.of(0, animationFrame)
      .repeat()
      .takeUntil(Observable.timer(1000))
      .subscribe(() => console.log(x++));
}

zip 是一个快速而肮脏的解决方案,它有一个潜在的严重缺陷:当滚动事件发出的速度快于帧速率时,滚动处理程序滞后于真正的滚动值。它看起来像:

Observable.fromEvent(window, 'scroll')
          .map(event => event.currentTarget)
          .zip(Observable.of(0, animationFrame)
                         .repeat(),
            (currentTarget, _) => this._renderScrollBar(currentTarget)
          );

如果您需要 event 包含最新的滚动信息,您可能需要求助于 the window operator 的更精细的解决方案,例如:

Observable.fromEvent(window, 'scroll')
          .map(event => event.currentTarget)
          .window(Observable.of(0, animationFrame).repeat())
          .mergeMap(w => w.takeLast(1))
          .subscribe(currentTarget => this._renderScrollBar(currentTarget));
  1. 最重要的是,window 从源创建一个 Observable 块流,每次参数 Observable 发出一个项目时都会分块。在这种情况下,一系列滚动事件由每个动画节拍分块。
  2. 如果块不为空,我们只从每个块中获取最新的,并且mergeMap将其输出到平面输出流。空块不会为这个最终流贡献任何项目。

如果您想限制滚动事件到 requestAnimationFrame,您可以使用 throttleTime(0, animationFrame) 运算符而不是 Observable.of(0, animationFrame)

例子

Observable.fromEvent(window, 'scroll')
    .throttleTime(0, animationFrame)

更新:

RxJS 6

import { fromEvent, animationFrameScheduler } from "rxjs";
import { throttleTime } from "rxjs/operators";

fromEvent(window, "scroll")
  .pipe(throttleTime(0, animationFrameScheduler))
interval(0)
  .pipe(throttleTime(0, animationFrameScheduler))
  .subscribe(() => {
    fifth++;
  });

scheduled(interval(0), animationFrameScheduler).subscribe(() => {
  sixth++;
});

无论我们将时间设置为 1、2 或 100 秒,这两个似乎只相差 1 帧 ;)

REAL:  181
FIRST:  109
SECOND:  217
THIRD:  108
FOURTH:  217
FIFTH:  180