如何使用 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,将 of
和 repeat
运算符替换为:
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();
我的目标是创建一个动画循环 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,将 of
和 repeat
运算符替换为:
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();