94 | {/* Notice that since we're using Timeline statelessly (by providing
95 | the timeliner prop), it can be removed from the DOM on mouse out */}
96 | {isOver &&
97 |
98 | {({tween}) =>
99 |
109 |
110 | {topList.map(item =>
111 |
{item}
)}
112 |
113 |
114 |
{currentText}
115 | {bottomList.map(item =>
116 |
123 | {item}
124 |
125 | )}
126 |
127 |
128 | }}
129 | {currentText}
130 |
131 |
132 |
133 |
)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/tween.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The following concepts are essential to this code:
3 | *
4 | * A VALUE FACTORY is a function which
5 | * - accepts any number of arguments representing the desired value and
6 | * - returns a WRAPPED VALUE object
7 | *
8 | * A WRAPPED VALUE is an object with a `tween` method and a `resolveValue` method.
9 | * - `resolveValue` returns the formatted string representation of the value.
10 | * - `tween` returns the formatted string representation of the result of tweening
11 | * two different WRAPPED VALUEs created with the same VALUE FACTORY.
12 | *
13 | */
14 |
15 | export const isNumber = x => typeof x === 'number';
16 | export const isWrapped = x => !!x.tween;
17 | export const isNotWrapped = x => !x.tween;
18 | export const identity = x => x;
19 |
20 | function mapObject(fn) {
21 | const result = {};
22 | Object.keys(this).forEach(key => result[key] = fn(this[key], key));
23 | return result;
24 | }
25 |
26 | export function tweenValues(progress, a, b, easer) {
27 | // for added flexibility with easing, we don't enforce
28 | // that b is wrapped
29 | if (a.tween)
30 | return a.tween(progress, a, b, easer);
31 |
32 | // now we enforce that a and b are the same type
33 | if (process.env.NODE_ENV !== 'production') {
34 | if (typeof(b) !== typeof(a))
35 | throw(Error(`Tried to tween mismatched types ${typeof(a)} !== ${typeof(b)}`));
36 | }
37 |
38 | if (Array.isArray(a))
39 | return a.map((value,index) => tweenValues(progress, value, b[index], easer));
40 |
41 | if (typeof a === 'number')
42 | return a + easer(progress) * (b-a);
43 |
44 | // object
45 | return a::mapObject((v,k) => k !== 'ease' && tweenValues(progress, v, b[k], easer))
46 | }
47 |
48 | export const resolveValue = x =>
49 | x.resolveValue ? x.resolveValue() : // is wrapped value
50 | typeof x === 'number' ? x : // is number
51 | x::mapObject(resolveValue); // is object
52 |
53 | /**
54 | * ## tween
55 | *
56 | * `position` is a number representing the current timeline position
57 | *
58 | * `keyframes` is an array
59 | * - Each item in the array should be a touple (a 2-item array) where the first
60 | * value of the touples represent positions on the timeline. Note that your
61 | * keyframes must *already* be sorted, `tween` will **not** sort them for you.
62 | * - The second value of the touples represent values at the given time.
63 | * the values are either numbers, objects, or wrapped values (wrapped values may also be nested)
64 | * * when the values are numbers `tween` returns a (tweened) Number
65 | * * when the values are objects `tween` returns an object.
66 | * * when the values are wrapped values `tween` returns the resolved result of the wrapped
67 | * value (usually a string)
68 | * - may optionally provide an `ease` property specifying an easing function
69 | * Note that all Keyframe values should be exactly the same type or shape.
70 | * (a value factory may make exceptions to this rule.
71 | * when doing `ease(easer, a)`, `b` does not have to be wrapped in `ease()`)
72 | *
73 | * `ease` is an (optional) easing function which should accept a number 0..1
74 | * and return a number usually 0..1 but for certain types of easing
75 | * you might want to go outside of the 0..1 range.
76 | *
77 | * - Adding an `ease` property to a keyframe will override the `ease`
78 | * argument of the `tween()` function.
79 | *
80 | * - Wrapping a value with the `ease()` value factory will override
81 | * any keyframe or `tween()`-level easing.
82 | */
83 | export function tween(position, keyframes, easer=identity) {
84 |
85 | // TODO: remove for v1.0
86 | if (process.env.NODE_ENV !== 'production') {
87 | if (!Array.isArray(keyframes)) {
88 | throw Error('tween: as of react-imation@0.5.0, keyframes must be an array')
89 | }
90 | }
91 |
92 | const positions = keyframes.map(k => k[0]);
93 |
94 | const n = positions.length-1;
95 | const position0 = positions[0];
96 | const positionN = positions[n];
97 |
98 | if (position <= position0) return resolveValue(keyframes[0][1]);
99 | if (position >= positionN) return resolveValue(keyframes[n][1]);
100 |
101 | let indexB = 0;
102 | while (position > positions[++indexB]);
103 | const indexA = indexB - 1;
104 |
105 | const positionA = positions[indexA];
106 | const positionB = positions[indexB];
107 |
108 | if (process.env.NODE_ENV !== 'production') {
109 | if (typeof positionA === 'function' || typeof positionB === 'function') {
110 | throw Error('Keyframes are not allowed to contain functions as keys', keyframes);
111 | }
112 | }
113 |
114 | const range = positionB - positionA;
115 | const delta = position - positionA;
116 | const progress = delta / range;
117 |
118 | return tweenValues(
119 | progress,
120 | keyframes[indexA][1],
121 | keyframes[indexB][1],
122 | keyframes[indexA][1].ease || easer);
123 | }
124 |
125 | /**
126 | * ## createTweenValueFactory
127 | *
128 | * The first argument, `formatter` should be a 1-arity function
129 | * which accepts an array (`value`) and returns the formatted result.
130 | * For example, `formatter` might transform the array `[100,0,255]` to "rgb(100,0,255)"
131 | *
132 | * The second (optional) argument, `defaultWrapper` will
133 | * be used to map the elements of the `value` array which is useful
134 | * for wrapping the values in a default unit (like px, %, deg, etc)
135 | *
136 | * return a value factory.
137 | */
138 | export function createTweenValueFactory(formatter, defaultWrapper) {
139 | const tween = (progress, a, b, easer) =>
140 | formatter(tweenValues(progress, a.value, b.value, easer));
141 |
142 | const wrap = v => isWrapped(v) ? v : defaultWrapper(v);
143 |
144 | return defaultWrapper ?
145 | (...value) =>
146 | new TweenValue(value.map(wrap), formatter, tween)
147 | :
148 | (...value) =>
149 | new TweenValue(value, formatter, tween);
150 | }
151 |
152 | class TweenValue {
153 | constructor(value, formatter, tween) {
154 | this.value = value;
155 | this.formatter = formatter;
156 | this.tween = tween;
157 | }
158 |
159 | resolveValue() {
160 | return this.formatter(this.value.map(resolveValue))
161 | }
162 | }
163 |
164 | /**
165 | * combine is a value factory that combines wrapped values (or numbers)
166 | * by seperating them with a space
167 | *
168 | * for example:
169 | *
170 | * combine(scale(0.9), translate3d(0,-160,0))
171 | *
172 | * note that `scale(0.9)` and `translate3d(0,-160,0)`
173 | * both return wrapped values. So in the non-tweened case,
174 | * combine produces:
175 | *
176 | * "scale(0.9) translate3d(0,-160,0)"
177 | */
178 | export function combine(...wrappedValues) {
179 | return new Combine(wrappedValues);
180 | }
181 |
182 | class Combine {
183 | constructor(wrappedValues) {
184 | this.wrappedValues = wrappedValues;
185 | }
186 |
187 | tween(progress,
188 | {wrappedValues: wrappedValuesA},
189 | {wrappedValues: wrappedValuesB},
190 | easer
191 | ) {
192 | return wrappedValuesA
193 | .map((wrappedValueA, index) =>
194 | tweenValues(progress, wrappedValueA, wrappedValuesB[index], easer))
195 | .join(' ');
196 | }
197 |
198 | resolveValue() {
199 | return this.wrappedValues.map(resolveValue).join(' ');
200 | }
201 | }
202 |
203 | /**
204 | * ease is a value factory that will apply
205 | * an easing function to any wrapped value or number.
206 | * Easing is applied between values a and b, but the
207 | * ease factory must wrap value a.
208 | *
209 | * Note:
210 | * Wrapping a value with the `ease()` value factory will override
211 | * tween and keyframe-level easing
212 | **/
213 | export function ease(easer, wrappedValue) {
214 | if (typeof wrappedValue === 'undefined') { // curry
215 | return wrappedValue => ease(easer, wrappedValue);
216 | }
217 |
218 | return new Ease(easer, wrappedValue);
219 | }
220 |
221 | class Ease {
222 | constructor(easer, wrappedValue) {
223 | this.easer = easer;
224 | this.easedValue = wrappedValue;
225 | }
226 |
227 | tween(progress, wrappedValueA, wrappedValueB) {
228 | return tweenValues(
229 | progress,
230 | wrappedValueA.easedValue,
231 | // give flexibility not to wrap b value in the ease factory
232 | wrappedValueB.easedValue ? wrappedValueB.easedValue : wrappedValueB,
233 | this.easer || identity)
234 | }
235 |
236 | resolveValue() {
237 | return resolveValue(this.easedValue);
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/examples/demo1/Game.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {tween, combine} from 'react-imation';
3 | import {Timeline, Timeliner} from 'react-imation/timeline';
4 | import {percent, translate3d, scale, translateX, rotateY} from 'react-imation/tween-value-factories';
5 | import stateful from 'react-stateful-stream';
6 | import {derive, track} from 'react-derive';
7 | import {elegant as optimize} from 'elegant-react';
8 | import {Easer} from 'functional-easing';
9 | import GameOver from './GameOver';
10 | import u from 'updeep';
11 | const immutable = u({});
12 |
13 | const easeOutSine = new Easer().using('out-sine');
14 | const easeInSine = new Easer().using('in-sine');
15 | const easeInBack = new Easer().using('in-back').withParameters(2.8);
16 | const MAX_DROPPED = 100;
17 |
18 | const fullViewportStyle = {
19 | position: 'absolute', height: '100vh', width: '100vw', overflow: 'hidden', minWidth: '800px'
20 | };
21 |
22 | const flakeImages = ["http://i.imgur.com/jbSVFgy.png",
23 | "http://i.imgur.com/TT2lmN4.png",
24 | "http://i.imgur.com/do8589m.png",
25 | "http://i.imgur.com/3BxEO8i.png"];
26 |
27 | const createFlake = id =>
28 | ({
29 | id,
30 | size: 22 + ~~(Math.random() * 30),
31 | rotationSpeed: Math.random() * 40 - 20,
32 | rotateX: ~~(Math.random()*50),
33 | rotateY: ~~(Math.random()*32),
34 | left: ~~(Math.random() * 100) + '%',
35 | drift: ~~(Math.random() * 40) - 15,
36 | image: flakeImages[~~(flakeImages.length * Math.random())],
37 | increment: 0.15 + Math.random()*0.2,
38 | });
39 |
40 | const pffSounds = [1,2,3].map(i => new Audio(`sounds/pf${i}.mp3`));
41 | const pffSoundsCount = pffSounds.length;
42 | const crrSound = new Audio('sounds/crr.mp3');
43 | const gameOverSound = new Audio('sounds/gameover.mp3');
44 |
45 | const randi = limit => ~~(Math.random() * limit);
46 | const playRandomPfSound = () => pffSounds[randi(pffSoundsCount)].play();
47 |
48 | const flakeHasId = id => flake => flake.id === id;
49 | const concat = newItem => items => items.concat(newItem);
50 | const lengthIsLessThan = length => items => items.length < length;
51 | const increment = x => x + 1;
52 | const decrement = x => x - 1;
53 |
54 | @stateful(
55 | immutable(
56 | { flakes: [],
57 | droppedCount: 0,
58 | gameIsOver: false,
59 | score: 0
60 | }),
61 | edit => ({
62 | addFlake: newFlake => edit(u({flakes: u.if(lengthIsLessThan(21),concat(newFlake)),
63 | droppedCount: increment })),
64 | removeFlake: flakeId => edit(u({flakes: u.reject(flakeHasId(flakeId)) })),
65 | explodeFlake: index => edit(u({flakes: { [index]: { explode: true } } })),
66 | playAgain: () => edit(u({gameIsOver: false, score: 0, flakes: [], droppedCount: 0})),
67 | gameOver: () => edit(u({gameIsOver: true})),
68 | addToScore: amount => edit(u({score: x => x + ~~amount })),
69 | }))
70 | export default class Game extends Component {
71 | render() {
72 | const {gameIsOver, flakes, score} = this.props;
73 |
74 | return (
75 |
}
349 | })
350 | }
351 | }
352 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-imation
2 |
3 | This library provides
4 | various composable utilities for creating complex timeline-based animation in a react-y component-driven fashion.
5 |
6 | npm install react-imation --save
7 |
8 | Since this is a library of composable utility functions and components that mostly
9 | don't rely on each other, there is no fully bundled import. This keeps `react-imation`
10 | light-weight. The following imports are available:
11 |
12 | - `react-imation`
13 | - `react-imation/animationFrame`
14 | - `react-imation/Interval`
15 | - `react-imation/timeline`
16 | - `react-imation/tween-value-factories`
17 |
18 | For react-native the following imports are available
19 | (support is limited to a subset of the above, atm):
20 |
21 | - `react-imation/native`
22 | - `react-imation/timeline/native`
23 | - `react-imation/tween-value-factories`
24 |
25 | ## Demos
26 |
27 | - [Demo1](http://gilbox.github.io/react-imation/examples/demo1/demo.html): [[source](https://github.com/gilbox/react-imation/blob/master/examples/demo1/Game.js)] `tween`, `` ***Exploding Snowflakes***
28 | - [Demo2](http://gilbox.github.io/react-imation/examples/demo2/demo.html): [[source](https://github.com/gilbox/react-imation/blob/master/examples/demo2/app.js)] `tween`, ``, `react-motion`
29 | - [Demo3](http://gilbox.github.io/react-imation/examples/demo3/demo.html): [[source](https://github.com/gilbox/react-imation/blob/master/examples/demo3/RollButton.js)] `tween`, ``, `Timeliner`
30 | - [Demo4](http://gilbox.github.io/react-imation/examples/demo4/demo.html): [[source](https://github.com/gilbox/react-imation/blob/master/examples/demo4/Demo4.js)] `tween`, ``
31 | - [Demo5](http://gilbox.github.io/react-imation/examples/demo5/demo.html): [[source](https://github.com/gilbox/react-imation/blob/master/examples/demo5/Demo5.js)] `tween`, ``, `ease`
32 |
33 | Also check out [`react-track`](https://github.com/gilbox/react-track)'s'
34 | demo which combines `react-imation` tweening with
35 | DOM tracking.
36 |
37 | If you clone this repo you can run the demos locally via:
38 |
39 | npm install
40 | npm run examples
41 |
42 | #### In the Wild
43 |
44 | - [airbnb javascript](http://airbnb.io/projects/javascript/)
45 | - Your demo? ([edit this file](https://github.com/gilbox/react-imation/edit/master/README.md))
46 |
47 | ## [`tween(currentFrame, keyframes, [ease])`](https://github.com/gilbox/react-imation/blob/master/src/tween.js)
48 |
49 | The first argument, `currentFrame` is a number representing the current
50 | position in the animation **timeline**.
51 |
52 | The aforementioned **timeline** is represented by the `keyframes`
53 | argument which is an array of `[key, value]` touples.
54 | The 2 components of each touple represents a timeline
55 | position and it's state, respectively.
56 | Note that `tween` assumes that the keyframes are sorted.
57 |
58 | ```jsx
59 | import {tween} from 'react-imation';
60 | import {rotate} from 'react-imation/tween-value-factories';
61 |
62 | // ...render:
63 |
69 | spin
70 |
71 | ```
72 |
73 | *Note: Support for object typed `keyframes` param
74 | has been removed as of `react-imation@0.5.0`*
75 |
76 | Tweening values that require special formatting is
77 | super-easy. All you have to do is create a new
78 | tween value factory. Check out
79 | [`tween-value-factories.js`](https://github.com/gilbox/react-imation/blob/master/src/tween-value-factories.js)
80 | and you'll see what I mean.
81 |
82 | #### `tween`: tweening numbers
83 |
84 | While `tween` works with more sophisticated *wrapped* values as demonstrated
85 | above, it also works with regular numbers. Here are some examples:
86 |
87 | tween(0.5, [[0, 10], [1, 20]]); //=> 15
88 |
89 | tween(5, [[0, 10], [10, 20]]); //=> 15
90 |
91 | tween(10, [[0, 0 ],
92 | [20, 10],
93 | [30, 20]]); //=> 5
94 |
95 | tween(5, [[0,10], [5,0]]); //=> 5
96 |
97 | You can use this approach to tween styles:
98 |
99 | ```jsx
100 |
101 | spin
102 |
103 | ```
104 |
105 | You can tween all of your styles this way and it will work fine.
106 | However, when you have a lot of styles this can get tedious and difficult
107 | to read. For this reason, `tween` supports using *wrapped values*.
108 | Read the next section about creating *wrapped values* using
109 | tween value factories (TvFs).
110 |
111 | #### `tween`: creating wrapped values with tween value factories (TvFs)
112 |
113 | Wrapped values represent complex values which we ultimately need
114 | to convert to strings in order to generate CSS values. We can
115 | create wrapped values easily with tween value factories.
116 |
117 | Here are the two most complex value factories:
118 |
119 | import {combine, ease} from 'react-imation';
120 |
121 | Here are some simple value factories:
122 |
123 | import {rotate, turn, px, translateX} from 'react-imation/tween-value-factories';
124 |
125 | I call these tween value factories *simple* because they are extremely easy to create.
126 | To create a simple tween value factory first import the `createTweenValueFactory`
127 | function
128 |
129 | import {createTweenValueFactory} from 'react-imation';
130 |
131 | and then use it like this:
132 |
133 | const px = createTweenValueFactory(value => `${value}px`);
134 | const translate3d = createTweenValueFactory(value => `translate3d(${value.join(',')})`, px);
135 |
136 | now the value of `translate3d` is a function which can create *wrapped values*.
137 | For example,
138 |
139 | const t = translate3d(100, 50, 80); // instantiate a wrapped value `t`
140 | t.resolveValue(); //=> "translate3d(100px,50px,80px)"
141 |
142 | note that calling `resolveValue` on the wrapped value `t` returns it's
143 | string representation. You will never have to do this explicitly because
144 | the `tween` function does it for you.
145 |
146 | Consider `translate3d` again
147 |
148 | const px = createTweenValueFactory(value => `${value}px`);
149 | const translate3d = createTweenValueFactory(value => `translate3d(${value.join(',')})`, px);
150 |
151 | Notice that we are passing the tween value factory `px` as the second
152 | argument of `createTweenValueFactory`. This tells `createTweenValueFactory`
153 | to create a value factory that automatically wraps each of its arguments
154 | which are *plain numbers* utilizing another
155 | value factory (`px`) before passing it into it's own value factory.
156 |
157 | Consider the TVF `percent`
158 |
159 | const percent = createTweenValueFactory(value => `${value}%`);
160 |
161 | We can use this with the `translate3d` TvF
162 |
163 | const t = translate3d(percent(50), percent(20), 200);
164 | t.resolveValue(); //=> "translate3d(50%,20%,200px)"
165 |
166 | Note that since we did not wrap the third argument in a TvF,
167 | it was wrapped automatically by the `px` TvF and that is why
168 | calling `t.resolveValue()` produced `200px` for the third argument.
169 |
170 | #### `tween`: tweening wrapped values
171 |
172 | The real power and elegance of the `tween` function becomes apparent
173 | when you use it with TvFs (that produce wrapped values).
174 | One of the primary goals of react-imation is to create a highly
175 | readable and intuitive API for animation.
176 |
177 |
178 | const t = tween(30, [ [ 0, rotate(0) ],
179 | [60, rotate(360)] ])
180 |
181 | t.resolveValue(); //=> "rotate(180deg)"
182 |
183 | In react we can use this in a style tag:
184 |
185 | ```jsx
186 |
192 | ```
193 |
194 | #### `tween`: tweening object literals
195 |
196 | Tweening object literals means that we are
197 | actually tweening the values within those objects and returning
198 | a new object with a similar shape. This works
199 | with both numbers and wrapped values.
200 |
201 | ```jsx
202 |
208 | spin
209 |
210 | ```
211 |
212 | The result is something like this:
213 |
214 | ```jsx
215 |
216 | spin
217 |
218 | ```
219 |
220 | The real advantage of using object literals is
221 | that it allows you to tween multiple style properties
222 | in one `tween()`:
223 |
224 | ```jsx
225 |
231 | spin
232 |
233 | ```
234 |
235 | the result is something like:
236 |
237 | ```jsx
238 |
240 | spin
241 |
242 | ```
243 |
244 | **warning**: All keyframes in a single tween must have exactly the same
245 | properties. The only exception to this is when using easing.
246 |
247 | #### `tween`: easing
248 |
249 | An easing function is a function that accepts a single argument,
250 | `time` and returns `time`. There are many libraries out there already
251 | that provide easing functions, or you can write your own. The one
252 | I've been using is `functional-easing`.
253 |
254 | There are three ways to ease with `tween`:
255 |
256 | 1. Pass the easing function as the third argument to `tween`.
257 | 2. When tweening a plain object, add an `ease` property.
258 | The easing will apply to all properties in the keyframe.
259 | For example:
260 |
261 | import {Easer} from 'functional-easing';
262 | const easeOutSine = new Easer().using('out-sine');
263 |
264 |
270 | spin
271 |
272 |
273 | 3. Wrap a TvF in the `ease` TvF. The `ease` TvF will override
274 | any other type of easing.
275 |
276 |
282 | spin
283 |
284 |
285 | **Heads-up: Doing `rotate(ease(easeOutSine, 0))`
286 | instead of `ease(easeOutSine, rotate(0))` unfortunately
287 | does *not* work.**
288 |
289 | Note that we did not wrap `rotate(360)` with `ease()`. Wrapping the
290 | destination value is optional because the source's easing function
291 | is always the one that `tween` applies.
292 |
293 | The `ease()` TvF is automatically curried, so we can also use
294 | it like this:
295 |
296 | const easeOutSine = ease(new Easer().using('out-sine'));
297 |
298 |
304 | spin
305 |
306 |
307 | **Heads-up: Doing `rotate(easeOutSine(0))` instead of
308 | `easeOutSine(rotate(0))` unfortunately
309 | does *not* work.**
310 |
311 | #### `tween`: combine TvF
312 |
313 | `combine` works as you might expect.
314 |
315 | combine(rotate(90), translateX(100))
316 | .resolveValue(); //=> "rotate(90deg) translateX(100px)"
317 |
318 |
319 | ## ``
320 |
321 | import Interval from 'react-imation/Interval';
322 |
323 | Stateless component providing an
324 | easy way to repeatedly set an interval.
325 | It extracts away the react lifecycle challenges
326 | so that all you have to think about is what to do
327 | every tick and how to schedule the next interval.
328 |
329 | {
330 | console.log('tick!');
331 | scheduleTick(1000); // schedule next tick for 1 second from now
332 | }} />;
333 |
334 | ## `animationFrame`
335 |
336 | import { animationFrame } from 'react-imation/animationFrame';
337 |
338 | Stateless ticking decorator that manages destroying
339 | requestAnimationFrame when component unmounts.
340 | All you have to supply is the only argument,
341 | a `callback` function
342 | which gets called every tick.
343 |
344 | **ES7 Decorator:** (with class-based component)
345 |
346 | @animationFrame(({onTick}) => onTick())
347 | class Foo extends Component {
348 | render() {
349 | return
359 | )
360 |
361 | In both examples above we assume an `onTick` prop
362 | is being passed down and it will handle each
363 | tick event.
364 |
365 | ## ``
366 |
367 | import { AnimationFrame } from 'react-imation/animationFrame';
368 |
369 | Stateless ticking component. Just supply a callback
370 | function to `onTick` prop.
371 |
372 | console.log('tick'))}>
373 |
374 |
375 | ## [``](https://github.com/gilbox/react-imation/blob/master/src/timeline/timeline.js)
376 |
377 | import { Timeline, Timeliner } from 'react-imation/timeline'
378 |
379 | Timeline as a component is super-handy. It manages the state of `time`.
380 |
381 | ```jsx
382 |
387 | {({time, playing, togglePlay, setTime}) =>
388 |
389 |
390 | The timeline is {playing ? '' : 'not '}playing!
391 | Current time is {time}.
392 |
393 | We can easily create a pause button like this:
394 |
397 |
398 |
399 |
400 | ... or jump around the timeline:
401 |
404 |
405 | ... and tween to spin some text:
406 |
412 | spin
413 |
414 |
415 |
416 | }
417 | ```
418 |
419 | #### ``: overview
420 |
421 | It accepts a single child which should be a function.
422 | When rendered, Timeline calls the function by passing in as the first
423 | argument an instance of the `Timeliner` class.
424 |
425 | **Note:** Because this is a stateful component, it will work well for simple
426 | use-cases. If you have more complex needs, using the `timeliner` prop
427 | (described below) might get you a *bit* further, but consider using
428 | the following lighter-weight stateless abstractions instead:
429 |
430 | - [``](#interval-)
431 | - [`animationFrame`](#animationframe)
432 | - [``](#animationframe-)
433 |
434 | they compose well in a system
435 | with reactive state management. Check out
436 | [react-three-ejecta-boilerplate](https://github.com/gilbox/react-three-ejecta-boilerplate)
437 | which is an example that utilizes
438 | [react-stateful-stream](https://github.com/gilbox/react-stateful-stream)
439 | for state management.
440 |
441 |
442 | #### ``: the [**`Timeliner`**](https://github.com/gilbox/react-imation/blob/master/src/timeline/timeline.js) class and `timeliner` prop
443 |
444 | The [`Timeliner`](https://github.com/gilbox/react-imation/blob/master/src/timeline/timeline.js)
445 | class does the heavy lifting of scheduling animation
446 | frames and storing the value of `time`. When using the ``
447 | component you can provide or omit a `timeliner` prop. By omitting the
448 | `timeliner` prop you are instructing `` to instantiate and
449 | manage an instance of the `Timeliner` class all by itself.
450 |
451 | In many cases,
452 | omitting the `timeliner` prop works very well. However, sometimes you
453 | need the added flexibility of *lifting* the state management functionality
454 | outside of the `` component. Here's what it looks like when
455 | we provide a `timeliner` prop:
456 |
457 | ```jsx
458 | const timeliner = new Timeliner();
459 | timeliner.play();
460 |
461 |
462 |
468 |
469 | {this.state.showTimeline &&
470 |
471 | {({time}) =>
472 | `The current time is {time}`
473 | }
474 |
475 | ```
476 |
477 | Notice how we can mount/unmount the `` component
478 | without losing it's state, and since the `timeliner` instance has
479 | been lifted outside of the `` component, when the component
480 | is re-mounted it works the same as if it had been mounted all along.
481 |
482 | The single most important property of the `Timeliner` class is `time`.
483 | Let's take a look at the function we passed in as the child of the
484 | `` component from the previous example:
485 |
486 | ({time}) => `The current time is {time}`
487 |
488 | Remember, when `` calls this function it will pass in
489 | an instance of the `Timeliner` class. Our function uses *object destructuring*
490 | to get the value of the `time` property.
491 |
492 | You can access *methods* on the `Timeliner` instance via destructuring
493 | as well. All of the methods exposed by `Timeliner` are automatically
494 | bound to the `Timeliner` instance so that they work in this way.
495 |
496 | #### ``: the partially applied `tween` function
497 |
498 | The Timeliner class exposes a `tween` method which is the same `tween` function
499 | we've discussed, with the first argument already applied. The following two expressions
500 | are equivalent:
501 |
502 | tween(timeliner.time, [[0,0], [60,100]]);
503 |
504 | timeliner.tween([[0,0], [60,100]]);
505 |
506 | The happy consequence is that with `` you can use destructuring
507 | to easily access `Timeliner#tween`:
508 |
509 | ```jsx
510 |
511 | {({tween}) =>
512 |
516 | I change color!
517 |
518 | }
519 | ```
520 |
521 |
522 | ## react-native support
523 |
524 | Supports react-native as of `v0.2.6`, however performance is not so good
525 | because react-native works best when native props are manipulated directly.
526 |
--------------------------------------------------------------------------------