├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── animatic.js ├── animatic.min.js ├── animatic.min.js.map ├── bower.json ├── editor ├── bookmarklet.js ├── css │ ├── editor.css │ └── styles.css ├── index.html └── js │ └── editor.js ├── example ├── bounce.html ├── bounce_css.html ├── cards.html ├── css │ └── main.css ├── css_animation.html ├── custom.html ├── delay.html ├── delay_css.html ├── empty.html ├── infinite.html ├── infinite_css.html ├── keyboard.html ├── keyboard_mixed.html ├── parallel.html ├── parallel_css.html ├── parallel_physics.html ├── parallel_sequence.html ├── physics.html └── timeline.html ├── jquery.animatic.js ├── package.json ├── src ├── animations │ ├── animation.js │ ├── collection.js │ ├── css_animation.js │ ├── easings.js │ ├── parallel.js │ ├── sequence.js │ └── tween.js ├── core.js ├── css.js ├── eventemitter.js ├── item.js ├── math │ ├── matrix.js │ └── vector.js ├── physics │ ├── forces │ │ ├── attraction.js │ │ ├── constant.js │ │ └── edge.js │ ├── particle.js │ └── verlet.js ├── timeline.js ├── utils.js └── world.js └── test ├── jquery.html └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | /src 3 | /test 4 | /Makefile 5 | /bower.json 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### [0.4.0](http://github.com/lvivski/animatic/tree/0.4.0) / 2015-04-13 [Diff](https://github.com/lvivski/animatic/compare/0.3.0...0.4.0) 2 | - physics timeline 3 | - edge bounce 4 | - fix CSS runner 5 | - better Editor 6 | - initial editor bookmarklet 7 | - over 60 bugfixes 8 | 9 | ### [0.3.0](http://github.com/lvivski/animatic/tree/0.3.0) / 2013-05-15 [Diff](https://github.com/lvivski/animatic/compare/0.2.0...0.3.0) 10 | - animations with physics 11 | - new animations runner with parallel sequences 12 | - ability to generate CSS without applying it 13 | — initial editor implementation 14 | - "use strict" 15 | - over 70 bugfixes 16 | 17 | ### [0.2.0](http://github.com/lvivski/animatic/tree/0.2.0) / 2013-03-07 [Diff](https://github.com/lvivski/animatic/compare/0.1.0...0.2.0) 18 | - Timeline 19 | - MS Internet Explorer 10 support 20 | - `Item.center()` and `Item.lookAt(vector)` 21 | - matrix decomposition 22 | - opacity animation 23 | - chainable API 24 | - cross-browser support 25 | - `CSS` mode control methods 26 | - infinite animations 27 | - over 50 bugfixes 28 | 29 | ### [0.1.0](http://github.com/lvivski/animatic/tree/0.1.0) / 2013-02-09 30 | - initial public release 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015 Yehor Lvivski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | JS_COMPILER ?= ./node_modules/uglify-js/bin/uglifyjs 2 | FILES = \ 3 | src/core.js \ 4 | src/utils.js \ 5 | src/math/vector.js \ 6 | src/math/matrix.js \ 7 | src/eventemitter.js \ 8 | src/animations/easings.js \ 9 | src/animations/tween.js \ 10 | src/animations/animation.js \ 11 | src/animations/css_animation.js \ 12 | src/animations/collection.js \ 13 | src/animations/parallel.js \ 14 | src/animations/sequence.js \ 15 | src/css.js \ 16 | src/world.js \ 17 | src/timeline.js \ 18 | src/item.js \ 19 | src/physics/forces/constant.js \ 20 | src/physics/forces/attraction.js \ 21 | src/physics/forces/edge.js \ 22 | src/physics/verlet.js \ 23 | src/physics/particle.js \ 24 | 25 | all: \ 26 | animatic.js \ 27 | animatic.min.js 28 | 29 | animatic.js: ${FILES} 30 | @rm -f $@ 31 | @echo "(function(root){" > $@.tmp 32 | @echo "'use strict'" >> $@.tmp 33 | @cat $(filter %.js,$^) >> $@.tmp 34 | @echo "}(Function('return this')()))" >> $@.tmp 35 | @$(JS_COMPILER) $@.tmp -b indent-level=2 -o $@ 36 | @rm $@.tmp 37 | @chmod a-w $@ 38 | 39 | animatic.min.js: animatic.js 40 | @rm -f $@ 41 | @$(JS_COMPILER) $< -c -m -o $@ \ 42 | --source-map $@.map \ 43 | && du -h $< $@ 44 | 45 | deps: 46 | mkdir -p node_modules 47 | npm install 48 | 49 | clean: 50 | rm -f animatic*.js* 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Animatic 2 | 3 | With `Animatic` it's easy to animate over a hundred objects at a time. Each item can have it's mass and viscosity to emulate realistic objects! 4 | 5 | And it's only 7k when gzipped. 6 | 7 | For Docs and Examples see: http://lvivski.github.com/animatic/ 8 | 9 | To suggest a feature, report a bug, or general discussion: http://github.com/lvivski/animatic/issues/ -------------------------------------------------------------------------------- /animatic.js: -------------------------------------------------------------------------------- 1 | (function(root) { 2 | "use strict"; 3 | var a = {}; 4 | a.world = function() { 5 | return new World(); 6 | }; 7 | a.timeline = function() { 8 | return new Timeline(); 9 | }; 10 | if (typeof module === "object" && typeof module.exports === "object") { 11 | module.exports = a; 12 | } else if (typeof define === "function" && define.amd) { 13 | define(a); 14 | } else { 15 | root.animatic = root.a = a; 16 | } 17 | var requestAnimationFrame = root.requestAnimationFrame, cancelAnimationFrame = root.cancelAnimationFrame, vendors = [ "moz", "webkit", "ms" ]; 18 | for (var i = 0; i < vendors.length && !requestAnimationFrame; i++) { 19 | requestAnimationFrame = root[vendors[i] + "RequestAnimationFrame"]; 20 | cancelAnimationFrame = root[vendors[i] + "CancelAnimationFrame"] || root[vendors[i] + "CancelRequestAnimationFrame"]; 21 | } 22 | var prefix = ([].slice.call(getComputedStyle(document.documentElement, null)).join("").match(/(-(moz|webkit|ms)-)transform/) || [])[1], transformProperty = getProperty("transform"), animationProperty = getProperty("animation"), fixTick; 23 | function getProperty(name) { 24 | return prefix ? prefix + name : name; 25 | } 26 | var performance = root.performance && root.performance.now ? root.performance : Date; 27 | requestAnimationFrame(function(tick) { 28 | fixTick = tick > 1e12 != performance.now() > 1e12; 29 | }); 30 | function merge(obj) { 31 | var i = 1; 32 | while (i < arguments.length) { 33 | var source = arguments[i++]; 34 | for (var property in source) { 35 | if (Array.isArray(source[property])) { 36 | for (var j = 0; j < source[property].length; ++j) { 37 | var value = source[property][j]; 38 | if (value) { 39 | obj[property][j] = value; 40 | } 41 | } 42 | } else { 43 | obj[property] = source[property]; 44 | } 45 | } 46 | } 47 | return obj; 48 | } 49 | var Vector = { 50 | set: function(x, y, z) { 51 | if (Array.isArray(x)) { 52 | y = x[1]; 53 | z = x[2]; 54 | x = x[0]; 55 | } 56 | if (x === undefined) { 57 | x = 0; 58 | } 59 | if (y === undefined) { 60 | y = x; 61 | z = x; 62 | } 63 | return [ x, y, z ]; 64 | }, 65 | length: function(x, y, z) { 66 | if (Array.isArray(x)) { 67 | y = x[1]; 68 | z = x[2]; 69 | x = x[0]; 70 | } 71 | return Math.sqrt(x * x + y * y + z * z); 72 | }, 73 | add: function(a, b) { 74 | return [ a[0] + b[0], a[1] + b[1], a[2] + b[2] ]; 75 | }, 76 | sub: function(a, b) { 77 | return [ a[0] - b[0], a[1] - b[1], a[2] - b[2] ]; 78 | }, 79 | norm: function(x, y, z) { 80 | if (Array.isArray(x)) { 81 | y = x[1]; 82 | z = x[2]; 83 | x = x[0]; 84 | } 85 | var len = this.length(x, y, z); 86 | if (len !== 0) { 87 | x /= len; 88 | y /= len; 89 | z /= len; 90 | } else { 91 | x = 0; 92 | y = 0; 93 | z = 0; 94 | } 95 | return [ x, y, z ]; 96 | }, 97 | dist: function(a, b) { 98 | var dx = a[0] - b[0], dy = a[1] - b[1], dz = a[2] - b[2]; 99 | return Math.sqrt(dx * dx + dy * dy + dz + dz); 100 | }, 101 | cross: function(a, b) { 102 | var x = a[1] * b[2] - a[2] * b[1], y = a[2] * b[0] - a[0] * b[2], z = a[1] * b[1] - a[1] * b[0]; 103 | return [ x, y, z ]; 104 | }, 105 | clone: function(v) { 106 | return v.slice(); 107 | }, 108 | scale: function(x, y, z, f) { 109 | if (Array.isArray(x)) { 110 | f = y; 111 | y = x[1]; 112 | z = x[2]; 113 | x = x[0]; 114 | } 115 | return [ x * f, y * f, z * f ]; 116 | }, 117 | zero: function() { 118 | return [ 0, 0, 0 ]; 119 | } 120 | }; 121 | var radians = Math.PI / 180; 122 | var Matrix = { 123 | identity: function() { 124 | return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; 125 | }, 126 | multiply: function multiply(a, b) { 127 | var c = this.identity(); 128 | c[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8]; 129 | c[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9]; 130 | c[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10]; 131 | c[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8]; 132 | c[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9]; 133 | c[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10]; 134 | c[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8]; 135 | c[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9]; 136 | c[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; 137 | c[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + b[12]; 138 | c[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + b[13]; 139 | c[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + b[14]; 140 | return 2 >= arguments.length ? c : multiply.apply(this, [ c ].concat(Array.prototype.slice.call(arguments, 2))); 141 | }, 142 | translate: function(tx, ty, tz) { 143 | if (!(tx || ty || tz)) return this.identity(); 144 | tx || (tx = 0); 145 | ty || (ty = 0); 146 | tz || (tz = 0); 147 | return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1 ]; 148 | }, 149 | scale: function(sx, sy, sz) { 150 | if (!(sx || sy || sz)) return this.identity(); 151 | sx || (sx = 1); 152 | sy || (sy = 1); 153 | sz || (sz = 1); 154 | return [ sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1 ]; 155 | }, 156 | rotate: function(ax, ay, az) { 157 | if (!(ax || ay || az)) return this.identity(); 158 | ax || (ax = 0); 159 | ay || (ay = 0); 160 | az || (az = 0); 161 | ax *= radians; 162 | ay *= radians; 163 | az *= radians; 164 | var sx = Math.sin(ax), cx = Math.cos(ax), sy = Math.sin(ay), cy = Math.cos(ay), sz = Math.sin(az), cz = Math.cos(az); 165 | return [ cy * cz, cx * sz + sx * sy * cz, sx * sz - cx * sy * cz, 0, -cy * sz, cx * cz - sx * sy * sz, sx * cz + cx * sy * sz, 0, sy, -sx * cy, cx * cy, 0, 0, 0, 0, 1 ]; 166 | }, 167 | rotate3d: function(x, y, z, a) { 168 | a || (a = 0); 169 | a *= radians; 170 | var s = Math.sin(a), c = Math.cos(a), norm = Vector.norm(x, y, z); 171 | x = norm[0]; 172 | y = norm[1]; 173 | z = norm[2]; 174 | var xx = x * x, yy = y * y, zz = z * z, _c = 1 - c; 175 | return [ xx + (1 - xx) * c, x * y * _c + z * s, x * z * _c - y * s, 0, x * y * _c - z * s, yy + (1 - yy) * c, y * z * _c + x * s, 0, x * z * _c + y * s, y * z * _c - x * s, zz + (1 - zz) * c, 0, 0, 0, 0, 1 ]; 176 | }, 177 | skew: function(ax, ay) { 178 | if (!(ax || ay)) return this.identity(); 179 | ax || (ax = 0); 180 | ay || (ay = 0); 181 | ax *= radians; 182 | ay *= radians; 183 | return [ 1, Math.tan(ay), 0, 0, Math.tan(ax), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; 184 | }, 185 | perspective: function(p) { 186 | p = -1 / p; 187 | return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, p, 0, 0, 0, 1 ]; 188 | }, 189 | parse: function(s) { 190 | var m = s.match(/\((.+)\)/)[1].split(/,\s?/); 191 | if (m.length === 6) { 192 | m.splice(2, 0, 0, 0); 193 | m.splice(6, 0, 0, 0); 194 | m.splice(8, 0, 0, 0, 1, 0); 195 | m.push(0, 1); 196 | } 197 | return m; 198 | }, 199 | inverse: function(m) { 200 | var a = this.identity(), inv0 = m[5] * m[10] - m[6] * m[9], inv1 = m[1] * m[10] - m[2] * m[9], inv2 = m[1] * m[6] - m[2] * m[5], inv4 = m[4] * m[10] - m[6] * m[8], inv5 = m[0] * m[10] - m[2] * m[8], inv6 = m[0] * m[6] - m[2] * m[4], inv8 = m[4] * m[9] - m[5] * m[8], inv9 = m[0] * m[9] - m[1] * m[8], inv10 = m[0] * m[5] - m[1] * m[4], det = 1 / (m[0] * inv0 - m[1] * inv4 + m[2] * inv8); 201 | a[0] = det * inv0; 202 | a[1] = -det * inv1; 203 | a[2] = det * inv2; 204 | a[4] = -det * inv4; 205 | a[5] = det * inv5; 206 | a[6] = -det * inv6; 207 | a[8] = det * inv8; 208 | a[9] = -det * inv9; 209 | a[10] = det * inv10; 210 | a[12] = -m[12] * a[0] - m[13] * a[4] - m[14] * a[8]; 211 | a[13] = -m[12] * a[1] - m[13] * a[5] - m[14] * a[9]; 212 | a[14] = -m[12] * a[2] - m[13] * a[6] - m[14] * a[10]; 213 | return a; 214 | }, 215 | compose: function(translate, rotate, scale) { 216 | translate || (translate = []); 217 | rotate || (rotate = []); 218 | scale || (scale = []); 219 | var a = this.rotate(rotate[0], rotate[1], rotate[2]); 220 | if (scale.length) { 221 | a[0] *= scale[0]; 222 | a[1] *= scale[0]; 223 | a[2] *= scale[0]; 224 | a[4] *= scale[1]; 225 | a[5] *= scale[1]; 226 | a[6] *= scale[1]; 227 | a[8] *= scale[2]; 228 | a[9] *= scale[2]; 229 | a[10] *= scale[2]; 230 | } 231 | if (translate.length) { 232 | a[12] = translate[0]; 233 | a[13] = translate[1]; 234 | a[14] = translate[2]; 235 | } 236 | return a; 237 | }, 238 | decompose: function(m) { 239 | var sX = Vector.length(m[0], m[1], m[2]), sY = Vector.length(m[4], m[5], m[6]), sZ = Vector.length(m[8], m[9], m[10]); 240 | var rX = Math.atan2(-m[9] / sZ, m[10] / sZ) / radians, rY = Math.asin(m[8] / sZ) / radians, rZ = Math.atan2(-m[4] / sY, m[0] / sX) / radians; 241 | if (m[4] === 1 || m[4] === -1) { 242 | rX = 0; 243 | rY = m[4] * -Math.PI / 2; 244 | rZ = m[4] * Math.atan2(m[6] / sY, m[5] / sY) / radians; 245 | } 246 | var tX = m[12], tY = m[13], tZ = m[14]; 247 | return { 248 | translate: [ tX, tY, tZ ], 249 | rotate: [ rX, rY, rZ ], 250 | scale: [ sX, sY, sZ ] 251 | }; 252 | }, 253 | transpose: function(m) { 254 | var t; 255 | t = m[1]; 256 | m[1] = m[4]; 257 | m[4] = t; 258 | t = m[2]; 259 | m[2] = m[8]; 260 | m[8] = t; 261 | t = m[6]; 262 | m[6] = m[9]; 263 | m[9] = t; 264 | t = m[3]; 265 | m[3] = m[12]; 266 | m[12] = t; 267 | t = m[7]; 268 | m[7] = m[13]; 269 | m[13] = t; 270 | t = m[11]; 271 | m[11] = m[14]; 272 | m[14] = t; 273 | return m; 274 | }, 275 | lookAt: function(eye, target, up) { 276 | var z = Vector.sub(eye, target); 277 | z = Vector.norm(z); 278 | if (Vector.length(z) === 0) z[2] = 1; 279 | var x = Vector.cross(up, z); 280 | if (Vector.length(x) === 0) { 281 | z[0] += 1e-4; 282 | x = Vector.norm(Vector.cross(up, z)); 283 | } 284 | var y = Vector.cross(z, x); 285 | var a = this.identity(); 286 | a[0] = x[0]; 287 | a[1] = x[1]; 288 | a[2] = x[2]; 289 | a[4] = y[0]; 290 | a[5] = y[1]; 291 | a[6] = y[2]; 292 | a[8] = z[0]; 293 | a[9] = z[1]; 294 | a[10] = z[2]; 295 | return a; 296 | }, 297 | stringify: function(m) { 298 | for (var i = 0; i < m.length; ++i) { 299 | if (Math.abs(m[i]) < 1e-5) m[i] = 0; 300 | } 301 | return "matrix3d(" + m.join() + ")"; 302 | } 303 | }; 304 | function EventEmitter() { 305 | this.handlers = {}; 306 | } 307 | EventEmitter.prototype.on = function(event, handler) { 308 | (this.handlers[event] = this.handlers[event] || []).push(handler); 309 | return this; 310 | }; 311 | EventEmitter.prototype.off = function(event, handler) { 312 | var handlers = this.handlers[event]; 313 | if (handler) { 314 | handlers.splice(handlers.indexOf(handler), 1); 315 | } else { 316 | delete this.handlers[event]; 317 | } 318 | return this; 319 | }; 320 | EventEmitter.prototype.emit = function(event) { 321 | var args = Array.prototype.slice.call(arguments, 1), handlers = this.handlers[event]; 322 | if (handlers) { 323 | for (var i = 0; i < handlers.length; ++i) { 324 | handlers[i].apply(this, args); 325 | } 326 | } 327 | return this; 328 | }; 329 | EventEmitter.prototype.listeners = function(event) { 330 | return this.handlers[event] || []; 331 | }; 332 | var easings = function() { 333 | var fn = { 334 | quad: function(p) { 335 | return Math.pow(p, 2); 336 | }, 337 | cubic: function(p) { 338 | return Math.pow(p, 3); 339 | }, 340 | quart: function(p) { 341 | return Math.pow(p, 4); 342 | }, 343 | quint: function(p) { 344 | return Math.pow(p, 5); 345 | }, 346 | expo: function(p) { 347 | return Math.pow(p, 6); 348 | }, 349 | sine: function(p) { 350 | return 1 - Math.cos(p * Math.PI / 2); 351 | }, 352 | circ: function(p) { 353 | return 1 - Math.sqrt(1 - p * p); 354 | }, 355 | back: function(p) { 356 | return p * p * (3 * p - 2); 357 | } 358 | }; 359 | var easings = { 360 | linear: function(p) { 361 | return p; 362 | } 363 | }; 364 | Object.keys(fn).forEach(function(name) { 365 | var ease = fn[name]; 366 | easings["ease-in-" + name] = ease; 367 | easings["ease-out-" + name] = function(p) { 368 | return 1 - ease(1 - p); 369 | }; 370 | easings["ease-in-out-" + name] = function(p) { 371 | return p < .5 ? ease(p * 2) / 2 : 1 - ease(p * -2 + 2) / 2; 372 | }; 373 | }); 374 | easings.css = { 375 | linear: "cubic-bezier(0.000, 0.000, 1.000, 1.000)", 376 | "ease-in-quad": "cubic-bezier(0.550, 0.085, 0.680, 0.530)", 377 | "ease-in-cubic": "cubic-bezier(0.550, 0.055, 0.675, 0.190)", 378 | "ease-in-quart": "cubic-bezier(0.895, 0.030, 0.685, 0.220)", 379 | "ease-in-quint": "cubic-bezier(0.755, 0.050, 0.855, 0.060)", 380 | "ease-in-sine": "cubic-bezier(0.470, 0.000, 0.745, 0.715)", 381 | "ease-in-expo": "cubic-bezier(0.950, 0.050, 0.795, 0.035)", 382 | "ease-in-circ": "cubic-bezier(0.600, 0.040, 0.980, 0.335)", 383 | "ease-in-back": "cubic-bezier(0.600, -0.280, 0.735, 0.045)", 384 | "ease-out-quad": "cubic-bezier(0.250, 0.460, 0.450, 0.940)", 385 | "ease-out-cubic": "cubic-bezier(0.215, 0.610, 0.355, 1.000)", 386 | "ease-out-quart": "cubic-bezier(0.165, 0.840, 0.440, 1.000)", 387 | "ease-out-quint": "cubic-bezier(0.230, 1.000, 0.320, 1.000)", 388 | "ease-out-sine": "cubic-bezier(0.390, 0.575, 0.565, 1.000)", 389 | "ease-out-expo": "cubic-bezier(0.190, 1.000, 0.220, 1.000)", 390 | "ease-out-circ": "cubic-bezier(0.075, 0.820, 0.165, 1.000)", 391 | "ease-out-back": "cubic-bezier(0.175, 0.885, 0.320, 1.275)", 392 | "ease-in-out-quad": "cubic-bezier(0.455, 0.030, 0.515, 0.955)", 393 | "ease-in-out-cubic": "cubic-bezier(0.645, 0.045, 0.355, 1.000)", 394 | "ease-in-out-quart": "cubic-bezier(0.770, 0.000, 0.175, 1.000)", 395 | "ease-in-out-quint": "cubic-bezier(0.860, 0.000, 0.070, 1.000)", 396 | "ease-in-out-sine": "cubic-bezier(0.445, 0.050, 0.550, 0.950)", 397 | "ease-in-out-expo": "cubic-bezier(1.000, 0.000, 0.000, 1.000)", 398 | "ease-in-out-circ": "cubic-bezier(0.785, 0.135, 0.150, 0.860)", 399 | "ease-in-out-back": "cubic-bezier(0.680, -0.550, 0.265, 1.550)" 400 | }; 401 | return easings; 402 | }(); 403 | function Tween(start, end, property) { 404 | var type = Tween.propTypes[property] || Tween.NUMERIC; 405 | this.type = type; 406 | this.start = Tween.parseValue(start, type); 407 | this.end = Tween.parseValue(end, type); 408 | this.suffix = Tween.px.indexOf(property) !== -1 ? "px" : ""; 409 | } 410 | Tween.NUMERIC = "NUMERIC"; 411 | Tween.COLOR = "COLOR"; 412 | Tween.propTypes = { 413 | color: Tween.COLOR, 414 | backgroundColor: Tween.COLOR, 415 | borderColor: Tween.COLOR 416 | }; 417 | Tween.px = "margin,marginTop,marginLeft,marginBottom,marginRight,padding,paddingTop,paddingLeft,paddingBottom,paddingRight,top,left,bottom,right,width,height,maxWidth,maxHeight,minWidth,minHeight,borderRadius,borderWidth".split(","); 418 | Tween.parseValue = function(value, type) { 419 | return type === Tween.COLOR ? Tween.parseColor(value) : Tween.parseNumeric(value); 420 | }; 421 | Tween.parseNumeric = function(numeric) { 422 | if (!Array.isArray(numeric)) { 423 | numeric = String(numeric).split(/\s+/); 424 | } 425 | return Array.isArray(numeric) ? numeric.map(parseFloat) : Number(numeric); 426 | }; 427 | Tween.parseColor = function(color) { 428 | var hex = color.match(/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i); 429 | if (hex) { 430 | return { 431 | r: parseInt(hex[1], 16), 432 | g: parseInt(hex[2], 16), 433 | b: parseInt(hex[3], 16), 434 | a: 1 435 | }; 436 | } 437 | var rgb = color.match(/^rgba?\(([0-9.]*), ?([0-9.]*), ?([0-9.]*)(?:, ?([0-9.]*))?\)$/); 438 | if (rgb) { 439 | return { 440 | r: parseFloat(rgb[1]), 441 | g: parseFloat(rgb[2]), 442 | b: parseFloat(rgb[3]), 443 | a: parseFloat(rgb[4] != null ? rgb[4] : 1) 444 | }; 445 | } 446 | }; 447 | Tween.prototype.interpolate = function(percent) { 448 | if (this.type === Tween.NUMERIC) { 449 | if (Array.isArray(this.end)) { 450 | return this.array(percent); 451 | } else if (this.end !== undefined) { 452 | return this.absolute(percent); 453 | } 454 | } else if (this.type === Tween.COLOR) { 455 | return this.color(percent); 456 | } 457 | }; 458 | Tween.prototype.array = function(percent) { 459 | var value = []; 460 | for (var i = 0; i < this.end.length; ++i) { 461 | if (this.end[i]) { 462 | value[i] = this.start[i] + this.end[i] * percent; 463 | if (this.suffix) { 464 | value[i] += this.suffix; 465 | } 466 | } 467 | } 468 | return value; 469 | }; 470 | Tween.prototype.absolute = function(percent) { 471 | var value = Number(this.start) + (Number(this.end) - Number(this.start)) * percent; 472 | if (this.suffix) { 473 | value += this.suffix; 474 | } 475 | return value; 476 | }; 477 | Tween.prototype.color = function(percent) { 478 | var rgb = { 479 | r: 0, 480 | g: 0, 481 | b: 0 482 | }; 483 | for (var spectra in rgb) { 484 | var value = Math.round(this.start[spectra] + (this.end[spectra] - this.start[spectra]) * percent); 485 | rgb[spectra] = clamp(value, 0, 255); 486 | } 487 | spectra = "a"; 488 | value = Math.round(this.start[spectra] + (this.end[spectra] - this.start[spectra]) * percent); 489 | rgb[spectra] = clamp(value, 0, 1); 490 | return "rgba(" + [ rgb.r, rgb.g, rgb.b, rgb.a ] + ")"; 491 | }; 492 | function clamp(value, min, max) { 493 | return Math.min(max, Math.max(min, value)); 494 | } 495 | function Animation(item, transform, duration, ease, delay) { 496 | this.item = item; 497 | this.transformation = transform; 498 | this.start = null; 499 | this.diff = null; 500 | this.duration = (transform.duration || duration) | 0; 501 | this.delay = (transform.delay || delay) | 0; 502 | ease = transform.ease || ease; 503 | this.ease = easings[ease] || easings.linear; 504 | this.easeName = transform.ease || ease || "linear"; 505 | } 506 | Animation.skip = { 507 | duration: null, 508 | delay: null, 509 | ease: null 510 | }; 511 | Animation.transform = { 512 | translate: null, 513 | rotate: null, 514 | scale: null 515 | }; 516 | Animation.getState = function(transform, item) { 517 | var initial = {}, computed; 518 | for (var property in transform) { 519 | if (property in Animation.skip) continue; 520 | if (transform.hasOwnProperty(property)) { 521 | if (item.get(property) == null) { 522 | if (!computed) { 523 | computed = getComputedStyle(item.dom, null); 524 | } 525 | Animation.setItemState(item, property, computed); 526 | } 527 | initial[property] = new Tween(item.get(property), transform[property], property); 528 | } 529 | } 530 | return initial; 531 | }; 532 | Animation.setItemState = function(item, property, computed) { 533 | if (property in Animation.transform) { 534 | var value = computed[transformProperty]; 535 | if (value === "none") { 536 | value = { 537 | translate: Vector.zero(), 538 | rotate: Vector.zero(), 539 | scale: Vector.set(1) 540 | }; 541 | } else { 542 | value = Matrix.decompose(Matrix.parse(value)); 543 | } 544 | item.set("translate", value.translate); 545 | item.set("rotate", value.rotate); 546 | item.set("scale", value.scale); 547 | } else { 548 | item.set(property, computed[property]); 549 | } 550 | }; 551 | Animation.prototype.init = function(tick, seek) { 552 | if (this.start !== null && !seek) return; 553 | if (this.start === null) { 554 | this.state = Animation.getState(this.transformation, this.item); 555 | } 556 | this.start = tick + this.delay; 557 | }; 558 | Animation.prototype.merge = function(transform, duration, ease, delay) { 559 | this.duration = (transform.duration || duration) | 0; 560 | this.delay = (transform.delay || delay) | 0; 561 | ease = transform.ease || ease; 562 | this.ease = easings[ease] || easings.linear; 563 | this.easeName = transform.ease || ease || "linear"; 564 | merge(this.transformation, transform); 565 | this.start = null; 566 | }; 567 | Animation.prototype.get = function(type) { 568 | return this.state[type]; 569 | }; 570 | Animation.prototype.run = function(tick, seek) { 571 | if (tick < this.start && !seek) return; 572 | var percent = 0; 573 | if (tick >= this.start) { 574 | percent = (tick - this.start) / this.duration; 575 | percent = this.ease(percent); 576 | } 577 | this.transform(percent); 578 | }; 579 | Animation.prototype.pause = function() { 580 | this.diff = performance.now() - this.start; 581 | }; 582 | Animation.prototype.resume = function() { 583 | this.start = performance.now() - this.diff; 584 | }; 585 | Animation.prototype.interpolate = function(property, percent) { 586 | return this.get(property).interpolate(percent); 587 | }; 588 | Animation.prototype.transform = function(percent) { 589 | for (var property in this.state) { 590 | this.item.set(property, this.interpolate(property, percent)); 591 | } 592 | }; 593 | Animation.prototype.end = function(abort, seek) { 594 | !abort && this.transform(this.ease(1)); 595 | !seek && (this.start = null); 596 | }; 597 | function CssAnimation(item, animation, duration, ease, delay, generated) { 598 | this.item = item; 599 | this.name = animation.name || animation; 600 | this.start = null; 601 | this.diff = null; 602 | this.duration = (animation.duration || duration) | 0; 603 | this.delay = (animation.delay || delay) | 0; 604 | this.ease = easings.css[animation.ease] || easings.css[ease] || easings.css.linear; 605 | this._infinite = false; 606 | this._generated = generated; 607 | } 608 | CssAnimation.prototype.init = function(tick, force) { 609 | if (this.start !== null && !force) return; 610 | this.start = tick + this.delay; 611 | this.item.style(animationProperty, this.name + " " + this.duration + "ms" + " " + this.ease + " " + this.delay + "ms" + (this._infinite ? " infinite" : "") + " " + "forwards"); 612 | }; 613 | CssAnimation.prototype.run = function() {}; 614 | CssAnimation.prototype.pause = function() { 615 | this.item.style(animationProperty + "-play-state", "paused"); 616 | this.diff = performance.now() - this.start; 617 | }; 618 | CssAnimation.prototype.resume = function() { 619 | this.item.style(animationProperty + "-play-state", "running"); 620 | this.start = performance.now() - this.diff; 621 | }; 622 | CssAnimation.prototype.end = function() { 623 | if (this._generated) { 624 | var computed = getComputedStyle(this.item.dom, null), transform = computed[transformProperty]; 625 | this.item.style(animationProperty, ""); 626 | this.item.state = Matrix.decompose(Matrix.parse(transform)); 627 | this.item.style(); 628 | } 629 | this.start = null; 630 | }; 631 | function Collection(item) { 632 | EventEmitter.call(this); 633 | this.start = null; 634 | this.item = item; 635 | this.delay = 0; 636 | this.duration = 0; 637 | this.ease = easings.linear; 638 | this.easeName = "linear"; 639 | this.animations = []; 640 | } 641 | Collection.prototype = Object.create(EventEmitter.prototype); 642 | Collection.prototype.constructor = Collection; 643 | Collection.prototype.add = function(transform, duration, ease, delay, generated) { 644 | if (Array.isArray(transform)) { 645 | transform = parallel(this.item, transform); 646 | } else if (typeof transform == "string" || transform.name != undefined) { 647 | transform = new CssAnimation(this.item, transform, duration, ease, delay, generated); 648 | } else if (!(transform instanceof Collection)) { 649 | transform = new Animation(this.item, transform, duration, ease, delay); 650 | } 651 | this.animations.push(transform); 652 | duration = this.animations.map(function(a) { 653 | return a.duration + a.delay; 654 | }); 655 | if (this instanceof Parallel) { 656 | this.duration = Math.max.apply(null, duration); 657 | } else { 658 | this.duration = duration.reduce(function(a, b) { 659 | return a + b; 660 | }, 0); 661 | } 662 | return this; 663 | function sequence(item, transforms) { 664 | var sequence = new Sequence(item); 665 | transforms.forEach(function(t) { 666 | sequence.add(t, duration, ease, delay); 667 | }); 668 | return sequence; 669 | } 670 | function parallel(item, transforms) { 671 | var parallel = new Parallel(item); 672 | transforms.forEach(function(t) { 673 | if (Array.isArray(t)) { 674 | parallel.add(sequence(item, t)); 675 | } else { 676 | parallel.add(t, duration, ease, delay); 677 | } 678 | }); 679 | return parallel; 680 | } 681 | }; 682 | Object.defineProperty(Collection.prototype, "length", { 683 | get: function() { 684 | return this.animations.length; 685 | } 686 | }); 687 | Collection.prototype.get = function(index) { 688 | return this.animations[index]; 689 | }; 690 | Collection.prototype.empty = function() { 691 | this.animations = []; 692 | }; 693 | Collection.prototype.animate = function(transform, duration, ease, delay) { 694 | return this.add(transform, duration, ease, delay); 695 | }; 696 | Collection.prototype.css = function() { 697 | return this.item.css(); 698 | }; 699 | function Parallel(item) { 700 | Collection.call(this, item); 701 | } 702 | Parallel.prototype = Object.create(Collection.prototype); 703 | Parallel.prototype.constructor = Parallel; 704 | Parallel.prototype.all = function(method) { 705 | var args = Array.prototype.slice.call(arguments, 1); 706 | for (var i = 0; i < this.animations.length; ++i) { 707 | var a = this.animations[i]; 708 | a[method].apply(a, args); 709 | } 710 | }; 711 | Parallel.prototype.init = function(tick, force) { 712 | if (this.start !== null && !force) return; 713 | this.start = tick; 714 | this.all("init", tick, force); 715 | this.emit("start"); 716 | }; 717 | Parallel.prototype.run = function(tick) { 718 | if (!this.animations.length) return; 719 | for (var i = 0; i < this.animations.length; ++i) { 720 | var a = this.animations[i]; 721 | if (a.start + a.duration <= tick) { 722 | this.animations.splice(i--, 1); 723 | a.end(); 724 | continue; 725 | } 726 | a.run(tick); 727 | } 728 | this.item.style(); 729 | if (!this.animations.length) { 730 | this.end(); 731 | } 732 | }; 733 | Parallel.prototype.seek = function(tick) { 734 | this.run(tick); 735 | }; 736 | Parallel.prototype.pause = function() { 737 | this.all("pause"); 738 | }; 739 | Parallel.prototype.resume = function() { 740 | this.all("resume"); 741 | }; 742 | Parallel.prototype.end = function(abort) { 743 | this.all("end", abort); 744 | this.emit("end"); 745 | }; 746 | function Sequence(item) { 747 | Collection.call(this, item); 748 | this._infinite = false; 749 | } 750 | Sequence.prototype = Object.create(Collection.prototype); 751 | Sequence.prototype.constructor = Sequence; 752 | Sequence.prototype.init = function(tick, force) { 753 | if (this.start !== null && !force) return; 754 | this.start = tick; 755 | this.animations[0].init(tick, force); 756 | this.emit("start"); 757 | }; 758 | Sequence.prototype.run = function(tick, a) { 759 | if (!this.animations.length) return; 760 | while (this.animations.length !== 0) { 761 | a = this.animations[0]; 762 | if (a instanceof CssAnimation) { 763 | a._infinite = this._infinite; 764 | } 765 | a.init(tick); 766 | if (a.start + a.duration <= tick) { 767 | if (!(this._infinite && a instanceof CssAnimation)) { 768 | this.animations.shift(); 769 | a.end(); 770 | } else { 771 | break; 772 | } 773 | if (this._infinite && !(a instanceof CssAnimation)) { 774 | this.animations.push(a); 775 | } 776 | continue; 777 | } 778 | a.run(tick); 779 | break; 780 | } 781 | if (!(a instanceof CssAnimation)) { 782 | this.item.style(); 783 | } 784 | if (!this.animations.length) { 785 | this.end(); 786 | } 787 | }; 788 | Sequence.prototype.seek = function(tick) { 789 | if (this.animations.length === 0) return; 790 | var time = 0; 791 | for (var i = 0; i < this.animations.length; ++i) { 792 | var a = this.animations[i]; 793 | a.init(time, true); 794 | if (a.start + a.duration <= tick) { 795 | time += a.delay + a.duration; 796 | a.end(false, true); 797 | continue; 798 | } else { 799 | a.run(tick, true); 800 | } 801 | break; 802 | } 803 | this.item.style(); 804 | }; 805 | Sequence.prototype.infinite = function() { 806 | this._infinite = true; 807 | return this; 808 | }; 809 | Sequence.prototype.pause = function() { 810 | this.animations.length && this.animations[0].pause(); 811 | }; 812 | Sequence.prototype.resume = function() { 813 | this.animations.length && this.animations[0].resume(); 814 | }; 815 | Sequence.prototype.end = function(abort) { 816 | for (var i = 0; i < this.animations.length; ++i) { 817 | this.animations[i].end(abort); 818 | } 819 | this.animations = []; 820 | this._infinite = false; 821 | this.emit("end"); 822 | }; 823 | function CSS(item, idle) { 824 | !document.styleSheets.length && this.createStyleSheet(); 825 | this.stylesheet = document.styleSheets[0]; 826 | this.item = item; 827 | this.animation = item.animation; 828 | !idle && this.style(); 829 | } 830 | CSS.skip = { 831 | translate: null, 832 | rotate: null, 833 | scale: null 834 | }; 835 | CSS.prototype.createStyleSheet = function() { 836 | var style = document.createElement("style"); 837 | document.getElementsByTagName("head")[0].appendChild(style); 838 | }; 839 | CSS.prototype.pause = function() { 840 | this.animation.pause(); 841 | }; 842 | CSS.prototype.resume = function() { 843 | this.animation.resume(); 844 | }; 845 | CSS.prototype.stop = function() { 846 | var computed = getComputedStyle(this.item.dom, null), transform = computed[transformProperty]; 847 | this.item.style(animationProperty, ""); 848 | this.item.state = Matrix.decompose(Matrix.parse(transform)); 849 | this.item.style(); 850 | return this; 851 | }; 852 | CSS.prototype.style = function() { 853 | var animation = "a" + Date.now() + "r" + Math.floor(Math.random() * 1e3); 854 | var cssRules = this.stylesheet.cssRules; 855 | this.stylesheet.insertRule(this.keyframes(animation), cssRules ? cssRules.length : 0); 856 | this.animation.empty(); 857 | this.animation.add(animation, this.animation.duration, "", 0, true); 858 | }; 859 | CSS.prototype.keyframes = function(name) { 860 | var time = 0, rule = [ "@" + getProperty("keyframes") + " " + name + "{" ]; 861 | for (var i = 0; i < this.animation.length; ++i) { 862 | var a = this.animation.get(i), aNext = this.animation.get(i + 1); 863 | a.init(); 864 | if (a instanceof Animation) { 865 | i === 0 && rule.push(this.frame(0, easings.css[a.easeName])); 866 | a.delay && rule.push(this.frame(time += a.delay)); 867 | a.transform(1); 868 | rule.push(this.frame(time += a.duration, aNext && easings.css[aNext.easeName])); 869 | } else { 870 | var frames = []; 871 | a.animations.forEach(function frame(a) { 872 | a.animations && a.animations.forEach(frame); 873 | a.delay && frames.indexOf(a.delay) === -1 && frames.push(a.delay); 874 | a.duration && frames.indexOf(a.delay + a.duration) === -1 && frames.push(a.delay + a.duration); 875 | }); 876 | frames = frames.sort(function(a, b) { 877 | return a - b; 878 | }); 879 | for (var k = 0; k < frames.length; ++k) { 880 | var frame = frames[k]; 881 | for (var j = 0; j < a.animations.length; ++j) { 882 | var pa = a.animations[j]; 883 | if (pa.delay >= frame || pa.delay + pa.duration < frame) continue; 884 | pa.transform(pa.ease((frame - pa.delay) / pa.duration)); 885 | } 886 | rule.push(this.frame(time += frame)); 887 | } 888 | } 889 | } 890 | rule.push("}"); 891 | return rule.join(""); 892 | }; 893 | CSS.prototype.percent = function(time) { 894 | return (time * 100 / this.animation.duration).toFixed(3); 895 | }; 896 | CSS.prototype.frame = function(time, ease) { 897 | var percent = this.percent(time), props = []; 898 | for (var property in this.item.state) { 899 | if (property in CSS.skip) continue; 900 | props.push(percent ? property.replace(/([A-Z])/g, "-$1") + ":" + this.item.get(property) + ";" : ""); 901 | } 902 | return percent + "% {" + (percent ? transformProperty + ":" + this.item.transform() + ";" : "") + props.join("") + (ease ? getProperty("animation-timing-function") + ":" + ease + ";" : "") + "}"; 903 | }; 904 | function World() { 905 | EventEmitter.call(this); 906 | this.items = []; 907 | this.frame = null; 908 | this.run(); 909 | } 910 | World.prototype = Object.create(EventEmitter.prototype); 911 | World.prototype.constructor = World; 912 | World.prototype.run = function() { 913 | var self = this; 914 | this.frame = requestAnimationFrame(update); 915 | function update(tick) { 916 | if (fixTick) { 917 | tick = performance.now(); 918 | } 919 | self.update(tick); 920 | self.frame = requestAnimationFrame(update); 921 | } 922 | }; 923 | World.prototype.update = function(tick) { 924 | for (var i = 0; i < this.items.length; ++i) { 925 | this.items[i].update(tick); 926 | } 927 | }; 928 | World.prototype.add = function(node, mass, viscosity, edge) { 929 | var item; 930 | if (mass) { 931 | item = new Particle(node, mass, viscosity, edge); 932 | } else { 933 | item = new Item(node); 934 | } 935 | this.items.push(item); 936 | return item; 937 | }; 938 | World.prototype.cancel = function() { 939 | this.frame && cancelAnimationFrame(this.frame); 940 | this.frame = 0; 941 | }; 942 | World.prototype.stop = function() { 943 | this.cancel(); 944 | for (var i = 0; i < this.items.length; ++i) { 945 | this.items[i].stop(); 946 | } 947 | }; 948 | World.prototype.pause = function() { 949 | this.cancel(); 950 | for (var i = 0; i < this.items.length; ++i) { 951 | this.items[i].pause(); 952 | } 953 | }; 954 | World.prototype.resume = function() { 955 | for (var i = 0; i < this.items.length; ++i) { 956 | this.items[i].resume(); 957 | } 958 | this.run(); 959 | }; 960 | function Timeline() { 961 | World.call(this, true); 962 | this.currentTime = 0; 963 | this.start = 0; 964 | } 965 | Timeline.prototype = Object.create(World.prototype); 966 | Timeline.prototype.constructor = Timeline; 967 | Timeline.prototype.run = function() { 968 | this.frame = requestAnimationFrame(update); 969 | var self = this; 970 | function update(tick) { 971 | if (fixTick) { 972 | tick = performance.now(); 973 | } 974 | if (self.running) { 975 | self.currentTime = tick - self.start; 976 | } 977 | self.update(self.currentTime); 978 | self.frame = requestAnimationFrame(update); 979 | } 980 | }; 981 | Timeline.prototype.update = function(tick) { 982 | for (var i = 0, length = this.items.length; i < length; ++i) { 983 | var item = this.items[i]; 984 | if (this.changed < length || this.running) { 985 | item.timeline(tick); 986 | this.changed++; 987 | this.emit("update", tick); 988 | } else { 989 | item.style(); 990 | } 991 | } 992 | }; 993 | Timeline.prototype.play = function() { 994 | this.running = true; 995 | this.start = performance.now() - this.currentTime; 996 | }; 997 | Timeline.prototype.pause = function() { 998 | this.running = false; 999 | }; 1000 | Timeline.prototype.stop = function() { 1001 | this.currentTime = 0; 1002 | this.running = false; 1003 | }; 1004 | Timeline.prototype.seek = function(time) { 1005 | this.changed = 0; 1006 | this.currentTime = time; 1007 | }; 1008 | function Item(node) { 1009 | EventEmitter.call(this); 1010 | this.dom = node; 1011 | this.animation = new Sequence(this); 1012 | this.running = true; 1013 | this.state = {}; 1014 | } 1015 | Item.prototype = Object.create(EventEmitter.prototype); 1016 | Item.prototype.constructor = Item; 1017 | Item.prototype.update = function(tick) { 1018 | if (!this.running) return; 1019 | this.animation.run(tick); 1020 | }; 1021 | Item.prototype.timeline = function(tick) { 1022 | this.clear(); 1023 | this.animation.seek(tick); 1024 | }; 1025 | Item.prototype.pause = function() { 1026 | if (!this.running) return; 1027 | this.animation.pause(); 1028 | this.running = false; 1029 | }; 1030 | Item.prototype.resume = function() { 1031 | if (this.running) return; 1032 | this.animation.resume(); 1033 | this.running = true; 1034 | }; 1035 | Item.prototype.style = function(property, value) { 1036 | var style = this.dom.style; 1037 | if (property && value) { 1038 | style[property] = value; 1039 | } else { 1040 | style[transformProperty] = this.transform(); 1041 | for (var property in this.state) { 1042 | style[property] = this.get(property); 1043 | } 1044 | } 1045 | }; 1046 | Item.prototype.transform = function() { 1047 | return Matrix.stringify(this.matrix()); 1048 | }; 1049 | Item.prototype.matrix = function() { 1050 | var state = this.state; 1051 | return Matrix.compose(state.translate, state.rotate, state.scale); 1052 | }; 1053 | Item.prototype.center = function() { 1054 | return Matrix.decompose(Matrix.inverse(this.matrix())); 1055 | }; 1056 | Item.prototype.lookAt = function(vector) { 1057 | var transform = Matrix.decompose(Matrix.lookAt(vector, this.get("translate"), Vector.set(0, 1, 0))); 1058 | this.set("rotate", transform.rotate); 1059 | }; 1060 | Item.prototype.set = function(type, value) { 1061 | if (Array.isArray(value)) { 1062 | this.state[type] || (this.state[type] = []); 1063 | for (var i = 0; i < value.length; ++i) { 1064 | if (value[i] !== undefined) { 1065 | this.state[type][i] = value[i]; 1066 | } 1067 | } 1068 | } else { 1069 | this.state[type] = value; 1070 | } 1071 | return this; 1072 | }; 1073 | Item.prototype.get = function(type) { 1074 | return this.state[type]; 1075 | }; 1076 | Item.prototype.clear = function() { 1077 | this.state.translate = Vector.zero(); 1078 | this.state.rotate = Vector.zero(); 1079 | this.state.scale = Vector.set(1); 1080 | }; 1081 | Item.prototype.animate = function(transform, duration, ease, delay) { 1082 | return this.animation.add(transform, duration, ease, delay); 1083 | }; 1084 | Item.prototype.alternate = function(transform, duration, ease, delay) { 1085 | if (this.animation.length) { 1086 | this.animation.get(0).merge(transform, duration, ease, delay); 1087 | } else { 1088 | this.animate.call(this, transform, duration, ease, delay); 1089 | } 1090 | }; 1091 | Item.prototype.finish = function(abort) { 1092 | this.animation.end(abort); 1093 | return this; 1094 | }; 1095 | Item.prototype.stop = function() { 1096 | return this.finish(true); 1097 | }; 1098 | Item.prototype.css = function(idle) { 1099 | return new CSS(this, idle); 1100 | }; 1101 | function Constant() { 1102 | var force = Vector.sub(this.state.translate, this.current.position); 1103 | this.current.acceleration = Vector.add(this.current.acceleration, force); 1104 | } 1105 | function Attraction(radius, strength) { 1106 | radius || (radius = 1e3); 1107 | strength || (strength = 100); 1108 | var force = Vector.sub(this.state.translate, this.current.position), distance = Vector.length(force); 1109 | if (distance < radius) { 1110 | force = Vector.scale(Vector.norm(force), 1 - distance * distance / (radius * radius)); 1111 | this.current.acceleration = Vector.add(this.current.acceleration, Vector.scale(force, strength)); 1112 | } 1113 | } 1114 | function Edge(min, max, bounce) { 1115 | min || (min = Vector.set(0)); 1116 | max || (max = Vector.set(0)); 1117 | bounce || (bounce = true); 1118 | for (var i = 0; i < 3; ++i) { 1119 | if (this.current.position[i] < min[i] || this.current.position[i] > max[i]) { 1120 | if (bounce) { 1121 | this.previous.position[i] = 2 * this.current.position[i] - this.previous.position[i]; 1122 | } else { 1123 | this.current.position[i] = Math.max(min[i], Math.min(max[i], this.current.position[i])); 1124 | } 1125 | } 1126 | } 1127 | } 1128 | function Verlet(delta, drag) { 1129 | var current = this.current, previous = this.previous; 1130 | current.acceleration = Vector.scale(current.acceleration, this.mass); 1131 | current.velocity = Vector.sub(current.position, previous.position); 1132 | if (drag !== undefined) { 1133 | current.velocity = Vector.scale(current.velocity, drag); 1134 | } 1135 | previous.position = current.position; 1136 | current.position = Vector.add(current.position, Vector.add(current.velocity, Vector.scale(current.acceleration, delta * delta))); 1137 | current.acceleration = Vector.zero(); 1138 | } 1139 | function Particle(node, mass, viscosity, edge) { 1140 | Item.call(this, node); 1141 | if (mass === Object(mass)) { 1142 | viscosity = mass.viscosity; 1143 | edge = mass.edge; 1144 | mass = mass.mass; 1145 | } 1146 | mass /= 100; 1147 | mass || (mass = .01); 1148 | viscosity || (viscosity = .1); 1149 | edge || (edge = false); 1150 | this.mass = 1 / mass; 1151 | this.viscosity = viscosity; 1152 | this.edge = edge; 1153 | this.current = { 1154 | position: Vector.zero(), 1155 | velocity: Vector.zero(), 1156 | acceleration: Vector.zero() 1157 | }; 1158 | this.previous = { 1159 | position: Vector.zero(), 1160 | velocity: Vector.zero(), 1161 | acceleration: Vector.zero() 1162 | }; 1163 | this.clock = null; 1164 | } 1165 | Particle.prototype = Object.create(Item.prototype); 1166 | Particle.prototype.constructor = Particle; 1167 | Particle.prototype.update = function(tick) { 1168 | this.animation.run(tick); 1169 | this.integrate(tick); 1170 | this.style(); 1171 | }; 1172 | Particle.prototype.timeline = function(tick) { 1173 | this.clear(); 1174 | this.animation.seek(tick); 1175 | this.integrate(tick, true); 1176 | this.style(); 1177 | }; 1178 | Particle.prototype.integrate = function(tick, clamp) { 1179 | this.clock || (this.clock = tick); 1180 | var delta = tick - this.clock; 1181 | if (delta) { 1182 | clamp && (delta = Math.max(-16, Math.min(16, delta))); 1183 | this.clock = tick; 1184 | delta *= .001; 1185 | Constant.call(this); 1186 | this.edge && Edge.call(this, Vector.set(this.edge.min), Vector.set(this.edge.max), this.edge.bounce); 1187 | Verlet.call(this, delta, 1 - this.viscosity); 1188 | } 1189 | }; 1190 | Particle.prototype.css = function() { 1191 | throw new Error("CSS is nor supported for physics"); 1192 | }; 1193 | Particle.prototype.matrix = function() { 1194 | var state = this.state; 1195 | return Matrix.compose(this.current.position, state.rotate, state.scale); 1196 | }; 1197 | })(Function("return this")()); -------------------------------------------------------------------------------- /animatic.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";function i(t){return x?x+t:t}function e(t){for(var i=1;in;++n)(this.current.position[n]i[n])&&(e?this.previous.position[n]=2*this.current.position[n]-this.previous.position[n]:this.current.position[n]=Math.max(t[n],Math.min(i[n],this.current.position[n])))}function g(t,i){var e=this.current,n=this.previous;e.acceleration=R.scale(e.acceleration,this.mass),e.velocity=R.sub(e.position,n.position),void 0!==i&&(e.velocity=R.scale(e.velocity,i)),n.position=e.position,e.position=R.add(e.position,R.add(e.velocity,R.scale(e.acceleration,t*t))),e.acceleration=R.zero()}function v(t,i,e,n){m.call(this,t),i===Object(i)&&(e=i.viscosity,n=i.edge,i=i.mass),i/=100,i||(i=.01),e||(e=.1),n||(n=!1),this.mass=1/i,this.viscosity=e,this.edge=n,this.current={position:R.zero(),velocity:R.zero(),acceleration:R.zero()},this.previous={position:R.zero(),velocity:R.zero(),acceleration:R.zero()},this.clock=null}var b={};b.world=function(){return new l},b.timeline=function(){return new f},"object"==typeof module&&"object"==typeof module.exports?module.exports=b:"function"==typeof define&&define.amd?define(b):t.animatic=t.a=b;for(var M=t.requestAnimationFrame,z=t.cancelAnimationFrame,w=["moz","webkit","ms"],A=0;A1e12!=q.now()>1e12});var R={set:function(t,i,e){return Array.isArray(t)&&(i=t[1],e=t[2],t=t[0]),void 0===t&&(t=0),void 0===i&&(i=t,e=t),[t,i,e]},length:function(t,i,e){return Array.isArray(t)&&(i=t[1],e=t[2],t=t[0]),Math.sqrt(t*t+i*i+e*e)},add:function(t,i){return[t[0]+i[0],t[1]+i[1],t[2]+i[2]]},sub:function(t,i){return[t[0]-i[0],t[1]-i[1],t[2]-i[2]]},norm:function(t,i,e){Array.isArray(t)&&(i=t[1],e=t[2],t=t[0]);var n=this.length(t,i,e);return 0!==n?(t/=n,i/=n,e/=n):(t=0,i=0,e=0),[t,i,e]},dist:function(t,i){var e=t[0]-i[0],n=t[1]-i[1],r=t[2]-i[2];return Math.sqrt(e*e+n*n+r+r)},cross:function(t,i){var e=t[1]*i[2]-t[2]*i[1],n=t[2]*i[0]-t[0]*i[2],r=t[1]*i[1]-t[1]*i[0];return[e,n,r]},clone:function(t){return t.slice()},scale:function(t,i,e,n){return Array.isArray(t)&&(n=i,i=t[1],e=t[2],t=t[0]),[t*n,i*n,e*n]},zero:function(){return[0,0,0]}},S=Math.PI/180,j={identity:function(){return[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},multiply:function E(t,i){var e=this.identity();return e[0]=t[0]*i[0]+t[1]*i[4]+t[2]*i[8],e[1]=t[0]*i[1]+t[1]*i[5]+t[2]*i[9],e[2]=t[0]*i[2]+t[1]*i[6]+t[2]*i[10],e[4]=t[4]*i[0]+t[5]*i[4]+t[6]*i[8],e[5]=t[4]*i[1]+t[5]*i[5]+t[6]*i[9],e[6]=t[4]*i[2]+t[5]*i[6]+t[6]*i[10],e[8]=t[8]*i[0]+t[9]*i[4]+t[10]*i[8],e[9]=t[8]*i[1]+t[9]*i[5]+t[10]*i[9],e[10]=t[8]*i[2]+t[9]*i[6]+t[10]*i[10],e[12]=t[12]*i[0]+t[13]*i[4]+t[14]*i[8]+i[12],e[13]=t[12]*i[1]+t[13]*i[5]+t[14]*i[9]+i[13],e[14]=t[12]*i[2]+t[13]*i[6]+t[14]*i[10]+i[14],2>=arguments.length?e:E.apply(this,[e].concat(Array.prototype.slice.call(arguments,2)))},translate:function(t,i,e){return t||i||e?(t||(t=0),i||(i=0),e||(e=0),[1,0,0,0,0,1,0,0,0,0,1,0,t,i,e,1]):this.identity()},scale:function(t,i,e){return t||i||e?(t||(t=1),i||(i=1),e||(e=1),[t,0,0,0,0,i,0,0,0,0,e,0,0,0,0,1]):this.identity()},rotate:function(t,i,e){if(!(t||i||e))return this.identity();t||(t=0),i||(i=0),e||(e=0),t*=S,i*=S,e*=S;var n=Math.sin(t),r=Math.cos(t),s=Math.sin(i),o=Math.cos(i),a=Math.sin(e),u=Math.cos(e);return[o*u,r*a+n*s*u,n*a-r*s*u,0,-o*a,r*u-n*s*a,n*u+r*s*a,0,s,-n*o,r*o,0,0,0,0,1]},rotate3d:function(t,i,e,n){n||(n=0),n*=S;var r=Math.sin(n),s=Math.cos(n),o=R.norm(t,i,e);t=o[0],i=o[1],e=o[2];var a=t*t,u=i*i,h=e*e,c=1-s;return[a+(1-a)*s,t*i*c+e*r,t*e*c-i*r,0,t*i*c-e*r,u+(1-u)*s,i*e*c+t*r,0,t*e*c+i*r,i*e*c-t*r,h+(1-h)*s,0,0,0,0,1]},skew:function(t,i){return t||i?(t||(t=0),i||(i=0),t*=S,i*=S,[1,Math.tan(i),0,0,Math.tan(t),1,0,0,0,0,1,0,0,0,0,1]):this.identity()},perspective:function(t){return t=-1/t,[1,0,0,0,0,1,0,0,0,0,1,t,0,0,0,1]},parse:function(t){var i=t.match(/\((.+)\)/)[1].split(/,\s?/);return 6===i.length&&(i.splice(2,0,0,0),i.splice(6,0,0,0),i.splice(8,0,0,0,1,0),i.push(0,1)),i},inverse:function(t){var i=this.identity(),e=t[5]*t[10]-t[6]*t[9],n=t[1]*t[10]-t[2]*t[9],r=t[1]*t[6]-t[2]*t[5],s=t[4]*t[10]-t[6]*t[8],o=t[0]*t[10]-t[2]*t[8],a=t[0]*t[6]-t[2]*t[4],u=t[4]*t[9]-t[5]*t[8],h=t[0]*t[9]-t[1]*t[8],c=t[0]*t[5]-t[1]*t[4],p=1/(t[0]*e-t[1]*s+t[2]*u);return i[0]=p*e,i[1]=-p*n,i[2]=p*r,i[4]=-p*s,i[5]=p*o,i[6]=-p*a,i[8]=p*u,i[9]=-p*h,i[10]=p*c,i[12]=-t[12]*i[0]-t[13]*i[4]-t[14]*i[8],i[13]=-t[12]*i[1]-t[13]*i[5]-t[14]*i[9],i[14]=-t[12]*i[2]-t[13]*i[6]-t[14]*i[10],i},compose:function(t,i,e){t||(t=[]),i||(i=[]),e||(e=[]);var n=this.rotate(i[0],i[1],i[2]);return e.length&&(n[0]*=e[0],n[1]*=e[0],n[2]*=e[0],n[4]*=e[1],n[5]*=e[1],n[6]*=e[1],n[8]*=e[2],n[9]*=e[2],n[10]*=e[2]),t.length&&(n[12]=t[0],n[13]=t[1],n[14]=t[2]),n},decompose:function(t){var i=R.length(t[0],t[1],t[2]),e=R.length(t[4],t[5],t[6]),n=R.length(t[8],t[9],t[10]),r=Math.atan2(-t[9]/n,t[10]/n)/S,s=Math.asin(t[8]/n)/S,o=Math.atan2(-t[4]/e,t[0]/i)/S;(1===t[4]||-1===t[4])&&(r=0,s=t[4]*-Math.PI/2,o=t[4]*Math.atan2(t[6]/e,t[5]/e)/S);var a=t[12],u=t[13],h=t[14];return{translate:[a,u,h],rotate:[r,s,o],scale:[i,e,n]}},transpose:function(t){var i;return i=t[1],t[1]=t[4],t[4]=i,i=t[2],t[2]=t[8],t[8]=i,i=t[6],t[6]=t[9],t[9]=i,i=t[3],t[3]=t[12],t[12]=i,i=t[7],t[7]=t[13],t[13]=i,i=t[11],t[11]=t[14],t[14]=i,t},lookAt:function(t,i,e){var n=R.sub(t,i);n=R.norm(n),0===R.length(n)&&(n[2]=1);var r=R.cross(e,n);0===R.length(r)&&(n[0]+=1e-4,r=R.norm(R.cross(e,n)));var s=R.cross(n,r),o=this.identity();return o[0]=r[0],o[1]=r[1],o[2]=r[2],o[4]=s[0],o[5]=s[1],o[6]=s[2],o[8]=n[0],o[9]=n[1],o[10]=n[2],o},stringify:function(t){for(var i=0;it?n(2*t)/2:1-n(-2*t+2)/2}}),i.css={linear:"cubic-bezier(0.000, 0.000, 1.000, 1.000)","ease-in-quad":"cubic-bezier(0.550, 0.085, 0.680, 0.530)","ease-in-cubic":"cubic-bezier(0.550, 0.055, 0.675, 0.190)","ease-in-quart":"cubic-bezier(0.895, 0.030, 0.685, 0.220)","ease-in-quint":"cubic-bezier(0.755, 0.050, 0.855, 0.060)","ease-in-sine":"cubic-bezier(0.470, 0.000, 0.745, 0.715)","ease-in-expo":"cubic-bezier(0.950, 0.050, 0.795, 0.035)","ease-in-circ":"cubic-bezier(0.600, 0.040, 0.980, 0.335)","ease-in-back":"cubic-bezier(0.600, -0.280, 0.735, 0.045)","ease-out-quad":"cubic-bezier(0.250, 0.460, 0.450, 0.940)","ease-out-cubic":"cubic-bezier(0.215, 0.610, 0.355, 1.000)","ease-out-quart":"cubic-bezier(0.165, 0.840, 0.440, 1.000)","ease-out-quint":"cubic-bezier(0.230, 1.000, 0.320, 1.000)","ease-out-sine":"cubic-bezier(0.390, 0.575, 0.565, 1.000)","ease-out-expo":"cubic-bezier(0.190, 1.000, 0.220, 1.000)","ease-out-circ":"cubic-bezier(0.075, 0.820, 0.165, 1.000)","ease-out-back":"cubic-bezier(0.175, 0.885, 0.320, 1.275)","ease-in-out-quad":"cubic-bezier(0.455, 0.030, 0.515, 0.955)","ease-in-out-cubic":"cubic-bezier(0.645, 0.045, 0.355, 1.000)","ease-in-out-quart":"cubic-bezier(0.770, 0.000, 0.175, 1.000)","ease-in-out-quint":"cubic-bezier(0.860, 0.000, 0.070, 1.000)","ease-in-out-sine":"cubic-bezier(0.445, 0.050, 0.550, 0.950)","ease-in-out-expo":"cubic-bezier(1.000, 0.000, 0.000, 1.000)","ease-in-out-circ":"cubic-bezier(0.785, 0.135, 0.150, 0.860)","ease-in-out-back":"cubic-bezier(0.680, -0.550, 0.265, 1.550)"},i}();r.NUMERIC="NUMERIC",r.COLOR="COLOR",r.propTypes={color:r.COLOR,backgroundColor:r.COLOR,borderColor:r.COLOR},r.px="margin,marginTop,marginLeft,marginBottom,marginRight,padding,paddingTop,paddingLeft,paddingBottom,paddingRight,top,left,bottom,right,width,height,maxWidth,maxHeight,minWidth,minHeight,borderRadius,borderWidth".split(","),r.parseValue=function(t,i){return i===r.COLOR?r.parseColor(t):r.parseNumeric(t)},r.parseNumeric=function(t){return Array.isArray(t)||(t=String(t).split(/\s+/)),Array.isArray(t)?t.map(parseFloat):Number(t)},r.parseColor=function(t){var i=t.match(/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);if(i)return{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16),a:1};var e=t.match(/^rgba?\(([0-9.]*), ?([0-9.]*), ?([0-9.]*)(?:, ?([0-9.]*))?\)$/);return e?{r:parseFloat(e[1]),g:parseFloat(e[2]),b:parseFloat(e[3]),a:parseFloat(null!=e[4]?e[4]:1)}:void 0},r.prototype.interpolate=function(t){if(this.type===r.NUMERIC){if(Array.isArray(this.end))return this.array(t);if(void 0!==this.end)return this.absolute(t)}else if(this.type===r.COLOR)return this.color(t)},r.prototype.array=function(t){for(var i=[],e=0;e=this.start&&(e=(t-this.start)/this.duration,e=this.ease(e)),this.transform(e)}},o.prototype.pause=function(){this.diff=q.now()-this.start},o.prototype.resume=function(){this.start=q.now()-this.diff},o.prototype.interpolate=function(t,i){return this.get(t).interpolate(i)},o.prototype.transform=function(t){for(var i in this.state)this.item.set(i,this.interpolate(i,t))},o.prototype.end=function(t,i){!t&&this.transform(this.ease(1)),!i&&(this.start=null)},a.prototype.init=function(t,i){(null===this.start||i)&&(this.start=t+this.delay,this.item.style(C,this.name+" "+this.duration+"ms "+this.ease+" "+this.delay+"ms"+(this._infinite?" infinite":"")+" forwards"))},a.prototype.run=function(){},a.prototype.pause=function(){this.item.style(C+"-play-state","paused"),this.diff=q.now()-this.start},a.prototype.resume=function(){this.item.style(C+"-play-state","running"),this.start=q.now()-this.diff},a.prototype.end=function(){if(this._generated){var t=getComputedStyle(this.item.dom,null),i=t[O];this.item.style(C,""),this.item.state=j.decompose(j.parse(i)),this.item.style()}this.start=null},u.prototype=Object.create(n.prototype),u.prototype.constructor=u,u.prototype.add=function(t,i,e,n,r){function s(t,r){var s=new c(t);return r.forEach(function(t){s.add(t,i,e,n)}),s}function p(t,r){var o=new h(t);return r.forEach(function(r){Array.isArray(r)?o.add(s(t,r)):o.add(r,i,e,n)}),o}return Array.isArray(t)?t=p(this.item,t):"string"==typeof t||void 0!=t.name?t=new a(this.item,t,i,e,n,r):t instanceof u||(t=new o(this.item,t,i,e,n)),this.animations.push(t),i=this.animations.map(function(t){return t.duration+t.delay}),this.duration=this instanceof h?Math.max.apply(null,i):i.reduce(function(t,i){return t+i},0),this},Object.defineProperty(u.prototype,"length",{get:function(){return this.animations.length}}),u.prototype.get=function(t){return this.animations[t]},u.prototype.empty=function(){this.animations=[]},u.prototype.animate=function(t,i,e,n){return this.add(t,i,e,n)},u.prototype.css=function(){return this.item.css()},h.prototype=Object.create(u.prototype),h.prototype.constructor=h,h.prototype.all=function(t){for(var i=Array.prototype.slice.call(arguments,1),e=0;e=c||l.delay+l.durationi;++i){var n=this.items[i];this.changed 2 | 3 | 4 | 5 | Animator 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /editor/js/editor.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function $(selector, context) { 5 | var result = (context || document).querySelectorAll(selector) 6 | return result.length > 1 ? result : result[0] 7 | } 8 | 9 | window.on = Node.prototype.on = function (event, fn) { 10 | this.addEventListener(event, fn, false) 11 | return this 12 | } 13 | 14 | window.off = Node.prototype.off = function (event, fn) { 15 | this.removeEventListener(event, fn) 16 | return this 17 | } 18 | 19 | NodeList.prototype.forEach = [].forEach 20 | 21 | NodeList.prototype.filter = [].filter 22 | 23 | NodeList.prototype.on = function (event, fn) { 24 | this.forEach(function (el) { 25 | el.on(event, fn) 26 | }) 27 | return this 28 | } 29 | 30 | NodeList.prototype.off = function (event, fn) { 31 | this.forEach(function (el) { 32 | el.off(event, fn) 33 | }) 34 | return this 35 | } 36 | 37 | var State = { 38 | initial: { 39 | translate: [0, 0, 0], 40 | rotate: [0, 0, 0], 41 | scale: [1, 1, 1] 42 | }, 43 | copy: function (state) { 44 | return { 45 | translate: state.translate.slice(), 46 | rotate: state.rotate.slice(), 47 | scale: state.scale.slice() 48 | } 49 | }, 50 | diff: function (prev, next) { 51 | function differ(type) { 52 | return function (t, i) { 53 | return t - prev[type][i] 54 | } 55 | } 56 | 57 | return { 58 | translate: next.translate.map(differ('translate')), 59 | rotate: next.rotate.map(differ('rotate')), 60 | scale: next.scale.map(differ('scale')) 61 | } 62 | } 63 | } 64 | 65 | var UI = {} 66 | 67 | UI.Panel = function (type, content) { 68 | this.type = type 69 | this.content = content 70 | } 71 | 72 | UI.Panel.prototype.toString = function () { 73 | return '
\ 74 |
\ 75 |
' + this.content + '
\ 76 |
' 77 | } 78 | 79 | UI.Timeline = function (max) { 80 | this.max = max || 5000 81 | } 82 | 83 | UI.Timeline.prototype.keyframes = function (keyframes) { 84 | keyframes || (keyframes = []) 85 | 86 | var container = $('.panel_timeline .keyframes'), 87 | width = $('.panel_timeline label').clientWidth - 8, 88 | content = container.innerHTML = '' 89 | 90 | for (var i = 0; i < keyframes.length; ++i) { 91 | content += '' 92 | } 93 | 94 | container.innerHTML = content 95 | } 96 | 97 | UI.Timeline.prototype.toString = function () { 98 | return '\ 99 | \ 100 | \ 101 | ' 102 | } 103 | 104 | UI.Controls = function () { 105 | this.config = { 106 | translate: { 107 | step: 1 108 | }, 109 | rotate: { 110 | step: .5 111 | }, 112 | scale: { 113 | step: .01 114 | } 115 | } 116 | } 117 | 118 | UI.Controls.prototype.toString = function () { 119 | var this_ = this 120 | return 'xyz' + 121 | Object.keys(this.config).map(function (t) { 122 | return '
' + t + '' + 123 | ['x', 'y', 'z'].map(function (a) { 124 | return '' 126 | }).join('') + '
' 127 | }).join('') 128 | } 129 | 130 | UI.Popup = function () {} 131 | 132 | UI.Popup.prototype.toString = function () { 133 | return '' 134 | } 135 | 136 | UI.Popup.prototype.show = function (string) { 137 | $('.editor .popup span').textContent = string.replace(/([;{}])/g, '$1\n') 138 | $('.editor .popup').style.display = 'block' 139 | } 140 | 141 | UI.Popup.prototype.hide = function () { 142 | $('.editor .popup').style.display = 'none' 143 | } 144 | 145 | UI.Toggler = function (size) { 146 | this.size = size 147 | } 148 | 149 | UI.Toggler.prototype.toString = function () { 150 | var html = '' 151 | for (var i = 0; i < this.size; ++i) { 152 | html += '' 153 | } 154 | return '
' + html + '
' 155 | } 156 | 157 | UI.Editor = function (timeline) { 158 | this.timeline = timeline 159 | this.init() 160 | } 161 | 162 | UI.Editor.prototype.init = function () { 163 | this.current = 0 164 | this.keyframes = [] 165 | 166 | var this_ = this 167 | 168 | var container = document.createElement('div') 169 | container.classList.add('editor') 170 | 171 | this.popup = new UI.Popup 172 | 173 | this.bar = new UI.Timeline 174 | 175 | container.innerHTML = new UI.Panel('right', new UI.Controls + new UI.Toggler(this.timeline.items.length)) + new UI.Panel('timeline', this.bar) + this.popup 176 | 177 | document.body.appendChild(container) 178 | 179 | this.timeline.on('update', function (time) { 180 | $('.panel_timeline input[type=range]').value = time 181 | populateData() 182 | }) 183 | 184 | function populateData() { 185 | ['translate', 'rotate', 'scale'].forEach(function (t) { 186 | ['x', 'y', 'z'].forEach(function (a, i) { 187 | if (this_.timeline.items[this_.current].state[t]) 188 | $('.panel_right input[data-transform=' + t + '][data-axis="' + a + '"]') 189 | .value = this_.timeline.items[this_.current].state[t][i] 190 | }) 191 | }) 192 | } 193 | 194 | $('.panel_timeline input[type=range]').on('input', function () { 195 | this_.timeline.seek(this.value) 196 | }) 197 | 198 | $('.panel_timeline input[value=keyframe]').on('click', function () { 199 | var keyframes = this_.keyframe(this_.timeline.currentTime) 200 | this_.bar.keyframes(keyframes) 201 | }) 202 | 203 | $('.panel_timeline input[value=code]').on('click', function () { 204 | this_.stringify(this_.timeline.items[this_.current]) 205 | }) 206 | 207 | function bind(node, step, callback) { 208 | var startX = 0, 209 | startValue = node.value 210 | 211 | function mouseMove(e) { 212 | e.preventDefault() 213 | callback(parseFloat(startValue) + (e.screenX - startX) * step) 214 | } 215 | 216 | function mouseUp(e) { 217 | e.preventDefault() 218 | document.off('mousemove', mouseMove) 219 | document.off('mouseup', mouseUp) 220 | } 221 | 222 | function mouseDown(e) { 223 | e.preventDefault() 224 | startX = e.screenX 225 | startValue = this.value 226 | document.on('mousemove', mouseMove) 227 | document.on('mouseup', mouseUp) 228 | } 229 | 230 | node.on('mousedown', mouseDown) 231 | 232 | node.on('dblclick', function () { 233 | this.focus() 234 | }) 235 | 236 | node.on('change', function () { 237 | callback(this.value) 238 | }) 239 | 240 | node.on('keydown', function (e) { 241 | if (e.keyCode == 13) 242 | this.blur() 243 | }) 244 | } 245 | 246 | var time = $('.panel_timeline input[type=text]') 247 | bind(time, 10, function (value) { 248 | value = Math.max(100, value) 249 | time.value = value 250 | this_.bar.max = value 251 | $('.panel_timeline input[type=range]').setAttribute('max', value) 252 | this_.bar.keyframes(this_.keyframes[this_.current]) 253 | }) 254 | 255 | $('.panel_right input[type=radio]').on('click', function (e) { 256 | this_.current = this.dataset['index'] 257 | populateData() 258 | this_.bar.keyframes(this_.keyframes[this_.current]) 259 | }) 260 | $('.panel_right input[type=radio]')[0].click() 261 | 262 | $('.panel_right input[type=text]').forEach(function (range) { 263 | bind(range, range.dataset['step'], function (value) { 264 | range.value = value 265 | this_.timeline.items[this_.current].set(range.dataset['transform'], Array.apply(null, Array(3)).map(function(_, i) { 266 | return i === ['x', 'y', 'z'].indexOf(range.dataset['axis']) ? value : _ 267 | })) 268 | }) 269 | }) 270 | 271 | $('.popup a').on('click', function () { 272 | this_.popup.hide() 273 | }) 274 | } 275 | 276 | UI.Editor.prototype.keyframe = function (time) { 277 | var index = this.current, 278 | keyframes = this.keyframes, 279 | item = this.timeline.items[this.current], 280 | state = State.copy(item.state) 281 | 282 | keyframes[index] || (keyframes[index] = []) 283 | keyframes[index].push({time: time, state: state}) 284 | keyframes[index] = keyframes[index].sort(function (a, b) { 285 | return a.time - b.time 286 | }) 287 | 288 | this.animate(item) 289 | item.state = State.copy(state) 290 | 291 | return keyframes[index] 292 | } 293 | 294 | UI.Editor.prototype.animate = function (item) { 295 | item.stop() 296 | item.clear() 297 | 298 | var index = this.current, 299 | prevState = State.initial, 300 | prevTime = 0 301 | 302 | this.keyframes[index].forEach(function (frame) { 303 | item.animate(State.diff(prevState, frame.state), frame.time - prevTime) 304 | prevState = State.copy(frame.state) 305 | prevTime = frame.time 306 | }) 307 | } 308 | 309 | UI.Editor.prototype.stringify = function (item) { 310 | item.clear() 311 | this.popup.show(item.css(true).keyframes('animation')) 312 | } 313 | 314 | animatic.editor = function (nodes) { 315 | var timeline = animatic.timeline() 316 | $(nodes).forEach(function (node) { 317 | if (node instanceof Element && node.tagName !== 'SCRIPT' && node.tagName !== 'STYLE') { 318 | var item = timeline.add(node) 319 | item.clear() 320 | } 321 | 322 | }) 323 | new UI.Editor(timeline) 324 | } 325 | 326 | function bsearch(needle, stack, comparator) { 327 | var low = 0, 328 | high = stack.length, 329 | middle = 0 330 | 331 | while (low <= high) { 332 | middle = (low + high) >> 1 333 | var comparison = comparator(stack[middle], needle) 334 | 335 | if (comparison > 0) { 336 | low = middle + 1 337 | } else if (comparison < 0) { 338 | high = middle - 1 339 | } else { 340 | break 341 | } 342 | } 343 | return middle 344 | } 345 | 346 | }()) 347 | -------------------------------------------------------------------------------- /example/bounce.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bounce 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 60 | 61 | -------------------------------------------------------------------------------- /example/bounce_css.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bounce CSS 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 61 | 62 | -------------------------------------------------------------------------------- /example/cards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cards 7 | 8 | 9 | 95 | 96 | 97 | 98 | 99 | 189 | 190 | -------------------------------------------------------------------------------- /example/css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | overflow: hidden; 4 | margin: 0; 5 | padding: 0; 6 | font: 16px/1.5 Arial, sans-serif; 7 | } 8 | 9 | code { 10 | background: #fafafa; 11 | border-radius: 5px; 12 | min-width: 1em; 13 | display: inline-block; 14 | text-align: center; 15 | padding: .1em .3em; 16 | } 17 | 18 | input[type=button] { 19 | -webkit-appearance: none; 20 | -moz-appearance: none; 21 | -ms-appearance: none; 22 | -o-appearance: none; 23 | appearance: none; 24 | font-size: 1em; 25 | background: #fafafa; 26 | border: 1px solid #999; 27 | border-radius: .3em; 28 | cursor: pointer; 29 | } 30 | 31 | input[type=button]:hover { 32 | background: -webkit-linear-gradient(#fff, #dfdfdf); 33 | background: -moz-linear-gradient(#fff, #dfdfdf); 34 | background: -ms-linear-gradient(#fff, #dfdfdf); 35 | background: -o-linear-gradient(#fff, #dfdfdf); 36 | background: linear-gradient(#fff, #dfdfdf); 37 | } 38 | 39 | input[type=button]:active { 40 | background: -webkit-linear-gradient(#dfdfdf, #fff); 41 | background: -moz-linear-gradient(#dfdfdf, #fff); 42 | background: -ms-linear-gradient(#dfdfdf, #fff); 43 | background: -o-linear-gradient(#dfdfdf, #fff); 44 | background: linear-gradient(#dfdfdf, #fff); 45 | } 46 | 47 | 48 | input[type=range] { 49 | width: 50%; 50 | box-sizing: border-box; 51 | margin: 15px; 52 | } 53 | 54 | 55 | .viewport { 56 | position: absolute; 57 | left: 50%; 58 | top: 50%; 59 | -webkit-perspective: 400px; 60 | -moz-perspective: 400px; 61 | -ms-perspective: 400px; 62 | -o-perspective: 400px; 63 | perspective: 400px; 64 | -webkit-transform: translateZ(-99999px); 65 | -moz-transform: translateZ(-99999px); 66 | -ms-transform: translateZ(-99999px); 67 | -o-transform: translateZ(-99999px); 68 | transform: translateZ(-99999px); 69 | } 70 | 71 | .world { 72 | position: absolute; 73 | left: 0; 74 | top: 0; 75 | width: 100%; 76 | height: 100%; 77 | -ms-perspective: inherit; 78 | -webkit-transform-style: preserve-3d; 79 | -moz-transform-style: preserve-3d; 80 | -ms-transform-style: preserve-3d; 81 | -o-transform-style: preserve-3d; 82 | transform-style: preserve-3d; 83 | } 84 | 85 | .card { 86 | position: absolute; 87 | left: 0; 88 | top: 0; 89 | width: 100px; 90 | height: 120px; 91 | display: inline-block; 92 | background: #5CD6BA; 93 | border: 1px solid; 94 | border-radius: 5px; 95 | box-sizing: border-box; 96 | padding-top: 25px; 97 | font-family: sans-serif; 98 | font-size: 3em; 99 | color: white; 100 | opacity: .7; 101 | -webkit-user-select: none; 102 | -moz-user-select: none; 103 | -o-user-select: none; 104 | user-select: none; 105 | text-align: center; 106 | } 107 | 108 | .name { 109 | font-size: .2em; 110 | } 111 | 112 | .controls { 113 | position: absolute; 114 | left: 0; 115 | bottom: 0; 116 | width: 100%; 117 | font-size: 1.4em; 118 | } 119 | 120 | .controls-bg { 121 | position: absolute; 122 | left: 0; 123 | bottom: 0; 124 | height: 100%; 125 | background: rgba(255,255,255,.9); 126 | width: 100%; 127 | z-index: 0; 128 | } 129 | .controls-content { 130 | position:relative; 131 | padding: 15px; 132 | } 133 | 134 | .ball { 135 | position: absolute; 136 | width: 200px; 137 | height: 200px; 138 | left: 0; 139 | top: 0; 140 | border-radius: 50%; 141 | -webkit-transform-origin: 50% 100%; 142 | -moz-transform-origin: 50% 100%; 143 | -o-transform-origin: 50% 100%; 144 | transform-origin: 50% 100%; 145 | background: -webkit-radial-gradient(40px 40px, circle, #D7E7F1 0%, #AFD3EE 10%, #6DA9D5 20%, #238FE2 30%, #2081CB 50%, #0E5D99 70%); 146 | background: -moz-radial-gradient(40px 40px, circle, #D7E7F1 0%, #AFD3EE 10%, #6DA9D5 20%, #238FE2 30%, #2081CB 50%, #0E5D99 70%); 147 | background: -ms-radial-gradient(40px 40px, circle, #D7E7F1 0%, #AFD3EE 10%, #6DA9D5 20%, #238FE2 30%, #2081CB 50%, #0E5D99 70%); 148 | background: -o-radial-gradient(40px 40px, circle, #D7E7F1 0%, #AFD3EE 10%, #6DA9D5 20%, #238FE2 30%, #2081CB 50%, #0E5D99 70%); 149 | background: radial-gradient(40px 40px, circle, #D7E7F1 0%, #AFD3EE 10%, #6DA9D5 20%, #238FE2 30%, #2081CB 50%, #0E5D99 70%); 150 | border: none; 151 | } 152 | .block { 153 | width: 200px; 154 | height: 200px; 155 | background: rgb(70,120,255); 156 | } 157 | -------------------------------------------------------------------------------- /example/css_animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite 7 | 8 | 9 | 10 | 11 | 12 |
13 | 36 | 37 | 38 | 47 | 48 | -------------------------------------------------------------------------------- /example/custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Custom properties animation 7 | 8 | 9 | 10 | 11 | 12 | 13 |
SOME TEXT
14 | 15 | 16 | 31 | 32 | -------------------------------------------------------------------------------- /example/delay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Delay 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 30 | 31 | -------------------------------------------------------------------------------- /example/delay_css.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Delay CSS 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 31 | 32 | -------------------------------------------------------------------------------- /example/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dry Run 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/infinite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /example/infinite_css.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Infinite CSS 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 35 | 36 | -------------------------------------------------------------------------------- /example/keyboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Keyboard 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 | 21 | 22 | (use to rotate and W A 23 | S D to move) 24 |
25 |
26 | 27 | 28 | 156 | 157 | -------------------------------------------------------------------------------- /example/keyboard_mixed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Keyboard 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 | 21 | 22 | (use to rotate and W A 23 | S D to move) 24 |
25 |
26 | 27 | 28 | 148 | 149 | -------------------------------------------------------------------------------- /example/parallel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Parallel 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 95 | 96 | -------------------------------------------------------------------------------- /example/parallel_css.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Parallel CSS 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 96 | 97 | -------------------------------------------------------------------------------- /example/parallel_physics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Parallel 7 | 8 | 9 | 10 | 11 | 12 | 17 |
18 | 19 | 20 | 37 | 38 | -------------------------------------------------------------------------------- /example/parallel_sequence.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Parallel sequence 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 77 | 78 | -------------------------------------------------------------------------------- /example/physics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Physics 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | 17 | 50 | 51 | -------------------------------------------------------------------------------- /example/timeline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Timeline 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 | (seek when paused) 20 |
21 |
22 | 23 | 24 | 118 | 119 | -------------------------------------------------------------------------------- /jquery.animatic.js: -------------------------------------------------------------------------------- 1 | (function ($, a) { 2 | var world; 3 | 4 | $.fn.anima = function () { 5 | if (!world) { 6 | world = a.world(); 7 | } 8 | 9 | var context = this; 10 | 11 | var items = $.map(context, function (elem) { 12 | var index = world.items.indexOf(elem); 13 | return index !== -1 ? world.items[index] : world.add(elem); 14 | }); 15 | 16 | var fcall = function (fname, args) { 17 | $.each(items, function (_, item) { 18 | item[fname].apply(item, args); 19 | }); 20 | }; 21 | 22 | return { 23 | pause: function () { 24 | fcall('pause', arguments); 25 | return this; 26 | }, 27 | resume: function () { 28 | fcall('resume', arguments); 29 | return this; 30 | }, 31 | animate: function (transform, duration, ease, delay) { 32 | fcall('animate', arguments); 33 | return this; 34 | }, 35 | exit: function () { 36 | return context; 37 | } 38 | } 39 | }; 40 | }(jQuery, anima)); 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "animatic", 3 | "version": "1.0.1", 4 | "description": "CSS/JS Animations Library with Physics support", 5 | "main": "animatic.js", 6 | "keywords": [ 7 | "animation", 8 | "animations", 9 | "transition", 10 | "transitions", 11 | "css", 12 | "javascript", 13 | "physics" 14 | ], 15 | "scripts": { 16 | "prepublish": "make -B" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/lvivski/animatic.git" 21 | }, 22 | "devDependencies": { 23 | "uglify-js": "^2.4.19" 24 | }, 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /src/animations/animation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates new animation 3 | * @param {Item} item Object to animate 4 | * @param {Object} transform 5 | * @param {number} duration 6 | * @param {string} ease Timing function 7 | * @param {number} delay 8 | * @constructor 9 | */ 10 | function Animation(item, transform, duration, ease, delay) { 11 | this.item = item 12 | 13 | this.transformation = transform 14 | 15 | this.start = null 16 | this.diff = null 17 | 18 | this.duration = (transform.duration || duration) | 0 19 | this.delay = (transform.delay || delay) | 0 20 | ease = transform.ease || ease 21 | this.ease = easings[ease] || easings.linear 22 | this.easeName = transform.ease || ease || 'linear' 23 | } 24 | 25 | Animation.skip = {duration: null, delay: null, ease: null} 26 | Animation.transform = {translate: null, rotate: null, scale: null} 27 | 28 | Animation.getState = function (transform, item) { 29 | var initial = {}, 30 | computed 31 | 32 | for (var property in transform) { 33 | if (property in Animation.skip) continue 34 | if (transform.hasOwnProperty(property)) { 35 | if (item.get(property) == null) { 36 | if (!computed) { 37 | computed = getComputedStyle(item.dom, null) 38 | } 39 | Animation.setItemState(item, property, computed) 40 | } 41 | initial[property] = new Tween(item.get(property), transform[property], property) 42 | } 43 | } 44 | return initial 45 | } 46 | 47 | Animation.setItemState = function (item, property, computed) { 48 | if (property in Animation.transform) { 49 | var value = computed[transformProperty] 50 | if (value === 'none') { 51 | value = { 52 | translate: Vector.zero(), 53 | rotate: Vector.zero(), 54 | scale: Vector.set(1) 55 | } 56 | } else { 57 | value = Matrix.decompose(Matrix.parse(value)) 58 | } 59 | item.set('translate', value.translate) 60 | item.set('rotate', value.rotate) 61 | item.set('scale', value.scale) 62 | } else { 63 | item.set(property, computed[property]) 64 | } 65 | } 66 | 67 | /** 68 | * Starts animation timer 69 | * @param {number} tick Timestamp 70 | * @param {boolean=} seek Is used in seek mode 71 | */ 72 | Animation.prototype.init = function (tick, seek) { 73 | if (this.start !== null && !seek) return 74 | if (this.start === null) { 75 | this.state = Animation.getState(this.transformation, this.item) 76 | } 77 | this.start = tick + this.delay 78 | } 79 | 80 | /** 81 | * Merges animation values 82 | * @param {Object} transform 83 | * @param {number} duration 84 | * @param {string} ease Timing function 85 | * @param {number} delay 86 | */ 87 | Animation.prototype.merge = function (transform, duration, ease, delay) { 88 | this.duration = (transform.duration || duration) | 0 89 | this.delay = (transform.delay || delay) | 0 90 | ease = transform.ease || ease 91 | this.ease = easings[ease] || easings.linear 92 | this.easeName = transform.ease || ease || 'linear' 93 | 94 | merge(this.transformation, transform) 95 | 96 | this.start = null 97 | } 98 | 99 | /** 100 | * Gets values from state params 101 | * @param {string} type 102 | */ 103 | Animation.prototype.get = function (type) { 104 | return this.state[type] 105 | } 106 | 107 | /** 108 | * Runs one tick of animation 109 | * @param {number} tick 110 | * @param {boolean} seek Is used in seek mode 111 | */ 112 | Animation.prototype.run = function (tick, seek) { 113 | if (tick < this.start && !seek) return 114 | var percent = 0 115 | 116 | if (tick >= this.start) { 117 | percent = (tick - this.start) / this.duration 118 | percent = this.ease(percent) 119 | } 120 | 121 | this.transform(percent) 122 | } 123 | 124 | /** 125 | * Pauses animation 126 | */ 127 | Animation.prototype.pause = function () { 128 | this.diff = performance.now() - this.start 129 | } 130 | 131 | /** 132 | * Resumes animation 133 | */ 134 | Animation.prototype.resume = function () { 135 | this.start = performance.now() - this.diff 136 | } 137 | 138 | Animation.prototype.interpolate = function (property, percent) { 139 | return this.get(property).interpolate(percent) 140 | } 141 | 142 | /** 143 | * Transforms item 144 | * @param {number} percent 145 | */ 146 | Animation.prototype.transform = function (percent) { 147 | for (var property in this.state) { 148 | this.item.set(property, this.interpolate(property, percent)) 149 | } 150 | } 151 | 152 | /** 153 | * Ends animation 154 | * @param {boolean} abort 155 | * @param {boolean} seek Is used in seek mode 156 | */ 157 | Animation.prototype.end = function (abort, seek) { 158 | !abort && this.transform(this.ease(1)) 159 | !seek && (this.start = null) 160 | } 161 | -------------------------------------------------------------------------------- /src/animations/collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a set of animations 3 | * @param {Item} item 4 | * @constructor 5 | */ 6 | function Collection(item) { 7 | EventEmitter.call(this) 8 | 9 | this.start = null 10 | this.item = item 11 | this.delay = 0 12 | this.duration = 0 13 | this.ease = easings.linear 14 | this.easeName = 'linear' 15 | this.animations = [] 16 | } 17 | 18 | Collection.prototype = Object.create(EventEmitter.prototype) 19 | Collection.prototype.constructor = Collection 20 | 21 | /** 22 | * Add item to the collection 23 | * @param transform 24 | * @param duration 25 | * @param ease 26 | * @param delay 27 | * @param generated 28 | */ 29 | Collection.prototype.add = function (transform, duration, ease, delay, generated) { 30 | if (Array.isArray(transform)) { 31 | transform = parallel(this.item, transform) 32 | } else if (typeof transform == 'string' || transform.name != undefined) { 33 | transform = new CssAnimation(this.item, transform, duration, ease, delay, generated) 34 | } else if (!(transform instanceof Collection)) { 35 | transform = new Animation(this.item, transform, duration, ease, delay) 36 | } 37 | 38 | this.animations.push(transform) 39 | 40 | duration = this.animations.map(function (a) { 41 | return a.duration + a.delay 42 | }) 43 | 44 | if (this instanceof Parallel) { 45 | this.duration = Math.max.apply(null, duration) 46 | } else { 47 | this.duration = duration.reduce(function (a, b) { 48 | return a + b 49 | }, 0) 50 | } 51 | 52 | return this 53 | 54 | function sequence(item, transforms) { 55 | var sequence = new Sequence(item) 56 | 57 | transforms.forEach(function (t) { 58 | sequence.add(t, duration, ease, delay) 59 | }) 60 | 61 | return sequence 62 | } 63 | 64 | function parallel(item, transforms) { 65 | var parallel = new Parallel(item) 66 | 67 | transforms.forEach(function (t) { 68 | if (Array.isArray(t)) { 69 | parallel.add(sequence(item, t)) 70 | } else { 71 | parallel.add(t, duration, ease, delay) 72 | } 73 | }) 74 | 75 | return parallel 76 | } 77 | } 78 | 79 | /** 80 | * Collection length 81 | */ 82 | Object.defineProperty(Collection.prototype, 'length', { 83 | get: function () { 84 | return this.animations.length 85 | } 86 | }); 87 | 88 | /** 89 | * Get element by index 90 | * @param {number} index 91 | * @returns {Animation|Collection} 92 | */ 93 | Collection.prototype.get = function (index) { 94 | return this.animations[index] 95 | } 96 | 97 | /** 98 | * Remove all elements from collection 99 | */ 100 | Collection.prototype.empty = function () { 101 | this.animations = [] 102 | } 103 | 104 | /** 105 | * Add animation to collection 106 | * chainable 107 | * @returns {Sequence} 108 | */ 109 | Collection.prototype.animate = function (transform, duration, ease, delay) { 110 | return this.add(transform, duration, ease, delay) 111 | } 112 | 113 | /** 114 | * Apply styles 115 | * @returns {CSS} 116 | */ 117 | Collection.prototype.css = function () { 118 | return this.item.css() 119 | } 120 | -------------------------------------------------------------------------------- /src/animations/css_animation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates new animation 3 | * @param {Item} item Object to animate 4 | * @param {Object || string} animation 5 | * @param {number} duration 6 | * @param {string} ease Timing function 7 | * @param {number} delay 8 | * @param {boolean} generated 9 | * @constructor 10 | */ 11 | function CssAnimation(item, animation, duration, ease, delay, generated) { 12 | this.item = item 13 | 14 | this.name = animation.name || animation 15 | 16 | this.start = null 17 | this.diff = null 18 | 19 | this.duration = (animation.duration || duration) | 0 20 | this.delay = (animation.delay || delay) | 0 21 | this.ease = easings.css[animation.ease] || easings.css[ease] || easings.css.linear 22 | 23 | this._infinite = false 24 | this._generated = generated 25 | } 26 | 27 | /** 28 | * Starts animation timer 29 | * @param {number} tick Timestamp 30 | * @param {boolean=} force Force initialization 31 | */ 32 | CssAnimation.prototype.init = function (tick, force) { 33 | if (this.start !== null && !force) return 34 | this.start = tick + this.delay 35 | 36 | this.item.style(animationProperty, 37 | this.name + ' ' + this.duration + 'ms' + ' ' + this.ease + ' ' + 38 | this.delay + 'ms' + (this._infinite ? ' infinite' : '') + ' ' + 'forwards') 39 | } 40 | 41 | /** 42 | * Runs one tick of animation 43 | */ 44 | CssAnimation.prototype.run = function () { 45 | } 46 | 47 | /** 48 | * Pauses animation 49 | */ 50 | CssAnimation.prototype.pause = function () { 51 | this.item.style(animationProperty + '-play-state', 'paused') 52 | this.diff = performance.now() - this.start 53 | } 54 | 55 | /** 56 | * Resumes animation 57 | */ 58 | CssAnimation.prototype.resume = function () { 59 | this.item.style(animationProperty + '-play-state', 'running') 60 | this.start = performance.now() - this.diff 61 | } 62 | 63 | /** 64 | * Ends animation 65 | */ 66 | CssAnimation.prototype.end = function () { 67 | if (this._generated) { 68 | var computed = getComputedStyle(this.item.dom, null), 69 | transform = computed[transformProperty] 70 | 71 | this.item.style(animationProperty, '') 72 | this.item.state = Matrix.decompose(Matrix.parse(transform)) 73 | this.item.style() 74 | } 75 | 76 | this.start = null 77 | } 78 | -------------------------------------------------------------------------------- /src/animations/easings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Different timing functions 3 | * used for Animations 4 | * @type {Object} 5 | */ 6 | var easings = (function () { 7 | var fn = { 8 | quad: function (p) { 9 | return Math.pow(p, 2) 10 | }, 11 | cubic: function (p) { 12 | return Math.pow(p, 3) 13 | }, 14 | quart: function (p) { 15 | return Math.pow(p, 4) 16 | }, 17 | quint: function (p) { 18 | return Math.pow(p, 5) 19 | }, 20 | expo: function (p) { 21 | return Math.pow(p, 6) 22 | }, 23 | sine: function (p) { 24 | return 1 - Math.cos(p * Math.PI / 2) 25 | }, 26 | circ: function (p) { 27 | return 1 - Math.sqrt(1 - p * p) 28 | }, 29 | back: function (p) { 30 | return p * p * (3 * p - 2) 31 | } 32 | } 33 | 34 | var easings = { 35 | linear: function (p) { 36 | return p 37 | } 38 | } 39 | 40 | Object.keys(fn).forEach(function (name) { 41 | var ease = fn[name] 42 | easings['ease-in-' + name] = ease 43 | easings['ease-out-' + name] = function (p) { 44 | return 1 - ease(1 - p) 45 | } 46 | easings['ease-in-out-' + name] = function (p) { 47 | return p < 0.5 48 | ? ease(p * 2) / 2 49 | : 1 - ease(p * -2 + 2) / 2 50 | } 51 | }) 52 | 53 | easings.css = { 54 | 'linear': 'cubic-bezier(0.000, 0.000, 1.000, 1.000)', 55 | 'ease-in-quad': 'cubic-bezier(0.550, 0.085, 0.680, 0.530)', 56 | 'ease-in-cubic': 'cubic-bezier(0.550, 0.055, 0.675, 0.190)', 57 | 'ease-in-quart': 'cubic-bezier(0.895, 0.030, 0.685, 0.220)', 58 | 'ease-in-quint': 'cubic-bezier(0.755, 0.050, 0.855, 0.060)', 59 | 'ease-in-sine': 'cubic-bezier(0.470, 0.000, 0.745, 0.715)', 60 | 'ease-in-expo': 'cubic-bezier(0.950, 0.050, 0.795, 0.035)', 61 | 'ease-in-circ': 'cubic-bezier(0.600, 0.040, 0.980, 0.335)', 62 | 'ease-in-back': 'cubic-bezier(0.600, -0.280, 0.735, 0.045)', 63 | 'ease-out-quad': 'cubic-bezier(0.250, 0.460, 0.450, 0.940)', 64 | 'ease-out-cubic': 'cubic-bezier(0.215, 0.610, 0.355, 1.000)', 65 | 'ease-out-quart': 'cubic-bezier(0.165, 0.840, 0.440, 1.000)', 66 | 'ease-out-quint': 'cubic-bezier(0.230, 1.000, 0.320, 1.000)', 67 | 'ease-out-sine': 'cubic-bezier(0.390, 0.575, 0.565, 1.000)', 68 | 'ease-out-expo': 'cubic-bezier(0.190, 1.000, 0.220, 1.000)', 69 | 'ease-out-circ': 'cubic-bezier(0.075, 0.820, 0.165, 1.000)', 70 | 'ease-out-back': 'cubic-bezier(0.175, 0.885, 0.320, 1.275)', 71 | 'ease-in-out-quad': 'cubic-bezier(0.455, 0.030, 0.515, 0.955)', 72 | 'ease-in-out-cubic': 'cubic-bezier(0.645, 0.045, 0.355, 1.000)', 73 | 'ease-in-out-quart': 'cubic-bezier(0.770, 0.000, 0.175, 1.000)', 74 | 'ease-in-out-quint': 'cubic-bezier(0.860, 0.000, 0.070, 1.000)', 75 | 'ease-in-out-sine': 'cubic-bezier(0.445, 0.050, 0.550, 0.950)', 76 | 'ease-in-out-expo': 'cubic-bezier(1.000, 0.000, 0.000, 1.000)', 77 | 'ease-in-out-circ': 'cubic-bezier(0.785, 0.135, 0.150, 0.860)', 78 | 'ease-in-out-back': 'cubic-bezier(0.680, -0.550, 0.265, 1.550)' 79 | } 80 | 81 | return easings 82 | }()) 83 | -------------------------------------------------------------------------------- /src/animations/parallel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a set of parallel animations 3 | * @param {Item} item 4 | * @constructor 5 | */ 6 | function Parallel(item) { 7 | Collection.call(this, item) 8 | } 9 | 10 | Parallel.prototype = Object.create(Collection.prototype) 11 | Parallel.prototype.constructor = Parallel 12 | 13 | /** 14 | * Calls a method on all animations 15 | * @param {string} method 16 | */ 17 | Parallel.prototype.all = function (method) { 18 | var args = Array.prototype.slice.call(arguments, 1) 19 | 20 | for (var i = 0; i < this.animations.length; ++i) { 21 | var a = this.animations[i] 22 | a[method].apply(a, args) 23 | } 24 | } 25 | 26 | /** 27 | * Initializes all animations in a set 28 | * @param {number} tick 29 | * @param {boolean=} force Force initialization 30 | * @fires Parallel#start 31 | */ 32 | Parallel.prototype.init = function (tick, force) { 33 | if (this.start !== null && !force) return 34 | this.start = tick 35 | this.all('init', tick, force) 36 | this.emit('start') 37 | } 38 | 39 | /** 40 | * Runs one tick of animations 41 | * @param {number} tick 42 | */ 43 | Parallel.prototype.run = function (tick) { 44 | if (!this.animations.length) return 45 | 46 | for (var i = 0; i < this.animations.length; ++i) { 47 | var a = this.animations[i] 48 | if (a.start + a.duration <= tick) { 49 | this.animations.splice(i--, 1) 50 | a.end() 51 | continue 52 | } 53 | a.run(tick) 54 | } 55 | this.item.style() 56 | 57 | if (!this.animations.length) { 58 | this.end() 59 | } 60 | } 61 | 62 | /** 63 | * Seeks to the animation tick 64 | * @param {number} tick 65 | */ 66 | Parallel.prototype.seek = function (tick) { 67 | this.run(tick) 68 | } 69 | 70 | /** 71 | * Pauses animations 72 | */ 73 | Parallel.prototype.pause = function () { 74 | this.all('pause') 75 | } 76 | 77 | /** 78 | * Resumes animations 79 | */ 80 | Parallel.prototype.resume = function () { 81 | this.all('resume') 82 | } 83 | 84 | /** 85 | * Ends all animations in a set 86 | * @param {boolean} abort 87 | * @fires Parallel#end 88 | */ 89 | Parallel.prototype.end = function (abort) { 90 | this.all('end', abort) 91 | this.emit('end') 92 | } 93 | -------------------------------------------------------------------------------- /src/animations/sequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a set of parallel animations 3 | * @param {Item} item 4 | * @constructor 5 | */ 6 | function Sequence(item) { 7 | Collection.call(this, item) 8 | 9 | this._infinite = false 10 | } 11 | 12 | Sequence.prototype = Object.create(Collection.prototype) 13 | Sequence.prototype.constructor = Sequence 14 | 15 | /** 16 | * Initializes all animations in a set 17 | * @param {number} tick 18 | * @param {boolean=} force Force initialization 19 | * @fires Sequence#start 20 | */ 21 | Sequence.prototype.init = function (tick, force) { 22 | if (this.start !== null && !force) return 23 | 24 | this.start = tick 25 | this.animations[0].init(tick, force) 26 | this.emit('start') 27 | } 28 | 29 | /** 30 | * Runs one tick of animations 31 | * @param {number} tick 32 | */ 33 | Sequence.prototype.run = function (tick, a) { 34 | if (!this.animations.length) return 35 | 36 | while (this.animations.length !== 0) { 37 | a = this.animations[0] 38 | if (a instanceof CssAnimation) { 39 | a._infinite = this._infinite 40 | } 41 | a.init(tick) 42 | if (a.start + a.duration <= tick) { 43 | if (!(this._infinite && a instanceof CssAnimation)) { 44 | this.animations.shift() 45 | a.end() 46 | } else { 47 | break 48 | } 49 | if (this._infinite && !(a instanceof CssAnimation)) { 50 | this.animations.push(a) 51 | } 52 | continue 53 | } 54 | a.run(tick) 55 | break 56 | } 57 | 58 | if (!(a instanceof CssAnimation)) { 59 | this.item.style() 60 | } 61 | 62 | if (!this.animations.length) { 63 | this.end() 64 | } 65 | } 66 | 67 | /** 68 | * Seeks animations 69 | * @param {number} tick 70 | */ 71 | Sequence.prototype.seek = function (tick) { 72 | if (this.animations.length === 0) return 73 | var time = 0 74 | for (var i = 0; i < this.animations.length; ++i) { 75 | var a = this.animations[i] 76 | a.init(time, true) 77 | if (a.start + a.duration <= tick) { 78 | time += a.delay + a.duration 79 | a.end(false, true) 80 | continue 81 | } else { 82 | a.run(tick, true) 83 | } 84 | break 85 | } 86 | this.item.style() 87 | } 88 | 89 | /** 90 | * Play animation infinitely 91 | * @returns {Sequence} 92 | */ 93 | Sequence.prototype.infinite = function () { 94 | this._infinite = true 95 | return this 96 | } 97 | 98 | /** 99 | * Pauses animations 100 | */ 101 | Sequence.prototype.pause = function () { 102 | this.animations.length && this.animations[0].pause() 103 | } 104 | 105 | /** 106 | * Resumes animations 107 | */ 108 | Sequence.prototype.resume = function () { 109 | this.animations.length && this.animations[0].resume() 110 | } 111 | 112 | /** 113 | * Ends all animations in a set 114 | * @param {boolean} abort 115 | * @fires Sequence#end 116 | */ 117 | Sequence.prototype.end = function (abort) { 118 | for (var i = 0; i < this.animations.length; ++i) { 119 | this.animations[i].end(abort) 120 | } 121 | this.animations = [] 122 | this._infinite = false 123 | this.emit('end') 124 | } 125 | -------------------------------------------------------------------------------- /src/animations/tween.js: -------------------------------------------------------------------------------- 1 | function Tween(start, end, property) { 2 | var type = Tween.propTypes[property] || Tween.NUMERIC 3 | this.type = type 4 | 5 | this.start = Tween.parseValue(start, type) 6 | this.end = Tween.parseValue(end, type) 7 | 8 | this.suffix = Tween.px.indexOf(property) !== -1 ? 'px' : '' 9 | } 10 | 11 | Tween.NUMERIC = 'NUMERIC' 12 | Tween.COLOR = 'COLOR' 13 | 14 | Tween.propTypes = { 15 | color: Tween.COLOR, 16 | backgroundColor: Tween.COLOR, 17 | borderColor: Tween.COLOR 18 | } 19 | 20 | Tween.px = '\ 21 | margin,marginTop,marginLeft,marginBottom,marginRight,\ 22 | padding,paddingTop,paddingLeft,paddingBottom,paddingRight,\ 23 | top,left,bottom,right,\ 24 | width,height,maxWidth,maxHeight,minWidth,minHeight,\ 25 | borderRadius,borderWidth'.split(',') 26 | 27 | Tween.parseValue = function (value, type) { 28 | return type === Tween.COLOR ? Tween.parseColor(value) : Tween.parseNumeric(value) 29 | } 30 | 31 | Tween.parseNumeric = function (numeric) { 32 | if (!Array.isArray(numeric)) { 33 | numeric = String(numeric).split(/\s+/) 34 | } 35 | return Array.isArray(numeric) ? numeric.map(parseFloat) : Number(numeric) 36 | } 37 | 38 | Tween.parseColor = function (color) { 39 | var hex = color.match(/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i) 40 | if (hex) { 41 | return { 42 | r: parseInt(hex[1], 16), 43 | g: parseInt(hex[2], 16), 44 | b: parseInt(hex[3], 16), 45 | a: 1 46 | } 47 | } 48 | 49 | var rgb = color.match(/^rgba?\(([0-9.]*), ?([0-9.]*), ?([0-9.]*)(?:, ?([0-9.]*))?\)$/) 50 | if (rgb) { 51 | return { 52 | r: parseFloat(rgb[1]), 53 | g: parseFloat(rgb[2]), 54 | b: parseFloat(rgb[3]), 55 | a: parseFloat(rgb[4] != null ? rgb[4] : 1) 56 | } 57 | } 58 | } 59 | 60 | Tween.prototype.interpolate = function (percent) { 61 | if (this.type === Tween.NUMERIC) { 62 | if (Array.isArray(this.end)) { 63 | return this.array(percent) 64 | } else if (this.end !== undefined) { 65 | return this.absolute(percent) 66 | } 67 | } else if (this.type === Tween.COLOR) { 68 | return this.color(percent) 69 | } 70 | } 71 | 72 | Tween.prototype.array = function (percent) { 73 | var value = [] 74 | for (var i = 0; i < this.end.length; ++i) { 75 | if (this.end[i]) { 76 | value[i] = this.start[i] + this.end[i] * percent 77 | if (this.suffix) { 78 | value[i] += this.suffix 79 | } 80 | } 81 | } 82 | return value 83 | } 84 | 85 | Tween.prototype.absolute = function (percent) { 86 | var value = Number(this.start) + (Number(this.end) - Number(this.start)) * percent 87 | if (this.suffix) { 88 | value += this.suffix 89 | } 90 | return value 91 | } 92 | 93 | Tween.prototype.color = function (percent) { 94 | var rgb = {r:0,g:0,b:0} 95 | for (var spectra in rgb) { 96 | var value = Math.round(this.start[spectra] + (this.end[spectra] - this.start[spectra]) * percent) 97 | rgb[spectra] = clamp(value, 0, 255) 98 | } 99 | spectra = 'a' 100 | value = Math.round(this.start[spectra] + (this.end[spectra] - this.start[spectra]) * percent) 101 | rgb[spectra] = clamp(value, 0, 1) 102 | return 'rgba(' + [rgb.r, rgb.g, rgb.b, rgb.a] + ')' 103 | } 104 | 105 | function clamp(value, min, max) { 106 | return Math.min(max, Math.max(min, value)); 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Animatic 3 | * @type {Object} 4 | */ 5 | var a = {} 6 | 7 | /** 8 | * Creates and initializes world with frame loop 9 | * @return {World} 10 | */ 11 | a.world = function () { 12 | return new World 13 | } 14 | 15 | /** 16 | * Creates and initializes timeline 17 | * @return {Timeline} 18 | */ 19 | a.timeline = function () { 20 | return new Timeline 21 | } 22 | 23 | if (typeof module === 'object' && typeof module.exports === 'object') { 24 | module.exports = a 25 | } else if (typeof define === 'function' && define.amd) { 26 | define(a) 27 | } else { 28 | root.animatic = root.a = a 29 | } 30 | -------------------------------------------------------------------------------- /src/css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CSSify animations 3 | * @param {Item} item 4 | * @param {boolean=} idle 5 | * @constructor 6 | */ 7 | function CSS(item, idle) { 8 | !document.styleSheets.length && this.createStyleSheet() 9 | this.stylesheet = document.styleSheets[0] 10 | 11 | this.item = item 12 | this.animation = item.animation 13 | 14 | !idle && this.style() 15 | } 16 | 17 | CSS.skip = {translate: null, rotate: null, scale: null}; 18 | 19 | /** 20 | * Creates new stylesheet and adds it to HEAD 21 | */ 22 | CSS.prototype.createStyleSheet = function () { 23 | var style = document.createElement('style') 24 | document.getElementsByTagName('head')[0].appendChild(style) 25 | } 26 | 27 | /** 28 | * Pauses CSS animation 29 | */ 30 | CSS.prototype.pause = function () { 31 | this.animation.pause() 32 | } 33 | 34 | /** 35 | * Resumes CSS animation 36 | */ 37 | CSS.prototype.resume = function () { 38 | this.animation.resume() 39 | } 40 | 41 | /** 42 | * Stops CSS animation 43 | * parses current transformation matrix 44 | * extracts values and sets item state 45 | */ 46 | CSS.prototype.stop = function () { 47 | var computed = getComputedStyle(this.item.dom, null), 48 | transform = computed[transformProperty] 49 | 50 | this.item.style(animationProperty, '') 51 | this.item.state = Matrix.decompose(Matrix.parse(transform)) 52 | this.item.style() 53 | 54 | return this 55 | } 56 | 57 | /** 58 | * Applies animations and sets item style 59 | */ 60 | CSS.prototype.style = function () { 61 | var animation = 'a' + Date.now() + 'r' + Math.floor(Math.random() * 1000) 62 | 63 | var cssRules = this.stylesheet.cssRules 64 | this.stylesheet.insertRule(this.keyframes(animation), cssRules ? cssRules.length : 0) 65 | 66 | this.animation.empty() 67 | this.animation.add(animation, this.animation.duration, '', 0, true) 68 | } 69 | 70 | /** 71 | * Generates @keyframes based on animations 72 | * @param {string} name Animation name 73 | * @return {string} 74 | */ 75 | CSS.prototype.keyframes = function (name) { 76 | var time = 0, 77 | rule = ['@' + getProperty('keyframes') + ' ' + name + '{'] 78 | 79 | for (var i = 0; i < this.animation.length; ++i) { 80 | var a = this.animation.get(i), 81 | aNext = this.animation.get(i + 1) 82 | 83 | a.init() 84 | 85 | if (a instanceof Animation) { // Single 86 | i === 0 && rule.push(this.frame(0, easings.css[a.easeName])) 87 | 88 | a.delay && rule.push(this.frame(time += a.delay)) 89 | 90 | a.transform(1) 91 | 92 | rule.push(this.frame(time += a.duration, aNext && easings.css[aNext.easeName])) 93 | } else { // Parallel (it doesn't work with custom easings for now) 94 | var frames = [] 95 | a.animations.forEach(function frame(a) { 96 | a.animations && a.animations.forEach(frame) 97 | a.delay && frames.indexOf(a.delay) === -1 && frames.push(a.delay) 98 | a.duration && frames.indexOf(a.delay + a.duration) === -1 && frames.push(a.delay + a.duration) 99 | }) 100 | 101 | frames = frames.sort(function (a, b) { 102 | return a - b 103 | }) 104 | 105 | for (var k = 0; k < frames.length; ++k) { 106 | var frame = frames[k] 107 | for (var j = 0; j < a.animations.length; ++j) { 108 | var pa = a.animations[j] 109 | // it's animation start or it's already ended 110 | if (pa.delay >= frame || pa.delay + pa.duration < frame) 111 | continue 112 | pa.transform(pa.ease((frame - pa.delay) / pa.duration)) 113 | } 114 | 115 | rule.push(this.frame(time += frame)) 116 | } 117 | } 118 | } 119 | rule.push('}') 120 | return rule.join('') 121 | } 122 | 123 | /** 124 | * Calcuates percent for keyframes 125 | * @param {number} time 126 | * @return {string} 127 | */ 128 | CSS.prototype.percent = function (time) { 129 | return (time * 100 / this.animation.duration).toFixed(3) 130 | } 131 | 132 | /** 133 | * Generates one frame for @keyframes 134 | * @param {number} time 135 | * @param {string=} ease 136 | * @return {string} 137 | */ 138 | CSS.prototype.frame = function (time, ease) { 139 | var percent = this.percent(time), 140 | props = [] 141 | for (var property in this.item.state) { 142 | if (property in CSS.skip) continue 143 | props.push(percent ? property.replace(/([A-Z])/g, '-$1') + ':' + this.item.get(property) + ';' : '') 144 | } 145 | return percent + '% {' + 146 | (percent ? transformProperty + ':' + this.item.transform() + ';' : '') + 147 | (props.join('')) + 148 | (ease ? getProperty('animation-timing-function') + ':' + ease + ';' : '') + 149 | '}' 150 | } 151 | -------------------------------------------------------------------------------- /src/eventemitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EventEmitter 3 | * @constructor 4 | */ 5 | function EventEmitter() { 6 | this.handlers = {} 7 | } 8 | 9 | /** 10 | * Adds handler for event 11 | * @param {string} event 12 | * @param {Function} handler 13 | * @return {EventEmitter} 14 | */ 15 | EventEmitter.prototype.on = function (event, handler) { 16 | (this.handlers[event] = this.handlers[event] || []) 17 | .push(handler) 18 | return this 19 | } 20 | 21 | /** 22 | * Removes event handler 23 | * @param {string} event 24 | * @param {Function} handler 25 | * @return {EventEmitter} 26 | */ 27 | EventEmitter.prototype.off = function (event, handler) { 28 | var handlers = this.handlers[event] 29 | 30 | if (handler) { 31 | handlers.splice(handlers.indexOf(handler), 1) 32 | } else { 33 | delete this.handlers[event] 34 | } 35 | 36 | return this 37 | } 38 | 39 | /** 40 | * Triggers event 41 | * @param {string} event 42 | * @return {EventEmitter} 43 | */ 44 | EventEmitter.prototype.emit = function (event) { 45 | var args = Array.prototype.slice.call(arguments, 1), 46 | handlers = this.handlers[event] 47 | 48 | if (handlers) { 49 | for (var i = 0; i < handlers.length; ++i) { 50 | handlers[i].apply(this, args) 51 | } 52 | } 53 | 54 | return this 55 | } 56 | 57 | /** 58 | * List all event listeners 59 | * @param {string} event 60 | * @returns {Array} 61 | */ 62 | EventEmitter.prototype.listeners = function (event) { 63 | return this.handlers[event] || [] 64 | } 65 | -------------------------------------------------------------------------------- /src/item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates new animated item 3 | * @param {HTMLElement} node 4 | * @constructor 5 | */ 6 | function Item(node) { 7 | EventEmitter.call(this) 8 | 9 | this.dom = node 10 | 11 | this.animation = new Sequence(this) 12 | 13 | this.running = true 14 | this.state = {} 15 | } 16 | 17 | Item.prototype = Object.create(EventEmitter.prototype) 18 | Item.prototype.constructor = Item 19 | 20 | /** 21 | * Updates item on frame 22 | * @param {number} tick 23 | */ 24 | Item.prototype.update = function (tick) { 25 | if (!this.running) return 26 | this.animation.run(tick) 27 | } 28 | 29 | /** 30 | * Updates item on timeline 31 | * @param {number} tick 32 | */ 33 | Item.prototype.timeline = function (tick) { 34 | this.clear() 35 | this.animation.seek(tick) 36 | } 37 | 38 | /** 39 | * Pauses item animation 40 | */ 41 | Item.prototype.pause = function () { 42 | if (!this.running) return 43 | this.animation.pause() 44 | this.running = false 45 | } 46 | 47 | /** 48 | * Resumes item animation 49 | */ 50 | Item.prototype.resume = function () { 51 | if (this.running) return 52 | this.animation.resume() 53 | this.running = true 54 | } 55 | 56 | /** 57 | * Sets style to the dom node 58 | * @param {string=} property 59 | * @param {string=} value 60 | */ 61 | Item.prototype.style = function (property, value) { 62 | var style = this.dom.style; 63 | if (property && value) { 64 | style[property] = value 65 | } else { 66 | style[transformProperty] = this.transform() 67 | for (var property in this.state) { 68 | style[property] = this.get(property) 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Returns transform CSS value 75 | * @return {string} 76 | */ 77 | Item.prototype.transform = function () { 78 | return Matrix.stringify(this.matrix()) 79 | } 80 | 81 | /** 82 | * Calculates transformation matrix for the state 83 | * @return {Object} 84 | */ 85 | Item.prototype.matrix = function () { 86 | var state = this.state 87 | return Matrix.compose( 88 | state.translate, state.rotate, state.scale 89 | ) 90 | } 91 | 92 | /** 93 | * Gets transformation needed to make Item in center 94 | * @return {Object} 95 | */ 96 | Item.prototype.center = function () { 97 | return Matrix.decompose(Matrix.inverse(this.matrix())) 98 | } 99 | 100 | /** 101 | * Rotates item to look at vector 102 | * @param {Array} vector 103 | */ 104 | Item.prototype.lookAt = function (vector) { 105 | var transform = Matrix.decompose(Matrix.lookAt( 106 | vector, this.get('translate'), Vector.set(0, 1, 0) 107 | )) 108 | this.set('rotate', transform.rotate) 109 | } 110 | 111 | /** 112 | * Sets values to state params 113 | * @param {string} type 114 | * @param {Array|Number|String} value 115 | * @return {Item} 116 | */ 117 | Item.prototype.set = function (type, value) { 118 | if (Array.isArray(value)) { 119 | this.state[type] || (this.state[type] = []) 120 | for (var i = 0; i < value.length; ++i) { 121 | if (value[i] !== undefined) { 122 | this.state[type][i] = value[i] 123 | } 124 | } 125 | } else { 126 | this.state[type] = value 127 | } 128 | 129 | return this 130 | } 131 | 132 | /** 133 | * Gets values from state params 134 | * @param {string} type 135 | */ 136 | Item.prototype.get = function (type) { 137 | return this.state[type] 138 | } 139 | 140 | /** 141 | * Clears item transform 142 | */ 143 | Item.prototype.clear = function () { 144 | this.state.translate = Vector.zero() 145 | this.state.rotate = Vector.zero() 146 | this.state.scale = Vector.set(1) 147 | } 148 | 149 | /** 150 | * Adds animation 151 | * @param {Object|Array} transform 152 | * @param {number} duration 153 | * @param {string} ease 154 | * @param {number} delay 155 | * @return {Sequence} 156 | */ 157 | Item.prototype.animate = function (transform, duration, ease, delay) { 158 | return this.animation.add(transform, duration, ease, delay) 159 | } 160 | 161 | /** 162 | * Alternates current animation 163 | * @param {Object|Array} transform 164 | * @param {number} duration 165 | * @param {string} ease 166 | * @param {number} delay 167 | */ 168 | Item.prototype.alternate = function (transform, duration, ease, delay) { 169 | if (this.animation.length) { 170 | this.animation.get(0).merge(transform, duration, ease, delay) 171 | } else { 172 | this.animate.call(this, transform, duration, ease, delay) 173 | } 174 | } 175 | 176 | /** 177 | * Finishes all Item animations 178 | * @param {boolean} abort 179 | */ 180 | Item.prototype.finish = function (abort) { 181 | this.animation.end(abort) 182 | return this 183 | } 184 | 185 | /** 186 | * Stops all Item animations 187 | */ 188 | Item.prototype.stop = function () { 189 | return this.finish(true) 190 | } 191 | 192 | /** 193 | * Generates CSS animation or transition 194 | * @param {boolean=} idle 195 | * @return {CSS} 196 | */ 197 | Item.prototype.css = function (idle) { 198 | return new CSS(this, idle) 199 | } 200 | -------------------------------------------------------------------------------- /src/math/matrix.js: -------------------------------------------------------------------------------- 1 | var radians = Math.PI / 180 2 | 3 | /** 4 | * Matrix object for transformation calculations 5 | * @type {Object} 6 | */ 7 | var Matrix = { 8 | identity: function () { 9 | return [1, 0, 0, 0, 10 | 0, 1, 0, 0, 11 | 0, 0, 1, 0, 12 | 0, 0, 0, 1] 13 | }, 14 | multiply: function multiply(a, b) { // doesn't work for perspective 15 | var c = this.identity() 16 | 17 | c[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] 18 | c[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] 19 | c[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] 20 | 21 | c[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] 22 | c[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] 23 | c[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] 24 | 25 | c[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] 26 | c[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] 27 | c[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] 28 | 29 | c[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + b[12] 30 | c[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + b[13] 31 | c[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + b[14] 32 | 33 | return 2 >= arguments.length 34 | ? c 35 | : multiply.apply(this, [c].concat(Array.prototype.slice.call(arguments, 2))) 36 | }, 37 | translate: function (tx, ty, tz) { 38 | if (!(tx || ty || tz)) return this.identity() 39 | 40 | tx || (tx = 0) 41 | ty || (ty = 0) 42 | tz || (tz = 0) 43 | 44 | return [1, 0, 0, 0, 45 | 0, 1, 0, 0, 46 | 0, 0, 1, 0, 47 | tx, ty, tz, 1] 48 | }, 49 | /* 50 | translateX: function translateX(t) { 51 | return this.translate(t, 0, 0) 52 | }, 53 | translateY: function translateY(t) { 54 | return this.translate(0, t, 0) 55 | }, 56 | translateZ: function translateZ(t) { 57 | return this.translate(0, 0, t) 58 | }, 59 | */ 60 | scale: function (sx, sy, sz) { 61 | if (!(sx || sy || sz)) return this.identity() 62 | 63 | sx || (sx = 1) 64 | sy || (sy = 1) 65 | sz || (sz = 1) 66 | 67 | return [sx, 0, 0, 0, 68 | 0, sy, 0, 0, 69 | 0, 0, sz, 0, 70 | 0, 0, 0, 1] 71 | }, 72 | /* 73 | scaleX: function scaleX(s) { 74 | return this.scale(s, 0, 0) 75 | }, 76 | scaleY: function scaleY(s) { 77 | return this.scale(0, s, 0) 78 | }, 79 | scaleZ: function scaleZ(s) { 80 | return this.scale(0, 0, s) 81 | }, 82 | */ 83 | rotate: function (ax, ay, az) { 84 | if (!(ax || ay || az)) return this.identity() 85 | 86 | ax || (ax = 0) 87 | ay || (ay = 0) 88 | az || (az = 0) 89 | 90 | ax *= radians 91 | ay *= radians 92 | az *= radians 93 | 94 | var sx = Math.sin(ax), 95 | cx = Math.cos(ax), 96 | 97 | sy = Math.sin(ay), 98 | cy = Math.cos(ay), 99 | 100 | sz = Math.sin(az), 101 | cz = Math.cos(az) 102 | 103 | return [cy * cz, cx * sz + sx * sy * cz, sx * sz - cx * sy * cz, 0, 104 | -cy * sz, cx * cz - sx * sy * sz, sx * cz + cx * sy * sz, 0, 105 | sy, -sx * cy, cx * cy, 0, 106 | 0, 0, 0, 1] 107 | }, 108 | /* 109 | rotateX: function rotateX(a) { 110 | a *= radians 111 | 112 | var s = Math.sin(a), 113 | c = Math.cos(a) 114 | 115 | return [1, 0, 0, 0, 116 | 0, c, s, 0, 117 | 0, -s, c, 0, 118 | 0, 0, 0, 1] 119 | }, 120 | rotateY: function rotateY(a) { 121 | a *= radians 122 | 123 | var s = Math.sin(a), 124 | c = Math.cos(a) 125 | 126 | return [c, 0, -s, 0, 127 | 0, 1, 0, 0, 128 | s, 0, c, 0, 129 | 0, 0, 0, 1] 130 | }, 131 | rotateZ: function rotateZ(a) { 132 | a *= radians 133 | 134 | var s = Math.sin(a), 135 | c = Math.cos(a) 136 | 137 | return [c, s, 0, 0, 138 | -s, c, 0, 0, 139 | 0, 0, 1, 0, 140 | 0, 0, 0, 1] 141 | }, 142 | */ 143 | rotate3d: function (x, y, z, a) { 144 | a || (a = 0) 145 | 146 | a *= radians 147 | 148 | var s = Math.sin(a), 149 | c = Math.cos(a), 150 | norm = Vector.norm(x, y, z) 151 | 152 | x = norm[0] 153 | y = norm[1] 154 | z = norm[2] 155 | 156 | var xx = x * x, 157 | yy = y * y, 158 | zz = z * z, 159 | _c = 1 - c 160 | 161 | return [xx + (1 - xx) * c, x * y * _c + z * s, x * z * _c - y * s, 0, 162 | x * y * _c - z * s, yy + (1 - yy) * c, y * z * _c + x * s, 0, 163 | x * z * _c + y * s, y * z * _c - x * s, zz + (1 - zz) * c, 0, 164 | 0, 0, 0, 1] 165 | }, 166 | skew: function (ax, ay) { 167 | if (!(ax || ay)) return this.identity() 168 | 169 | ax || (ax = 0) 170 | ay || (ay = 0) 171 | 172 | ax *= radians 173 | ay *= radians 174 | 175 | return [1, Math.tan(ay), 0, 0, 176 | Math.tan(ax), 1, 0, 0, 177 | 0, 0, 1, 0, 178 | 0, 0, 0, 1] 179 | }, 180 | /* 181 | skewX: function skewX(a) { 182 | return this.skew(a, 0) 183 | }, 184 | skewY: function skewY(a) { 185 | return this.skew(0, a) 186 | }, 187 | */ 188 | perspective: function (p) { 189 | p = -1 / p 190 | 191 | return [1, 0, 0, 0, 192 | 0, 1, 0, 0, 193 | 0, 0, 1, p, 194 | 0, 0, 0, 1] 195 | }, 196 | parse: function (s) { 197 | var m = s.match(/\((.+)\)/)[1].split(/,\s?/) 198 | if (m.length === 6) { 199 | m.splice(2, 0, 0, 0) 200 | m.splice(6, 0, 0, 0) 201 | m.splice(8, 0, 0, 0, 1, 0) 202 | m.push(0, 1) 203 | } 204 | 205 | return m 206 | }, 207 | inverse: function (m) { 208 | var a = this.identity(), 209 | 210 | inv0 = m[5] * m[10] - m[6] * m[9], 211 | inv1 = m[1] * m[10] - m[2] * m[9], 212 | inv2 = m[1] * m[6] - m[2] * m[5], 213 | 214 | inv4 = m[4] * m[10] - m[6] * m[8], 215 | inv5 = m[0] * m[10] - m[2] * m[8], 216 | inv6 = m[0] * m[6] - m[2] * m[4], 217 | 218 | inv8 = m[4] * m[9] - m[5] * m[8], 219 | inv9 = m[0] * m[9] - m[1] * m[8], 220 | inv10 = m[0] * m[5] - m[1] * m[4], 221 | 222 | det = 1 / (m[0] * inv0 - m[1] * inv4 + m[2] * inv8) 223 | 224 | a[0] = det * inv0 225 | a[1] = -det * inv1 226 | a[2] = det * inv2 227 | 228 | a[4] = -det * inv4 229 | a[5] = det * inv5 230 | a[6] = -det * inv6 231 | 232 | a[8] = det * inv8 233 | a[9] = -det * inv9 234 | a[10] = det * inv10 235 | 236 | a[12] = -m[12] * a[0] - m[13] * a[4] - m[14] * a[8] 237 | a[13] = -m[12] * a[1] - m[13] * a[5] - m[14] * a[9] 238 | a[14] = -m[12] * a[2] - m[13] * a[6] - m[14] * a[10] 239 | 240 | return a 241 | }, 242 | compose: function (translate, rotate, scale) { 243 | translate || (translate = []) 244 | rotate || (rotate = []) 245 | scale || (scale = []) 246 | 247 | var a = this.rotate(rotate[0], rotate[1], rotate[2]) 248 | 249 | if (scale.length) { 250 | a[0] *= scale[0] 251 | a[1] *= scale[0] 252 | a[2] *= scale[0] 253 | 254 | a[4] *= scale[1] 255 | a[5] *= scale[1] 256 | a[6] *= scale[1] 257 | 258 | a[8] *= scale[2] 259 | a[9] *= scale[2] 260 | a[10] *= scale[2] 261 | } 262 | 263 | if (translate.length) { 264 | a[12] = translate[0] 265 | a[13] = translate[1] 266 | a[14] = translate[2] 267 | } 268 | 269 | return a 270 | }, 271 | decompose: function (m) { // supports only scale*rotate*translate matrix 272 | var sX = Vector.length(m[0], m[1], m[2]), 273 | sY = Vector.length(m[4], m[5], m[6]), 274 | sZ = Vector.length(m[8], m[9], m[10]) 275 | 276 | var rX = Math.atan2(-m[9] / sZ, m[10] / sZ) / radians, 277 | rY = Math.asin(m[8] / sZ) / radians, 278 | rZ = Math.atan2(-m[4] / sY, m[0] / sX) / radians 279 | 280 | if (m[4] === 1 || m[4] === -1) { 281 | rX = 0 282 | rY = m[4] * -Math.PI / 2 283 | rZ = m[4] * Math.atan2(m[6] / sY, m[5] / sY) / radians 284 | } 285 | 286 | var tX = m[12], 287 | tY = m[13], 288 | tZ = m[14] 289 | 290 | return { 291 | translate: [tX, tY, tZ], 292 | rotate: [rX, rY, rZ], 293 | scale: [sX, sY, sZ] 294 | } 295 | }, 296 | transpose: function (m) { 297 | var t 298 | 299 | t = m[1] 300 | m[1] = m[4] 301 | m[4] = t 302 | 303 | t = m[2] 304 | m[2] = m[8] 305 | m[8] = t 306 | 307 | t = m[6] 308 | m[6] = m[9] 309 | m[9] = t 310 | 311 | t = m[3] 312 | m[3] = m[12] 313 | m[12] = t 314 | 315 | t = m[7] 316 | m[7] = m[13] 317 | m[13] = t 318 | 319 | t = m[11] 320 | m[11] = m[14] 321 | m[14] = t 322 | 323 | return m 324 | }, 325 | lookAt: function (eye, target, up) { 326 | var z = Vector.sub(eye, target) 327 | z = Vector.norm(z) 328 | if (Vector.length(z) === 0) 329 | z[2] = 1 330 | 331 | var x = Vector.cross(up, z) 332 | if (Vector.length(x) === 0) { 333 | z[0] += 0.0001 334 | x = Vector.norm(Vector.cross(up, z)) 335 | } 336 | 337 | var y = Vector.cross(z, x) 338 | 339 | var a = this.identity() 340 | 341 | a[0] = x[0] 342 | a[1] = x[1] 343 | a[2] = x[2] 344 | 345 | a[4] = y[0] 346 | a[5] = y[1] 347 | a[6] = y[2] 348 | 349 | a[8] = z[0] 350 | a[9] = z[1] 351 | a[10] = z[2] 352 | 353 | return a 354 | }, 355 | stringify: function (m) { 356 | for (var i = 0; i < m.length; ++i) { 357 | if (Math.abs(m[i]) < 1e-5) m[i] = 0 358 | } 359 | return 'matrix3d(' + m.join() + ')' 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /src/math/vector.js: -------------------------------------------------------------------------------- 1 | var Vector = { 2 | set: function (x, y, z) { 3 | if (Array.isArray(x)) { 4 | y = x[1] 5 | z = x[2] 6 | x = x[0] 7 | } 8 | if (x === undefined) { 9 | x = 0 10 | } 11 | if (y === undefined) { 12 | y = x 13 | z = x 14 | } 15 | return [x, y, z] 16 | }, 17 | length: function (x, y, z) { 18 | if (Array.isArray(x)) { 19 | y = x[1] 20 | z = x[2] 21 | x = x[0] 22 | } 23 | return Math.sqrt(x * x + y * y + z * z) 24 | }, 25 | add: function (a, b) { 26 | return [ 27 | a[0] + b[0], 28 | a[1] + b[1], 29 | a[2] + b[2] 30 | ] 31 | }, 32 | sub: function (a, b) { 33 | return [ 34 | a[0] - b[0], 35 | a[1] - b[1], 36 | a[2] - b[2] 37 | ] 38 | }, 39 | norm: function (x, y, z) { 40 | if (Array.isArray(x)) { 41 | y = x[1] 42 | z = x[2] 43 | x = x[0] 44 | } 45 | var len = this.length(x, y, z) 46 | 47 | if (len !== 0) { 48 | x /= len 49 | y /= len 50 | z /= len 51 | } else { 52 | x = 0 53 | y = 0 54 | z = 0 55 | } 56 | 57 | return [x, y, z] 58 | }, 59 | dist: function (a, b) { 60 | var dx = a[0] - b[0], 61 | dy = a[1] - b[1], 62 | dz = a[2] - b[2] 63 | 64 | return Math.sqrt(dx * dx + dy * dy + dz + dz) 65 | }, 66 | cross: function (a, b) { 67 | var x = a[1] * b[2] - a[2] * b[1], 68 | y = a[2] * b[0] - a[0] * b[2], 69 | z = a[1] * b[1] - a[1] * b[0] 70 | 71 | return [x, y, z] 72 | }, 73 | clone: function (v) { 74 | return v.slice() 75 | }, 76 | scale: function (x, y, z, f) { 77 | if (Array.isArray(x)) { 78 | f = y 79 | y = x[1] 80 | z = x[2] 81 | x = x[0] 82 | } 83 | return [x * f, y * f, z * f] 84 | }, 85 | zero: function () { 86 | return [0, 0, 0] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/physics/forces/attraction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Attraction force 3 | * @param {number} radius 4 | * @param {number} strength 5 | * @constructor 6 | */ 7 | function Attraction(radius, strength) { 8 | radius || (radius = 1000) 9 | strength || (strength = 100) 10 | 11 | var force = Vector.sub(this.state.translate, this.current.position), 12 | distance = Vector.length(force) 13 | 14 | if (distance < radius) { 15 | force = Vector.scale(Vector.norm(force), 1.0 - (distance * distance) / (radius * radius)) 16 | 17 | this.current.acceleration = Vector.add(this.current.acceleration, Vector.scale(force, strength)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/physics/forces/constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Constant force 3 | * @constructor 4 | */ 5 | function Constant() { 6 | var force = Vector.sub(this.state.translate, this.current.position) 7 | 8 | this.current.acceleration = Vector.add(this.current.acceleration, force) 9 | } 10 | -------------------------------------------------------------------------------- /src/physics/forces/edge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Edge force 3 | * @param {Vector} min 4 | * @param {Vector} max 5 | * @constructor 6 | */ 7 | function Edge(min, max, bounce) { 8 | min || (min = Vector.set(0)) 9 | max || (max = Vector.set(0)) 10 | bounce || (bounce = true) 11 | 12 | for (var i = 0; i < 3; ++i) { 13 | if (this.current.position[i] < min[i] || this.current.position[i] > max[i]) { 14 | if (bounce) { 15 | this.previous.position[i] = 2 * this.current.position[i] - this.previous.position[i] 16 | } else { 17 | this.current.position[i] = Math.max(min[i], Math.min(max[i], this.current.position[i])) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/physics/particle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creatites particle with physics 3 | * @param {HTMLElement} node 4 | * @param {number=} mass 5 | * @param {number=} viscosity 6 | * @constructor 7 | */ 8 | function Particle(node, mass, viscosity, edge) { 9 | Item.call(this, node) 10 | 11 | if (mass === Object(mass)) { 12 | viscosity = mass.viscosity 13 | edge = mass.edge 14 | mass = mass.mass 15 | } 16 | 17 | mass /= 100 18 | 19 | mass || (mass = 0.01) 20 | viscosity || (viscosity = 0.1) 21 | edge || (edge = false) 22 | 23 | this.mass = 1 / mass 24 | this.viscosity = viscosity 25 | this.edge = edge 26 | 27 | this.current = { 28 | position: Vector.zero(), 29 | velocity: Vector.zero(), 30 | acceleration: Vector.zero() 31 | } 32 | 33 | this.previous = { 34 | position: Vector.zero(), 35 | velocity: Vector.zero(), 36 | acceleration: Vector.zero() 37 | } 38 | 39 | this.clock = null 40 | } 41 | 42 | Particle.prototype = Object.create(Item.prototype) 43 | Particle.prototype.constructor = Particle 44 | 45 | /** 46 | * Updates particle and applies integration 47 | * @param {number} tick 48 | */ 49 | Particle.prototype.update = function (tick) { 50 | this.animation.run(tick) 51 | 52 | this.integrate(tick) 53 | 54 | this.style() 55 | } 56 | 57 | Particle.prototype.timeline = function (tick) { 58 | this.clear() 59 | this.animation.seek(tick) 60 | 61 | this.integrate(tick, true) 62 | 63 | this.style() 64 | } 65 | 66 | /** 67 | * Integrates particle 68 | * @param {number} delta 69 | */ 70 | Particle.prototype.integrate = function (tick, clamp) { 71 | this.clock || (this.clock = tick) 72 | 73 | var delta = tick - this.clock 74 | 75 | if (delta) { 76 | clamp && (delta = Math.max(-16, Math.min(16, delta))) 77 | 78 | this.clock = tick 79 | 80 | delta *= 0.001 81 | 82 | Constant.call(this) 83 | this.edge && Edge.call(this, Vector.set(this.edge.min), Vector.set(this.edge.max), this.edge.bounce) 84 | 85 | Verlet.call(this, delta, 1.0 - this.viscosity) 86 | } 87 | } 88 | 89 | Particle.prototype.css = function () { 90 | throw new Error('CSS is nor supported for physics'); 91 | } 92 | 93 | /** 94 | * Gets particle matrix 95 | * @returns {Array} 96 | */ 97 | Particle.prototype.matrix = function () { 98 | var state = this.state 99 | return Matrix.compose( 100 | this.current.position, state.rotate, state.scale 101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /src/physics/verlet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Velocity Verlet Integrator 3 | * @param {number} delta 4 | * @param {number} drag 5 | * @constructor 6 | */ 7 | function Verlet(delta, drag) { 8 | // velocity = position - old_position 9 | // position = position + (velocity + acceleration * delta * delta) 10 | var current = this.current, 11 | previous = this.previous 12 | 13 | current.acceleration = Vector.scale(current.acceleration, this.mass) 14 | current.velocity = Vector.sub(current.position, previous.position) 15 | 16 | if (drag !== undefined) { 17 | current.velocity = Vector.scale(current.velocity, drag) 18 | } 19 | 20 | previous.position = current.position 21 | current.position = Vector.add(current.position, Vector.add(current.velocity, Vector.scale(current.acceleration, delta * delta))) 22 | 23 | current.acceleration = Vector.zero() 24 | } 25 | -------------------------------------------------------------------------------- /src/timeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates new Timeline and start frame loop 3 | * @constructor 4 | */ 5 | function Timeline() { 6 | World.call(this, true) 7 | this.currentTime = 0 8 | this.start = 0 9 | } 10 | 11 | Timeline.prototype = Object.create(World.prototype) 12 | Timeline.prototype.constructor = Timeline 13 | 14 | /** 15 | * Starts new frame loop 16 | */ 17 | Timeline.prototype.run = function () { 18 | this.frame = requestAnimationFrame(update) 19 | 20 | var self = this 21 | 22 | function update(tick) { 23 | if (fixTick) { 24 | tick = performance.now() 25 | } 26 | if (self.running) { 27 | self.currentTime = tick - self.start 28 | } 29 | self.update(self.currentTime) 30 | self.frame = requestAnimationFrame(update) 31 | } 32 | } 33 | 34 | /** 35 | * Updates Items in Timeline 36 | * @param {number} tick 37 | * @fires Timeline#update 38 | */ 39 | Timeline.prototype.update = function (tick) { 40 | for (var i = 0, length = this.items.length; i < length; ++i) { 41 | var item = this.items[i] 42 | if (this.changed < length || this.running) { 43 | item.timeline(tick) 44 | this.changed++ 45 | this.emit('update', tick) 46 | } else { 47 | item.style() 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Plays/Resumes Timeline 54 | */ 55 | Timeline.prototype.play = function () { 56 | this.running = true 57 | this.start = performance.now() - this.currentTime 58 | } 59 | 60 | /** 61 | * Pauses Timeline 62 | */ 63 | Timeline.prototype.pause = function () { 64 | this.running = false 65 | } 66 | 67 | /** 68 | * Stops Timeline 69 | */ 70 | Timeline.prototype.stop = function () { 71 | this.currentTime = 0 72 | this.running = false 73 | } 74 | 75 | /** 76 | * Sets Timeline time 77 | * @param {number} time 78 | */ 79 | Timeline.prototype.seek = function (time) { 80 | this.changed = 0 81 | this.currentTime = time 82 | } 83 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vendor specific stuff 3 | */ 4 | 5 | var requestAnimationFrame = root.requestAnimationFrame, 6 | cancelAnimationFrame = root.cancelAnimationFrame, 7 | vendors = ['moz', 'webkit', 'ms'] 8 | 9 | for (var i = 0; i < vendors.length && !requestAnimationFrame; i++) { 10 | requestAnimationFrame = root[vendors[i] + 'RequestAnimationFrame'] 11 | cancelAnimationFrame = root[vendors[i] + 'CancelAnimationFrame'] 12 | || root[vendors[i] + 'CancelRequestAnimationFrame'] 13 | } 14 | 15 | var prefix = ([].slice.call(getComputedStyle(document.documentElement, null)) 16 | .join('').match(/(-(moz|webkit|ms)-)transform/) || [])[1], 17 | transformProperty = getProperty('transform'), 18 | animationProperty = getProperty('animation'), 19 | fixTick 20 | 21 | function getProperty(name) { 22 | return prefix ? prefix + name : name 23 | } 24 | 25 | var performance = root.performance && root.performance.now ? root.performance : Date 26 | 27 | requestAnimationFrame(function(tick) { 28 | fixTick = tick > 1e12 != performance.now() > 1e12 29 | }) 30 | 31 | function merge(obj) { 32 | var i = 1 33 | while (i < arguments.length) { 34 | var source = arguments[i++] 35 | for (var property in source) { 36 | if (Array.isArray(source[property])) { 37 | for (var j = 0; j < source[property].length; ++j) { 38 | var value = source[property][j] 39 | if (value) { 40 | obj[property][j] = value 41 | } 42 | } 43 | } else { 44 | obj[property] = source[property] 45 | } 46 | } 47 | } 48 | return obj 49 | } 50 | -------------------------------------------------------------------------------- /src/world.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates new world and start frame loop 3 | * @constructor 4 | */ 5 | function World() { 6 | EventEmitter.call(this) 7 | this.items = [] 8 | this.frame = null 9 | this.run() 10 | } 11 | 12 | World.prototype = Object.create(EventEmitter.prototype) 13 | World.prototype.constructor = World 14 | 15 | /** 16 | * Starts new frame loop 17 | */ 18 | World.prototype.run = function () { 19 | var self = this 20 | 21 | this.frame = requestAnimationFrame(update) 22 | 23 | function update(tick) { 24 | if (fixTick) { 25 | tick = performance.now() 26 | } 27 | self.update(tick) 28 | self.frame = requestAnimationFrame(update) 29 | } 30 | } 31 | 32 | /** 33 | * Update the World on frame 34 | * @param {number} tick 35 | */ 36 | World.prototype.update = function (tick) { 37 | for (var i = 0; i < this.items.length; ++i) { 38 | this.items[i].update(tick) 39 | } 40 | } 41 | 42 | /** 43 | * Adds node to the animated world 44 | * @param {HTMLElement} node 45 | * @param {number=} mass 46 | * @param {number=} viscosity 47 | * @return {Item} 48 | */ 49 | World.prototype.add = function (node, mass, viscosity, edge) { 50 | var item 51 | if (mass) { 52 | item = new Particle(node, mass, viscosity, edge) 53 | } else { 54 | item = new Item(node) 55 | } 56 | this.items.push(item) 57 | return item 58 | } 59 | 60 | /** 61 | * Cancels next frame 62 | */ 63 | World.prototype.cancel = function () { 64 | this.frame && cancelAnimationFrame(this.frame) 65 | this.frame = 0 66 | } 67 | 68 | /** 69 | * Stops the World 70 | */ 71 | World.prototype.stop = function () { 72 | this.cancel() 73 | for (var i = 0; i < this.items.length; ++i) { 74 | this.items[i].stop() 75 | } 76 | } 77 | 78 | /** 79 | * Pauses all animations 80 | */ 81 | World.prototype.pause = function () { 82 | this.cancel() 83 | for (var i = 0; i < this.items.length; ++i) { 84 | this.items[i].pause() 85 | } 86 | } 87 | 88 | /** 89 | * Resumes all animations 90 | */ 91 | World.prototype.resume = function () { 92 | for (var i = 0; i < this.items.length; ++i) { 93 | this.items[i].resume() 94 | } 95 | this.run() 96 | } 97 | -------------------------------------------------------------------------------- /test/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Jqery plugin tests 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Matrix tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 102 | 103 | 104 | --------------------------------------------------------------------------------