UNPKG

es6-tween

Version:

ES6 implementation of amazing tween.js

330 lines (291 loc) 8.86 kB
import { add, now, remove, isRunning, isLagSmoothing } from './core' import PlaybackPosition from './PlaybackPosition' import Tween from './Tween' import { EVENT_START, EVENT_COMPLETE, EVENT_REPEAT, EVENT_REVERSE, EVENT_RESTART, EVENT_UPDATE, FRAME_MS, TOO_LONG_FRAME_MS } from './constants' import Selector from './selector' export const shuffle = (a) => { let j let x let i for (i = a.length; i; i -= 1) { j = Math.floor(Math.random() * i) x = a[i - 1] a[i - 1] = a[j] a[j] = x } return a } let _id = 0 /** * Timeline main constructor. * * It works same as `Tween` instance, using `.repeat`, `.restart` or `etc` works like a `Tween`, so please see `Tween` class for methods * @constructor * @class * @namespace TWEEN.Timeline * @param {Object=} params Default params for new tweens * @example let tl = new Timeline({delay:200}) * @extends Tween */ class Timeline extends Tween { constructor (params) { super() this._duration = 0 this._startTime = params && params.startTime !== undefined ? params.startTime : now() this._tweens = [] this.elapsed = 0 this._id = _id++ this._defaultParams = params this.position = new PlaybackPosition() this.position.addLabel('afterLast', this._duration) this.position.addLabel('afterInit', this._startTime) this._onStartCallbackFired = false return this } mapTotal (fn) { fn.call(this, this._tweens) return this } timingOrder (fn) { const timing = fn(this._tweens.map((t) => t._startTime)) this._tweens.map((tween, i) => { tween._startTime = timing[i] }) return this } getTiming (mode, nodes, params, offset = 0) { if (mode === 'reverse') { const { stagger } = params const totalStagger = (stagger || 0) * (nodes.length - 1) return nodes.map((node, i) => totalStagger - (stagger || 0) * i + offset) } else if (mode === 'async') { return nodes.map((node) => offset) } else if (mode === 'sequence' || mode === 'delayed') { let { stagger } = params if (!stagger) { stagger = (params.duration || 1000) / (nodes.length - 1) } return nodes.map((node, i) => stagger * i + offset) } else if (mode === 'oneByOne') { return nodes.map((node) => params.duration) } else if (mode === 'shuffle') { const { stagger } = params return shuffle(nodes.map((node, i) => (stagger || 0) * i + offset)) } else { const { stagger } = params return nodes.map((node, i) => (stagger || 0) * i + offset) } } /** * @param {Array<Element>} nodes DOM Elements Collection (converted to Array) * @param {object} from - Initial value * @param {object} to - Target value * @param {object} params - Options of tweens * @example tl.fromTo(nodes, {x:0}, {x:200}, {duration:1000, stagger:200}) * @memberof Timeline * @static */ fromTo (nodes, from, to, params) { nodes = Selector(nodes, true, true) if (nodes && nodes.length) { if (this._defaultParams) { params = params ? { ...this._defaultParams, ...params } : this._defaultParams } const position = params.label const offset = typeof position === 'number' ? position : this.position.parseLabel(typeof position !== 'undefined' ? position : 'afterLast', null) const mode = this.getTiming(params.mode, nodes, params, offset) for (let i = 0, node, len = nodes.length; i < len; i++) { node = nodes[i] this.add( Tween.fromTo( node, typeof from === 'function' ? from(i, nodes.length) : typeof from === 'object' && !!from ? { ...from } : null, typeof to === 'function' ? to(i, nodes.length) : to, typeof params === 'function' ? params(i, nodes.length) : params ), mode[i] ) } } return this.start() } /** * @param {Array<Element>} nodes DOM Elements Collection (converted to Array) * @param {object} from - Initial value * @param {object} params - Options of tweens * @example tl.from(nodes, {x:200}, {duration:1000, stagger:200}) * @memberof Timeline * @static */ from (nodes, from, params) { return this.fromTo(nodes, from, null, params) } /** * @param {Array<Element>} nodes DOM Elements Collection (converted to Array) * @param {object} to - Target value * @param {object} params - Options of tweens * @example tl.to(nodes, {x:200}, {duration:1000, stagger:200}) * @memberof Timeline * @static */ to (nodes, to, params) { return this.fromTo(nodes, null, to, params) } /** * Add label to Timeline * @param {string} name Label name * @param {any} offset Label value, can be `number` and/or `string` * @example tl.add('label1', 200) * @memberof Timeline */ addLabel (name, offset) { this.position.addLabel(name, offset) return this } map (fn) { for (let i = 0, len = this._tweens.length; i < len; i++) { const _tween = this._tweens[i] fn(_tween, i) this._duration = Math.max(this._duration, _tween._duration + _tween._startTime) } return this } /** * Add tween to Timeline * @param {Tween} tween Tween instance * @param {position} position Can be label name, number or relative number to label * @example tl.add(new Tween(node, {x:0}).to({x:200}, 200)) * @memberof Timeline */ add (tween, position) { if (Array.isArray(tween)) { tween.map((_tween) => { this.add(_tween, position) }) return this } else if (typeof tween === 'object' && !(tween instanceof Tween)) { tween = new Tween(tween.from).to(tween.to, tween) } const { _defaultParams, _duration } = this if (_defaultParams) { for (const method in _defaultParams) { if (typeof tween[method] === 'function') { tween[method](_defaultParams[method]) } } } const offset = typeof position === 'number' ? position : this.position.parseLabel(typeof position !== 'undefined' ? position : 'afterLast', null) tween._startTime = Math.max(this._startTime, tween._delayTime, offset) tween._delayTime = offset tween._isPlaying = true this._duration = Math.max(_duration, Math.max(tween._startTime + tween._delayTime, tween._duration)) this._tweens.push(tween) this.position.setLabel('afterLast', this._duration) return this } restart () { this._startTime += now() add(this) return this.emit(EVENT_RESTART) } easing (easing) { return this.map((tween) => tween.easing(easing)) } interpolation (interpolation) { return this.map((tween) => tween.interpolation(interpolation)) } update (time) { const { _tweens, _duration, _reverseDelayTime, _startTime, _reversed, _yoyo, _repeat, _isFinite, _isPlaying, _prevTime, _onStartCallbackFired } = this let elapsed time = time !== undefined ? time : now() let delta = time - _prevTime this._prevTime = time if (delta > TOO_LONG_FRAME_MS && isRunning() && isLagSmoothing()) { time -= delta - FRAME_MS } if (!_isPlaying || time < _startTime) { return true } elapsed = (time - _startTime) / _duration elapsed = elapsed > 1 ? 1 : elapsed elapsed = _reversed ? 1 - elapsed : elapsed this.elapsed = elapsed if (!_onStartCallbackFired) { this.emit(EVENT_START) this._onStartCallbackFired = true } const timing = time - _startTime const _timing = _reversed ? _duration - timing : timing let i = 0 while (i < _tweens.length) { _tweens[i].update(_timing) i++ } this.emit(EVENT_UPDATE, elapsed, timing) if (elapsed === 1 || (_reversed && elapsed === 0)) { if (_repeat) { if (_isFinite) { this._repeat-- } this.emit(_reversed ? EVENT_REVERSE : EVENT_REPEAT) if (_yoyo) { this._reversed = !_reversed this.timingOrder((timing) => timing.reverse()) } if (_reversed && _reverseDelayTime) { this._startTime = time + _reverseDelayTime } else { this._startTime = time } i = 0 while (i < _tweens.length) { _tweens[i].reassignValues(time) i++ } return true } else { this.emit(EVENT_COMPLETE) this._repeat = this._r remove(this) this._isPlaying = false return false } } return true } progress (value) { return value !== undefined ? this.update(value * this._duration) : this.elapsed } } export default Timeline