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

afdcj2ne  于 2023-06-04  发布在  Java
关注(0)|答案(4)|浏览(487)

我有两个可观测量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++));
}
2fjabf4q

2fjabf4q1#

如果你想将scroll事件限制为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))
a0x5cqrl

a0x5cqrl2#

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

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

如果你需要event来包含最新的滚动信息,你可能需要使用window操作符来解决这个问题,比如:

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发出一个项目时,都会分块。在这种情况下,一系列滚动事件按每个动画节拍分块。
1.如果每个块不是空的,我们只从每个块中提取最新的,然后将其mergeMap到一个平面输出流。空块不会为最终流贡献任何项目。

sycxhyv7

sycxhyv73#

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
7cwmlq89

7cwmlq894#

相信浏览器!

我在一个全局服务中使用以下代码的时间最长:

windowScroll$ = fromEvent(window, 'scroll').pipe(
        map(x => window.scrollY), 
        startWith(0), 
        distinctUntilChanged(), 
        shareReplay({ refCount: true, bufferSize: 1 }))

决定做一些测试,看看是否可以通过使用animationFrame来提高性能(并不是说我有任何已知的问题)。

结果:

在我的桌面版Chrome和iOS版Safari的测试中,我找不到任何方法通过添加animationFrame调度来改进上述功能。

方法:

创建上述可观察值的第二个变体,将throttleTimeanimationFrameScheduler相加。然后订阅两者,看看哪个先达到100个排放量。

let v1 = 0;
    let v2 = 0;

    this.windowScroll$.pipe(tap(() => v1++)).subscribe((t) => { (v1 >= 100) && alert('v1=' + v1 + ', v2=' + v2); });
    this.windowScroll2$.pipe(tap(() => v2++)).subscribe((t) => { (v2 >= 100) && alert('v1=' + v1 + ', v2=' + v2); });

我总是得到v1=100, v2=100,证明它们在发射计数方面是相同的。

总结

浏览器已经只在值发生变化时才会发出scroll事件。它不会比动画帧更频繁地这样做-为什么会这样?那样做是没有意义的。

如何处理:

为了提高性能,你可以做的第一件事就是把你的可观察对象放在一个全局(providedIn: 'root')服务中(注意shareReplay)。然后,无论何时使用它,您都将引用同一个示例并仅创建一个浏览器事件。忘记所有关于animationFrame调度器的东西。

windowScroll$ = fromEvent(window, 'scroll').pipe(
         map(x => window.scrollY), 
         startWith(0), 
         distinctUntilChanged(),
         shareReplay({ refCount: true, bufferSize: 1 }));

顺便说一句:distinctUntilChanged()在这里只是为了忽略水平滚动(因为y值保持不变,但仍然会得到事件)。如果你没有水平滚动条,你永远不会注意到差异。
如果您实际上要执行一个复杂的操作,那么根据您的具体需要使用throttleTimedebounceTime。AnimationFrame在这里不会帮助您。

// immediately emit when scroll starts, then report at most every 500ms
 service.windowScroll$.pipe(throttleTime(500, { leading: true, trailing: true })).subscribe(...)

相关问题