My goal is to create an animation loop à la requestAnimationFrame
so that I could do something like this:
animationObservable.subscribe(() =>
{
// drawing code here
});
I tried this code as a basic test:
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.
I expected this to log the numbers from 0 to approximately 60 (because that is my monitor's refresh rate) over 1 second. Instead, it rapidly logs numbers (much faster than requestAnimationFrame
would), begins to cause the page to lag, and finally overflows the stack around 10000 and several seconds later.
Why does the animationFrame
scheduler behave this way, and what is the correct way to run an animation loop with RxJS?
It's because the default behaviour of Observable.of
is to emit immediately.
To change this behaviour, you should specify the Scheduler
when calling Observable.of
:
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/[email protected]/dist/global/Rx.min.js"></script>
Or, more simply, replace the of
and repeat
operators with:
Observable.interval(0, Rx.Scheduler.animationFrame)
This is how I use requestAnimationFrame with rxjs. I've seen a lot of developers using 0 instead of animationFrame.now(). It's much better to pass the time because you often need that in animations.
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/[email protected]/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>
As of RxJs >= 5.5 you do it the following way:
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++);
});
OR:
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With