├── .versions
├── README.md
├── flow-transition-tests.js
├── flow-transition.js
├── package.js
└── section.html
/.versions:
--------------------------------------------------------------------------------
1 | base64@1.0.3
2 | binary-heap@1.0.3
3 | blaze@2.1.2
4 | blaze-tools@1.0.3
5 | callback-hook@1.0.3
6 | check@1.0.5
7 | coffeescript@1.0.6
8 | cosmos:browserify@0.5.0
9 | ddp@1.1.0
10 | deps@1.0.7
11 | ejson@1.0.6
12 | geojson-utils@1.0.3
13 | html-tools@1.0.4
14 | htmljs@1.0.4
15 | id-map@1.0.3
16 | jquery@1.11.3_2
17 | json@1.0.3
18 | kadira:flow-router@2.1.1
19 | local-test:mcissel:flow-transition@1.0.1
20 | logging@1.0.7
21 | mcissel:flow-transition@1.0.1
22 | meteor@1.1.6
23 | minifiers@1.1.5
24 | minimongo@1.0.8
25 | mongo@1.1.0
26 | observe-sequence@1.0.6
27 | ordered-dict@1.0.3
28 | random@1.0.3
29 | reactive-dict@1.1.0
30 | reactive-var@1.0.5
31 | retry@1.0.3
32 | spacebars-compiler@1.0.6
33 | templating@1.1.1
34 | tinytest@1.0.5
35 | tracker@1.0.7
36 | underscore@1.0.3
37 | velocityjs:velocityjs@1.2.1
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FlowTransition (mcissel:flow-transition)
2 |
3 | ## A Blaze Layout Renderer and Transitioner
4 | A Blaze layout renderer that uses VelocityJS powered transitions. It currently works only with [Kadira Flow-Router](https://github.com/kadirahq/flow-router) (previously meteorhacks:flow-router). This renderer enables:
5 |
6 | * Rendering layouts to different sections (regions) using Blaze
7 | * Assign route-to-route transitions for each section
8 | * Transitions can be full page motion, or Velocity / VelocityUI Pack animations
9 | * Sections will only re-render when necessary or when explicitly requested
10 | * Not all sections have to be used on every route
11 |
12 | ## Demo
13 |
14 | [flowtransition.meteor.com/](http://flowtransition.meteor.com/)
15 |
16 | [https://github.com/mcissel/flowtransition-demo](https://github.com/mcissel/flowtransition-demo)
17 |
18 | ## Getting Started
19 |
20 | Add this package and kadira:flow-router (if you haven't already):
21 | ~~~
22 | meteor add kadira:flow-router
23 | meteor add mcissel:flow-transition
24 | ~~~
25 |
26 | You will need a few templates and a couple routes, before you can add a transition.
27 |
28 | ~~~html
29 |
30 | {{> section name="head"}}
31 | {{> section name="body"}}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Articles page
44 | Inline elements don't animate
45 | This will animate, because it's wrapped in a block level element
46 |
47 | ~~~
48 |
49 | And set up these routes. **Route names are required**, because that is how the transitions are assigned
50 | ~~~js
51 | if (Meteor.isClient) {
52 | FlowRouter.route("/", {
53 | name: "home", // required
54 | action: function() {
55 | FlowTransition.flow({body: "welcome"});
56 | }
57 | });
58 |
59 | FlowRouter.route("/articles", {
60 | name: "articles", // required
61 | action: function() {
62 | FlowTransition.flow({head: "header"}, {body: "articles"});
63 | }
64 | });
65 | }
66 | ~~~
67 |
68 | Now you can set up these transitions by assigning the:
69 |
70 | * Section that will be animated (`section`)
71 | * Name of departure route (`from`)
72 | * Name of destination route (`to`)
73 | * Transition object (`txFull` or `txIn`/`txOut`)
74 |
75 | ~~~js
76 | FlowTransition.addTransition({
77 | section: 'body',
78 | from: 'home',
79 | to: 'articles',
80 | txFull: 'left' // direction of motion, content will be moving left
81 | });
82 |
83 | FlowTransition.addTransition({
84 | section: 'head',
85 | from: 'home',
86 | to: 'articles',
87 | txIn: {
88 | hooks: {translateY: '-100%'},
89 | properties: {translateY: [0, '-100%']},
90 | options: {
91 | duration: 220,
92 | easing: 'spring',
93 | queue: false,
94 | complete: function() {
95 | console.log('You can add a callback function here');
96 | }
97 | }
98 | }
99 | });
100 | ~~~
101 |
102 | ### Re-rendering
103 | When a section's content doesn't change, it will not re-render, UNLESS you assign a transition to that section for that route. For example, if you change your header template to:
104 |
105 | ~~~html
106 |
107 |
108 |
109 |
110 | Mission Statement and Contact Info
111 | ~~~
112 |
113 | and add the following code:
114 |
115 | ~~~js
116 | FlowRouter.route("/about",
117 | name: "about",
118 | action: function() {
119 | FlowTransition.flow({head: "header"}, {body: "about_us"});
120 | }
121 | });
122 |
123 | FlowTransition.addTransition({
124 | section: 'head',
125 | from: 'articles',
126 | to: 'about',
127 | txIn: 'flipBounceYIn'
128 | });
129 | ~~~
130 |
131 | In the above case, the `head` section is re-rendered when navigating from '/articles' to '/about', but it will not be re-rendered when navigating from '/' to '/about'.
132 |
133 | ~~~js
134 | ~~~
135 |
136 | ## Transition Animations
137 |
138 | ### Full Screen (full section) Transitions
139 | 4 full screen transitions are included by default. Technically, they are "full section" transitions, wherein they use 100% of the width and height of the section. Also see: [CSS Suggestions](#css-suggestions)
140 |
141 | Use a txFull transition object. The name is one of `[left, right, down, up]` referring to the content's direction of travel. Use just the string, or use it with an `options` object, just like a VelocityJS UI Pack animation. A pair transitions using both forms:
142 |
143 | ~~~js
144 | FlowTransition.addTransition({
145 | section: 'body',
146 | from: 'home',
147 | to: 'articles',
148 | txFull: 'left'
149 | });
150 |
151 | FlowTransition.addTransition({
152 | section: 'body',
153 | from: 'articles',
154 | to: 'home',
155 | txFull:
156 | properties: 'right',
157 | options: {
158 | duration: 350,
159 | easing: 'spring',
160 | queue: false
161 | }
162 | });
163 | ~~~
164 |
165 | Currently, if you use `txFull`, the Flow Transition package will not evaluate the `txIn` or `txOut` objects.
166 |
167 | ### VelocityJS Animations
168 | The last transition example in the [Getting Started](#getting-started) section shows a VelocityJS animation for the transion in object (`txIn`). Both `txIn` and `txOut` are optional. In that example, I did not include a transition out object (`txOut`) since there was no `head` content present in the `from` route.
169 |
170 | A transition object has three parts, each of which hold "key, value" objects:
171 |
172 | The `hooks` will be applied BEFORE the transition animation starts, via a $.Velocity.hook() call
173 | ```
174 | hooks: {translateY: '-100%'},
175 | ```
176 | The `properties` object holds either a string name or animation properties. These are VelocityJS animation properties
177 | ```
178 | properties: {translateY: [0, '-100%']}, // move to translateY = 0, from translateY = -100%
179 | ```
180 | The `options` object is a set of VelocityJS options
181 | ```
182 | options: {duration: 220, easing: 'spring', queue: false}
183 | ```
184 |
185 | ## CSS Suggestions
186 | CSS info
187 | The `section` templates include a wrapper div with the class `transitionable-section`. This div is where Blaze renders the templates. You can also style them by using their id, which is the same as their section name.
188 |
189 | ~~~css
190 | body {
191 | margin: 0;
192 | overflow: hidden;
193 | }
194 |
195 | .transitionable-section {
196 | position: absolute;
197 | left: 0;
198 | right: 0;
199 | }
200 |
201 | #head {
202 | height: 40px;
203 | }
204 |
205 | #body {
206 | top: 40px;
207 | }
208 | ~~~
209 | I suggest using at least the above CSS, plus: the inner templates should fill their parent section
210 |
211 | ## `@TODO:`
212 | - *Put a delay or stop all animations to fix the multiple animations bug
213 | - Offer default animation on a per section basis
214 | - Add automatic reverse animation for return routes
215 | - Allow for multiple parent layouts
216 | - Make some tests for tinyTest
217 |
--------------------------------------------------------------------------------
/flow-transition-tests.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('example', function (test) {
2 | test.equal(true, true);
3 | });
4 |
--------------------------------------------------------------------------------
/flow-transition.js:
--------------------------------------------------------------------------------
1 | FlowTransition = {};
2 | FlowTransition.transitionStore = {};
3 | FlowTransition._sections = {};
4 |
5 | var _ready = false;
6 |
7 | /**
8 | * The animations will be registered like usual velocity animations, using
9 | * a two part array:
10 | * {translateX: [0, -10]}, {easing: "spring", duration: 325}
11 | * -or use a VelocityUI animation:
12 | * "transition.fadeIn", {delay: 400, duration: 325}
13 | * -or- use an built in txFull screen traversal:
14 | * "down", {easing: "spring", duration: 325}
15 | * You can use a txFull screen option "down", which refers to the direction
16 | * of travel of the template. It can be moving: down, up, left, or right
17 | */
18 |
19 | Template.section.rendered = function() {
20 | section = this.data.name;
21 | FlowTransition._sections[section] = document.getElementById(section);
22 | };
23 |
24 | function _setUiHooks(parentElement, transitions) {
25 | if (!parentElement) {
26 | return;
27 | }
28 |
29 | var uiHooks = {};
30 |
31 | if (transitions) {
32 |
33 | if (transitions.txIn) {
34 | uiHooks.insertElement = function(node) {
35 | var _tx = transitions.txIn;
36 |
37 | // set up the hooks to apply properties before insertion
38 | _.each(_tx.hook, function(value, property) {
39 | $.Velocity.hook(node, property, value);
40 | });
41 |
42 | // insert the new element
43 | $(node).prependTo(parentElement);
44 |
45 | // start the animation when the DOM is ready
46 | Meteor.defer(function() {
47 | $(node).velocity(_tx);
48 | });
49 | };
50 | }
51 |
52 | if (transitions.txOut) {
53 | uiHooks.removeElement = function(node) {
54 | var _tx = transitions.txOut;
55 |
56 | // callback = node.remove + user defined callback
57 | _tx.options = _tx.options || {};
58 | _tx.options.complete = (function(complete) {
59 |
60 | return function() {
61 | if (complete) {
62 | complete.apply(complete, arguments);
63 | }
64 | $(node).remove();
65 | };
66 |
67 | })(_tx.options.complete);
68 |
69 | // set up the hooks to apply properties before insertion
70 | _.each(_tx.hook, function(value, property) {
71 | $.Velocity.hook(node, property, value);
72 | });
73 |
74 | // start the animation when the DOM is ready
75 | Meteor.defer(function() {
76 | $(node).velocity(_tx);
77 | });
78 | };
79 | }
80 | }
81 |
82 | parentElement._uihooks = uiHooks;
83 | }
84 |
85 | function _attachDeepObject() {
86 | var missingKey = false;
87 |
88 | _.reduce(arguments, function(mem, key) {
89 |
90 | if (!key) {
91 | missingKey = true;
92 | }
93 |
94 | return mem = mem[key] = mem[key] || {};
95 |
96 | }, this);
97 |
98 | return !missingKey;
99 | }
100 |
101 | function _attachFullPageAnimations() {
102 | var _txName, _options, _property, _value, _valueOpposite;
103 |
104 | // can either be a string name, or an object in the form {properties: {}, options: {}}
105 | this.txFull = (typeof this.txFull === 'string') ? {properties: this.txFull} : this.txFull;
106 | // if (typeof this.txFull === 'string') {
107 | // this.txFull = [this.txFull];
108 | // }
109 | _txName = this.txFull.properties;
110 | _options = this.txFull.options || {
111 | duration: 350,
112 | easing: 'ease-out',
113 | queue: false
114 | };
115 |
116 | // ensure that a valid full page transition is being requested
117 | if (_.indexOf(['down', 'up', 'left', 'right'], _txName) === -1) {
118 | console.log("'" + _txName + "' is not a registered txFull page transition");
119 | return;
120 | }
121 |
122 | // get the property and value for the animation
123 | _property = (_txName === 'down' || _txName === 'up') ? 'translateY' : 'translateX';
124 | _value = (_txName === 'down' || _txName === 'right') ? '-100%' : '100%';
125 | _valueOpposite = (_txName === 'down' || _txName === 'right') ? '100%' : '-100%';
126 |
127 | // attach the txIn and txOut objects
128 | this.txOut = {properties: {} };
129 | this.txIn = {pre: {}, properties: {}, hooks: {}};
130 |
131 | this.txIn.hooks[_property] = _value;
132 | this.txIn.properties[_property] = [0, _value];
133 |
134 | this.txOut.properties[_property] = [_valueOpposite, 0];
135 | this.txOut.options = this.txIn.options = _options;
136 | }
137 |
138 | // FlowTransition.transitionStore holds objects in the form:
139 | // [section][newRoute][oldRoute][txDirection]{ TRANSITION OBJECT }
140 | FlowTransition.addTransition = function(transition) {
141 | var _tx = transition;
142 | var _fts = FlowTransition.transitionStore;
143 |
144 | var attached = _attachDeepObject.apply(_fts, [_tx.section, _tx.to, _tx.from]);
145 | if (!attached) {
146 | console.log("A FlowTransition transition object must have the parameters:" +
147 | " section, from, to; and should have the parameters: txFull or txIn & txOut.");
148 | }
149 |
150 | if (_tx.txFull) {
151 | _attachFullPageAnimations.call(_tx);
152 | }
153 | if (_tx.txIn) {
154 | var _txIn = (typeof _tx.txIn === 'string') ? {properties: _tx.txIn} : _tx.txIn;
155 | _fts[_tx.section][_tx.to][_tx.from].txIn = _txIn;
156 | }
157 | if (_tx.txOut) {
158 | var _txOut = (typeof _tx.txOut === 'string') ? {properties: _tx.txOut} : _tx.txOut;
159 | _fts[_tx.section][_tx.to][_tx.from].txOut = _txOut;
160 | }
161 | };
162 |
163 | FlowTransition.applyTransitions = function(newRoute, oldRoute) {
164 | var _fts = FlowTransition.transitionStore;
165 | var hasTransition = {};
166 |
167 | _.each(FlowTransition._sections, function(parentElement, section) {
168 |
169 | // get the transition object or set it to null
170 | var transitions = oldRoute && _fts[section] && _fts[section][newRoute] && _fts[section][newRoute][oldRoute] &&
171 | _fts[section][newRoute][oldRoute];
172 | hasTransition[section] = transitions ? true : false;
173 |
174 | // when transitions is null, stale _uihooks will be removed from the parentElement
175 | _setUiHooks(parentElement, transitions);
176 | });
177 |
178 | // let the caller know which sections have transitions
179 | return hasTransition;
180 | };
181 |
182 | /**
183 | * This is the function to call externally to kick off a layout render.
184 | *
185 | * arguments = are in the form:
186 | * [{section1: contentTemplateName2}, {section2: contentTemplateName2}, ...]
187 | */
188 | FlowTransition.flow = function() {
189 | var layoutAssignment = arguments;
190 |
191 | if (!_ready) { // make sure the initial Template sections are loaded
192 | Meteor.defer(function() {
193 | _ready = true;
194 | FlowTransition.flow.apply(this, layoutAssignment);
195 | });
196 | return;
197 | }
198 |
199 | var _newLayout = _.extend.apply(null, layoutAssignment);
200 |
201 | var flowCurrent = FlowRouter.current();
202 | var newRoute = flowCurrent.route.name;
203 | var oldRoute = flowCurrent.oldRoute ? flowCurrent.oldRoute.name : null;
204 | var hasTransition = FlowTransition.applyTransitions(newRoute, oldRoute);
205 |
206 | _.each(FlowTransition._sections, function(parentElement, section) {
207 | var oldNode = parentElement.firstElementChild;
208 | if (!_newLayout[section]) {
209 | if (oldNode) {
210 | Blaze.remove(Blaze.getView(oldNode));
211 | }
212 | }
213 | else {
214 | var newContent = Template[_newLayout[section]];
215 | var sameContent = (oldNode && (Blaze.getView(oldNode).name === newContent.viewName));
216 | if (!sameContent || hasTransition[section]) {
217 | Blaze.render(newContent, parentElement);
218 | if (oldNode) {
219 | Blaze.remove(Blaze.getView(oldNode));
220 | }
221 | }
222 | }
223 | });
224 | };
225 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: 'mcissel:flow-transition',
3 | version: '1.0.1',
4 | summary: 'A transition and layout renderer for FlowRouter',
5 | git: 'https://github.com/mcissel/FlowTransition',
6 | documentation: 'README.md'
7 | });
8 |
9 | Package.onUse(function(api) {
10 | api.versionsFrom('1.1');
11 | api.use('blaze');
12 | api.use('templating');
13 | api.use('underscore');
14 | api.use('velocityjs:velocityjs@1.2.1');
15 | api.use('kadira:flow-router@2.1.1');
16 |
17 | api.addFiles([
18 | 'section.html',
19 | 'flow-transition.js'
20 | ], ['client']);
21 |
22 | api.export("FlowTransition", 'client');
23 | });
24 |
25 | Package.onTest(function(api) {
26 | api.use('tinytest');
27 | api.use('mcissel:flow-transition');
28 | api.addFiles('flow-transition-tests.js');
29 | });
30 |
--------------------------------------------------------------------------------
/section.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------