├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── dist ├── cascade-min.js ├── cascade.css ├── cascade.js └── cascade.min.css ├── docs ├── cascadejs-logo.png ├── css │ ├── font │ │ └── Quicksand-Regular.otf │ ├── src │ │ ├── _code_example.scss │ │ ├── _sidebar.scss │ │ ├── _syntax.scss │ │ ├── cascade.css │ │ ├── concise.css │ │ └── normalize.css │ ├── styles.css │ └── styles.scss ├── index.html └── js │ ├── cascade.js │ ├── extras.js │ └── vendor │ └── html5shiv.js ├── gulpfile.js ├── package.json └── src ├── css └── cascade.scss └── js └── cascade.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Joshua Bartlett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CascadeJS 2 | 3 | [Official Docs](https://bartboy011.github.io/CascadeJS/) 4 | 5 | CascadeJS makes it simple to add cascading animations to individual letters or elements, using only vanilla javascript. 6 | 7 | ## Installation 8 | 9 | Installation is easy, you have three options: 10 | 11 | ```bash 12 | npm install cascadejs 13 | bower install cascadejs 14 | ``` 15 | 16 | or just download from GitHub. Then: 17 | 18 | ```html 19 | 20 | 37 | ``` 38 | 39 | ## Usage 40 | 41 | CascadeJS works by reading a line of text, splitting that text into spans around each character, and then adding animations to each span (by default). The splitting is modeled after charming.js, a vanilla javascript version of Lettering.js. Cascade requires an element be passed in on initialization: 42 | 43 | ```javascript 44 | var cascade = new Cascade(element); 45 | ``` 46 | 47 | Cascade has two methods that can be called: 48 | 49 | ```javascript 50 | var cascade = new Cascade(element); 51 | cascade.fragment(); 52 | cascade.flow(); 53 | ``` 54 | 55 | Calling `fragment()` will split the text without adding the animations, and must be called on an element with only a text node for a child. Calling `flow()` will first fragment the text, and then animate all children elements in a cascade. I would suggest calling `flow()` whenever possible. 56 | 57 | ### Usage of `flow()` 58 | 59 | Initialize Cascade with an element: 60 | 61 | ```javascript 62 | var cascade = new Cascade(element); 63 | ``` 64 | 65 | Then call `flow()` with any or none of the following options: 66 | 67 | ```javascript 68 | cascade.flow({ 69 | tagName: 'span', 70 | classPrefix: 'cascade char', 71 | effect: 'fadeUp', 72 | totalTime: 0.5, 73 | duration: 1, 74 | shouldFragment: true, 75 | shouldAppendTargetClass: true, 76 | targetClass: 'cascade-container' 77 | }); 78 | ``` 79 | 80 | `tagName:` - The tag that will wrap each character, ie `A`. Defaults to 'span'. 81 | 82 | `classPrefix:` - The class(es) to be added to the tags, with an increasing number appended at the end, ie `A`. Defaults to 'cascade char'. 83 | 84 | `effect:` - The class name that adds the animation. In this default, 'fadeUp' uses a keyframe animation to fade in text while utilizing translateY to slide it up. Defaults to 'fadeUp'. 85 | 86 | `totalTime:` - The amount of time in seconds as a float/integer from the first letter beginning it's animation to the last letter beginning it's animation. Defaults to `0.5`. 87 | 88 | `duration:` - How long each letter's animation lasts. If you'd rather specify this in your CSS, just leave this option empty. Defaults to `null` and doesn't add this styling. 89 | 90 | `shouldFragment:` - If you've previously called `fragment()` on this node, set this option to false otherwise the fragmentation will run again and throw an error. Defaults to true. 91 | 92 | `shouldAppendTargetClass` - Defaults to true, will append a class to the target element after fragmenting. 93 | 94 | `targetClass` - The class to be appended to the target element. Defaults to 'cascade-container'. Note: if you've already added the class to the element that you'd like appended, CascadeJS will skip the appending. Example: 95 | 96 | ```html 97 | 98 |

Hello!

99 | 100 | 107 | 108 | 109 |

Hello!

110 | 111 | 118 | ``` 119 | 120 | #### Example: 121 | 122 | ```html 123 |

Hello!

124 | 125 | 132 | ``` 133 | 134 | Produces: 135 | 136 | ```html 137 |

138 | H 139 | e 140 | l 141 | l 142 | o 143 | ! 144 |

145 | ``` 146 | 147 | Use Animate.css? Try this: 148 | 149 | ```javascript 150 | var element = document.getElementsByClassName('cascade-container')[0]; 151 | var cascade = new Cascade(element).flow({ 152 | classPrefix: 'animated char', 153 | effect: 'bounceInLeft' 154 | }); 155 | ``` 156 | 157 | You can also call `flow()` on an element that already has children nodes of any kind and they'll be animated as well: 158 | 159 | ```html 160 |
161 |
Element One
162 |
Element Two
163 |
Element Three
164 |
Element Four
165 |
166 | 167 | 13 | 14 | 17 | 18 | 19 |
20 |
21 |
22 |

CascadeJS

23 |

Create cascading effects with ease.

24 |
25 |
26 | 32 |
33 |
34 | 61 |
62 |
63 |

Introduction

64 |

65 | CascadeJS makes it simple to add cascading animations to individual letters or elements, using only vanilla javascript. 66 |

67 |
68 |
69 |

Installation

70 |

71 | Installation is easy, you have three options: 72 |

73 |
 74 | npm install cascadejs
 75 | bower install cascadejs
76 |

77 | or just download from GitHub. Then: 78 |

79 | 80 |
<link href='stylesheet' type='text/css' href='cascade.min.css' />
 81 | <script type='text/javascript' src='cascade.min.js' />
82 |

Quick start

83 |

84 | CascadeJS comes with default settings, so as long as you've included both the JS and CSS files, it will work right out of the box. Here's an example usage: 85 |

86 | 123 |
124 |
125 |

Usage

126 |

127 | CascadeJS works by reading a line of text, splitting that text into spans around each character, and then adding animations to each span (by default). The splitting is modeled after charming.js, a vanilla javascript version of Lettering.js. Cascade requires an element be passed in on initialization: 128 |

129 | 130 |
var cascade = new Cascade(element);
131 | 132 |

133 | Cascade has two methods that can be called: 134 |

135 | 136 |
var cascade = new Cascade(element);
137 | cascade.fragment();
138 | cascade.flow();
139 | 140 |

141 | Calling fragment() will split the text without adding the animations, and must be called on an element with only a text node for a child. Calling flow() will first fragment the text, and then animate all children elements in a cascade. 142 |

143 |
144 |
145 |

Usage of flow()

146 | 147 |

148 | Initialize Cascade with an element: 149 |

150 | 151 |
var cascade = new Cascade(element);
152 | 153 |

154 | Then call flow() with any or none of the following options: 155 |

156 | 157 |
cascade.flow({
158 |   tagName: 'span',
159 |   classPrefix: 'cascade char',
160 |   effect: 'fadeUp',
161 |   totalTime: 0.5,
162 |   duration: 1,
163 |   shouldFragment: true,
164 |   shouldAppendTargetClass: true,
165 |   targetClass: 'cascade-container'
166 | });
167 | 168 |

169 | tagName: - The tag that will wrap each character, ie <span>A</span>. Defaults to 'span'. 170 |

171 | 172 |

173 | classPrefix: - The class(es) to be added to the tags, with an increasing number appended at the end, ie <span class="cascade char1">A</span>. Defaults to 'cascade char'. 174 |

175 | 176 |

177 | effect: - The class name that adds the animation. In this default, 'fadeUp' uses a keyframe animation to fade in text while utilizing translateY to slide it up. Defaults to 'fadeUp'.
178 |

179 | 180 |

181 | totalTime: - The amount of time in seconds as a float/integer from the first letter beginning it's animation to the last letter beginning it's animation. Defaults to 0.5. 182 |

183 | 184 |

185 | duration: - How long each letter's animation lasts. If you'd rather specify this in your CSS, just leave this option empty. Defaults to null and doesn't add this styling. 186 |

187 | 188 |

189 | shouldFragment: - If you've previously called fragment() on this node, set this option to false otherwise the fragmentation will run again and throw an error. Defaults to true. 190 |

191 | 192 |

193 | shouldAppendTargetClass: - Defaults to true, will append a class to the target element after fragmenting. 194 |

195 | 196 |

197 | targetClass: - The class to be appended to the target element. Defaults to 'cascade-container'. Note: if you've already added the class to the element that you'd like appended, CascadeJS will skip the appending. Example: 198 |

199 | 200 |
<!-- This element will have a class appended to it -->
201 | <h1 class='text-center'>Hello!</h1>
202 | 
203 | <script>
204 |   var element = document.getElementsByTagName('h1')[0];
205 |   var cascade = new Cascade(element);
206 |   cascade.flow({
207 |     targetClass: 'cascade-container'
208 |   });
209 | </script>
210 | 
211 | <!-- This element will NOT have a class appended -->
212 | <h1 class='text-center cascade-container'>Hello!</h1>
213 | 
214 | <script>
215 |   var element = document.getElementsByTagName('h1')[0];
216 |   var cascade = new Cascade(element);
217 |   cascade.flow({
218 |     targetClass: 'cascade-container'
219 |   });
220 | </script>
221 |
222 | 223 |
224 |

Usage of fragment()

225 | 226 |

227 | fragment() will split and wrap your text, but not animate it. Just want to style each letter in an interesting way? Want a vanilla javascript replacement for lettering.js? This is it. fragment() accepts all the same options as flow() , but only the following will be applied, while the rest will be stored in the object for when flow() is called: 228 |

229 | 230 |
var cascade = new Cascade(element).fragment({
231 |   tagName: 'span',
232 |   classPrefix: 'cascade char'
233 | });
234 | 235 |

236 | You can then call: 237 |

238 | 239 |
cascade.flow({
240 |   shouldFragment: false
241 | });
242 | 243 |

244 | when you're ready for some cool animations. 245 |

246 |
247 | 248 |
249 |

Examples

250 |
<h1>Hello!</h1>
251 | 
252 | <script>
253 |   var element = document.getElementsByClassName('cascade-container')[0];
254 |   cascade = new Cascade(element).flow({
255 |     totalTime: 2,
256 |     duration: 1.5
257 |   })
258 | </script>
259 | 260 |

261 | Produces: 262 |

263 | 264 |
<h1 class='cascade-container'>
265 |   <span class='cascade char1 fadeUp' style='animation-delay: 0s; animation-duration: 1.5s;'>H</span>
266 |   <span class='cascade char2 fadeUp' style='animation-delay: 0.4s; animation-duration: 1.5s;'>e</span>
267 |   <span class='cascade char3 fadeUp' style='animation-delay: 0.8s; animation-duration: 1.5s;'>l</span>
268 |   <span class='cascade char4 fadeUp' style='animation-delay: 1.2s; animation-duration: 1.5s;'>l</span>
269 |   <span class='cascade char5 fadeUp' style='animation-delay: 1.6s; animation-duration: 1.5s;'>o</span>
270 |   <span class='cascade char6 fadeUp' style='animation-delay: 2s; animation-duration: 1.5s;'>!</span>
271 | </h1>
272 | 273 |

274 | Use Animate.css? Try this: 275 |

276 | 277 |
var element = document.getElementsByClassName('cascade-container')[0];
278 | var cascade = new Cascade(element).flow({
279 |   classPrefix: 'animated char',
280 |   effect: 'bounceInLeft'
281 | });
282 |
283 |
284 |
285 | 286 | 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /docs/js/cascade.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 Joshua Bartlett 3 | * Available under MIT License 4 | */ 5 | 6 | function Cascade(element, options) { 7 | // set defaults 8 | this.targetElement = element; // element to segment and animate 9 | this.children = this.targetElement.childNodes; // pre-segment child nodes 10 | 11 | this.setDefaults = function (options) { 12 | var opts = options || {}; 13 | this.classPrefix = opts.classPrefix || 'cascade char'; 14 | this.tagName = opts.tagName || 'span'; 15 | this.effect = opts.effect || 'fadeUp'; 16 | this.totalTime = opts.totalTime || 0.5; 17 | this.duration = opts.duration; 18 | this.shouldFragment = opts.shouldFragment != null ? opts.shouldFragment : true; 19 | this.shouldAppendTargetClass = opts.shouldAppendTargetClass != null ? opts.shouldAppendTargetClass : true; 20 | this.targetClass = opts.targetClass != null ? opts.targetClass : ' cascade-container'; 21 | }.bind(this); 22 | 23 | function appendTargetClass(target, targetClass) { 24 | var string = target.className, 25 | match = string.match(targetClass); 26 | 27 | if (match === null) { 28 | target.className += targetClass; 29 | } 30 | } 31 | 32 | // take a string inside an element, split it, wrap them 33 | // in their own tags, then inject the whole thing back 34 | // into the parent node 35 | function inject(element, tagName, classPrefix, shouldAppendTargetClass, targetClass) { 36 | var parent = element.parentNode, 37 | string = element.nodeValue, 38 | length = string.length, 39 | i = -1, 40 | count = 1; 41 | 42 | while (++i < length) { 43 | var newNode = document.createElement(tagName), 44 | newNodeText = document.createTextNode(string[i]); 45 | 46 | newNode.className = classPrefix + count; 47 | newNode.appendChild(newNodeText); 48 | 49 | parent.insertBefore(newNode, element); 50 | 51 | ++count; 52 | } 53 | 54 | parent.removeChild(element); 55 | 56 | this.targetElement = parent; 57 | this.children = parent.childNodes; 58 | 59 | parent.setAttribute('data-made-with', 'CascadeJS'); 60 | 61 | if (shouldAppendTargetClass) { 62 | appendTargetClass(parent, targetClass); 63 | } 64 | } 65 | 66 | function animate(item, animationEffect, delay, duration) { 67 | item.style.animationDelay = delay + 's'; 68 | 69 | // only add animation-duration to element if it was 70 | // specified in the options. Otherwise, let whatever is specified 71 | // in the CSS handle this. 72 | if (duration != null) { 73 | item.style.animationDuration = duration + 's'; 74 | } 75 | 76 | item.className += ' ' + animationEffect; 77 | } 78 | 79 | this.flow = function (options) { 80 | // set defaults 81 | var opts = options || {}; 82 | this.setDefaults(opts); 83 | 84 | // split the text into spans if segmentation 85 | // hasn't been performed yet 86 | if (this.shouldFragment) { 87 | this.fragment({}, false); 88 | } 89 | 90 | // iterate through spans and animate them 91 | var array = this.children, 92 | totalTime = this.totalTime, 93 | effect = this.effect, 94 | duration = this.duration, 95 | i = -1; 96 | while (i++ < array.length - 1) { 97 | var item = array[i], 98 | time = totalTime / (array.length - 1) * i; 99 | animate(item, effect, time, duration); 100 | } 101 | }.bind(this); 102 | 103 | this.fragment = function (options, setDefaults) { 104 | // set defaults only if they are being passed in from an 105 | // external call, ie new Cascade.fragment(options) to prevent 106 | // overwriting defaults when called internally 107 | var shouldSetDefaults = setDefaults != null ? setDefaults : true; 108 | var opts = options || {}; 109 | 110 | // set defaults only if 111 | if (shouldSetDefaults) { 112 | this.setDefaults(opts); 113 | } 114 | 115 | var children = this.children; 116 | 117 | // throw some errors if elements are set up improperly 118 | if (children.length > 1) { 119 | console.error('CascadeJS Error - error in target element:\n Please limit the amount children of this element to 1 (population control and all that).'); 120 | } else if (children[0].nodeType !== Node.TEXT_NODE) { 121 | console.error('CascadeJS Error - error in target element:\n Please ensure that the immediate child of this element is text node. If you would like to animate a collection of divs, please add the option shouldFragment: false when calling Cascade.flow().'); 122 | } else { 123 | inject(children[0], this.tagName, this.classPrefix, this.shouldAppendTargetClass, this.targetClass); 124 | } 125 | }.bind(this); 126 | } -------------------------------------------------------------------------------- /docs/js/extras.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | window.sideBarLocked = false; 3 | window.addEventListener('scroll', function() { 4 | if (!window.sideBarLocked && window.scrollY >= window.innerHeight) { 5 | document.getElementById('sidebar').style.position = 'fixed'; 6 | window.sideBarLocked = true; 7 | } else if (window.sideBarLocked && window.scrollY < window.innerHeight) { 8 | document.getElementById('sidebar').style.position = 'absolute'; 9 | window.sideBarLocked = false; 10 | } 11 | }) 12 | }).call(this); 13 | -------------------------------------------------------------------------------- /docs/js/vendor/html5shiv.js: -------------------------------------------------------------------------------- 1 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 2 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 3 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 4 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 5 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d' 16 | }) 17 | }; 18 | 19 | gulp.task('sass', function () { 20 | gulp.src('./src/css/cascade.scss') 21 | .pipe(plumber(plumberErrorHandler)) 22 | .pipe(sass({ 23 | outputStyle: 'compressed' 24 | })) 25 | .pipe(autoprefixer()) 26 | .pipe(rename('cascade.min.css')) 27 | .pipe(gulp.dest('dist')) 28 | .pipe(livereload()); 29 | 30 | gulp.src('./src/css/cascade.scss') 31 | .pipe(plumber(plumberErrorHandler)) 32 | .pipe(sass()) 33 | .pipe(autoprefixer()) 34 | .pipe(gulp.dest('dist')) 35 | .pipe(livereload()); 36 | 37 | gulp.src('./src/css/cascade.scss') 38 | .pipe(plumber(plumberErrorHandler)) 39 | .pipe(sass()) 40 | .pipe(autoprefixer()) 41 | .pipe(gulp.dest('docs/css/src')) 42 | .pipe(livereload()); 43 | 44 | gulp.src('./docs/css/styles.scss') 45 | .pipe(plumber(plumberErrorHandler)) 46 | .pipe(sass()) 47 | .pipe(autoprefixer()) 48 | .pipe(gulp.dest('docs/css')) 49 | .pipe(livereload()); 50 | }); 51 | 52 | gulp.task('js', function () { 53 | gulp.src([ 54 | 'src/js/initializers/*.js', 55 | 'src/js/cascade.js' 56 | ]) 57 | .pipe(plumber(plumberErrorHandler)) 58 | .pipe(babel({ 59 | presets: [ 60 | ["es2015", { "modules": false }] 61 | ] 62 | })) 63 | .pipe(concat('cascade.js')) 64 | .pipe(minify()) 65 | .pipe(gulp.dest('dist')) 66 | .pipe(livereload()); 67 | 68 | gulp.src([ 69 | 'src/js/initializers/*.js', 70 | 'src/js/cascade.js' 71 | ]) 72 | .pipe(plumber(plumberErrorHandler)) 73 | .pipe(babel({ 74 | presets: [ 75 | ["es2015", { "modules": false }] 76 | ] 77 | })) 78 | .pipe(concat('cascade.js')) 79 | .pipe(gulp.dest('docs/js')) 80 | .pipe(livereload()); 81 | }); 82 | 83 | /*gulp.task('img', function() { 84 | gulp.src('src/img/*.{png,jpg,gif,svg}') 85 | .pipe(plumber(plumberErrorHandler)) 86 | .pipe(imagemin({ 87 | optimizationLevel: 7, 88 | progressive: true 89 | })) 90 | .pipe(gulp.dest('dist/img')) 91 | .pipe(livereload()); 92 | });*/ 93 | 94 | gulp.task('watch', function() { 95 | livereload.listen(); 96 | gulp.watch('src/css/**/*.scss', ['sass']); 97 | gulp.watch('docs/css/**/*.scss', ['sass']); 98 | gulp.watch('src/js/**/*.js', ['js']); 99 | //gulp.watch('img/*.{png,jpg,gif,svg}', ['img']); 100 | }); 101 | 102 | gulp.task('default', ['sass', 'js', 'watch']); 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cascadejs", 3 | "version": "1.0.7", 4 | "description": "CascadeJS makes it simple to add cascading animations to individual letters in a text string, using only vanilla javascript, for dynamic typography.", 5 | "main": "cascade.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/bartboy011/CascadeJS.git" 12 | }, 13 | "keywords": [ 14 | "javascript", 15 | "npm", 16 | "animation", 17 | "cascading", 18 | "typography", 19 | "letters" 20 | ], 21 | "author": "Joshua Bartlett", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/bartboy011/CascadeJS/issues" 25 | }, 26 | "homepage": "https://github.com/bartboy011/CascadeJS#readme", 27 | "devDependencies": { 28 | "babel-preset-es2015": "^6.22.0", 29 | "gulp-autoprefixer": "^3.1.1", 30 | "gulp-babel": "^6.1.2", 31 | "gulp-concat": "^2.6.1", 32 | "gulp-imagemin": "^3.1.1", 33 | "gulp-livereload": "^3.8.1", 34 | "gulp-minify": "0.0.15", 35 | "gulp-notify": "^3.0.0", 36 | "gulp-plumber": "^1.1.0", 37 | "gulp-rename": "^1.2.2", 38 | "gulp-sass": "^3.1.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/css/cascade.scss: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Joshua Bartlett */ 2 | /* Available under MIT License */ 3 | 4 | .cascade-container * { 5 | display : inline-block; 6 | white-space: pre; 7 | } 8 | 9 | .cascade { 10 | animation-fill-mode: both; 11 | animation-duration : 1s; 12 | } 13 | @keyframes fadeUp { 14 | from { 15 | opacity : 0; 16 | transform: translate3d(0, 100%, 0); 17 | } 18 | 19 | to { 20 | opacity : 1; 21 | transform: none; 22 | } 23 | } 24 | 25 | .fadeUp { 26 | animation-name: fadeUp; 27 | } 28 | @keyframes fadeDown { 29 | from { 30 | opacity : 0; 31 | transform: translate3d(0, -100%, 0); 32 | } 33 | 34 | to { 35 | opacity : 1; 36 | transform: none; 37 | } 38 | } 39 | 40 | .fadeDown { 41 | animation-name: fadeDown; 42 | } 43 | @keyframes doTheWave { 44 | to { 45 | transform: translateY(100px); 46 | } 47 | } 48 | 49 | .doTheWave { 50 | animation-name : doTheWave; 51 | animation-timing-function: ease-in-out; 52 | animation-direction : alternate; 53 | animation-iteration-count: infinite; 54 | } 55 | -------------------------------------------------------------------------------- /src/js/cascade.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 Joshua Bartlett 3 | * Available under MIT License 4 | */ 5 | 6 | function Cascade(element, options) { 7 | // set defaults 8 | this.targetElement = element; // element to segment and animate 9 | this.children = this.targetElement.childNodes; // pre-segment child nodes 10 | 11 | this.setDefaults = function(options) { 12 | var opts = options || {}; 13 | this.classPrefix = opts.classPrefix || 'cascade char'; 14 | this.tagName = opts.tagName || 'span'; 15 | this.effect = opts.effect || 'fadeUp'; 16 | this.totalTime = opts.totalTime || 0.5; 17 | this.duration = opts.duration; 18 | this.shouldFragment = opts.shouldFragment != null ? opts.shouldFragment : true; 19 | this.shouldAppendTargetClass = opts.shouldAppendTargetClass != null ? opts.shouldAppendTargetClass : true; 20 | this.targetClass = opts.targetClass != null ? opts.targetClass : ' cascade-container'; 21 | }.bind(this); 22 | 23 | function appendTargetClass(target, targetClass) { 24 | let string = target.className, 25 | match = string.match(targetClass); 26 | 27 | if (match === null) { 28 | target.className += targetClass; 29 | } 30 | } 31 | 32 | // take a string inside an element, split it, wrap them 33 | // in their own tags, then inject the whole thing back 34 | // into the parent node 35 | function inject(element, tagName, classPrefix, shouldAppendTargetClass, targetClass) { 36 | let parent = element.parentNode, 37 | string = element.nodeValue, 38 | length = string.length, 39 | i = -1, 40 | count = 1; 41 | 42 | while (++i < length) { 43 | let newNode = document.createElement(tagName), 44 | newNodeText = document.createTextNode(string[i]); 45 | 46 | newNode.className = classPrefix + count; 47 | newNode.appendChild(newNodeText); 48 | 49 | parent.insertBefore(newNode, element); 50 | 51 | ++count; 52 | } 53 | 54 | parent.removeChild(element); 55 | 56 | this.targetElement = parent; 57 | this.children = parent.childNodes; 58 | 59 | parent.setAttribute('data-made-with', 'CascadeJS'); 60 | 61 | if (shouldAppendTargetClass) { 62 | appendTargetClass(parent, targetClass); 63 | } 64 | } 65 | 66 | function animate(item, animationEffect, delay, duration) { 67 | item.style.animationDelay = `${delay}s`; 68 | 69 | // only add animation-duration to element if it was 70 | // specified in the options. Otherwise, let whatever is specified 71 | // in the CSS handle this. 72 | if (duration != null) { 73 | item.style.animationDuration = `${duration}s`; 74 | } 75 | 76 | item.className += (` ${animationEffect}`); 77 | } 78 | 79 | this.flow = function(options) { 80 | // set defaults 81 | let opts = options || {}; 82 | this.setDefaults(opts); 83 | 84 | // split the text into spans if segmentation 85 | // hasn't been performed yet 86 | if (this.shouldFragment) { 87 | this.fragment({}, false); 88 | } 89 | 90 | // iterate through spans and animate them 91 | let array = this.children, 92 | totalTime = this.totalTime, 93 | effect = this.effect, 94 | duration = this.duration, 95 | i = -1; 96 | while (i++ < array.length - 1) { 97 | let item = array[i], 98 | time = (totalTime / (array.length - 1)) * i; 99 | animate(item, effect, time, duration); 100 | } 101 | }.bind(this); 102 | 103 | this.fragment = function(options, setDefaults) { 104 | // set defaults only if they are being passed in from an 105 | // external call, ie new Cascade.fragment(options) to prevent 106 | // overwriting defaults when called internally 107 | let shouldSetDefaults = setDefaults != null ? setDefaults : true; 108 | let opts = options || {}; 109 | 110 | // set defaults only if 111 | if (shouldSetDefaults) { 112 | this.setDefaults(opts); 113 | } 114 | 115 | let children = this.children; 116 | 117 | // throw some errors if elements are set up improperly 118 | if (children.length > 1) { 119 | console.error('CascadeJS Error - error in target element:\n Please limit the amount children of this element to 1 (population control and all that).'); 120 | } else if (children[0].nodeType !== Node.TEXT_NODE) { 121 | console.error('CascadeJS Error - error in target element:\n Please ensure that the immediate child of this element is text node. If you would like to animate a collection of divs, please add the option shouldFragment: false when calling Cascade.flow().'); 122 | } else { 123 | inject(children[0], this.tagName, this.classPrefix, this.shouldAppendTargetClass, this.targetClass); 124 | } 125 | }.bind(this); 126 | } 127 | --------------------------------------------------------------------------------