├── .gitignore
├── .npmignore
├── bower.json
├── src
├── physics
│ ├── forces
│ │ ├── constant.js
│ │ ├── attraction.js
│ │ └── edge.js
│ ├── verlet.js
│ └── particle.js
├── core.js
├── eventemitter.js
├── utils.js
├── math
│ ├── vector.js
│ └── matrix.js
├── timeline.js
├── world.js
├── animations
│ ├── parallel.js
│ ├── css_animation.js
│ ├── easings.js
│ ├── sequence.js
│ ├── collection.js
│ ├── tween.js
│ └── animation.js
├── css.js
└── item.js
├── README.md
├── example
├── empty.html
├── delay.html
├── delay_css.html
├── custom.html
├── infinite.html
├── infinite_css.html
├── parallel_physics.html
├── physics.html
├── css_animation.html
├── bounce.html
├── bounce_css.html
├── parallel_sequence.html
├── parallel.html
├── parallel_css.html
├── timeline.html
├── css
│ └── main.css
├── cards.html
├── keyboard_mixed.html
└── keyboard.html
├── editor
├── css
│ ├── styles.css
│ └── editor.css
├── bookmarklet.js
├── index.html
└── js
│ └── editor.js
├── package.json
├── jquery.animatic.js
├── test
├── jquery.html
└── test.html
├── LICENSE
├── CHANGELOG.md
├── Makefile
├── animatic.min.js
├── animatic.min.js.map
└── animatic.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*
2 | /src
3 | /test
4 | /Makefile
5 | /bower.json
6 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "animatic",
3 | "version": "1.0.1",
4 | "main": [
5 | "animatic.js"
6 | ],
7 | "ignore": [
8 | ".*",
9 | "src",
10 | "test",
11 | "Makefile",
12 | "package.json"
13 | ],
14 | "dependencies": {}
15 | }
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/
--------------------------------------------------------------------------------
/example/empty.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dry Run
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/editor/css/styles.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | overflow: hidden;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .viewport {
9 | position: absolute;
10 | left: 50%;
11 | top: 50%;
12 | -webkit-perspective: 400px;
13 | -moz-perspective: 400px;
14 | -ms-perspective: 400px;
15 | -o-perspective: 400px;
16 | perspective: 400px;
17 | -webkit-transform: translateZ(-99999px);
18 | -moz-transform: translateZ(-99999px);
19 | -ms-transform: translateZ(-99999px);
20 | -o-transform: translateZ(-99999px);
21 | transform: translateZ(-99999px);
22 | }
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/editor/bookmarklet.js:
--------------------------------------------------------------------------------
1 | (function (node, editor) {
2 | node = document.createElement('link')
3 | node.setAttribute('rel', 'stylesheet')
4 | node.href = 'https://rawgithub.com/lvivski/animatic/master/editor/css/editor.css'
5 | document.head.appendChild(node)
6 |
7 | node = document.createElement('script')
8 | node.onload = function () {
9 | editor = document.createElement('script')
10 | editor.onload = function () {
11 | var selector = prompt('Anima Items Selector')
12 | if (selector) {
13 | animatic.editor(selector)
14 | }
15 | }
16 | editor.src = 'https://rawgithub.com/lvivski/animatic/master/editor/js/editor.js'
17 | document.head.appendChild(editor)
18 | }
19 | node.src = 'https://rawgithub.com/lvivski/animatic/master/animatic.js'
20 | document.head.appendChild(node)
21 | }())
22 |
--------------------------------------------------------------------------------
/example/delay.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Delay
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
30 |
31 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/delay_css.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Delay CSS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
31 |
32 |
--------------------------------------------------------------------------------
/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/infinite.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Infinite
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
34 |
35 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/infinite_css.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Infinite CSS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
35 |
36 |
--------------------------------------------------------------------------------
/editor/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Animator
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/parallel_physics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Parallel
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
37 |
38 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/physics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Physics
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
50 |
51 |
--------------------------------------------------------------------------------
/example/css_animation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Infinite
7 |
8 |
9 |
10 |
11 |
12 |
13 |
36 |
37 |
38 |
47 |
48 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/example/parallel_sequence.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Parallel sequence
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
77 |
78 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/example/timeline.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Timeline
7 |
8 |
9 |
10 |
11 |
12 |
13 |
22 |
23 |
24 |
118 |
119 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/editor/css/editor.css:
--------------------------------------------------------------------------------
1 | .editor {
2 | position: absolute;
3 | left: 0;
4 | top: 0;
5 | width: 100%;
6 | height: 100%;
7 | -webkit-transform: translateZ(99999px);
8 | transform: translateZ(99999px);
9 | font: 16px/1.5 Arial, sans-serif;
10 | z-index: 100;
11 | }
12 |
13 | .editor label {
14 | display: block;
15 | }
16 |
17 | .editor input[type=button] {
18 | -webkit-appearance: none;
19 | -moz-appearance: none;
20 | -ms-appearance: none;
21 | -o-appearance: none;
22 | appearance: none;
23 | font-size: 1em;
24 | background: #fafafa;
25 | border: 1px solid #999;
26 | border-radius: .3em;
27 | cursor: pointer;
28 | }
29 |
30 | .editor input[type=button]:hover {
31 | background: -webkit-linear-gradient(#fff, #dfdfdf);
32 | background: -moz-linear-gradient(#fff, #dfdfdf);
33 | background: -ms-linear-gradient(#fff, #dfdfdf);
34 | background: -o-linear-gradient(#fff, #dfdfdf);
35 | background: linear-gradient(#fff, #dfdfdf);
36 | }
37 |
38 | .editor input[type=button]:active {
39 | background: -webkit-linear-gradient(#dfdfdf, #fff);
40 | background: -moz-linear-gradient(#dfdfdf, #fff);
41 | background: -ms-linear-gradient(#dfdfdf, #fff);
42 | background: -o-linear-gradient(#dfdfdf, #fff);
43 | background: linear-gradient(#dfdfdf, #fff);
44 | }
45 |
46 | .editor label {
47 | display: inline-block;
48 | cursor: pointer;
49 | }
50 |
51 | .editor input[type=text],
52 | .editor .axis i {
53 | border: 1px solid rgba(0, 0, 0, 0);
54 | font-size: 1em;
55 | width: 3.5em;
56 | margin: 0 .1em;
57 | cursor: col-resize;
58 | text-align: center;
59 | }
60 |
61 | .editor input[type=text]:focus {
62 | outline: 0;
63 | border-color: #ccc;
64 | }
65 |
66 | .editor input[type=radio] {
67 | display: block;
68 | font-size: 1em;
69 | margin: 0;
70 | cursor: pointer;
71 | }
72 |
73 | .editor input[type=range] {
74 | width: 100%;
75 | height: 1.6em;
76 | background: #fff;
77 | border: 1px solid #ccc;
78 | margin: 0 0 .2em;
79 | position: relative;
80 | -webkit-appearance: none;
81 | appearance: none;
82 | cursor: col-resize;
83 | vertical-align: middle;
84 | box-sizing: border-box;
85 | }
86 |
87 | .editor input[type=range]::-webkit-slider-thumb {
88 | -webkit-appearance: none;
89 | appearance: none;
90 | height: 2.2em;
91 | width: 5px;
92 | background: #d0a000;
93 | }
94 |
95 | .editor input[type=range]:before {
96 | content: '';
97 | display: block;
98 | position: absolute;
99 | left: 0;
100 | right: 0;
101 | top: 0;
102 | bottom: 0;
103 | pointer-event: 0;
104 | }
105 |
106 | .editor ::-webkit-slider-thumb {
107 | cursor: col-resize;
108 | }
109 |
110 | .editor .panel {
111 | position: absolute;
112 | z-index: 100;
113 | }
114 |
115 | .editor .panel_right {
116 | right: 0;
117 | top: 0;
118 | width: 18.5em;
119 | }
120 |
121 | .editor .panel_timeline {
122 | left: 0;
123 | bottom: 0;
124 | width: 100%;
125 | }
126 |
127 | .editor .panel__bg {
128 | position: absolute;
129 | left: 0;
130 | bottom: 0;
131 | height: 100%;
132 | background: rgba(255, 255, 255, .9);
133 | width: 100%;
134 | z-index: 0;
135 | }
136 |
137 | .editor .panel__controls {
138 | position: relative;
139 | padding: 15px;
140 | }
141 |
142 | .editor .panel_right span {
143 | width: 4.5em;
144 | display: inline-block;
145 | color: #aaa;
146 | }
147 |
148 | .editor .panel_right .axis {
149 | margin-left: 4.5em;
150 | width: auto;
151 | }
152 |
153 | .editor .panel_right .axis i {
154 | display: inline-block;
155 | font-style: normal;
156 | padding: 1px;
157 | cursor: auto;
158 | }
159 |
160 | .editor .panel_right label {
161 | text-align: center;
162 | padding: 0 .4em;
163 | }
164 |
165 | .editor .panel_timeline label {
166 | width: 80%;
167 | position: relative;
168 | }
169 |
170 | .editor .popup {
171 | width: 70%;
172 | height: 70%;
173 | box-shadow: 0 0 50px rgba(0, 0, 0, .2);
174 | position: absolute;
175 | left: 15%;
176 | top: 15%;
177 | display: none;
178 | font: 13px/1.8 Menlo, Monaco, Mono-Space;
179 | background: white;
180 | }
181 |
182 | .editor .popup span {
183 | white-space: pre;
184 | overflow: scroll;
185 | height: 100%;
186 | display: inline-block;
187 | width: 100%;
188 | }
189 |
190 | .editor .popup a {
191 | position: absolute;
192 | left: -1em;
193 | top: -1em;
194 | text-decoration: none;
195 | font-size: 2em;
196 | line-height: 1;
197 | border: 1px solid;
198 | border-radius: 1em;
199 | padding: 0 .25em .1em;
200 | color: inherit;
201 | }
202 |
203 | .editor .toggler {
204 | padding: 10px 0;
205 | text-align: center;
206 | }
207 |
208 | .editor .keyframes {
209 | pointer-events: none;
210 | }
211 |
212 | .editor .keyframes i {
213 | display: inline-block;
214 | height: 100%;
215 | width: 1px;
216 | background: rgba(0, 0, 0, .5);
217 | margin-left: 3px;
218 | position: absolute;
219 | left: 0;
220 | top: 0;
221 | }
--------------------------------------------------------------------------------
/test/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Matrix tests
7 |
8 |
9 |
10 |
11 |
12 |
13 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/example/cards.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Cards
7 |
8 |
9 |
95 |
96 |
97 |
98 |
99 |
189 |
190 |
--------------------------------------------------------------------------------
/example/keyboard_mixed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Keyboard
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
26 |
27 |
28 |
148 |
149 |
--------------------------------------------------------------------------------
/example/keyboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Keyboard
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
26 |
27 |
28 |
156 |
157 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 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")());
--------------------------------------------------------------------------------