// Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ease from 'tween-functions'; import { TNil } from './types'; interface TweenState { done: boolean; value: number; } type TTweenCallback = (state: TweenState) => void; type TTweenOptions = { delay?: number; duration: number; from: number; onComplete?: TTweenCallback; onUpdate?: TTweenCallback; to: number; }; export default class Tween { callbackComplete: TTweenCallback | TNil; callbackUpdate: TTweenCallback | TNil; delay: number | TNil; duration: number; from: number; requestID: number | TNil; startTime: number; timeoutID: number | TNil; to: number; constructor({ duration, from, to, delay, onUpdate, onComplete }: TTweenOptions) { this.startTime = Date.now() + (delay || 0); this.duration = duration; this.from = from; this.to = to; if (!onUpdate && !onComplete) { this.callbackComplete = undefined; this.callbackUpdate = undefined; this.timeoutID = undefined; this.requestID = undefined; } else { this.callbackComplete = onComplete; this.callbackUpdate = onUpdate; if (delay) { // setTimeout from @types/node returns NodeJS.Timeout, so prefix with `window.` this.timeoutID = window.setTimeout(this._frameCallback, delay); this.requestID = undefined; } else { this.requestID = window.requestAnimationFrame(this._frameCallback); this.timeoutID = undefined; } } } _frameCallback = () => { this.timeoutID = undefined; this.requestID = undefined; const current = Object.freeze(this.getCurrent()); if (this.callbackUpdate) { this.callbackUpdate(current); } if (this.callbackComplete && current.done) { this.callbackComplete(current); } if (current.done) { this.callbackComplete = undefined; this.callbackUpdate = undefined; } else { this.requestID = window.requestAnimationFrame(this._frameCallback); } }; cancel() { if (this.timeoutID != null) { clearTimeout(this.timeoutID); this.timeoutID = undefined; } if (this.requestID != null) { window.cancelAnimationFrame(this.requestID); this.requestID = undefined; } this.callbackComplete = undefined; this.callbackUpdate = undefined; } getCurrent(): TweenState { const t = Date.now() - this.startTime; if (t <= 0) { // still in the delay period return { done: false, value: this.from }; } if (t >= this.duration) { // after the expiration return { done: true, value: this.to }; } // mid-tween return { done: false, value: ease.easeOutQuint(t, this.from, this.to, this.duration) }; } }