如何使用 RxJS 生成 requestAnimationFrame 循环?

How can I use RxJS to generate a requestAnimationFrame loop?

我的目标是创建一个动画循环 la requestAnimationFrame 这样我就可以做这样的事情:

animationObservable.subscribe(() =>
{
    // drawing code here
});

我尝试将此代码作为基本测试:

let x = 0;

Rx.Observable
    .of(0)
    .repeat(Rx.Scheduler.animationFrame)
    .takeUntil(Rx.Observable.timer(1000))
    .subscribe(() => console.log(x++));

Here is a JSFiddle but I'm not liable for any browser crashes from running this.

我预计这会在 1 秒内记录从 0 到大约 60(因为这是我的显示器的刷新率)的数字。相反,它会快速记录数字(比 requestAnimationFrame 快得多),开始导致页面滞后,并最终在大约 10000 和几秒后溢出堆栈。

为什么 animationFrame 调度程序会这样,运行 使用 RxJS 进行动画循环的正确方法是什么?

因为Observable.of的默认行为是立即发射

要更改此行为,您应该在调用 Observable.of:

时指定 Scheduler

let x = 0;

Rx.Observable
    .of(0, Rx.Scheduler.animationFrame)
    .repeat()
    .takeUntil(Rx.Observable.timer(1000))
    .subscribe(() => console.log(x++));
<script src="https://npmcdn.com/@reactivex/rxjs@5.0.3/dist/global/Rx.min.js"></script>

或者,more simply,将 ofrepeat 运算符替换为:

Observable.interval(0, Rx.Scheduler.animationFrame)

这就是我在 rxjs 中使用 requestAnimationFrame 的方式。我见过很多开发人员使用 0 而不是 animationFrame.now()。打发时间要好得多,因为你经常在动画中需要它。

const { Observable, Scheduler } = Rx;

const requestAnimationFrame$ = Observable
  .defer(() => Observable
    .of(Scheduler.animationFrame.now(), Scheduler.animationFrame)
    .repeat()
    .map(start => Scheduler.animationFrame.now() - start)
  );

// Example usage
const duration$ = duration => requestAnimationFrame$
  .map(time => time / duration)
  .takeWhile(progress => progress < 1)
  .concat([1])

duration$(60000)
  .subscribe((i) => {
    clockPointer.style.transform = `rotate(${i * 360}deg)`;
  });
<script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>

<div style="border: 3px solid black; border-radius: 50%; width: 150px; height: 150px;">
  <div id="clockPointer" style="width: 2px; height: 50%; background: black; margin-left: 50%; padding-left: -1px; transform-origin: 50% 100%;"></div>
</div>

从 RxJs >= 5.5 开始,您可以按以下方式进行操作:

import { animationFrameScheduler, of, timer } from 'rxjs';
import { repeat,takeUntil } from 'rxjs/operators';

let x = 0;

    of(null, animationFrameScheduler)
      .pipe(
        repeat(),
        takeUntil(timer(1000)),
      )
      .subscribe(() => {
        console.log(x++);
      });

或:

import { animationFrameScheduler, of, timer } from 'rxjs';
import { repeat, tap, takeUntil } from 'rxjs/operators';

let x = 0;

of(null, animationFrameScheduler)
  .pipe(
    tap(() => {
      console.log(x++);
    }),
    repeat(),
    takeUntil(timer(1000)),
  )
  .subscribe();

Ben Lesh's presentation about animation in 2017 开始并调整到 RxJS 7,加上我的小调整以添加与上一帧的时间差:

let animationFrameObservable = rxjs.defer(() => {
    // using defer so startTime is set on subscribe, not now
    const startTime = rxjs.animationFrameScheduler.now();
    let lastTime = startTime;
    // using interval but not using the default scheduler
    return rxjs.interval(0, rxjs.animationFrameScheduler).pipe(
        // modify output to return elapsed time and time diff from last frame
        rxjs.map(() => {
            const currentTime = rxjs.animationFrameScheduler.now();
            const diff = currentTime - lastTime;
            lastTime = currentTime;
            return [currentTime - startTime, diff];
        }));
});

// use the observable
animationFrameObservable.pipe(
    rxjs.tap(x => console.log(x)),
    rxjs.takeUntil(rxjs.timer(10000))
).subscribe();