├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE.txt
├── README.md
├── demo
├── app.js
├── index.html
└── webpack.config.dev.js
├── karma.conf.js
├── lib
└── bundle.js
├── package.json
├── src
├── Animation.js
├── CSSProperty.js
├── Transition.js
├── easings.js
├── index.js
├── index.spec.js
├── utils.js
└── utils.spec.js
├── test
└── test.html
├── webpack.config.dev.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "optional": ["runtime"],
4 | "env": {
5 | "production": {
6 | "optional": []
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf # doesn't work yet
7 | charset = utf-8
8 | trim_trailing_whitespace = true # doesn't work yet
9 | insert_final_newline = true # doesn't work yet
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | node_modules/*
3 | webpack.*
4 | karma.*
5 | demo/webpack.*
6 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "ecmaFeatures": {
8 | "classes": true,
9 | "jsx": true,
10 | "modules": true
11 | },
12 | "plugins": [
13 | "react"
14 | ],
15 | "rules": {
16 | "quotes": [2, "single"],
17 | "eol-last": [0],
18 | "no-mixed-requires": [0],
19 | "no-underscore-dangle": [0],
20 | "space-after-keywords": [1, "always"],
21 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
22 | "react/jsx-uses-react": 2,
23 | "react/jsx-no-undef": 2,
24 | "react/self-closing-comp": 1,
25 | "react/wrap-multilines": 1,
26 | "react/jsx-quotes": [1, "single", "avoid-escape"],
27 | "react/jsx-uses-vars": 2,
28 | "react/react-in-jsx-scope": 2,
29 | "react/no-did-mount-set-state": 2,
30 | "react/no-did-update-set-state": 2,
31 | "react/jsx-curly-spacing": [2, "always"]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### SublimeText ###
2 | *.sublime-workspace
3 |
4 | ### OSX ###
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 | Icon
9 |
10 | # Thumbnails
11 | ._*
12 |
13 | # Files that might appear on external disk
14 | .Spotlight-V100
15 | .Trashes
16 |
17 | ### Windows ###
18 | # Windows image file caches
19 | Thumbs.db
20 | ehthumbs.db
21 |
22 | # Folder config file
23 | Desktop.ini
24 |
25 | # Recycle Bin used on file shares
26 | $RECYCLE.BIN/
27 |
28 | # App specific
29 |
30 | coverage
31 | node_modules
32 | bower_components
33 | .tmp
34 | npm-debug.log
35 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Cruft
2 | *.sublime-workspace
3 | .DS_Store
4 | .AppleDouble
5 | .LSOverride
6 | Icon
7 | ._*
8 | .Spotlight-V100
9 | .Trashes
10 | Thumbs.db
11 | ehthumbs.db
12 | Desktop.ini
13 | $RECYCLE.BIN/
14 | .tmp
15 | npm-debug.log
16 |
17 | # Code / build
18 | coverage
19 | node_modules
20 | bower_components
21 | demo
22 | test
23 | karma*
24 | webpack*
25 | .eslint*
26 | .editor*
27 | .travis*
28 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Formidable Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Move it
2 |
3 | 
4 |
5 | ### Description
6 | Javascript animation and transition utility. Uses CSS under the hood. Useful for React performant animations.
7 |
8 | ### Features
9 | - Supports animations and transitions with the same API
10 | - Animations/transitions definitions are in plain javascript. This means you can create functions that create animations.
11 | - Cleans up everything on completion
12 | - Automatically prefixed
13 | - Supports only setting `from` or `to`
14 |
15 | ### Installation
16 | ```
17 | npm i --save moveit
18 | ```
19 |
20 | ### Demo
21 | `npm start` then visit http://127.0.0.1:3000
22 |
23 | ### Usage
24 | ```js
25 | import { transition, animation } from 'moveit';
26 |
27 | transition(node, definition, override?, callback?);
28 | ```
29 | `definition` is an object with `keyframes` and standard CSS properties for
30 | animation / transition. `keyframes` takes percentages (or `from` and `to`) as
31 | keys and CSS maps as values.
32 |
33 | Possible properties :
34 | - `delay`
35 | - `duration`
36 | - `ease`
37 |
38 | Animation only :
39 | - `iterationCount`
40 | - `direction`
41 | - `fillMode`
42 |
43 | ### Easing
44 | You can pass a string corresponding to one of these [easings](https://github.com/jide/moveit/blob/master/src/easings.js), or use a custom function using CSS `cubic-bezier()` syntax.
45 |
46 | ### Example
47 | ```js
48 | import { transition } from 'moveit';
49 |
50 | const definition = {
51 | keyframes: {
52 | from: {
53 | opacity: '0'
54 | },
55 | to: {
56 | opacity: '1'
57 | }
58 | },
59 | ease: 'ease-in',
60 | duration: '1s'
61 | };
62 |
63 | transition(node, definition);
64 | ```
65 |
66 | ### You can use transition or animation
67 | API is the same, but with animation you can add intermediate keyframes.
68 | ```js
69 | import { animation } from 'moveit';
70 |
71 | const definition = {
72 | keyframes: {
73 | from: {
74 | opacity: '0'
75 | },
76 | '50%': {
77 | opacity: '.3'
78 | }
79 | to: {
80 | opacity: '1'
81 | }
82 | },
83 | ease: 'ease-in',
84 | duration: '1s'
85 | };
86 |
87 | animation(node, definition);
88 | ```
89 |
90 | ### Callback on end
91 | ```js
92 | transition(node, definition, () => console.log('done !'));
93 | ```
94 |
95 | ### Override definition
96 | ```js
97 | const override = {
98 | ease: 'ease-out'
99 | };
100 |
101 | transition(node, definition, override);
102 | ```
103 |
104 | ### With React
105 | ```js
106 | // You could import this from an animations.js file, pass it through props...
107 | const openMenu = props => {
108 | return {
109 | keyframes: {
110 | to: {
111 | transform: `translate3d(${props.left}px, 0, 0)`
112 | }
113 | },
114 | ease: 'ease-out-cubic',
115 | duration: '1s'
116 | };
117 | };
118 |
119 | class Menu extends Component {
120 | handleClick() {
121 | transition(React.findDOMNode(this.refs.animated), openMenu({ left: this.props.menuWidth - window.innerWidth }));
122 | }
123 |
124 | render() {
125 | return (
126 |
127 |
128 |
129 | );
130 | }
131 | }
132 | ```
133 |
--------------------------------------------------------------------------------
/demo/app.js:
--------------------------------------------------------------------------------
1 | import { transition } from '../src';
2 |
3 | let node = document.createElement('div');
4 | node.style.background = 'red';
5 | node.style.width = '30px';
6 | node.style.height = '30px';
7 |
8 | document.body.appendChild(node);
9 |
10 | let definition = {
11 | keyframes: {
12 | from: {
13 | opacity: 0,
14 | transform: 'scale(0)'
15 | },
16 | to: {
17 | opacity: 1,
18 | transform: 'scale(1)'
19 | }
20 | },
21 | ease: 'ease-in',
22 | duration: '5s'
23 | };
24 |
25 | transition(node, definition, () => console.log('done'));
26 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 | Demo
10 |
11 |
12 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/demo/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | /*globals __dirname:false */
2 | "use strict";
3 |
4 | var webpack = require("webpack");
5 |
6 | module.exports = {
7 | devServer: {
8 | contentBase: __dirname,
9 | noInfo: false
10 | },
11 | output: {
12 | path: __dirname,
13 | filename: "main.js"
14 | },
15 | cache: true,
16 | devtool: "source-map",
17 | entry: {
18 | app: ["./demo/app.js"]
19 | },
20 | stats: {
21 | colors: true,
22 | reasons: true
23 | },
24 | resolve: {
25 | extensions: ["", ".js"]
26 | },
27 | module: {
28 | loaders: [
29 | {
30 | test: /\.js$/,
31 | exclude: [/node_modules/],
32 | loaders: ["babel-loader?optional[]=runtime&stage=0"]
33 | },
34 | {
35 | test: /\.json$/,
36 | loaders: ['json']
37 | }
38 | ]
39 | },
40 | plugins: [
41 | new webpack.NoErrorsPlugin()
42 | ]
43 | };
44 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 |
3 | module.exports = function(config) {
4 | config.set({
5 | browsers: ['Chrome'],
6 | files: [
7 | 'src/**/*.spec.*'
8 | ],
9 | frameworks: ['jasmine'],
10 | reporters: ["spec"],
11 | preprocessors: {
12 | 'src/**/*.spec.*': ['webpack']
13 | },
14 | webpack: {
15 | module: {
16 | loaders: [
17 | {
18 | test: /\.js$/,
19 | exclude: [/node_modules/],
20 | loader: "babel-loader?optional[]=runtime&stage=0"
21 | }
22 | ]
23 | },
24 | node: {
25 | fs: 'empty'
26 | },
27 | watch: true,
28 | plugins: [new webpack.SourceMapDevToolPlugin("[file].map")]
29 | },
30 | webpackServer: {
31 | noInfo: true
32 | }
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/lib/bundle.js:
--------------------------------------------------------------------------------
1 | !function(t,e){for(var n in e)t[n]=e[n]}(exports,function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return t[r].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t,e){if("undefined"==typeof e||null===e)return null;for(var n=arguments.length,r=Array(n>2?n-2:0),i=2;n>i;i++)r[i-2]=arguments[i];var o=s.getArguments.apply(void 0,[t].concat(r)),u=a(o,2),f=u[0],l=u[1],v="transition"===t?new c["default"](e,f):new d["default"](e,f);return v.start(l),v}function i(t){for(var e=arguments.length,n=Array(e>1?e-1:0),i=1;e>i;i++)n[i-1]=arguments[i];return r.apply(void 0,["transition",t].concat(n))}function o(t){for(var e=arguments.length,n=Array(e>1?e-1:0),i=1;e>i;i++)n[i-1]=arguments[i];return r.apply(void 0,["animation",t].concat(n))}var a=n(31)["default"],u=n(7)["default"];Object.defineProperty(e,"__esModule",{value:!0}),e.transition=i,e.animation=o;var s=n(6),f=n(26),c=u(f),l=n(24),d=u(l)},function(t,e){var n=t.exports={version:"1.2.1"};"number"==typeof __e&&(__e=n)},function(t,e,n){var r=n(44)("wks"),i=n(9).Symbol;t.exports=function(t){return r[t]||(r[t]=i&&i[t]||(i||n(50))("Symbol."+t))}},function(t,e){t.exports={}},function(t,e,n){var r=n(5),i=n(19);t.exports=n(46)?function(t,e,n){return r.setDesc(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e){var n=Object;t.exports={create:n.create,getProto:n.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:n.getOwnPropertyDescriptor,setDesc:n.defineProperty,setDescs:n.defineProperties,getKeys:n.keys,getNames:n.getOwnPropertyNames,getSymbols:n.getOwnPropertySymbols,each:[].forEach}},function(t,e,n){"use strict";function r(t,e){var n=[];for(var r in e){var i=e[r],o=[];for(var a in i){var u=a.replace(/([A-Z])/g,"-$1").toLowerCase(),s="number"==typeof i[a]?i[a]+"px":i[a];o.push(u+":"+s+";")}n.push(r+"{"+o.join("")+"}")}var f=n.join(""),c=b["default"].dash("animation").replace("animation","")+"keyframes";return"@"+c+" "+t+" {"+f+"}"}function i(t,e){var n=N.sheet.insertRule(r(t,e),N.sheet.cssRules.length);return N.sheet.rules[n]}function o(t){for(var e=0;e2?i-2:0),a=2;i>a;a++)o[a-2]=arguments[a];1===o.length?"function"==typeof o[0]?n=o[0]:r=o[0]:(r=o[0],n=o[1]),e=d(t,e),r=d(t,r);for(var u in r)e[u]=r[u];return[e,n]}var y=n(10)["default"],h=n(7)["default"];Object.defineProperty(e,"__esModule",{value:!0}),e.getAnimationText=r,e.insertAnimation=i,e.getKeyframesRuleIndex=o,e.applyStyle=a,e.removeTransition=u,e.removeAnimation=s,e.removeRule=f,e.addListener=c,e.removeListener=l,e.getNormalizedDefinition=d,e.getTransition=v,e.getArguments=p;var m=n(23),b=h(m),x=n(27),g=h(x),_=n(25),N=document.createElement("style");N.type="text/css",document.head.appendChild(N)},function(t,e){"use strict";e["default"]=function(t){return t&&t.__esModule?t:{"default":t}},e.__esModule=!0},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e){var n="undefined",r=t.exports=typeof window!=n&&window.Math==Math?window:typeof self!=n&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,e,n){t.exports={"default":n(32),__esModule:!0}},function(t,e){"use strict";e["default"]=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},e.__esModule=!0},function(t,e,n){"use strict";var r=n(29)["default"];e["default"]=function(){function t(t,e){for(var n=0;n=e.length?{value:void 0,done:!0}:(t=r(e,n),this._i+=t.length,{value:t,done:!1})})},function(t,e,n){n(55);var r=n(3);r.NodeList=r.HTMLCollection=r.Array},function(t,e){t.exports=require("vendor-prefix")},function(t,e,n){"use strict";var r=n(12)["default"],i=n(11)["default"],o=n(7)["default"];Object.defineProperty(e,"__esModule",{value:!0});var a=n(23),u=o(a),s=n(6),f=0,c=function(){function t(e,n){i(this,t),this.type="animation",this.DOMNode=e,this.definition=n}return r(t,[{key:"start",value:function(t){var e=this;f++;var n="animator_"+f,r=s.insertAnimation(n,this.definition.keyframes),i=function c(n){n&&n.target!==e.DOMNode||(s.removeListener(e.type,e.DOMNode,c),s.removeAnimation(e.DOMNode),s.removeRule(r),"function"==typeof t&&t())},o={};o[u["default"].dash("animation-name")]=n;for(var a in this.definition)-1!==a.indexOf("animation-")&&(o[a]=this.definition[a]);return s.applyStyle(this.DOMNode,o),"0ms"===this.definition.duration?void i():void s.addListener(this.type,this.DOMNode,i)}}]),t}();e["default"]=c,t.exports=e["default"]},function(t,e,n){"use strict";function r(t,e){return t+e.charAt(0).toUpperCase()+e.substring(1)}var i=n(30)["default"];Object.defineProperty(e,"__esModule",{value:!0});var o,a;void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend?e.TRANSITIONEND_EVENT=o=["webkitTransitionEnd","transitionend"]:e.TRANSITIONEND_EVENT=o="transitionend",void 0===window.onanimationend&&void 0!==window.onwebkitanimationend?e.ANIMATIONEND_EVENT=a=["webkitAnimationEnd","animationend"]:e.ANIMATIONEND_EVENT=a="animationend";var u={animationIterationCount:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,stopOpacity:!0,strokeDashoffset:!0,strokeOpacity:!0,strokeWidth:!0},s=["Webkit","ms","Moz","O"];i(u).forEach(function(t){s.forEach(function(e){u[r(e,t)]=u[t]})}),e.isUnitlessNumber=u,e.TRANSITIONEND_EVENT=o,e.ANIMATIONEND_EVENT=a},function(t,e,n){"use strict";var r=n(12)["default"],i=n(11)["default"];Object.defineProperty(e,"__esModule",{value:!0});var o=n(6),a=function(){function t(e,n){i(this,t),this.type="transition",this.DOMNode=e,this.definition=n,this.transition=o.getTransition(n)}return r(t,[{key:"start",value:function(t){var e=this;this.transition.keyframes.from&&(o.applyStyle(this.DOMNode,this.transition.keyframes.from),this.DOMNode.offsetWidth+1);var n=function a(n){n&&n.target!==e.DOMNode||(o.removeListener(e.type,e.DOMNode,a),o.removeTransition(e.DOMNode),"function"==typeof t&&t())},r={};for(var i in this.transition)-1!==i.indexOf("transition-")&&(r[i]=this.transition[i]);for(var i in this.transition.keyframes.to)r[i]=this.transition.keyframes.to[i];return o.applyStyle(this.DOMNode,r),"0ms"===this.definition["transition-duration"]?void n():void o.addListener(this.type,this.DOMNode,n)}}]),t}();e["default"]=a,t.exports=e["default"]},function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n={linear:"linear",ease:"ease","ease-in":"ease-in","ease-out":"ease-out","ease-in-out":"ease-in-out","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)"};e["default"]=n,t.exports=e["default"]},function(t,e,n){t.exports={"default":n(33),__esModule:!0}},function(t,e,n){t.exports={"default":n(34),__esModule:!0}},function(t,e,n){t.exports={"default":n(35),__esModule:!0}},function(t,e,n){"use strict";var r=n(10)["default"],i=n(28)["default"];e["default"]=function(){function t(t,e){var n=[],i=!0,o=!1,a=void 0;try{for(var u,s=r(t);!(i=(u=s.next()).done)&&(n.push(u.value),!e||n.length!==e);i=!0);}catch(f){o=!0,a=f}finally{try{!i&&s["return"]&&s["return"]()}finally{if(o)throw a}}return n}return function(e,n){if(Array.isArray(e))return e;if(i(Object(e)))return t(e,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),e.__esModule=!0},function(t,e,n){n(22),n(21),t.exports=n(53)},function(t,e,n){n(22),n(21),t.exports=n(54)},function(t,e,n){var r=n(5);t.exports=function(t,e,n){return r.setDesc(t,e,n)}},function(t,e,n){n(56),t.exports=n(1).Object.keys},function(t,e,n){var r=n(38);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,e,n){var r=n(14);t.exports=0 in Object("z")?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e,n){"use strict";var r=n(5),i={};n(4)(i,n(2)("iterator"),function(){return this}),t.exports=function(t,e,o){t.prototype=r.create(i,{next:n(19)(1,o)}),n(20)(t,e+" Iterator")}},function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},function(t,e){t.exports=!0},function(t,e,n){t.exports=function(t,e){var r=n(15),i=(n(1).Object||{})[t]||Object[t],o={};o[t]=e(i),r(r.S+r.F*n(16)(function(){i(1)}),"Object",o)}},function(t,e,n){t.exports=n(4)},function(t,e,n){var r=n(9),i="__core-js_shared__",o=r[i]||(r[i]={});t.exports=function(t){return o[t]||(o[t]={})}},function(t,e,n){var r=n(47),i=n(8);t.exports=function(t){return function(e,n){var o,a,u=String(i(e)),s=r(n),f=u.length;return 0>s||s>=f?t?"":void 0:(o=u.charCodeAt(s),55296>o||o>56319||s+1===f||(a=u.charCodeAt(s+1))<56320||a>57343?t?u.charAt(s):o:t?u.slice(s,s+2):(o-55296<<10)+(a-56320)+65536)}}},function(t,e,n){t.exports=!n(16)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e,n){var r=n(37),i=n(8);t.exports=function(t){return r(i(t))}},function(t,e,n){var r=n(8);t.exports=function(t){return Object(r(t))}},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e){t.exports=function(){}},function(t,e,n){var r=n(13),i=n(2)("iterator"),o=n(3);t.exports=n(1).getIteratorMethod=function(t){return void 0!=t?t[i]||t["@@iterator"]||o[r(t)]:void 0}},function(t,e,n){var r=n(36),i=n(52);t.exports=n(1).getIterator=function(t){var e=i(t);if("function"!=typeof e)throw TypeError(t+" is not iterable!");return r(e.call(t))}},function(t,e,n){var r=n(13),i=n(2)("iterator"),o=n(3);t.exports=n(1).isIterable=function(t){var e=Object(t);return i in e||"@@iterator"in e||o.hasOwnProperty(r(e))}},function(t,e,n){"use strict";var r=n(51),i=n(40),o=n(3),a=n(48);n(18)(Array,"Array",function(t,e){this._t=a(t),this._i=0,this._k=e},function(){var t=this._t,e=this._k,n=this._i++;return!t||n>=t.length?(this._t=void 0,i(1)):"keys"==e?i(0,n):"values"==e?i(0,t[n]):i(0,[n,t[n]])},"values"),o.Arguments=o.Array,r("keys"),r("values"),r("entries")},function(t,e,n){var r=n(49);n(42)("keys",function(t){return function(e){return t(r(e))}})}]));
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "moveit",
3 | "version": "0.0.12",
4 | "description": "Move it",
5 | "main": "lib/bundle.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/jide/moveit.git"
9 | },
10 | "author": "Julien De Luca",
11 | "license": "MIT",
12 | "bugs": {
13 | "url": "https://github.com/jide/moveit/issues"
14 | },
15 | "homepage": "https://github.com/jide/moveit",
16 | "scripts": {
17 | "clean": "rimraf lib",
18 | "build": "npm run clean && ./node_modules/webpack/bin/webpack.js -p",
19 | "server-dev": "webpack-dev-server --port 3000 --config demo/webpack.config.dev.js --colors --content-base demo",
20 | "start": "npm run server-dev",
21 | "test": "node node_modules/karma/bin/karma start karma.conf.js",
22 | "test:watch": "./node_modules/karma/bin/karma start karma.conf.js --no-single-run"
23 | },
24 | "devDependencies": {
25 | "babel": "5.8.23",
26 | "babel-eslint": "4.0.5",
27 | "babel-loader": "5.3.2",
28 | "babel-runtime": "5.8.20",
29 | "eslint": "^1.8.0",
30 | "eslint-config-defaults": "7.0.1",
31 | "eslint-ecma-features": "1.0.0",
32 | "eslint-plugin-filenames": "0.1.2",
33 | "eslint-plugin-react": "2.7.0",
34 | "jasmine": "^2.3.2",
35 | "karma": ">=0.13.2 < 1",
36 | "karma-chrome-launcher": "~0.1.5",
37 | "karma-jasmine": "^0.3.6",
38 | "karma-spec-reporter": "~0.0.16",
39 | "karma-webpack": "1.7.0",
40 | "rimraf": "^2.4.3",
41 | "webpack": "1.12.2",
42 | "webpack-dev-server": "^1.10.0"
43 | },
44 | "dependencies": {
45 | "vendor-prefix": "^0.1.0"
46 | },
47 | "keywords": [
48 | "animation",
49 | "transition",
50 | "css",
51 | "react"
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/src/Animation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import prefix from 'vendor-prefix';
4 | import { applyStyle, insertAnimation, removeRule, removeAnimation, addListener, removeListener } from './utils';
5 |
6 | var uniqueID = 0;
7 |
8 | export default class Animation {
9 |
10 | constructor(DOMNode, definition) {
11 | this.type = 'animation';
12 | this.DOMNode = DOMNode;
13 | this.definition = definition;
14 | }
15 |
16 | start(onEnd) {
17 | uniqueID++;
18 |
19 | let animationName = `animator_${uniqueID}`;
20 | let cssRule = insertAnimation(animationName, this.definition.keyframes);
21 |
22 | let callback = (event) => {
23 | if (!event || event.target === this.DOMNode) {
24 | removeListener(this.type, this.DOMNode, callback);
25 | removeAnimation(this.DOMNode);
26 | removeRule(cssRule);
27 |
28 | if (typeof onEnd === 'function') {
29 | onEnd();
30 | }
31 | }
32 | };
33 |
34 | let style = {};
35 |
36 | style[prefix.dash('animation-name')] = animationName;
37 |
38 | for (let property in this.definition) {
39 | if (property.indexOf('animation-') !== -1) {
40 | style[property] = this.definition[property];
41 | }
42 | }
43 |
44 | applyStyle(this.DOMNode, style);
45 |
46 | if (this.definition.duration === '0ms') {
47 | callback();
48 | return;
49 | }
50 | else {
51 | addListener(this.type, this.DOMNode, callback);
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/CSSProperty.js:
--------------------------------------------------------------------------------
1 | // Borrowed from https://github.com/angular/angular.js/blob/0fc58516f4e92a46f6d445421c1f04ff9729c549/src/ngAnimate/animateCss.js
2 | var TRANSITIONEND_EVENT;
3 | var ANIMATIONEND_EVENT;
4 |
5 | if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
6 | TRANSITIONEND_EVENT = ['webkitTransitionEnd', 'transitionend'];
7 | }
8 | else {
9 | TRANSITIONEND_EVENT = 'transitionend';
10 | }
11 |
12 | if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
13 | ANIMATIONEND_EVENT = ['webkitAnimationEnd', 'animationend'];
14 | }
15 | else {
16 | ANIMATIONEND_EVENT = 'animationend';
17 | }
18 |
19 | // Borrowed from React.
20 | var isUnitlessNumber = {
21 | animationIterationCount: true,
22 | boxFlex: true,
23 | boxFlexGroup: true,
24 | boxOrdinalGroup: true,
25 | columnCount: true,
26 | flex: true,
27 | flexGrow: true,
28 | flexPositive: true,
29 | flexShrink: true,
30 | flexNegative: true,
31 | flexOrder: true,
32 | fontWeight: true,
33 | lineClamp: true,
34 | lineHeight: true,
35 | opacity: true,
36 | order: true,
37 | orphans: true,
38 | tabSize: true,
39 | widows: true,
40 | zIndex: true,
41 | zoom: true,
42 |
43 | // SVG-related properties
44 | fillOpacity: true,
45 | stopOpacity: true,
46 | strokeDashoffset: true,
47 | strokeOpacity: true,
48 | strokeWidth: true,
49 | };
50 |
51 | function prefixKey(prefix, key) {
52 | return prefix + key.charAt(0).toUpperCase() + key.substring(1);
53 | }
54 |
55 | var prefixes = ['Webkit', 'ms', 'Moz', 'O'];
56 |
57 | Object.keys(isUnitlessNumber).forEach(function(prop) {
58 | prefixes.forEach(function(prefix) {
59 | isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
60 | });
61 | });
62 |
63 | export { isUnitlessNumber, TRANSITIONEND_EVENT, ANIMATIONEND_EVENT };
64 |
--------------------------------------------------------------------------------
/src/Transition.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { getTransition, applyStyle, removeTransition, addListener, removeListener } from './utils';
4 |
5 | export default class Transition {
6 |
7 | constructor(DOMNode, definition) {
8 | this.type = 'transition';
9 | this.DOMNode = DOMNode;
10 | this.definition = definition;
11 | this.transition = getTransition(definition);
12 | }
13 |
14 | start(onEnd) {
15 | if (this.transition.keyframes.from) {
16 | applyStyle(this.DOMNode, this.transition.keyframes.from);
17 |
18 | // Trigger a repaint.
19 | let width = this.DOMNode.offsetWidth + 1; // jshint ignore:line
20 | }
21 |
22 | let callback = (event) => {
23 | if (!event || event.target === this.DOMNode) {
24 | removeListener(this.type, this.DOMNode, callback);
25 | removeTransition(this.DOMNode);
26 |
27 | if (typeof onEnd === 'function') {
28 | onEnd();
29 | }
30 | }
31 | };
32 |
33 | let style = {};
34 |
35 | for (let property in this.transition) {
36 | if (property.indexOf('transition-') !== -1) {
37 | style[property] = this.transition[property];
38 | }
39 | }
40 |
41 | for (let property in this.transition.keyframes.to) {
42 | style[property] = this.transition.keyframes.to[property];
43 | }
44 |
45 | applyStyle(this.DOMNode, style);
46 |
47 | if (this.definition['transition-duration'] === '0ms') {
48 | callback();
49 | return;
50 | }
51 | else {
52 | addListener(this.type, this.DOMNode, callback);
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/easings.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const easings = {
4 | linear: 'linear',
5 | ease: 'ease',
6 | 'ease-in': 'ease-in',
7 | 'ease-out': 'ease-out',
8 | 'ease-in-out': 'ease-in-out',
9 | 'ease-in-quad': 'cubic-bezier(0.550, 0.085, 0.680, 0.530)',
10 | 'ease-in-cubic': 'cubic-bezier(0.550, 0.055, 0.675, 0.190)',
11 | 'ease-in-quart': 'cubic-bezier(0.895, 0.030, 0.685, 0.220)',
12 | 'ease-in-quint': 'cubic-bezier(0.755, 0.050, 0.855, 0.060)',
13 | 'ease-in-sine': 'cubic-bezier(0.470, 0.000, 0.745, 0.715)',
14 | 'ease-in-expo': 'cubic-bezier(0.950, 0.050, 0.795, 0.035)',
15 | 'ease-in-circ': 'cubic-bezier(0.600, 0.040, 0.980, 0.335)',
16 | 'ease-in-back': 'cubic-bezier(0.600, -0.280, 0.735, 0.045)',
17 | 'ease-out-quad': 'cubic-bezier(0.250, 0.460, 0.450, 0.940)',
18 | 'ease-out-cubic': 'cubic-bezier(0.215, 0.610, 0.355, 1.000)',
19 | 'ease-out-quart': 'cubic-bezier(0.165, 0.840, 0.440, 1.000)',
20 | 'ease-out-quint': 'cubic-bezier(0.230, 1.000, 0.320, 1.000)',
21 | 'ease-out-sine': 'cubic-bezier(0.390, 0.575, 0.565, 1.000)',
22 | 'ease-out-expo': 'cubic-bezier(0.190, 1.000, 0.220, 1.000)',
23 | 'ease-out-circ': 'cubic-bezier(0.075, 0.820, 0.165, 1.000)',
24 | 'ease-out-back': 'cubic-bezier(0.175, 0.885, 0.320, 1.275)',
25 | 'ease-in-out-quad': 'cubic-bezier(0.455, 0.030, 0.515, 0.955)',
26 | 'ease-in-out-cubic': 'cubic-bezier(0.645, 0.045, 0.355, 1.000)',
27 | 'ease-in-out-quart': 'cubic-bezier(0.770, 0.000, 0.175, 1.000)',
28 | 'ease-in-out-quint': 'cubic-bezier(0.860, 0.000, 0.070, 1.000)',
29 | 'ease-in-out-sine': 'cubic-bezier(0.445, 0.050, 0.550, 0.950)',
30 | 'ease-in-out-expo': 'cubic-bezier(1.000, 0.000, 0.000, 1.000)',
31 | 'ease-in-out-circ': 'cubic-bezier(0.785, 0.135, 0.150, 0.860)',
32 | 'ease-in-out-back': 'cubic-bezier(0.680, -0.550, 0.265, 1.550)'
33 | };
34 |
35 | export default easings;
36 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { getArguments } from './utils';
4 | import Transition from './Transition';
5 | import Animation from './Animation';
6 |
7 | function init(type, DOMNode, ...rest) {
8 | if (typeof DOMNode === 'undefined' || DOMNode === null) {
9 | return null;
10 | }
11 |
12 | let [definition, onEnd] = getArguments(type, ...rest);
13 |
14 | let instance = type === 'transition' ? new Transition(DOMNode, definition) : new Animation(DOMNode, definition);
15 |
16 | instance.start(onEnd);
17 |
18 | return instance;
19 | }
20 |
21 | export function transition(DOMNode, ...rest) {
22 | return init('transition', DOMNode, ...rest);
23 | }
24 |
25 | export function animation(DOMNode, ...rest) {
26 | return init('animation', DOMNode, ...rest);
27 | }
28 |
--------------------------------------------------------------------------------
/src/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global expect */
2 |
3 | 'use strict';
4 |
5 | import { transition, animation } from './index';
6 | import Transition from './Transition';
7 | import Animation from './Animation';
8 |
9 | describe('animata', () => {
10 | describe('return an instance when called with shortcuts', () => {
11 | it('should return a Transition instance when called with transition', done => {
12 | let node = document.createElement('div');
13 | node.style.background = 'red';
14 | node.style.width = '30px';
15 | node.style.height = '30px';
16 | node.style.position = 'fixed';
17 | node.style.bottom = 0;
18 | node.style.right = 0;
19 |
20 | document.body.appendChild(node);
21 |
22 | let definition = {
23 | keyframes: {
24 | from: {
25 | opacity: '0'
26 | },
27 | to: {
28 | opacity: '1'
29 | }
30 | },
31 | ease: 'ease-in',
32 | duration: '1s'
33 | };
34 |
35 | let callback = function() {
36 | callbackSpy();
37 | expect(callbackSpy).toHaveBeenCalled();
38 | done();
39 | }
40 |
41 | let callbackSpy = jasmine.createSpy('callback');
42 |
43 | let instance = transition(node, definition, () => callback());
44 |
45 | expect(instance instanceof Transition).toBeTruthy();
46 | });
47 |
48 | it('should return an Animation instance when called with animation', done => {
49 | let node = document.createElement('div');
50 | node.style.background = 'blue';
51 | node.style.width = '30px';
52 | node.style.height = '30px';
53 | node.style.position = 'fixed';
54 | node.style.bottom = 0;
55 | node.style.right = '30px';
56 |
57 | document.body.appendChild(node);
58 |
59 | let definition = {
60 | keyframes: {
61 | from: {
62 | opacity: '0'
63 | },
64 | to: {
65 | opacity: '1'
66 | }
67 | },
68 | ease: 'ease-in',
69 | duration: '1s'
70 | };
71 |
72 | let callback = function() {
73 | callbackSpy();
74 | expect(callbackSpy).toHaveBeenCalled();
75 | done();
76 | }
77 |
78 | let callbackSpy = jasmine.createSpy('callback');
79 |
80 | let instance = animation(node, definition, () => callback());
81 |
82 | expect(instance instanceof Animation).toBeTruthy();
83 | });
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import prefix from 'vendor-prefix';
4 | import easings from './easings.js';
5 |
6 | import { isUnitlessNumber, TRANSITIONEND_EVENT, ANIMATIONEND_EVENT } from './CSSProperty';
7 |
8 | var DOMStyle = document.createElement('style');
9 | DOMStyle.type = 'text/css';
10 | document.head.appendChild(DOMStyle);
11 |
12 | export function getAnimationText(name, keyframes) {
13 | let list = [];
14 |
15 | for (let step in keyframes) {
16 | let rules = keyframes[step];
17 |
18 | let style = [];
19 | for (let property in rules) {
20 | let CSSProperty = property.replace(/([A-Z])/g, '-$1').toLowerCase();
21 | let value = typeof rules[property] === 'number' ? rules[property] + 'px' : rules[property];
22 | style.push(`${CSSProperty}:${value};`);
23 | }
24 |
25 | list.push(`${step}{${style.join('')}}`);
26 | }
27 |
28 | let cssRuleText = list.join('');
29 | let keyframesRule = prefix.dash('animation').replace('animation', '') + 'keyframes';
30 |
31 | return `@${keyframesRule} ${name} {${cssRuleText}}`;
32 | }
33 |
34 | export function insertAnimation(animationName, animation) {
35 | let cssRuleIndex = DOMStyle.sheet.insertRule(getAnimationText(animationName, animation), DOMStyle.sheet.cssRules.length);
36 | return DOMStyle.sheet.rules[cssRuleIndex];
37 | }
38 |
39 | export function getKeyframesRuleIndex(rule) {
40 | for (var i = 0; i < DOMStyle.sheet.cssRules.length; ++i) {
41 | if (DOMStyle.sheet.cssRules[i] === rule) {
42 | return i;
43 | }
44 | }
45 |
46 | return null;
47 | }
48 |
49 | export function applyStyle(DOMNode, style) {
50 | for (let property in style) {
51 | DOMNode.style[property] = style[property];
52 | }
53 | }
54 |
55 | export function removeTransition(DOMNode) {
56 | let properties = ['transition-delay', 'transition-duration', 'transition-property', 'transition-timing-function'];
57 |
58 | for (let property of properties) {
59 | DOMNode.style.removeProperty(prefix.dash(property));
60 | }
61 | }
62 |
63 | export function removeAnimation(DOMNode) {
64 | let properties = ['animation-name', 'animation-duration', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction', 'animation-fill-mode'];
65 |
66 | for (let property of properties) {
67 | DOMNode.style.removeProperty(prefix.dash(property));
68 | }
69 | }
70 |
71 | export function removeRule(cssRule) {
72 | DOMStyle.sheet.deleteRule(getKeyframesRuleIndex(cssRule));
73 | }
74 |
75 | export function addListener(eventName, DOMNode, callback) {
76 | if (eventName === 'transition') {
77 | eventName = TRANSITIONEND_EVENT;
78 | }
79 | else {
80 | eventName = ANIMATIONEND_EVENT;
81 | }
82 |
83 | if (Array.isArray(eventName)) {
84 | eventName.forEach(eventName => DOMNode.addEventListener(eventName, callback, false));
85 | }
86 | else {
87 | DOMNode.addEventListener(eventName, callback, false);
88 | }
89 | }
90 |
91 | export function removeListener(eventName, DOMNode, callback) {
92 | if (eventName === 'transition') {
93 | eventName = TRANSITIONEND_EVENT;
94 | }
95 | else {
96 | eventName = ANIMATIONEND_EVENT;
97 | }
98 |
99 | if (Array.isArray(eventName)) {
100 | eventName.forEach(eventName => DOMNode.removeEventListener(eventName, callback));
101 | }
102 | else {
103 | DOMNode.removeEventListener(eventName, callback);
104 | }
105 | }
106 |
107 | export function getNormalizedDefinition(type, definition) {
108 | let newDefinition = {};
109 |
110 | for (let property in definition) {
111 | if (property === 'keyframes') {
112 | newDefinition.keyframes = {};
113 |
114 | for (let i in definition.keyframes) {
115 | let step = i === '0%' || i === 0 || i === '0' ? 'from' : i;
116 | step = step === '100%' ? 'to' : step;
117 |
118 | newDefinition.keyframes[step] = {};
119 |
120 | for (let ruleProperty in definition.keyframes[i]) {
121 | let value = definition.keyframes[i][ruleProperty];
122 | newDefinition.keyframes[step][prefix.dash(ruleProperty)] = typeof value === 'number' && !isUnitlessNumber[ruleProperty] ? value + 'px' : value;
123 | }
124 | }
125 | }
126 | else {
127 | property = property.replace(/([A-Z])/g, '-$1').toLowerCase();
128 | let value = definition[property];
129 |
130 | if (property === 'ease' || property === 'timing-function') {
131 | property = 'timing-function';
132 | value = easings[value];
133 | }
134 | else if (property === 'duration' && typeof value === 'number') {
135 | value = `${value}ms`;
136 | }
137 |
138 | newDefinition[prefix.dash(type + '-' + property)] = value;
139 | }
140 | }
141 |
142 | return newDefinition;
143 | }
144 |
145 | export function getTransition(definition) {
146 | let transition = { keyframes: { from: {}, to: {} } };
147 | let transitionableProps = [];
148 |
149 | for (let step in definition.keyframes) {
150 | for (let CSSProperty in definition.keyframes[step]) {
151 | transition.keyframes[step][CSSProperty] = definition.keyframes[step][CSSProperty];
152 |
153 | if (transitionableProps.indexOf(CSSProperty) === -1) {
154 | transitionableProps.push(CSSProperty);
155 | }
156 | }
157 | }
158 |
159 | // Each transition property should have a group per CSS property that is
160 | // transitionable. E.g: transition-duration: 1s, 1s;
161 | for (let property in definition) {
162 | if (property.indexOf('transition-') !== -1) {
163 | let properties = [];
164 |
165 | for (let i = 0; i < transitionableProps.length; i++) {
166 | properties[i] = definition[property];
167 | }
168 |
169 | transition[property] = properties.join(',');
170 | }
171 | }
172 |
173 | transition[prefix.dash('transition-property')] = transitionableProps.join(',');
174 |
175 | return transition;
176 | }
177 |
178 | export function getArguments(type, definition, ...rest) {
179 | let onEnd;
180 | let overrides = {};
181 |
182 | // Build args.
183 | if (rest.length === 1) {
184 | if (typeof rest[0] === 'function') {
185 | onEnd = rest[0];
186 | }
187 | else {
188 | overrides = rest[0];
189 | }
190 | }
191 | else {
192 | overrides = rest[0];
193 | onEnd = rest[1];
194 | }
195 |
196 | definition = getNormalizedDefinition(type, definition);
197 | overrides = getNormalizedDefinition(type, overrides);
198 |
199 | for (let i in overrides) {
200 | definition[i] = overrides[i];
201 | }
202 |
203 | return [definition, onEnd];
204 | }
205 |
--------------------------------------------------------------------------------
/src/utils.spec.js:
--------------------------------------------------------------------------------
1 | /* global expect */
2 |
3 | 'use strict';
4 |
5 | import { getAnimationText, applyStyle, insertAnimation, removeTransition, getNormalizedDefinition, getArguments, getTransition } from './utils';
6 | import easings from './easings.js';
7 |
8 | describe('utils', () => {
9 |
10 | describe('getAnimationText', () => {
11 | it('should return the correct keyframes string', () => {
12 | let keyframes = {
13 | from: {
14 | background: 'blue'
15 | },
16 | '50%': {
17 | background: 'yellow'
18 | },
19 | to: {
20 | background: 'green'
21 | }
22 | };
23 |
24 | let text = /@(.*)keyframes test {from{background:blue;}50%{background:yellow;}to{background:green;}}/;
25 |
26 | expect(getAnimationText('test', keyframes)).toMatch(text);
27 | });
28 | });
29 |
30 | describe('insertAnimation', () => {
31 | it('should insert an animation rule', () => {
32 | let keyframes = {
33 | from: {
34 | width: '30px'
35 | },
36 | to: {
37 | width: '200px'
38 | }
39 | };
40 |
41 | let rule = insertAnimation('test', keyframes);
42 |
43 | let rules = document.styleSheets[document.styleSheets.length - 1].rules;
44 | let toRule = rules[rules.length - 1];
45 |
46 | expect(rule).toEqual(toRule);
47 | });
48 | });
49 |
50 | describe('applyStyle', () => {
51 | it('should apply style to node without style', () => {
52 | let node = document.createElement('div');
53 | let style = {
54 | background: 'blue',
55 | color: 'red',
56 | width: '100px'
57 | };
58 |
59 | applyStyle(node, style);
60 |
61 | for (let property in style) {
62 | expect(node.style[property]).toEqual(style[property]);
63 | }
64 | });
65 |
66 | it('should apply style to node with style', () => {
67 | let node = document.createElement('div');
68 | node.style.height = '20px';
69 | node.style.background = 'yellow';
70 |
71 | let style = {
72 | background: 'blue',
73 | color: 'red',
74 | width: '100px'
75 | };
76 |
77 | applyStyle(node, style);
78 |
79 | for (let property in style) {
80 | expect(node.style[property]).toEqual(style[property]);
81 | }
82 |
83 | expect(node.style.height).toEqual('20px');
84 | });
85 | });
86 |
87 | describe('removeTransition', () => {
88 | it('should remove transition styles from node', () => {
89 | let node = document.createElement('div');
90 |
91 | let style = {
92 | background: 'blue',
93 | color: 'red',
94 | 'transition-duration': '1s',
95 | 'transition-property': 'opacity',
96 | 'transition-timing-function': 'linear'
97 | };
98 |
99 | applyStyle(node, style);
100 |
101 | removeTransition(node, style);
102 |
103 | expect(node.style.background).toEqual('blue');
104 | expect(node.style.color).toEqual('red');
105 | expect(node.style['transition-duration']).toEqual('');
106 | expect(node.style['transition-property']).toEqual('');
107 | expect(node.style['transition-timing-function']).toEqual('');
108 | });
109 | });
110 |
111 | describe('getNormalizedDefinition', () => {
112 | it('should return normalized definition', () => {
113 | let easing = easings['ease-in-sine'];
114 |
115 | let definition = {
116 | keyframes: {
117 | 0: {
118 | opacity: '0',
119 | width: 10
120 | },
121 | '100%': {
122 | opacity: '1',
123 | width: 100
124 | }
125 | },
126 | ease: 'ease-in-sine',
127 | duration: '1s'
128 | };
129 |
130 | let normalizedDefinition = {
131 | keyframes: {
132 | from: {
133 | opacity: '0',
134 | width: '10px'
135 | },
136 | to: {
137 | opacity: '1',
138 | width: '100px'
139 | }
140 | },
141 | 'transition-timing-function': easing,
142 | 'transition-duration': '1s'
143 | };
144 |
145 | expect(getNormalizedDefinition('transition', definition)).toEqual(normalizedDefinition);
146 | });
147 | });
148 |
149 | describe('getTransition', () => {
150 | it('should return a correct transition object', () => {
151 | let definition = {
152 | keyframes: {
153 | from: {
154 | opacity: '0',
155 | height: '100px'
156 | },
157 | to: {
158 | opacity: '1',
159 | width: '100px',
160 | height: '100px'
161 | }
162 | },
163 | 'transition-timing-function': 'ease-in',
164 | 'transition-duration': '1s'
165 | };
166 |
167 | let transition = {
168 | keyframes: {
169 | from: {
170 | opacity: '0',
171 | height: '100px'
172 | },
173 | to: {
174 | opacity: '1',
175 | width: '100px',
176 | height: '100px'
177 | }
178 | },
179 | 'transition-timing-function': 'ease-in,ease-in,ease-in',
180 | 'transition-duration': '1s,1s,1s',
181 | 'transition-property': 'opacity,height,width'
182 | };
183 |
184 | expect(getTransition(definition)).toEqual(transition);
185 | });
186 | });
187 |
188 | describe('getArguments', () => {
189 | it('should return correct arguments', () => {
190 | let definition = {
191 | keyframes: {
192 | '0%': {
193 | opacity: '0',
194 | height: 100
195 | },
196 | to: {
197 | opacity: '1',
198 | width: 100,
199 | height: 100
200 | }
201 | },
202 | ease: 'ease-in',
203 | duration: '1s'
204 | };
205 |
206 | let overrides = {
207 | keyframes: {
208 | from: {
209 | opacity: '1',
210 | width: 30
211 | },
212 | to: {
213 | height: 200
214 | }
215 | },
216 | duration: '10s',
217 | ease: 'ease-out'
218 | };
219 |
220 | let onEnd = () => console.log('done');
221 |
222 | let toDefinition = {
223 | keyframes: {
224 | from: {
225 | opacity: '1',
226 | width: '30px'
227 | },
228 | to: {
229 | height: '200px'
230 | }
231 | },
232 | 'transition-timing-function': 'ease-out',
233 | 'transition-duration': '10s'
234 | };
235 |
236 | let args = [toDefinition, onEnd];
237 |
238 | expect(getArguments('transition', definition, overrides, onEnd)).toEqual(args);
239 | });
240 | });
241 |
242 | });
243 |
--------------------------------------------------------------------------------
/test/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Frontend Tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var webpack = require("webpack");
4 | var config = require("./webpack.config");
5 |
6 | // **WARNING**: Mutates base configuration.
7 | // We do this because lodash isn't available in `production` mode.
8 | config.plugins = [
9 | new webpack.SourceMapDevToolPlugin("[file].map")
10 | ];
11 |
12 | // Export mutated base.
13 | module.exports = config;
14 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var webpack = require("webpack");
4 | var path = require("path");
5 |
6 | module.exports = {
7 | cache: true,
8 | entry: path.join(__dirname, "src/index.js"),
9 | output: {
10 | path: path.join(__dirname, "lib"),
11 | filename: "bundle.js",
12 | libraryTarget: "commonjs"
13 | },
14 | resolve: {
15 | extensions: ["", ".js", "jsx"]
16 | },
17 | externals: [
18 | "vendor-prefix"
19 | ],
20 | module: {
21 | loaders: [
22 | {
23 | test: /\.jsx?$/,
24 | exclude: [/node_modules/],
25 | loader: "babel-loader?stage=0"
26 | }
27 | ]
28 | },
29 | plugins: [
30 | new webpack.optimize.DedupePlugin(),
31 | new webpack.optimize.UglifyJsPlugin({
32 | compress: {
33 | warnings: false
34 | }
35 | }),
36 | new webpack.DefinePlugin({
37 | // Signal production, so that webpack removes non-production code that
38 | // is in condtionals like: `if (process.env.NODE_ENV === "production")`
39 | "process.env.NODE_ENV": JSON.stringify("production")
40 | })
41 | ]
42 | };
43 |
--------------------------------------------------------------------------------