如何使用RxJS生成requestAnimationFrame循环?

18

我的目标是创建一个类似于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++));

这里有一个 JSFiddle,但是请注意,运行此代码可能会导致浏览器崩溃,我不对此负责。

我原本期望它会在一秒钟内记录从 0 到大约 60 的数字(因为这是我的显示器的刷新率),但实际上,它迅速记录数字(比 requestAnimationFrame 快得多),开始导致页面卡顿,并最终在大约 10000 时出现堆栈溢出,并几秒钟后停止记录。

为什么 animationFrame 调度程序会表现出这种方式,以及使用 RxJS 运行动画循环的正确方法是什么?

4个回答

22
这是因为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>

或者,更简单地,用以下方式替换ofrepeat运算符:

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

不知何故我错过了这个,可能是因为当我第一次遇到这个问题时我没有使用 of,而是使用了一个行为不同的东西。现在突然间更加清楚了。谢谢! - Kendall Frey
我尝试了间隔和of + repeat两种方法,我注意到间隔版本每帧会发出两次,请查看此Stackblitz:https://stackblitz.com/edit/angular-rxjs-animationframe-bug - waterplea
@pokrishka 我认为你应该在 GitHub 存储库中将其作为错误打开:https://github.com/ReactiveX/rxjs/issues,或许可以在问题中引用您的评论和推文。 - cartant

12

这是我如何在rxjs中使用requestAnimationFrame。我看到很多开发人员在使用0代替animationFrame.now()。将时间传递给requestAnimationFrame更好,因为在动画中通常需要它。

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>


1
这真的很酷!你能讲解一下 requestAnimationFrame$ Observable 的构建过程吗? - Dean Radcliffe

4

从 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();

0

Ben Lesh在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();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接