├── .gitignore
├── LICENSE.md
├── README.md
├── chief.js
├── example
├── chief
│ ├── button.js
│ └── index.js
└── f1
│ ├── index.js
│ ├── parsers.js
│ ├── states.js
│ └── transitions.js
├── images
├── chief-preview.gif
└── f1-preview.gif
├── index.js
├── lib
├── parsers
│ └── getParser.js
├── states
│ └── parseStates.js
├── targets
│ └── parseTargets.js
└── transitions
│ ├── createTransitions.js
│ ├── defaultTransition.js
│ └── parseTransitions.js
├── package.json
└── test
├── advancedAnimation
├── expectedStates.js
├── expectedUpdates.js
└── index.js
├── callbackAndUpdate.js
├── chief
├── getUI.js
└── index.js
├── defineConstrutor.js
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | *.log
4 | .DS_Store
5 | bundle.js
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2014 Mikko Haapoja
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
20 | OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # f1
2 |
3 | [](http://github.com/badges/stability-badges)
4 |
5 | F1 is a stateful ui library. F1 is the "core" for modules such as:
6 | - [`f1-dom`](https://www.npmjs.com/f1-dom)
7 | - [`react-f1`](https://www.npmjs.com/react-f1)
8 |
9 | [](https://github.com/Jam3/f1/tree/master/example/chief)
10 |
11 | ## Table Of Contents
12 |
13 | - [Two parts of `f1`](#two-parts-of-f1)
14 | + [`f1`](#f1-1)
15 | + [`chief`](#chief)
16 | - [Examples](#example)
17 | + [`f1`](#example-f1)
18 | + [`chief`](#example-chief)
19 | - [API Documentation](#api-documentation)
20 | + [`var ui = f1()`](#var-ui--requiref1opts)
21 | * [`ui.targets(targets)`](#uitargetstargets)
22 | * [`ui.states(states)`](#uistatesstates)
23 | * [`ui.transitions(transitions)`](#uitransitionstransitions)
24 | * [`ui.parsers(parserDefinition)`](#uiparsersparserdefinition)
25 | * [`ui.init(initState)`](#uiinitinitstate)
26 | * [`ui.go(state, [cb])`](#uigostate-cb-1)
27 | * [`ui.update()`](#uiupdate)
28 | + [`var page = chief()`](#var-page--requiref1chiefopts)
29 | * [`page.targets(targets)`](#pagetargetstargets)
30 | * [`page.states(states)`](#pagestatesstates)
31 | * [`page.transitions(transitions)`](#pagetransitionstransitions)
32 | * [`page.init(initState)`](#pageinitinitstate)
33 | * [`page.go(state, [cb])`](#pagegostate-cb)
34 |
35 |
36 | ## Two parts of `f1`
37 |
38 | There are two parts to this module `f1` and `chief`.
39 |
40 | #### `f1`
41 |
42 | Designed to build complete UI animations.
43 |
44 | First define states/the look of the piece of ui.
45 |
46 | After designing the look you define transitions/animations for the piece of ui.
47 |
48 | Since `f1` is designed to target many platforms you will also need to define parers which transfers the designed look to the target being animated.
49 |
50 | #### `chief`
51 |
52 | Designed to control `f1` ui instances. So let's assume you have a "page" of a site you can have `chief` control ui pieces's states by calling their `go` functions.
53 |
54 |
55 | ## Usage
56 |
57 | [](https://www.npmjs.com/package/f1)
58 |
59 | ### Example
60 |
61 | There are two small examples for `f1` and `Chief`. They exist in the `example/`folder in the `f1` repo. You can run the examples locally via npm scripts.
62 |
63 | Each example is commented to explain as much as possible and can be easily modified and played around with when running via npm scripts mentioned below.
64 |
65 | ### Example `f1`
66 | [](https://github.com/Jam3/f1/tree/master/example/f1)
67 |
68 | ```bash
69 | $ npm run example-f1
70 | ```
71 |
72 | [Source can be viewed here](https://github.com/Jam3/f1/tree/master/example/f1)
73 |
74 | ### Example `chief`
75 | [](https://github.com/Jam3/f1/tree/master/example/chief)
76 |
77 | ```bash
78 | $ npm run example-chief
79 | ```
80 |
81 | [Source can be viewed here](https://github.com/Jam3/f1/tree/master/example/f1)
82 |
83 | ## API Documentation
84 |
85 | ### `var ui = require('f1')([opts])`
86 |
87 | To construct an `f1` instance you can pass in an optional settings object. The following are properties you can pass in settings:
88 | ```javascript
89 | {
90 | // an object which contains all elements/items that you will be animating
91 | // eg. bgElement could be a
if working with the DOM
92 | targets: {
93 | bg: bgElement
94 | },
95 |
96 | // all states for the ui
97 | // states contain ui piece which "hook" up to targets
98 | // eg out.bg defines what targets.bg will look like in the out state
99 | states: {
100 | out: {
101 | bg: { alpha: 0 }
102 | },
103 |
104 | idle: {
105 | bg: { alpha: 1 }
106 | }
107 | },
108 |
109 | // an array which defines the transitions for the ui
110 | transitions: [
111 | // this ui can go from out to idle and idle to out
112 | { from: 'out', to: 'idle', bi: true}
113 | ],
114 |
115 | // An Object contains init and update functions. These will be used
116 | // to initialize your ui elements and apply state to targets during update.
117 | // Parsers are defined to make `f1` work on any platform eg. React, DOM,
118 | // Canvas, SVG, Three.js, etc.
119 | //
120 | // Above in states we define alpha this could be passed
121 | // to the DOM's style.opacity for instance
122 | parsers: {
123 | init: [ initPosition ],
124 | update: [ applyPosition ]
125 | },
126 |
127 | // callback called whenever f1 reaches a state
128 | onState: listenerState,
129 |
130 | // callback called whenever f1 is updating
131 | onUpdate: listenerUpdater
132 | }
133 | ```
134 |
135 | ### ui.targets(targets)
136 |
137 | define which items are going to be animated. Pass in an object
138 | which will look something like this:
139 | ```javascript
140 | var ui = require('f1')();
141 |
142 | ui.targets( {
143 | itemToAnimate1: find('#itemToAnimate1'),
144 | itemToAnimate2: find('#itemToAnimate2')
145 | });
146 | ```
147 | The `Object` being passed in should have variable names which will
148 | associate to data which will be defined when setting up states in the
149 | `ui.states` method. In this case `itemToAnimate1` and `itemToAnimate2` should be also defined in `ui.states`.
150 |
151 | The values of the targets object can be for instance DOM elements if working with the DOM or Three.js `THREE.Mesh` if working with Three.js.
152 |
153 |
154 |
155 |
156 | ### `ui.states(states)`
157 |
158 | defines the states which this `f1` instance will use.
159 |
160 | States are defined as objects. It could look something like this:
161 | ```javascript
162 | var ui = require('f1')();
163 |
164 | ui.states( {
165 |
166 | out: {
167 | itemToAnimate1: {
168 | variableToAnimate: 0
169 | },
170 |
171 | itemToAnimate2: {
172 | variableToAnimate: 0
173 | }
174 | },
175 |
176 | idle: {
177 | itemToAnimate1: {
178 | variableToAnimate: 1
179 | },
180 |
181 | itemToAnimate2: {
182 | variableToAnimate: 2
183 | }
184 | }
185 | });
186 | ```
187 | Above two states would be created: `out` and `idle`. Both would animate two
188 | objects: `itemToAnimate1` and `itemToAnimate2`. And in both of those objects
189 | the property `variableToAnimate` is defined. So if we were to transition from
190 | `out` to `idle` in `itemToAnimate1` `variableToAnimate` would transition from
191 | 0 to 1 and in `itemToAnimate2` from 0 to 2.
192 |
193 | States can also be defined by passing in objects for instance the above could
194 | be changed to look like this:
195 | ```javascript
196 | var ui = require('f1')();
197 |
198 | ui.states( {
199 | out: function(stateName) {
200 | console.log(stateName); // "out"
201 |
202 | return {
203 | itemToAnimate1: {
204 | variableToAnimate: 0
205 | },
206 |
207 | itemToAnimate2: {
208 | variableToAnimate: 0
209 | }
210 | };
211 | },
212 |
213 | idle: function(stateName) {
214 | console.log(stateName); // "idle"
215 |
216 | return {
217 | itemToAnimate1: {
218 | variableToAnimate: 1
219 | },
220 |
221 | itemToAnimate2: {
222 | variableToAnimate: 2
223 | }
224 | };
225 | }
226 | });
227 | ```
228 | The above can be handy when there are many items which states must be defined for instance a menu with many buttons.
229 |
230 |
231 |
232 | ### `ui.transitions(transitions)`
233 |
234 | defines how this `f1` instance can animate between states.
235 |
236 | For instance if we had two states out and idle you could define your transitions like this:
237 |
238 | ```javascript
239 | var ui = require('f1')();
240 |
241 | ui.transitions( [
242 | { from: 'idle', to: 'rollOver', animation: { duration: 0.25 } },
243 | { from: 'rollOver', to: 'idle', animation: { duration: 0.1 } }
244 | ]);
245 | ```
246 |
247 | Note that transitions are not bi-directional. If you'd like to create a bi-directional transition use `bi: true`:
248 | ```javascript
249 | var ui = require('f1')();
250 |
251 | ui.transitions( [
252 | { from: 'idle', to: 'rollOver', bi: true, animation: { duration: 0.25 } }
253 | ]);
254 | ```
255 |
256 | If you simply just defined `from` and `to` and omitted the `animation` Object a default animation would be applied between states. This default transition will have a duration of 0.5 seconds and use a Linear ease.
257 |
258 | If you want to modify the animation duration and ease you can define your transitions like this:
259 |
260 | ```javascript
261 | var eases = require('eases');
262 | var ui = require('f1')();
263 |
264 | ui.transitions( [
265 | {
266 | from: 'idle',
267 | to: 'rollOver',
268 | animation: {
269 | duration: 0.25,
270 | ease: eases.quadOut
271 | }
272 | },
273 | {
274 | from: 'rollOver',
275 | to: 'idle',
276 | animation: {
277 | duration: 0.1
278 | ease: eases.expoOut
279 | }
280 | }
281 | ]);
282 | ```
283 |
284 | Defining your transitions using the above syntax will cause all properties to animate using the duration and ease defined.
285 |
286 | Ease functions should take a t or time value between 0-1 and return a modified time value between 0-1. Typically you might use the [`eases`](https://www.npmjs.com/eases) module.
287 |
288 | You can also animate ui properties individually:
289 | ```javascript
290 | var eases = require('eases');
291 | var ui = require('f1')();
292 |
293 | ui.transitions( [
294 | {
295 | from: 'out',
296 | to: 'idle',
297 | animation: {
298 | duration: 1, ease: eases.expoOut,
299 |
300 | position: { duration: 0.5, delay: 0.5, ease: eases.quadOut },
301 | alpha: { duration: 0.5 }
302 | }
303 | },
304 | {
305 | from: 'idle',
306 | to: 'out',
307 | animation: { duration: 0.5, ease: eases.expoIn }
308 | }
309 | ]);
310 | ```
311 |
312 | In the above example every property besides `position` and `alpha` will have a duration of one second using `eases.quadOut` ease. `position` will have a duration of 0.5 seconds and will be delayed 0.5 seconds and will use the `eases.quadOut` easing function. `alpha` will simply have a duration of 0.5 seconds.
313 |
314 | For advanced transitions you can pass in a function instead like so:
315 | ```javascript
316 |
317 | ui.transitions( [
318 | {
319 | from: 'out',
320 | to: 'idle',
321 | animation: {
322 | duration: 1, ease: eases.expoOut,
323 | alpha: function(time, start, end) {
324 | return (end - start) * time + start;
325 | }
326 | }
327 | },
328 | {
329 | from: 'idle',
330 | to: 'out',
331 | animation: { duration: 0.5, ease: eases.expoIn }
332 | }
333 | ]);
334 | ```
335 |
336 | The animation is the same as in the previous example however `alpha` will be calculated using a custom transition function.
337 |
338 |
339 |
340 |
341 | ### `ui.parsers(parserDefinition)`
342 |
343 | `f1` can target many different platforms. How it does this is by using parsers which can target different platforms. Parsers apply calculated state objects to targets.
344 |
345 | If working with the DOM for instance your state could define values which will be applied by the parser to the DOM elements style object. (see the `f1` example)
346 |
347 | When calling parsers pass in an Object that can contain variables `init`, and `update`. Both should contain Array's of functions which will be used to either init or update ui.
348 |
349 | `init` functions will receive: states definition, targets definition, and transitions definition. `init` will only be called once when the `f1` instance will inited.
350 |
351 | Example:
352 | ```javascript
353 | function initPosition(states, targets, transitions) {
354 | // usesPosition would check if the position property is used
355 | // by states if so initialize targets to be able to do something
356 | // with position
357 | if(usesPosition(states)) {
358 | // do whatever is needed to targets
359 | // if the position property is used
360 | }
361 | }
362 | ```
363 |
364 | `update` functions will receive: target and state. Where target could be for instance a dom element and state is the currently calculated state.
365 |
366 | Example:
367 | ```javascript
368 | function updatePosition(target, state) {
369 | target.style.left = state.position[ 0 ] + 'px';
370 | target.style.top = state.position[ 1 ] + 'px';
371 | }
372 | ```
373 |
374 | It should be noted that parsers can be called multiple times with different definitions and `init` and `update` functions will be merged.
375 |
376 |
377 |
378 |
379 | ### `ui.init(initState)`
380 |
381 | Initializes `f1`. `init` will throw errors if required parameters such as
382 | states, transitions, targets, and parsers are missing. The `initState` for the `f1` instance should be passed in as string.
383 |
384 |
385 |
386 |
387 | ### `ui.go(state, [cb])`
388 |
389 | Will tell `f1` to animate to another state. Calling `go` will cause `f1` to calculate a path defined through transitions to the state which was passed to it.
390 |
391 | An optional callback can be passed which is called once `f1` has reached that state.
392 |
393 |
394 |
395 |
396 | ### `ui.go(state, [cb])`
397 |
398 | Will tell `f1` to immediately jump to another state. If an animation is in progress that animation is stopped immediately.
399 |
400 |
401 |
402 |
403 | ### `ui.update()`
404 |
405 | Will force `f1` to update. This is useful if updating a state values dynamically by mouse movement or using some other method.
406 |
407 | Call `ui.update` to ensure the state gets applied through `parsers` to `targets`.
408 |
409 |
410 |
411 | ### `var page = require('f1/chief')([opts])`
412 |
413 | `chief` is designed to control `f1` instances so it's ideal for creating "pages" or ui components which merge many other ui components.
414 |
415 | `require('f1/chief')` is a function that you can optionally pass options/settings to. It should be noted that all options have an associated function. You can pass in the following:
416 |
417 | ```javascript
418 | {
419 | // ui and chief instances this chief instance will control
420 | // it should be noted that one chief instance can
421 | // control another chief instance
422 | targets: {
423 | ui1: f1Instance,
424 | ui2: f1Instance,
425 | ui3: chiefInstance,
426 | },
427 |
428 | // define the states in which all above ui instances per state
429 | states: {
430 | out: {
431 | ui1: 'out',
432 | ui2: 'out',
433 | ui3: 'out'
434 | },
435 |
436 | idle: {
437 | ui1: 'idle',
438 | ui2: 'idle',
439 | ui3: 'idle'
440 | }
441 | },
442 |
443 | // defines transitions between chief's states
444 | // it should be noted you can apply delay's when defining
445 | // animations.
446 | transitions: [
447 | {
448 | from: 'out', to: 'idle', bi: true, animation: {
449 | ui3: { delay: 0.5 }
450 | }
451 | }
452 | ]
453 | }
454 | ```
455 |
456 |
457 |
458 |
459 | ### `page.targets(targets)`
460 |
461 | `targets` is an Object that will define what ui/`f1` instances `chief` will control. It should be noted that chiefs can control other chiefs.
462 |
463 | The `targets` arguments might look like this:
464 | ```javascript
465 | targets: {
466 | ui1: f1Instance,
467 | ui2: f1Instance,
468 | ui3: chiefInstance,
469 | }
470 | ```
471 |
472 | ### `page.states(states)`
473 |
474 | `states` is an Object that will define what state the associated targets should be in as the `chief` instance changes states.
475 |
476 | An example `states` arguments:
477 | ```javascript
478 | states: {
479 | out: {
480 | ui1: 'out',
481 | ui2: 'out',
482 | ui3: 'out'
483 | },
484 |
485 | idle: {
486 | ui1: 'idle',
487 | ui2: 'idle',
488 | ui3: 'idle'
489 | }
490 | }
491 | ```
492 |
493 |
494 |
495 | ### `page.transitions(transitions)`
496 |
497 | `transitions` is an Array that will define how chief will be abe to traverse/navigate through states.
498 |
499 | Example `transitions` argument:
500 | ```
501 | [
502 | {
503 | from: 'out', to: 'idle', bi: true, animation: {
504 | ui3: { delay: 0.5 }
505 | }
506 | }
507 | ]
508 | ```
509 |
510 |
511 |
512 | ### `page.init(initState)`
513 |
514 | `init` will simply initialize chief to be in a state.
515 |
516 |
517 |
518 | ### `page.go(state, [cb])`
519 |
520 | `go` will tell chief to animate to a state.
521 |
522 |
523 | ## License
524 |
525 | MIT, see [LICENSE.md](http://github.com/mikkoh/f1/blob/master/LICENSE.md) for details.
526 |
--------------------------------------------------------------------------------
/chief.js:
--------------------------------------------------------------------------------
1 | var kimi = require('kimi');
2 | var noOp = require('no-op');
3 | var parseStates = require('./lib/states/parseStates');
4 | var parseTransitions = require('./lib/transitions/parseTransitions');
5 | var extend = require('deep-extend');
6 |
7 | module.exports = function(options) {
8 |
9 | var opts = options || {};
10 | var driver = kimi({
11 | manualStep: opts.autoUpdate === undefined ? false : !opts.autoUpdate,
12 | onUpdate: onUpdate.bind(undefined, onTargetInState)
13 | });
14 | var onInState = noOp;
15 | var stateChief;
16 | var currentTargetState;
17 | var targetsInState;
18 |
19 | var chief = {
20 | targets: function(targets) {
21 | opts.targets = targets;
22 | },
23 |
24 | states: function(states) {
25 | opts.states = states;
26 | },
27 |
28 | transitions: function(transitions) {
29 | opts.transitions = transitions;
30 | },
31 |
32 | init: function(state) {
33 | var transitions;
34 |
35 | if(opts.targets === undefined) {
36 | throw new Error('You must pass in targets to chief');
37 | }
38 |
39 | if(opts.states === undefined) {
40 | throw new Error('You must pass in states to chief');
41 | }
42 |
43 | if(opts.transitions === undefined) {
44 | throw new Error('You must pass in transitions to chief');
45 | }
46 |
47 | // for chief we want to make the default duration to be 0
48 | transitions = opts.transitions.map(function(transition) {
49 | transition = extend(
50 | {},
51 | transition
52 | );
53 |
54 | transition.animation = transition.animation || {};
55 | transition.animation.duration = transition.animation.duration === undefined ? 0 : transition.animation.duration;
56 |
57 | return transition;
58 | });
59 |
60 | parseStates(driver, opts.states);
61 | parseTransitions(driver, opts.states, transitions);
62 |
63 | stateChief = state;
64 | currentTargetState = {};
65 |
66 | driver.init(state);
67 |
68 | return this;
69 | },
70 |
71 | destroy: function() {
72 | driver.destroy();
73 | },
74 |
75 | go: function(state, onComplete) {
76 | stateChief = state;
77 | currentTargetState = {};
78 |
79 | targetsInState = Object.keys(opts.states[ state ])
80 | .reduce(function(targetsInState, key) {
81 | targetsInState[ key ] = false;
82 |
83 | return targetsInState;
84 | }, {});
85 |
86 | onInState = onComplete || noOp;
87 |
88 | driver.go(state);
89 |
90 | return this;
91 | },
92 |
93 | step: function(deltaTime) {
94 |
95 | driver.step(deltaTime);
96 |
97 | return this;
98 | }
99 | };
100 |
101 | return chief;
102 |
103 | function onUpdate(onTargetInState, state, stateName, time) {
104 | for(var target in state) {
105 | var ui = opts.targets[ target ];
106 | var toState = state[ target ];
107 |
108 | if(ui) {
109 | if(!ui.isInitialized) {
110 | currentTargetState[ target ] = toState;
111 | ui.init(toState);
112 | } else if(currentTargetState[ target ] !== toState) {
113 | currentTargetState[ target ] = toState;
114 | ui.go(toState, onTargetInState.bind(undefined, target, toState));
115 | }
116 | }
117 | }
118 |
119 | if(opts.onUpdate) {
120 | opts.onUpdate(state, stateName);
121 | }
122 | }
123 |
124 | function onTargetInState(target, targetState) {
125 | // set the current target as being in the state it should be in
126 | targetsInState[ target ] = opts.states[ stateChief ][ target ] === targetState;
127 |
128 | // now check if all states are in the state they should be in
129 | var allInState = Object.keys(targetsInState)
130 | .reduce(function(allInState, key) {
131 | return allInState && targetsInState[ key ];
132 | }, true);
133 |
134 | if(allInState) {
135 | onInState(opts.states[ stateChief ], stateChief);
136 | }
137 | }
138 | };
--------------------------------------------------------------------------------
/example/chief/button.js:
--------------------------------------------------------------------------------
1 | var f1 = require('../..');
2 |
3 | module.exports = function(opts) {
4 |
5 | opts = opts || {};
6 | opts.width = opts.width || 200;
7 | opts.height = opts.height || 40;
8 | opts.color1 = opts.color1 || '#00CAFE';
9 | opts.color2 = opts.color2 || '#CAFE00';
10 | opts.text = opts.text || "I'm a button";
11 | opts.zIndex = opts.zIndex || 0;
12 |
13 | var elButton = document.createElement('div');
14 | var elColor1 = document.createElement('div');
15 | var elColor2 = document.createElement('div');
16 |
17 | elButton.style.zIndex = opts.zIndex;
18 |
19 | elColor1.style.boxSizing =
20 | elColor2.style.boxSizing = 'border-box';
21 |
22 | elColor1.style.fontFamily =
23 | elColor1.style.fontFamily = 'Georgia, serif';
24 |
25 | elColor1.innerText = elColor2.innerText = opts.text;
26 |
27 | elButton.style.position = 'relative';
28 | elColor1.style.position = elColor2.style.position = 'absolute';
29 |
30 | elButton.style.width =
31 | elColor1.style.width =
32 | elColor2.style.width = opts.width + 'px';
33 |
34 | elButton.style.height =
35 | elColor1.style.height =
36 | elColor2.style.height = opts.height + 'px';
37 |
38 | elColor1.style.backgroundColor = opts.color1;
39 | elColor2.style.backgroundColor = opts.color2;
40 | elColor1.style.transformOrigin = '0% 0%';
41 | elColor2.style.transformOrigin = '0% 0%';
42 |
43 | elColor1.style.paddingLeft =
44 | elColor2.style.paddingLeft =
45 | elColor1.style.paddingTop =
46 | elColor2.style.paddingTop = '12px';
47 |
48 | elButton.appendChild(elColor1);
49 | elButton.appendChild(elColor2);
50 |
51 | var ui = f1({
52 | targets: {
53 | color1: elColor1,
54 | color2: elColor2
55 | },
56 |
57 | states: {
58 | out: {
59 | color1: {
60 | rotate: 0,
61 | alpha: 0,
62 | brightness: 1
63 | },
64 |
65 | color2: {
66 | rotate: -90,
67 | alpha: 0,
68 | brightness: 0.8
69 | }
70 | },
71 |
72 | idle: {
73 | color1: {
74 | rotate: 0,
75 | alpha: 1,
76 | brightness: 1
77 | },
78 |
79 | color2: {
80 | rotate: -90,
81 | alpha: 1,
82 | brightness: 0.8
83 | }
84 | },
85 |
86 | over: {
87 | color1: {
88 | rotate: 90,
89 | alpha: 1,
90 | brightness: 0.4
91 | },
92 |
93 | color2: {
94 | rotate: 0,
95 | alpha: 1,
96 | brightness: 1
97 | }
98 | },
99 |
100 | selected: {
101 | color1: {
102 | rotate: -20,
103 | alpha: 1,
104 | brightness: 0.9
105 | },
106 |
107 | color2: {
108 | rotate: -90,
109 | alpha: 1,
110 | brightness: 0.8
111 | }
112 | }
113 | },
114 |
115 | transitions: [
116 | { from: 'out', to: 'idle', bi: true },
117 | { from: 'idle', to: 'over', bi: true },
118 | { from: 'over', to: 'selected', bi: true }
119 | ],
120 |
121 | parsers: {
122 | init: [],
123 | update: [
124 | function(target, state) {
125 | target.style.opacity = state.alpha;
126 | target.style.transform = 'perspective(600px) rotateY(' + state.rotate + 'deg)';
127 | target.style.filter = 'brightness(' + state.brightness + ')';
128 | target.style.webkitFilter = 'brightness(' + state.brightness + ')';
129 | }
130 | ]
131 | }
132 | });
133 |
134 | return {
135 | el: elButton,
136 | ui: ui
137 | };
138 | };
--------------------------------------------------------------------------------
/example/chief/index.js:
--------------------------------------------------------------------------------
1 | var chief = require('../../chief');
2 | var button = require('./button');
3 |
4 | // we'll use this variable to keep track of the button we've selected previously
5 | var selectedButton = null;
6 |
7 | // the button function returns an object which will have two variables
8 | // ui - an f1 instance
9 | // el - a dom element we should append to the dom
10 | var button1 = button({ text: 'Toronto', zIndex: 4 });
11 | var button2 = button({ text: 'Helsinki', zIndex: 3 });
12 | var button3 = button({ text: 'Vancouver', zIndex: 2 });
13 | var button4 = button({ text: 'Seinäjoki', zIndex: 1 });
14 |
15 | // the following events will add mouse events to all buttons
16 | addMouseEvents(button1, 1);
17 | addMouseEvents(button2, 2);
18 | addMouseEvents(button3, 3);
19 | addMouseEvents(button4, 4);
20 |
21 | // add the elements to the body
22 | document.body.appendChild(button1.el);
23 | document.body.appendChild(button2.el);
24 | document.body.appendChild(button3.el);
25 | document.body.appendChild(button4.el);
26 |
27 | // this will create our chief instance
28 | var menu = chief({
29 | // here we define what f1 instances
30 | // this chief instance will target
31 | targets: {
32 | button1: button1.ui,
33 | button2: button2.ui,
34 | button3: button3.ui,
35 | button4: button4.ui
36 | },
37 |
38 | // here we'll define the states of the chief instance
39 | states: {
40 |
41 | // an out state
42 | // each value willd define what state
43 | // each ui instance should be in when we're in the
44 | // out state eg button1 should be in "out" when this
45 | // chief is in it's "out" state
46 | out: {
47 | button1: 'out',
48 | button2: 'out',
49 | button3: 'out',
50 | button4: 'out'
51 | },
52 |
53 | // an idle state
54 | idle: {
55 | button1: 'idle',
56 | button2: 'idle',
57 | button3: 'idle',
58 | button4: 'idle'
59 | },
60 |
61 | // a state when button1 is selected
62 | selected1: {
63 | button1: 'selected',
64 | button2: 'idle',
65 | button3: 'idle',
66 | button4: 'idle'
67 | },
68 |
69 | // a state when button2 is selected
70 | selected2: {
71 | button1: 'idle',
72 | button2: 'selected',
73 | button3: 'idle',
74 | button4: 'idle'
75 | },
76 |
77 | // a state when button3 is selected
78 | selected3: {
79 | button1: 'idle',
80 | button2: 'idle',
81 | button3: 'selected',
82 | button4: 'idle'
83 | },
84 |
85 | // a state when button4 is selected
86 | selected4: {
87 | button1: 'idle',
88 | button2: 'idle',
89 | button3: 'idle',
90 | button4: 'selected'
91 | }
92 | },
93 |
94 | // this will define the transitions of this chief instance
95 | // you cannot use durations or eases
96 | transitions: [
97 | // for out to idle we'll want to stagger animate in
98 | // the buttons
99 | {
100 | from: 'out', to: 'idle', bi: true, animation: {
101 | button2: { delay: 0.3 },
102 | button3: { delay: 0.6 },
103 | button4: { delay: 0.9 }
104 | }
105 | },
106 | // to go from the selected states we'll want to go
107 | // back to the idle state and then go to the selected
108 | // state
109 | { from: 'idle', to: 'selected1', bi: true },
110 | { from: 'idle', to: 'selected2', bi: true },
111 | { from: 'idle', to: 'selected3', bi: true },
112 | { from: 'idle', to: 'selected4', bi: true }
113 | ]
114 | });
115 |
116 | // initialize the chief instance
117 | menu.init('out');
118 |
119 | // animate the chief instance to the idle state
120 | menu.go('idle', function() {
121 | console.log('is in idle');
122 | });
123 |
124 | function addMouseEvents(button, buttonNum) {
125 |
126 | // when we mouse over and out we'll animate individual
127 | // buttons if they are not selected
128 | button.el.addEventListener('mouseover', function() {
129 | if(selectedButton !== buttonNum) {
130 | button.ui.go('over');
131 | }
132 | });
133 |
134 | button.el.addEventListener('mouseout', function() {
135 | if(selectedButton !== buttonNum) {
136 | button.ui.go('idle');
137 | }
138 | });
139 |
140 | // when a button is selected we want to tell the menu
141 | // to animate to the proper selected state
142 | button.el.addEventListener('click', function() {
143 | selectedButton = buttonNum;
144 | page.go('selected' + buttonNum, function() {
145 | console.log('is in', 'selected' + buttonNum);
146 | });
147 | });
148 | }
--------------------------------------------------------------------------------
/example/f1/index.js:
--------------------------------------------------------------------------------
1 | var f1 = require('./../..');
2 |
3 | // lets make a div we'll animate
4 | // really this could be whatever an object
5 | // whatever you want
6 | var itemToAnimate = document.body.appendChild(
7 | document.createElement('div')
8 | );
9 |
10 | // then we'll create the ui instance which will animate
11 | // the div we crated
12 | var ui = f1({
13 | // this will define what the targets are that will be animating
14 | // it will be associated in states
15 | targets: {
16 | item: itemToAnimate
17 | },
18 | // states defines what the div should look like in each state
19 | states: require('./states'),
20 | transitions: require('./transitions'),
21 | parsers: require('./parsers')
22 | });
23 |
24 | // this will initialize the ui instance in a state
25 | // it will cause the parsers update functions to be run for the first time
26 | ui.init('out');
27 |
28 | // this will tell the ui to animate to the idle state
29 | ui.go('idle');
30 |
31 | // on mouse over we'll want to animate to the over state
32 | itemToAnimate.addEventListener('mouseover', function() {
33 | ui.go('over');
34 | });
35 |
36 | // on mouse out we'll want to animate back to the idle state
37 | itemToAnimate.addEventListener('mouseout', function() {
38 | ui.go('idle');
39 | });
40 |
41 | // on mouse click we'll want to animate out the button
42 | itemToAnimate.addEventListener('click', function() {
43 | ui.go('out');
44 | });
--------------------------------------------------------------------------------
/example/f1/parsers.js:
--------------------------------------------------------------------------------
1 | // parsers is an object which has two arrays init and update
2 | // the main purpose of parsers is have the ability to apply
3 | // animated properties to targets
4 | module.exports = {
5 |
6 | // init is an array of functions that will be run once the
7 | // f1 instance initializes
8 | // typically you'd drop code in here that is required to initialize
9 | // the targets to animate.
10 | //
11 | // In this case we'll just give the item we want to animate a width, height
12 | // and add a background color so it can be seen
13 | init: [
14 | function(states, targets, transitions) {
15 |
16 | for(var i in targets) {
17 | var itemToAnimate = targets[ i ];
18 |
19 | itemToAnimate.style.position = 'absolute';
20 | itemToAnimate.style.backgroundColor = '#CAFE00';
21 | itemToAnimate.style.width = itemToAnimate.style.height = '100px';
22 | }
23 | }
24 | ],
25 |
26 | // update is an array of functions that will be run each time
27 | // the ui should be animated. It accepts the target and currently
28 | // calculated state.
29 | //
30 | // In our case we will parse out the x, y, alpha properties and apply to
31 | // css style left, top, and opacity
32 | update: [
33 | function(target, state) {
34 | target.style.left = state.x + 'px';
35 | target.style.top = state.y + 'px';
36 | target.style.opacity = state.alpha;
37 |
38 | target.innerText = state.text;
39 | }
40 | ]
41 | };
--------------------------------------------------------------------------------
/example/f1/states.js:
--------------------------------------------------------------------------------
1 | // states is an object which defines what targets will look like in each
2 | // state of the ui
3 | module.exports = {
4 | // out is a state name
5 | // later we can say ui.go('out') to
6 | // animate to the out state
7 | out: {
8 | // the following will define what the item
9 | // should look like in the out state
10 | item: {
11 | // x, y, and alpha are obviously not valid style properties
12 | // we define in parsers how this will be handled
13 | x: 200,
14 | y: -100,
15 | alpha: 0,
16 | text: ''
17 | }
18 | },
19 |
20 | preIdle: {
21 | item: {
22 | x: 200,
23 | y: 100,
24 | alpha: 1,
25 | text: ''
26 | }
27 | },
28 |
29 | // idle is a state name
30 | idle: {
31 | // the following will define what the item
32 | // should look like in the idle state
33 | item: {
34 | x: 100,
35 | y: 100,
36 | alpha: 1,
37 | text: 'ROLL ME'
38 | }
39 | },
40 |
41 | // over is a state name
42 | over: {
43 | // the following will define what the item
44 | // should look like in the over state
45 | item: {
46 | x: 100,
47 | y: 105,
48 | alpha: 0.5,
49 | text: 'PRESS ME'
50 | }
51 | }
52 | };
--------------------------------------------------------------------------------
/example/f1/transitions.js:
--------------------------------------------------------------------------------
1 | // transitions is an array which defines how the the ui component
2 | // should animate through states
3 | module.exports = [
4 | // this defines that we can animate from the out state to the idle state
5 | // and from the idle state to the out state because we say it's a "bi-directional"
6 | // transition by saying `bi: true`
7 | //
8 | // You generally would not have bi: true and would be required to define something like
9 | // { from: 'out', to: 'idle' },
10 | // { from: 'idle', to: 'out' }
11 | { from: 'out', to: 'preIdle', bi: true },
12 |
13 | { from: 'preIdle', to: 'idle', bi: true },
14 |
15 | // this defines how we can animate to the over state from idle and back again
16 | // for this specific example we're also defining the duration of the animation
17 | { from: 'idle', to: 'over', bi: true, animation: {
18 | duration: 0.25
19 | }
20 | }
21 | ];
--------------------------------------------------------------------------------
/images/chief-preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Experience-Monks/f1/31bf0f9f4491f08d8b453aff744ba22ceab94607/images/chief-preview.gif
--------------------------------------------------------------------------------
/images/f1-preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Experience-Monks/f1/31bf0f9f4491f08d8b453aff744ba22ceab94607/images/f1-preview.gif
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var kimi = require('kimi');
2 | var getTween = require('tween-function');
3 | var noop = require('no-op');
4 | var extend = require('deep-extend');
5 | var Emitter = require('events').EventEmitter;
6 |
7 | var getParser = require('./lib/parsers/getParser');
8 | var parseStates = require('./lib/states/parseStates');
9 | var parseTransitions = require('./lib/transitions/parseTransitions');
10 | var parseTargets = require('./lib/targets/parseTargets');
11 |
12 | var numInstances = 0;
13 |
14 | module.exports = f1;
15 |
16 | /**
17 | * To construct a new `f1` instance you can do it in two ways.
18 | *
19 | * ```javascript
20 | * ui = f1([ settigns ]);
21 | * ```
22 | * or
23 | * ```javascript
24 | * ui = new f1([ settings ]);
25 | * ```
26 | *
27 | * To construct an `f1` instance you can pass in an optional settings object. The following are properties you can pass in settings:
28 | * ```javascript
29 | * {
30 | * onState: listenerState, // this callback will be called whenever f1 reaches a state
31 | * onUpdate: listenerUpdater, // this callback will be called whenever f1 is updating
32 | *
33 | * // you can pass a name for the ui. This is useful when you're using an external tool or want
34 | * // to differentiate between f1 instances
35 | * name: 'someNameForTheUI',
36 | *
37 | * // this is an object which contains all elements/items that you will be animating
38 | * targets: {
39 | * bg: bgElement
40 | * },
41 | *
42 | * // all states for the ui
43 | * // states are the top level object and anything after that are the properties
44 | * // for that state
45 | * states: {
46 | * out: {
47 | *
48 | * bg: { alpha: 0 }
49 | * },
50 | *
51 | * idle: {
52 | *
53 | * bg: { alpha: 1 }
54 | * }
55 | * },
56 | *
57 | * // an array which defines the transitions for the ui
58 | * transitions: [
59 | * 'out', 'idle', // this ui can go from out to idle
60 | * 'idle', 'out' // and idle to out
61 | * ],
62 | *
63 | * // an Object contains init and update functions. These will be used
64 | * // to initialize your ui elements and apply state to targets during update
65 | * parsers: {
66 | * init: [ initPosition ],
67 | * update: [ applyPosition ]
68 | * }
69 | * }
70 | * ```
71 | *
72 | * @param {Object} [settings] An optional settings Object described above
73 | * @chainable
74 | */
75 | function f1(settings) {
76 |
77 | if(!(this instanceof f1)) {
78 |
79 | return new f1(settings);
80 | }
81 |
82 | settings = settings || {};
83 |
84 | var emitter = this;
85 | var onUpdate = settings.onUpdate || noop;
86 | var onState = settings.onState || noop;
87 |
88 | // this is used to generate a "name" for an f1 instance if one isn't given
89 | numInstances++;
90 |
91 | this.onState = function() {
92 | emitter.emit.apply(emitter, getEventArgs('state', arguments));
93 |
94 | if(onState) {
95 | onState.apply(undefined, arguments);
96 | }
97 | };
98 |
99 | this.onUpdate = function() {
100 | emitter.emit.apply(emitter, getEventArgs('update', arguments));
101 |
102 | if(onUpdate) {
103 | onUpdate.apply(undefined, arguments);
104 | }
105 | };
106 |
107 | this.name = settings.name || 'ui_' + numInstances;
108 | this.isInitialized = false;
109 | this.data = null; // current animation data
110 | this.defTargets = null;
111 | this.defStates = null;
112 | this.defTransitions = null;
113 | this.parser = null;
114 |
115 | if(settings.transitions) {
116 | this.transitions(settings.transitions);
117 | }
118 |
119 | if(settings.states) {
120 | this.states(settings.states);
121 | }
122 |
123 | if(settings.targets) {
124 | this.targets(settings.targets);
125 | }
126 |
127 | if(settings.parsers) {
128 | this.parsers(settings.parsers);
129 | }
130 |
131 | // kimi is the man who does all the work under the hood
132 | this.driver = kimi( {
133 |
134 | manualStep: settings.autoUpdate === undefined ? false : !settings.autoUpdate,
135 | onState: _onState.bind(this),
136 | onUpdate: _onUpdate.bind(this)
137 | });
138 | }
139 |
140 | f1.prototype = extend(Emitter.prototype, {
141 |
142 | /**
143 | * define which items are going to be animated. Pass in an object
144 | * which will look something like this:
145 | * ```javascript
146 | * var ui = require('f1')();
147 | *
148 | * ui.targets( {
149 | *
150 | * itemToAnimate1: find('#itemToAnimate1'),
151 | * itemToAnimate2: find('#itemToAnimate2')
152 | * });
153 | * ```
154 | * The `Object` being passed in should have variable names which will
155 | * associate to data which will be defined when setting up states in the
156 | * `f1.states` method. The value which you pass these can be anything.
157 | *
158 | * In this case `itemToAnimate1` and `itemToAnimate2` will be a HTML Elements.
159 | *
160 | * @param {Object} targets An Object which will define which items will be animated
161 | * @chainable
162 | */
163 | targets: function(targets) {
164 |
165 | this.defTargets = targets;
166 | this.parsedTargets = parseTargets(targets);
167 |
168 | return this;
169 | },
170 |
171 | /**
172 | * defines the states which this `f1` instance will use.
173 | *
174 | * States are defined as objects. It could look something like this:
175 | * ```javascript
176 | * var ui = require('f1')();
177 | *
178 | * ui.states( {
179 | *
180 | * out: {
181 | * itemToAnimate1: {
182 | * variableToAnimate: 0
183 | * },
184 | *
185 | * itemToAnimate2: {
186 | * variableToAnimate: 0
187 | * }
188 | * },
189 | *
190 | * idle: {
191 | * itemToAnimate1: {
192 | * variableToAnimate: 1
193 | * },
194 | *
195 | * itemToAnimate2: {
196 | * variableToAnimate: 2
197 | * }
198 | * }
199 | * });
200 | * ```
201 | * Above two states would be created: `out` and `idle`. Both would animate two
202 | * objects: `itemToAnimate1` and `itemToAnimate2`. And in both of those objects
203 | * the property `variableToAnimate` is defined. So if we were to transition from
204 | * `out` to `idle` in `itemToAnimate1` `variableToAnimate` would transition from
205 | * 0 to 1 and in `itemToAnimate2` from 0 to 2.
206 | *
207 | * States can also be defined by passing in objects for instance the above could
208 | * be changed to look like this:
209 | * ```javascript
210 | * var ui = require('f1')();
211 | *
212 | * ui.states( {
213 | *
214 | * out: function(stateName) {
215 | *
216 | * console.log(stateName); // "out"
217 | *
218 | * return {
219 | * itemToAnimate1: {
220 | * variableToAnimate: 0
221 | * },
222 | *
223 | * itemToAnimate2: {
224 | * variableToAnimate: 0
225 | * }
226 | * };
227 | * },
228 | *
229 | * idle: function(stateName) {
230 | *
231 | * console.log(stateName); // "idle"
232 | *
233 | * return {
234 | * itemToAnimate1: {
235 | * variableToAnimate: 1
236 | * },
237 | *
238 | * itemToAnimate2: {
239 | * variableToAnimate: 2
240 | * }
241 | * };
242 | * }
243 | * });
244 | * ```
245 | * The above can be handy when there are many items which states must be defined for
246 | * instance a menu with many buttons.
247 | *
248 | * @param {Object} states defines all of the states for an `f1` instance
249 | * @chainable
250 | */
251 | states: function(states) {
252 |
253 | this.defStates = states;
254 |
255 | return this;
256 | },
257 |
258 | /**
259 | * defines how this `f1` instance can move between states.
260 | *
261 | * For instance if we had two states out and idle you could define your transitions
262 | * like this:
263 | *
264 | * ```javascript
265 | * var ui = require('f1')();
266 | *
267 | * ui.transitions( [
268 | * 'out', 'idle', // defines that you can go from the out state to the idle state
269 | * 'idle', 'out' // defines that you can go from the idle state to the out state
270 | * ]);
271 | * ```
272 | *
273 | * Note that transitions are not bi-directional.
274 | *
275 | * If you simply just defined state names a default animation would be applied between
276 | * states. This default transition will have a duration of 0.5 seconds and use no ease.
277 | *
278 | * If you want to modify the animation duration and ease you can define your transitions
279 | * like this:
280 | *
281 | * ```javascript
282 | * var eases = require('eases');
283 | * var ui = require('f1')();
284 | *
285 | * ui.transitions( [
286 | * 'out', 'idle', { duration: 1, ease: eases.expoOut },
287 | * 'idle', 'out', { duration: 0.5, ease: eases.expoIn }
288 | * ]);
289 | * ```
290 | *
291 | * Defining your transitions using the above syntax will cause all properties to animate
292 | * using the duration and ease defined.
293 | *
294 | * Ease functions should take a time property between 0-1 and return a modified value between
295 | * 0-1.
296 | *
297 | * You can also animate properties individually. Here passing a delay maybe sometimes
298 | * userful:
299 | *
300 | * ```javascript
301 | * var eases = require('eases');
302 | * var ui = require('f1')();
303 | *
304 | * ui.transitions( [
305 | * 'out', 'idle', {
306 | * duration: 1, ease: eases.expoOut,
307 | *
308 | * position: { duration: 0.5, delay: 0.5, ease: eases.quadOut },
309 | * alpha: { duration: 0.5 }
310 | * },
311 | * 'idle', 'out', { duration: 0.5, ease: eases.expoIn }
312 | * ]);
313 | * ```
314 | *
315 | * In that example every property besides `position` and `alpha` will have a duration of one second
316 | * using the `eases.quadOut` ease equation. `position` will have a duration of 0.5 seconds and will
317 | * be delayed 0.5 seconds and will use the `eases.quadOut` easing function. `alpha` will simply have
318 | * a duration of 0.5 seconds.
319 | *
320 | * For advanced transitions you can pass in a function instead like so:
321 | * ```javascript
322 | *
323 | * ui.transitions( [
324 | * 'out', 'idle', {
325 | * duration: 1, ease: eases.expoOut,
326 | *
327 | * position: { duration: 0.5, delay: 0.5, ease: eases.quadOut },
328 | *
329 | * alpha: function(time, start, end) {
330 | *
331 | * return (end - start) * time + start;
332 | * }
333 | * },
334 | * 'idle', 'out', { duration: 0.5, ease: eases.expoIn }
335 | * ]);
336 | * ```
337 | *
338 | * There the animation is the same as in the previous example however `alpha` will be calculated using
339 | * a custom transition function.
340 | *
341 | * @param {Array} transitions An array which descriptes transitions
342 | * @chainable
343 | */
344 | transitions: function(transitions) {
345 |
346 | this.defTransitions = Array.isArray(transitions) ? transitions : Array.prototype.slice.apply(arguments);
347 |
348 | return this;
349 | },
350 |
351 | /**
352 | * `f1` can target many different platforms. How it does this is by using parsers which
353 | * can target different platforms. Parsers apply calculated state objects to targets.
354 | *
355 | * If working with the dom for instance your state could define values which will be applied
356 | * by the parser to the dom elements style object.
357 | *
358 | * When calling parsers pass in an Object that can contain variables init, and update. Both should contain
359 | * an Array's of functions which will be used to either init or update ui.
360 | *
361 | * init's functions will receive: states definition, targets definition, and transitions definition.
362 | * update functions will receive: target and state. Where target could be for instance a dom element and
363 | * state is the currently calculated state.
364 | *
365 | * @param {Object} parsersDefinitions an Object which may define arrays of init and update functions
366 | * @chainable
367 | */
368 | parsers: function(parsersDefinitions) {
369 |
370 | // check that the parsersDefinitions is an object
371 | if(typeof parsersDefinitions !== 'object' || Array.isArray(parsersDefinitions)) {
372 | throw new Error('parsers should be an Object that contains arrays of functions under init and update');
373 | }
374 |
375 | this.parser = this.parser || getParser();
376 |
377 | this.parser.add(parsersDefinitions);
378 |
379 | return this;
380 | },
381 |
382 | /**
383 | * Initializes `f1`. `init` will throw errors if required parameters such as
384 | * states and transitions are missing. The initial state for the `f1` instance
385 | * should be passed in.
386 | *
387 | * @param {String} Initial state for the `f1` instance
388 | * @chainable
389 | */
390 | init: function(initState) {
391 |
392 | if(!this.isInitialized) {
393 | this.isInitialized = true;
394 |
395 | var driver = this.driver;
396 |
397 | if(!this.defStates) {
398 |
399 | throw new Error('You must define states before attempting to call init');
400 | } else if(!this.defTransitions) {
401 |
402 | throw new Error('You must define transitions before attempting to call init');
403 | } else if(!this.parser) {
404 |
405 | throw new Error('You must define parsers before attempting to call init');
406 | } else if(!this.defTargets) {
407 |
408 | throw new Error('You must define targets before attempting to call init');
409 | } else {
410 |
411 | parseStates(driver, this.defStates);
412 | parseTransitions(driver, this.defStates, this.defTransitions);
413 |
414 | this.parser.init(this.defStates, this.defTargets, this.defTransitions);
415 |
416 | driver.init(initState);
417 | }
418 |
419 | if(global.__f1__) {
420 | global.__f1__.init(this);
421 | }
422 | }
423 |
424 | return this;
425 | },
426 |
427 | /**
428 | * Destroys an `f1` instances. This should be called when you don't need the f1 instance anymore.
429 | */
430 | destroy: function() {
431 |
432 | if(global.__f1__) {
433 | global.__f1__.destroy(this);
434 | }
435 |
436 | this.driver.destroy();
437 | },
438 |
439 | /**
440 | * Will tell `f1` to go to animate to another state. Calling `go` will cause `f1` to calculate a path defined
441 | * through transitions to the state which was passed to it.
442 | *
443 | * @param {String} state The new state you'd like to go to
444 | * @param {Function} [cb] An optional callback which will be called once f1 reaches the state
445 | * @chainable
446 | */
447 | go: function(state, cb) {
448 |
449 | this.driver.go(state, cb);
450 |
451 | return this;
452 | },
453 |
454 | /**
455 | * Will tell `f1` to go to immediately jump to another state without animating. If an animation is currently
456 | * happening that animation is stopped and the jump to state will happen immediately.
457 | *
458 | * @param {String} state The new state you'd like to go to
459 | * @chainable
460 | */
461 | set: function(state) {
462 |
463 | this.driver.set(state);
464 |
465 | return this;
466 | },
467 |
468 | /**
469 | * This method can be used to manually update f1 by certain deltaTime. deltaTime should be in milliseconds.
470 | * In order to use this function you must pass in autoUpdate: false otherwise a raf loop will be run after
471 | * go.
472 | *
473 | * @param {Number} deltaTime How much time has passed since the last render in milliseconds
474 | * @chainable
475 | */
476 | step: function(deltaTime) {
477 | this.driver.step(deltaTime);
478 |
479 | return this;
480 | },
481 |
482 | /**
483 | * Will force `f1` to update. This is useful if you're updating a states values lets say by mouse movement.
484 | * You'd call `f1.update` to ensure the state gets applied.
485 | *
486 | * @chainable
487 | */
488 | update: function() {
489 |
490 | _onUpdate.call(this, this.data, this.state, this.time, this.duration);
491 |
492 | return this;
493 | },
494 |
495 | /**
496 | * An advanced method where you can apply the current state f1
497 | * has calculated to any object.
498 | *
499 | * Basically allows you to have one f1 object control multiple objects
500 | * or manually apply animations to objects.
501 | *
502 | * @param {String} pathToTarget A path in the current state to the object you'd like to apply. The path should
503 | * be defined using dot notation. So if your state had an object named `thing` and it
504 | * contained another object you'd like to apply called `data`. Your `pathToTarget`
505 | * would be `'thing.data'`
506 | * @param {Object} target The object you'd like to apply the currently calculated state to. For instance target
507 | * could be an html element.
508 | * @param {Object} [parserDefinition] An optional Object which defines init and update functions for a parser.
509 | */
510 | apply: function(pathToTarget, target, parserDefinition) {
511 |
512 | var data = this.data;
513 | var parser = this.parser;
514 | var animationData;
515 |
516 | // if parse functions were passed in then create a new parser
517 | if(parserDefinition) {
518 |
519 | parser = new getParser(parserDefinition);
520 | }
521 |
522 | // if we have a parser then apply the parsers (parsers set css etc)
523 | if(parser) {
524 |
525 | if(typeof pathToTarget === 'string') {
526 |
527 | pathToTarget = pathToTarget.split('.');
528 | }
529 |
530 | animationData = data[ pathToTarget[ 0 ] ];
531 |
532 | for(var i = 1, len = pathToTarget.length; i < len; i++) {
533 |
534 | animationData = animationData[ pathToTarget[ i ] ];
535 | }
536 |
537 | parser.update(target, animationData);
538 | }
539 | }
540 | });
541 |
542 | function getEventArgs(name, args) {
543 |
544 | args = Array.prototype.slice.apply(args);
545 |
546 | args.unshift(name);
547 |
548 | return args;
549 | }
550 |
551 | function _onUpdate(data, state, time, duration) {
552 |
553 | var pathToTarget;
554 | var target;
555 |
556 | if(data !== undefined && state !== undefined && time !== undefined) {
557 |
558 | this.data = data;
559 | this.state = state;
560 | this.time = time;
561 | this.duration = duration;
562 |
563 | if(this.parsedTargets) {
564 |
565 | for(var i = 0, len = this.parsedTargets.length; i < len; i += 2) {
566 |
567 | pathToTarget = this.parsedTargets[ i ];
568 | target = this.parsedTargets[ i + 1 ];
569 |
570 | this.apply(pathToTarget, target);
571 | }
572 | }
573 |
574 | // this is kind nasty because _onUpdate is called manually on update manual calls
575 | // in this case we should emit an event duration could be undefined in that case
576 | if(duration !== undefined) {
577 | this.onUpdate(data, state, time, duration);
578 | }
579 | }
580 | }
581 |
582 | function _onState(data, state) {
583 |
584 | this.data = data;
585 | this.state = state;
586 | this.time = 0;
587 | this.duration = undefined;
588 |
589 | this.onState(data, state);
590 | }
--------------------------------------------------------------------------------
/lib/parsers/getParser.js:
--------------------------------------------------------------------------------
1 | module.exports = function getParser(parserDefinition) {
2 |
3 | var initMethods = [];
4 | var parserMethods = [];
5 |
6 | var parser = {
7 |
8 | add: function(parserDefinition) {
9 | if(parserDefinition.init) {
10 | parserDefinition.init.forEach(function(initFunc) {
11 | parser.addInit(initFunc);
12 | });
13 | }
14 |
15 | if(parserDefinition.update) {
16 | parserDefinition.update.forEach(function(parserFunc) {
17 | parser.addUpdate(parserFunc);
18 | });
19 | }
20 | },
21 |
22 | addInit: function(init) {
23 | initMethods.push(init);
24 | },
25 |
26 | addUpdate: function(parser) {
27 |
28 | parserMethods.push(parser);
29 | },
30 |
31 | /**
32 | * This will be called when the `f1` instance is initialized.
33 | *
34 | * @param {Object} states states the `f1` instance currently is using
35 | * @param {Object} targets targets the `f1` instance currently is using
36 | * @param {Array} transitions transitions the `f1` instance currently is using
37 | */
38 | init: function(states, targets, transitions) {
39 |
40 | initMethods.forEach( function(method) {
41 |
42 | method(states, targets, transitions);
43 | });
44 | },
45 |
46 | /**
47 | * This will be called when `f1` has calculated state updates.
48 | *
49 | * @param {Object} item This will be an item defined in targets
50 | * @param {Object} calculatedState current calculated state from f1
51 | */
52 | update: function(item, calculatedState) {
53 |
54 | parserMethods.forEach( function(method) {
55 |
56 | method(item, calculatedState);
57 | });
58 | }
59 | };
60 |
61 | // if a parserDefinition was passed then we want to add all to this parser
62 | if(parserDefinition) {
63 | parser.add(parserDefinition);
64 | }
65 |
66 | return parser;
67 | };
--------------------------------------------------------------------------------
/lib/states/parseStates.js:
--------------------------------------------------------------------------------
1 | module.exports = function(driver, states) {
2 |
3 | var state, stateName;
4 |
5 | for(var stateName in states) {
6 |
7 | state = states[ stateName ];
8 |
9 | // if the state is defined as a function
10 | // call the function and expect it to return a
11 | // state Object back
12 | if(typeof state == 'function') {
13 |
14 | state = states[ stateName ] = state(stateName);
15 | }
16 |
17 | driver.state(stateName, state);
18 | }
19 | };
--------------------------------------------------------------------------------
/lib/targets/parseTargets.js:
--------------------------------------------------------------------------------
1 | module.exports = function(animatables) {
2 |
3 | var returnValue = [];
4 |
5 | // if it's an array we expect the data to be in correct format eg
6 | // [
7 | // 'outer.inner.item', DOMElement
8 | // ]
9 | if(Array.isArray( animatables )) {
10 |
11 | for(var i = 0, len = animatables.length; i < len; i += 2) {
12 |
13 | returnValue.push(animatables[ i ].split( '.' ));
14 | returnValue.push(animatables[ i + 1 ]);
15 | }
16 | // if it's not an array but an Object instead we will iterate
17 | // over each item and split the key by .
18 | } else {
19 |
20 | for(var i in animatables) {
21 |
22 | returnValue.push(i.split( '.' ), animatables[ i ]);
23 | }
24 | }
25 |
26 | return returnValue;
27 | };
--------------------------------------------------------------------------------
/lib/transitions/createTransitions.js:
--------------------------------------------------------------------------------
1 | var tweenFunction = require('tween-function');
2 | var defaultTransition = require('./defaultTransition');
3 |
4 | module.exports = function createTransitions(animation, stateFrom, stateTo) {
5 |
6 | var paths = [];
7 | var pathAnimations;
8 |
9 | // paths will contain an array of arrays which will be paths to all properties to evaluate
10 | // we will pass both stateFrom and stateTo so that we can evaluate that both states contain
11 | // the prop to animate
12 | getPathsToProperties(stateFrom, stateTo, paths);
13 |
14 | // build animation definitions to each property using the path created above
15 | pathAnimations = paths.map(getAnimationDefinitions.bind(undefined, animation));
16 |
17 | return buildTransitionDefinition(paths, pathAnimations, stateFrom);
18 | };
19 |
20 | function thisOrThat(value1, value2) {
21 | return value1 !== undefined ? value1 : value2;
22 | }
23 |
24 | function getPathsToProperties(stateFrom, stateTo, paths, keys) {
25 |
26 | var newKeys;
27 |
28 | keys = keys || [];
29 |
30 | for(var i in stateFrom) {
31 |
32 | // both states have this property
33 | if(stateTo[ i ] !== undefined) {
34 |
35 | newKeys = keys.slice();
36 | newKeys.push(i);
37 |
38 | if(typeof stateFrom[ i ] === 'object') {
39 |
40 | getPathsToProperties(stateFrom[ i ], stateTo[ i ], paths, newKeys);
41 | } else {
42 |
43 | paths.push(newKeys);
44 | }
45 | }
46 | }
47 | }
48 |
49 | function getAnimationDefinitions(animation, path) {
50 |
51 | var animationDefinition = animation;
52 | var reducedDefinition = path.reduce( function(curDef, pathPart) {
53 |
54 | animationDefinition = animationDefinition[ pathPart ] || {};
55 |
56 | return {
57 | duration: thisOrThat(animationDefinition.duration, curDef.duration),
58 | delay: thisOrThat(animationDefinition.delay, curDef.delay),
59 | ease: thisOrThat(animationDefinition.ease, curDef.ease)
60 | };
61 | }, { duration: animation.duration, delay: animation.delay, ease: animation.ease });
62 |
63 | // apply the defaults if no durations and delays and eases have been created
64 | reducedDefinition.duration = thisOrThat(reducedDefinition.duration, defaultTransition.duration);
65 | reducedDefinition.delay = thisOrThat(reducedDefinition.delay, defaultTransition.delay);
66 | reducedDefinition.ease = thisOrThat(reducedDefinition.ease, defaultTransition.ease);
67 |
68 | return reducedDefinition;
69 | }
70 |
71 | function buildTransitionDefinition(paths, pathAnimations, state) {
72 | var transitions = {};
73 | var overallDuration = pathAnimations.reduce( function(longestDuration, animationDef) {
74 | var curDuration = animationDef.duration + animationDef.delay;
75 |
76 | return curDuration > longestDuration ? curDuration : longestDuration;
77 | }, 0);
78 | var transitionsDef;
79 | var stateDef;
80 | var aniDef;
81 |
82 | // now create the transitions object
83 | paths.forEach( function(path, i) {
84 | var duration;
85 | var delay;
86 | var ease;
87 |
88 | transitionsDef = transitions;
89 | stateDef = state;
90 | aniDef = pathAnimations[ i ];
91 |
92 | duration = aniDef.duration;
93 | delay = aniDef.delay;
94 | ease = aniDef.ease;
95 |
96 | path.forEach( function(pathPart, j) {
97 | if(j === path.length - 1) {
98 |
99 | // Special consideration if animating percents
100 | if (typeof stateDef[ pathPart ] === 'string' && stateDef[ pathPart ].charAt(stateDef[ pathPart ].length-1)==='%') {
101 | transitionsDef[ pathPart ] = percentTweenFunction( {
102 | duration: duration / overallDuration,
103 | delay: delay / overallDuration,
104 | ease: ease,
105 | cap: true
106 | });
107 |
108 | // if the value is a number the transition will be built from tweenFunction
109 | } else if(typeof stateDef[ pathPart ] === 'number') {
110 |
111 | transitionsDef[ pathPart ] = tweenFunction( {
112 | duration: duration / overallDuration,
113 | delay: delay / overallDuration,
114 | ease: ease,
115 | cap: true
116 | });
117 | // if the value is a String we'll need to use this transition function
118 | } else if(typeof stateDef[ pathPart ] === 'string' || typeof stateDef[ pathPart ] === 'boolean') {
119 |
120 | transitionsDef[ pathPart ] = function(time, start, end) {
121 |
122 | if(time * overallDuration < duration + delay) {
123 |
124 | return start;
125 | } else {
126 |
127 | return end;
128 | }
129 | };
130 | }
131 | } else {
132 | if(transitionsDef[ pathPart ] === undefined) {
133 | transitionsDef[ pathPart ] = {};
134 | }
135 |
136 | stateDef = stateDef[ pathPart ];
137 | transitionsDef = transitionsDef[ pathPart ];
138 | }
139 | });
140 | });
141 |
142 | return {
143 | duration: overallDuration,
144 | transitions: transitions
145 | };
146 | }
147 |
148 | function percentTweenFunction(obj) {
149 | var tween = tweenFunction(obj);
150 | return function(time,start,end) {
151 | return tween(time,Number(start.slice(0,-1)),Number(end.slice(0,-1)))+'%';
152 | }
153 | };
--------------------------------------------------------------------------------
/lib/transitions/defaultTransition.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | duration: 0.5,
3 | delay: 0
4 | };
--------------------------------------------------------------------------------
/lib/transitions/parseTransitions.js:
--------------------------------------------------------------------------------
1 | var getInterpolation = require('interpolation-builder');
2 | var createTransitions = require('./createTransitions');
3 |
4 | module.exports = function(driver, states, transitions) {
5 |
6 | // go through each transition and setup kimi with a function that works
7 | // with values between 0 and 1
8 | transitions.forEach( function(transition, i) {
9 |
10 | var from = transition.from || throwError('from', i, transition);
11 | var to = transition.to || throwError('to', i, transition);
12 | var animation = transition.animation;
13 | var duration;
14 | var animationDefinition;
15 |
16 | // if animation is an object then it's a Tween like definition
17 | // otherwise we'll assume that animation is a function and we can simply
18 | // pass that to the driver
19 | if(typeof animation == 'object' || animation === undefined) {
20 | animation = animation || {};
21 |
22 | animationDefinition = createTransitions(animation, states[ from ], states[ to ]);
23 |
24 | // this
25 | animation = getInterpolation(
26 | animationDefinition.transitions
27 | );
28 |
29 | duration = animationDefinition.duration;
30 | } else {
31 |
32 | duration = animation.duration;
33 | }
34 |
35 | // animation will either be a function passed in or generated from an object definition
36 | driver.fromTo(from, to, duration, animation);
37 |
38 | // handle adding bi directional transitions
39 | if(transition.bi) {
40 | driver.fromTo(to, from, duration, animation);
41 | }
42 | });
43 | };
44 |
45 | function throwError(type, idx, transition) {
46 |
47 | throw new Error(
48 | 'For the transition at ' + idx + ':\n' +
49 | JSON.stringify(transition, undefined, ' ') + '\n' +
50 | 'you did not define ' + type + '\n\n'
51 | );
52 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "f1",
3 | "version": "8.0.0",
4 | "description": "A stateful ui library",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "author": {
8 | "name": "Mikko Haapoja",
9 | "email": "me@mikkoh.com",
10 | "url": "https://github.com/jam3"
11 | },
12 | "dependencies": {
13 | "deep-extend": "^0.4.0",
14 | "interpolation-builder": "^2.1.0",
15 | "kimi": "^6.0.0",
16 | "no-op": "^1.0.3",
17 | "tween-function": "^2.0.0"
18 | },
19 | "devDependencies": {
20 | "budo": "^8.0.4",
21 | "eases": "^1.0.8",
22 | "right-now": "^1.0.0",
23 | "tape": "^3.5.0",
24 | "test-fuzzy-array": "^1.0.1"
25 | },
26 | "scripts": {
27 | "test": "node test/",
28 | "testBrowser": "budo test/ --live --open",
29 | "example-f1": "budo example/f1/ --live --open",
30 | "example-chief": "budo example/chief/ --live --open",
31 | "f1-example": "npm run example-f1",
32 | "chief-example": "npm run example-chief"
33 | },
34 | "keywords": [
35 | "state,stateful,ui,ease,tween"
36 | ],
37 | "repository": {
38 | "type": "git",
39 | "url": "git://github.com/jam3/f1.git"
40 | },
41 | "homepage": "https://github.com/jam3/f1",
42 | "bugs": {
43 | "url": "https://github.com/jam3/f1/issues"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/advancedAnimation/expectedStates.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | [
3 | {
4 | "item": {
5 | "delayedValue": 0,
6 | "easedValue": 0,
7 | "durationValue": 0
8 | }
9 | },
10 | "out"
11 | ],
12 | [
13 | {
14 | "item": {
15 | "delayedValue": 100,
16 | "easedValue": 100,
17 | "durationValue": 100
18 | }
19 | },
20 | "idle"
21 | ]
22 | ];
--------------------------------------------------------------------------------
/test/advancedAnimation/expectedUpdates.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "delayedValue":[0,0,0,0,0,0,0,0,0,0,0,0,0,2.0000000000000044,6.000000000000005,10.000000000000005,14.000000000000007,18,22,26.00000000000001,30.000000000000004,34.000000000000014,38.00000000000001,42,46.00000000000001,50,54.000000000000014,58.00000000000001,62.000000000000014,66,70.00000000000001,74.00000000000001,78,82,85.99999999999999,90.00000000000001,94,98, 100],
3 | "easedValue":[0,24.2141716744801,42.56508225014826,56.472471835193794,67.01230223067765,75,81.05354291862002,85.64127056253706,89.11811795879845,91.7530755576694,93.75,95.263385729655,96.41031764063428,97.27952948969961,97.93826888941736,98.4375,98.81584643241375,99.10257941015857,99.3198823724249,99.48456722235434,99.609375,99.70396160810344,99.77564485253964,99.82997059310622,99.87114180558858,100,100,100,100,100,100,100,100,100,100,100,100,100,100],
4 | "durationValue":[0,8.000000000000002,16.000000000000004,24.000000000000004,32.00000000000001,40,48.00000000000001,56.00000000000001,64.00000000000001,72,80,88,96.00000000000001,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100]
5 | };
--------------------------------------------------------------------------------
/test/advancedAnimation/index.js:
--------------------------------------------------------------------------------
1 | var f1 = require('./../..');
2 | var eases = require('eases');
3 | var getFuzzyTest = require('test-fuzzy-array');
4 |
5 | var EXPECTED_STATES = require('./expectedStates');
6 | var EXPECTED_UPDATES = require('./expectedUpdates');
7 |
8 | module.exports = function(t) {
9 |
10 | var cbStates = [];
11 | var cbUpdates = {
12 | delayedValue: [],
13 | easedValue: [],
14 | durationValue: []
15 | };
16 |
17 | var item = {};
18 | var ui = f1({
19 | autoUpdate: false,
20 | onState: callBackState,
21 | onUpdate: callBackUpdate,
22 | states: {
23 | out: {
24 | item: {
25 | delayedValue: 0,
26 | easedValue: 0,
27 | durationValue: 0
28 | }
29 | },
30 |
31 | idle: {
32 | item: {
33 | delayedValue: 100,
34 | easedValue: 100,
35 | durationValue: 100
36 | }
37 | }
38 | },
39 | transitions: [
40 | { from: 'out', to: 'idle', animation: {
41 | duration: 1,
42 |
43 | item: {
44 |
45 | delayedValue: {
46 | delay: 0.5
47 | },
48 |
49 | easedValue: {
50 | ease: eases.expoOut
51 | },
52 |
53 | durationValue: {
54 | duration: 0.5
55 | }
56 | }
57 | }
58 | }
59 | ],
60 | targets: {
61 | item: item
62 | },
63 | parsers: {
64 | update: [
65 | function(target, state) {
66 | for(var i in state) {
67 | target[ i ] = state[ i ];
68 | }
69 | }
70 | ]
71 | }
72 | });
73 |
74 | ui.init('out');
75 | ui.go('idle');
76 |
77 | for(var i = 0; i < 100000; i++) {
78 | ui.step(40);
79 | }
80 |
81 | getFuzzyTest(t, 0.01)(cbUpdates.delayedValue, EXPECTED_UPDATES.delayedValue, 'delayedValue were correctish');
82 | getFuzzyTest(t, 0.01)(cbUpdates.easedValue, EXPECTED_UPDATES.easedValue, 'easedValue were correctish');
83 | getFuzzyTest(t, 0.01)(cbUpdates.durationValue, EXPECTED_UPDATES.durationValue, 'durationValue were correctish');
84 | t.end();
85 |
86 | function callBackState() {
87 | cbStates.push(Array.prototype.slice.call(arguments));
88 | }
89 |
90 | function callBackUpdate(value) {
91 |
92 | cbUpdates.delayedValue.push(item.delayedValue);
93 | cbUpdates.durationValue.push(item.durationValue);
94 | cbUpdates.easedValue.push(item.easedValue);
95 | }
96 | };
--------------------------------------------------------------------------------
/test/callbackAndUpdate.js:
--------------------------------------------------------------------------------
1 | var f1 = require('./..');
2 |
3 | var EXPECTED_GO_CALLBACK = [ { item: { value: 100 } }, 'idle' ];
4 |
5 | var EXPECTED_STATES = [
6 | [
7 | {
8 | "item": {
9 | "value": 0
10 | }
11 | },
12 | "out"
13 | ],
14 | [
15 | {
16 | "item": {
17 | "value": 100
18 | }
19 | },
20 | "idle"
21 | ]
22 | ];
23 |
24 |
25 | var EXPECTED_UPDATES = [
26 | [
27 | {
28 | "item": {
29 | "value": 0
30 | }
31 | },
32 | "out",
33 | 0,
34 | 0
35 | ],
36 | [
37 | {
38 | "item": {
39 | "value": 30
40 | }
41 | },
42 | "out",
43 | 0.3,
44 | 1
45 | ],
46 | [
47 | {
48 | "item": {
49 | "value": 60
50 | }
51 | },
52 | "out",
53 | 0.6,
54 | 1
55 | ],
56 | [
57 | {
58 | "item": {
59 | "value": 90
60 | }
61 | },
62 | "out",
63 | 0.9,
64 | 1
65 | ],
66 | [
67 | {
68 | "item": {
69 | "value": 100
70 | }
71 | },
72 | "idle",
73 | 0,
74 | 1
75 | ]
76 | ];
77 |
78 |
79 |
80 |
81 |
82 |
83 | module.exports = function(t) {
84 |
85 | var eventStates = [];
86 | var eventUpdates = [];
87 | var cbStates = [];
88 | var cbUpdates = [];
89 | var goCallback = null;
90 |
91 | var item = {};
92 | var ui = f1({
93 | autoUpdate: false,
94 | onState: callBackState,
95 | onUpdate: callBackUpdate
96 | });
97 |
98 | ui.states({
99 | out: {
100 | item: {
101 | value: 0
102 | }
103 | },
104 |
105 | idle: {
106 | item: {
107 | value: 100
108 | }
109 | }
110 | });
111 |
112 | ui.transitions([
113 | { from: 'out', to: 'idle', animation: {
114 | duration: 1
115 | }
116 | }
117 | ]);
118 |
119 | ui.targets({
120 | item: item
121 | });
122 |
123 | ui.parsers({
124 | update: [
125 | function(target, state) {
126 | for(var i in state) {
127 | target[ i ] = state[ i ];
128 | }
129 | }
130 | ]
131 | });
132 |
133 | ui.on('state', eventState);
134 | ui.on('update', eventUpdate);
135 |
136 | ui.init('out');
137 | ui.go('idle', function() {
138 | goCallback = Array.prototype.slice.call(arguments);
139 | });
140 |
141 | ui.step(300);
142 | ui.step(300);
143 | ui.step(300);
144 | ui.step(300);
145 | ui.step(300);
146 | ui.step(300);
147 | ui.step(300);
148 | ui.step(300);
149 |
150 | t.deepEqual(goCallback, EXPECTED_GO_CALLBACK, 'go callback was correct');
151 | t.deepEqual(eventStates, EXPECTED_STATES, 'event states matched expected');
152 | t.deepEqual(eventUpdates, EXPECTED_UPDATES, 'event updates matched expected');
153 | t.deepEqual(cbStates, EXPECTED_STATES, 'callback states matched expected');
154 | t.deepEqual(cbUpdates, EXPECTED_UPDATES, 'callback updates matched expected');
155 | t.end();
156 |
157 | function eventState() {
158 | eventStates.push(Array.prototype.slice.call(arguments));
159 | }
160 |
161 | function eventUpdate() {
162 | eventUpdates.push(Array.prototype.slice.call(arguments));
163 | }
164 |
165 | function callBackState() {
166 | cbStates.push(Array.prototype.slice.call(arguments));
167 | }
168 |
169 | function callBackUpdate() {
170 | cbUpdates.push(Array.prototype.slice.call(arguments));
171 | }
172 | };
--------------------------------------------------------------------------------
/test/chief/getUI.js:
--------------------------------------------------------------------------------
1 | var f1 = require('../..');
2 |
3 | module.exports = function(target, values, onUpdate) {
4 |
5 | target = target || {};
6 | values = values || {
7 | out: 0,
8 | idle: 100,
9 | rolled: 200
10 | };
11 |
12 | var states = {
13 | out: {
14 | item: {
15 | value: values.out
16 | }
17 | },
18 |
19 | idle: {
20 | item: {
21 | value: values.idle
22 | }
23 | },
24 |
25 | rolled: {
26 | item: {
27 | value: values.rolled
28 | }
29 | }
30 | };
31 |
32 | var transitions = [
33 | { from: 'out', to: 'idle' },
34 | { from: 'idle', to: 'rolled', bi: true }
35 | ];
36 |
37 | var parsers = {
38 | update: [
39 | function(target, state) {
40 | for(var i in state) {
41 | target[ i ] = state[ i ];
42 | }
43 | }
44 | ]
45 | };
46 |
47 | return f1({
48 | states: states,
49 |
50 | transitions: transitions,
51 |
52 | targets: {
53 | item: target
54 | },
55 |
56 | parsers: parsers,
57 |
58 | onUpdate: onUpdate
59 | });
60 | };
--------------------------------------------------------------------------------
/test/chief/index.js:
--------------------------------------------------------------------------------
1 | var chief = require('../../chief');
2 | var getUI = require('./getUI');
3 |
4 | module.exports = function(t) {
5 |
6 | var hasGoneIntoRolled = false;
7 | var hasGoneIntoRolled2 = false;
8 | var hasGoneIntoIdle = false;
9 |
10 | var values = {
11 | out: 0,
12 | idle: 100,
13 | rolled: 200
14 | };
15 | var target1 = {};
16 | var target2 = {};
17 |
18 | var ui1 = getUI(target1, values);
19 | var ui2 = getUI(target2, values);
20 |
21 | var controller = chief({
22 | // autoUpdate: false,
23 |
24 | states: {
25 | out: {
26 | ui1: 'out',
27 | ui2: 'out'
28 | },
29 |
30 | idle: {
31 | ui1: 'idle',
32 | ui2: 'idle'
33 | },
34 |
35 | rolled: {
36 | ui1: 'rolled',
37 | ui2: 'rolled'
38 | },
39 |
40 | rolled2: {
41 | ui1: 'idle',
42 | ui2: 'rolled'
43 | }
44 | },
45 |
46 | targets: {
47 | ui1: ui1,
48 | ui2: ui2
49 | },
50 |
51 | transitions: [
52 | { from: 'out', to: 'idle', animation: {
53 |
54 | ui2: {
55 | delay: 0.5
56 | }
57 | }
58 | },
59 |
60 | { from: 'idle', to: 'rolled', bi: true },
61 | { from: 'rolled', to: 'rolled2', bi: true }
62 | ]
63 | });
64 |
65 | controller.init('out');
66 | t.equal(target1.value, values.out, 'target1 init to out');
67 | t.equal(target2.value, values.out, 'target2 init to out');
68 |
69 | // just a regular animation
70 | controller.go('rolled', function() {
71 | hasGoneIntoRolled = true;
72 | t.equal(target1.value, values.rolled, 'target1 went to rolled');
73 | t.equal(target2.value, values.rolled, 'target2 went to rolled');
74 |
75 |
76 | // this is to test attempting to control ui that are going to the same states
77 | controller.go('rolled2', function() {
78 | hasGoneIntoRolled2 = true;
79 | t.equal(target1.value, values.idle, 'target1 went to idle');
80 | t.equal(target2.value, values.rolled, 'target2 went to rolled');
81 |
82 | // test going back to idle
83 | controller.go('idle', function() {
84 | hasGoneIntoIdle = true;
85 | t.equal(target1.value, values.idle, 'target1 went to idle');
86 | t.equal(target2.value, values.idle, 'target2 went to idle');
87 | });
88 | });
89 | });
90 |
91 | setTimeout(function() {
92 | t.ok(hasGoneIntoRolled, 'went from from out to rolled state');
93 | t.ok(hasGoneIntoRolled2, 'went from rolled to rolled2 state');
94 | t.ok(hasGoneIntoIdle, 'went from rolled2 to idle state');
95 | t.end();
96 | }, 3000);
97 | };
--------------------------------------------------------------------------------
/test/defineConstrutor.js:
--------------------------------------------------------------------------------
1 | var f1 = require('./..');
2 |
3 |
4 | var EXPECTED_STATES = [
5 | [
6 | {
7 | "item": {
8 | "value": 0
9 | }
10 | },
11 | "out"
12 | ],
13 | [
14 | {
15 | "item": {
16 | "value": 100
17 | }
18 | },
19 | "idle"
20 | ]
21 | ];
22 |
23 |
24 | var EXPECTED_UPDATES = [
25 | [
26 | {
27 | "item": {
28 | "value": 0
29 | }
30 | },
31 | "out",
32 | 0,
33 | 0
34 | ],
35 | [
36 | {
37 | "item": {
38 | "value": 30
39 | }
40 | },
41 | "out",
42 | 0.3,
43 | 1
44 | ],
45 | [
46 | {
47 | "item": {
48 | "value": 60
49 | }
50 | },
51 | "out",
52 | 0.6,
53 | 1
54 | ],
55 | [
56 | {
57 | "item": {
58 | "value": 90
59 | }
60 | },
61 | "out",
62 | 0.9,
63 | 1
64 | ],
65 | [
66 | {
67 | "item": {
68 | "value": 100
69 | }
70 | },
71 | "idle",
72 | 0,
73 | 1
74 | ]
75 | ];
76 |
77 |
78 |
79 |
80 |
81 |
82 | module.exports = function(t) {
83 |
84 | var cbStates = [];
85 | var cbUpdates = [];
86 |
87 | var item = {};
88 | var ui = f1({
89 | autoUpdate: false,
90 | onState: callBackState,
91 | onUpdate: callBackUpdate,
92 | states: {
93 | out: {
94 | item: {
95 | value: 0
96 | }
97 | },
98 |
99 | idle: {
100 | item: {
101 | value: 100
102 | }
103 | }
104 | },
105 | transitions: [
106 | { from: 'out', to: 'idle', animation: {
107 | duration: 1
108 | }
109 | }
110 | ],
111 | targets: {
112 | item: item
113 | },
114 | parsers: {
115 | update: [
116 | function(target, state) {
117 | for(var i in state) {
118 | target[ i ] = state[ i ];
119 | }
120 | }
121 | ]
122 | }
123 | });
124 |
125 | ui.init('out');
126 | ui.go('idle');
127 |
128 | ui.step(300);
129 | ui.step(300);
130 | ui.step(300);
131 | ui.step(300);
132 | ui.step(300);
133 | ui.step(300);
134 | ui.step(300);
135 | ui.step(300);
136 |
137 | t.deepEqual(cbStates, EXPECTED_STATES, 'callback states matched expected');
138 | t.deepEqual(cbUpdates, EXPECTED_UPDATES, 'callback updates matched expected');
139 | t.end();
140 |
141 | function callBackState() {
142 | cbStates.push(Array.prototype.slice.call(arguments));
143 | }
144 |
145 | function callBackUpdate() {
146 | cbUpdates.push(Array.prototype.slice.call(arguments));
147 | }
148 | };
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 |
3 | test('callback and events return same', require('./callbackAndUpdate'));
4 | test('passing states, targets, transitions, parsers through constructor', require('./defineConstrutor'));
5 | test('advanced animation', require('./advancedAnimation'));
6 |
7 | // theres some issues with kimi step
8 | test('chief', require('./chief'));
--------------------------------------------------------------------------------