diff options
Diffstat (limited to 'node_modules/rxjs/src/internal/observable/dom/animationFrames.ts')
| -rw-r--r-- | node_modules/rxjs/src/internal/observable/dom/animationFrames.ts | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/node_modules/rxjs/src/internal/observable/dom/animationFrames.ts b/node_modules/rxjs/src/internal/observable/dom/animationFrames.ts new file mode 100644 index 0000000..38b338b --- /dev/null +++ b/node_modules/rxjs/src/internal/observable/dom/animationFrames.ts @@ -0,0 +1,132 @@ +import { Observable } from '../../Observable'; +import { TimestampProvider } from '../../types'; +import { performanceTimestampProvider } from '../../scheduler/performanceTimestampProvider'; +import { animationFrameProvider } from '../../scheduler/animationFrameProvider'; + +/** + * An observable of animation frames + * + * Emits the amount of time elapsed since subscription and the timestamp on each animation frame. + * Defaults to milliseconds provided to the requestAnimationFrame's callback. Does not end on its own. + * + * Every subscription will start a separate animation loop. Since animation frames are always scheduled + * by the browser to occur directly before a repaint, scheduling more than one animation frame synchronously + * should not be much different or have more overhead than looping over an array of events during + * a single animation frame. However, if for some reason the developer would like to ensure the + * execution of animation-related handlers are all executed during the same task by the engine, + * the `share` operator can be used. + * + * This is useful for setting up animations with RxJS. + * + * ## Examples + * + * Tweening a div to move it on the screen + * + * ```ts + * import { animationFrames, map, takeWhile, endWith } from 'rxjs'; + * + * function tween(start: number, end: number, duration: number) { + * const diff = end - start; + * return animationFrames().pipe( + * // Figure out what percentage of time has passed + * map(({ elapsed }) => elapsed / duration), + * // Take the vector while less than 100% + * takeWhile(v => v < 1), + * // Finish with 100% + * endWith(1), + * // Calculate the distance traveled between start and end + * map(v => v * diff + start) + * ); + * } + * + * // Setup a div for us to move around + * const div = document.createElement('div'); + * document.body.appendChild(div); + * div.style.position = 'absolute'; + * div.style.width = '40px'; + * div.style.height = '40px'; + * div.style.backgroundColor = 'lime'; + * div.style.transform = 'translate3d(10px, 0, 0)'; + * + * tween(10, 200, 4000).subscribe(x => { + * div.style.transform = `translate3d(${ x }px, 0, 0)`; + * }); + * ``` + * + * Providing a custom timestamp provider + * + * ```ts + * import { animationFrames, TimestampProvider } from 'rxjs'; + * + * // A custom timestamp provider + * let now = 0; + * const customTSProvider: TimestampProvider = { + * now() { return now++; } + * }; + * + * const source$ = animationFrames(customTSProvider); + * + * // Log increasing numbers 0...1...2... on every animation frame. + * source$.subscribe(({ elapsed }) => console.log(elapsed)); + * ``` + * + * @param timestampProvider An object with a `now` method that provides a numeric timestamp + */ +export function animationFrames(timestampProvider?: TimestampProvider) { + return timestampProvider ? animationFramesFactory(timestampProvider) : DEFAULT_ANIMATION_FRAMES; +} + +/** + * Does the work of creating the observable for `animationFrames`. + * @param timestampProvider The timestamp provider to use to create the observable + */ +function animationFramesFactory(timestampProvider?: TimestampProvider) { + return new Observable<{ timestamp: number; elapsed: number }>((subscriber) => { + // If no timestamp provider is specified, use performance.now() - as it + // will return timestamps 'compatible' with those passed to the run + // callback and won't be affected by NTP adjustments, etc. + const provider = timestampProvider || performanceTimestampProvider; + + // Capture the start time upon subscription, as the run callback can remain + // queued for a considerable period of time and the elapsed time should + // represent the time elapsed since subscription - not the time since the + // first rendered animation frame. + const start = provider.now(); + + let id = 0; + const run = () => { + if (!subscriber.closed) { + id = animationFrameProvider.requestAnimationFrame((timestamp: DOMHighResTimeStamp | number) => { + id = 0; + // Use the provider's timestamp to calculate the elapsed time. Note that + // this means - if the caller hasn't passed a provider - that + // performance.now() will be used instead of the timestamp that was + // passed to the run callback. The reason for this is that the timestamp + // passed to the callback can be earlier than the start time, as it + // represents the time at which the browser decided it would render any + // queued frames - and that time can be earlier the captured start time. + const now = provider.now(); + subscriber.next({ + timestamp: timestampProvider ? now : timestamp, + elapsed: now - start, + }); + run(); + }); + } + }; + + run(); + + return () => { + if (id) { + animationFrameProvider.cancelAnimationFrame(id); + } + }; + }); +} + +/** + * In the common case, where the timestamp provided by the rAF API is used, + * we use this shared observable to reduce overhead. + */ +const DEFAULT_ANIMATION_FRAMES = animationFramesFactory(); |
