UNPKG

@tdb/util

Version:
97 lines (89 loc) 2.48 kB
import { Subject, Observable } from 'rxjs'; import { share } from 'rxjs/operators'; const dynamics = require('dynamics.js'); export type MoveType = | 'spring' | 'bounce' | 'forceWithGravity' | 'gravity' | 'easeInOut' | 'easeIn' | 'easeOut' | 'linear' | 'bezier'; export type MoveObservable = Observable<MoveTargetProps>; export type MoveTargetProps = { [key: string]: number | string }; export interface IMoveOptions { type: MoveType; duration: number; frequency?: number; friction?: number; bounciness?: number; delay?: number; anticipationSize?: number; anticipationStrength?: number; } export interface IMoveObservableOptions extends IMoveOptions { target: MoveTargetProps; current: () => object; } /** * Animates an HtmlElement or object to the given set of property values. * See: * https://github.com/michaelvillar/dynamics.js#usage * * Example: * * private animate(target: { value1: number; value2: number }) { * this.stop$.next(); // Stop currently executing animation (if any). * const current = () => this.state; * const duration = 200; * animation * .start({ target, current, duration, type: 'easeInOut' }) * .takeUntil(this.stop$) * .subscribe({ * next: data => this.setState(data as any), * complete: () => { * // Done. * }, * }); * } * */ function animate( target: MoveTargetProps | HTMLElement, props: object, options: IMoveOptions, ) { return new Promise((resolve, reject) => { const type = dynamics[options.type]; const args = { ...options, type, complete: () => resolve(), }; dynamics.animate(target, props, args); }); } /** * Animates to a target set of values via an Observable. */ export function start(options: IMoveObservableOptions) { const { target } = options; const subject = new Subject<MoveTargetProps>(); const obj = {}; const define = (key: string) => { Object.defineProperty(obj, key, { get: () => options.current()[key], set: (value: any) => subject.next({ [key]: value }), }); }; Object.keys(target).forEach(key => define(key)); setTimeout(async () => { // NB: Allow for the Observable to be returned to // the call-site before starting the animation. await animate(obj, target, options); subject.complete(); }, 0); return subject.pipe(share()) as MoveObservable; }