From ffac0b48decafcddb9665f681ced50e3901f9daf Mon Sep 17 00:00:00 2001 From: Hovhannes Babayan Date: Wed, 21 Dec 2016 00:21:46 +0400 Subject: [PATCH] feat: allow pass custom state reducer to the animation process --- README.md | 45 +++++++++++++++++++ index.d.ts | 12 ++++- index.js | 3 +- ...dditiveTweening.js => AdditiveTweening.js} | 17 +++---- src/PlainObjectReducer.js | 20 +++++++++ 5 files changed, 85 insertions(+), 12 deletions(-) rename src/{additiveTweening.js => AdditiveTweening.js} (90%) create mode 100644 src/PlainObjectReducer.js diff --git a/README.md b/README.md index e0c59d6c..46f57ff4 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,51 @@ Name | Signature | Description **onRender** | `function(state)` | (**required**) a callback for rendering current animation state. **onFinish** | `function(finalState)` | Fires after the last animation is completed. **onCancel** | `function()` | Fires if animation is canceled. +**stateReducer** | `IStateReducer` | An object, which provides `clone()` and `reduce()` methods thus implementing `IStateReducer` interface. + +##### State reducers + +State reducer is an object, which provides `clone()` and `reduce()` methods thus implementing `IStateReducer` interface. + +```typescript +interface IStateReducer { + clone: (state: T) => T, + reduce: (targetState: T, toState: T, fromState: T, pos: number) => T +} +``` + +`clone()` method is called once per each animation frame in order to get full clone of the target animation state. + +`reduce()` method is called at least once per each animation frame in order to get animation state for the given tweening position `pos` - a number from [0,1] interval. `targetState` - is the current animation state. + +If there are more than one tweening processes in progress, `reduce()` will be called once for each tweening process during single animation frame. + +The default state reducer is called and exported as `PlainObjectReducer`. Its implementation is below: + +```javascript +var PlainObjectReducer = { + clone: function (obj) { + var target = {}, + key + for (key in obj) { + target[key] = obj[key] + } + return target + }, + + reduce: function (targetState, toState, fromState, pos) { + var key + for (key in targetState) { + targetState[key] -= (toState[key] - fromState[key]) * pos + } + return targetState + } +} +``` + +It can be used to animate states which are plain JavaScript objects with numeric values, such as `{ width: 10, height: 20 }`. + + #### anim.tween(fromState, toState, duration, easing) diff --git a/index.d.ts b/index.d.ts index 57a79940..fd3a0963 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,12 +10,22 @@ declare namespace additween { export type EasingFunction = (t: number) => number + export interface IStateReducer { + clone: (state: T) => T, + reduce: (targetState: T, toState: T, fromState: T, pos: number) => T + } + + export const ArrayReducer: IStateReducer + + export const PlainObjectReducer: IStateReducer + export class AdditiveTweening { constructor(options: { onRender: (state: T) => void, onFinish?: (finalState: T) => void, - onCancel?: () => void + onCancel?: () => void, + stateReducer?: IStateReducer }) tween(fromState: T, toState: T, duration?: number, easing?: EasingFunction): number diff --git a/index.js b/index.js index 09f274a7..9cd10d26 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ 'use strict' module.exports = { - AdditiveTweening: require('./src/additiveTweening') + AdditiveTweening: require('./src/AdditiveTweening'), + PlainObjectReducer: require('./src/PlainObjectReducer') } diff --git a/src/additiveTweening.js b/src/AdditiveTweening.js similarity index 90% rename from src/additiveTweening.js rename to src/AdditiveTweening.js index 34481794..76b3f640 100644 --- a/src/additiveTweening.js +++ b/src/AdditiveTweening.js @@ -2,6 +2,8 @@ 'use strict' var now = require('./now') +var PlainObjectReducer = require('./PlainObjectReducer') + function noop() {} @@ -15,6 +17,7 @@ function AdditiveTweening(options) { currentState = null, animationStack = [], onRender = options.onRender || noop, + stateReducer = options.stateReducer || PlainObjectReducer, onFinish = options.onFinish || noop, onCancel = options.onCancel || noop, stepFunc @@ -34,14 +37,10 @@ function AdditiveTweening(options) { } function getCurrentState(time) { - var target = {}, - animation, - remain, - key + var animation, + remain - for (key in lastTargetState) { - target[key] = lastTargetState[key] - } + var target = stateReducer.clone(lastTargetState) for (var i = animationStack.length - 1; i >= 0; i--) { animation = animationStack[i] @@ -49,9 +48,7 @@ function AdditiveTweening(options) { continue } remain = (animation.end - time) / animation.duration - for (key in target) { - target[key] -= (animation.toState[key] - animation.fromState[key]) * animation.easing(remain) - } + target = stateReducer.reduce(target, animation.toState, animation.fromState, animation.easing(remain)) } return target diff --git a/src/PlainObjectReducer.js b/src/PlainObjectReducer.js new file mode 100644 index 00000000..16baa34b --- /dev/null +++ b/src/PlainObjectReducer.js @@ -0,0 +1,20 @@ +'use strict' + +module.exports = { + clone: function (obj) { + var target = {}, + key + for (key in obj) { + target[key] = obj[key] + } + return target + }, + + reduce: function (targetState, toState, fromState, pos) { + var key + for (key in targetState) { + targetState[key] -= (toState[key] - fromState[key]) * pos + } + return targetState + } +}