33 |
36 |
this.foo = c}
38 | className="foo"
39 | style={{ transform: this.state.transform }}
40 | />
41 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/css/Root.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | margin: 0;
5 | }
6 |
7 | * {
8 | box-sizing: border-box;
9 | }
10 |
11 | .app-root {
12 | display: flex;
13 | flex-flow: column nowrap;
14 | justify-content: center;
15 | align-items: center;
16 | width: 100%;
17 | height: 100%;
18 | background: hsl(0, 0%, 90%);
19 | }
20 |
21 | nav.controls {
22 | position: absolute;
23 | top: 0;
24 | left: 0;
25 | right: 0;
26 | padding: 20px 10px;
27 | border-bottom: 1px solid hsl(0, 0%, 80%);
28 | background: hsl(0, 0%, 100%);
29 | font: 12px/1.2 monospace;
30 | color: hsl(210, 15%, 20%)
31 | }
32 |
33 | span.selector { color: hsl(0, 0%, 10%); }
34 | span.property { color: hsl(0, 100%, 39%); }
35 | span.value { }
36 |
37 | nav.controls input {
38 | border: 1px solid transparent;
39 | padding: 4px 2px;
40 | margin: -4px -2px;
41 | border-radius: 3px;
42 | font: 12px/1.2 monospace;
43 | background: none;
44 | color: currentColor;
45 | }
46 |
47 | nav.controls input:focus {
48 | color: black;
49 | border: 1px solid hsl(0, 0%, 70%);
50 | background: white;
51 | box-shadow: 0 2px 1px -1px hsla(0, 0%, 10%, 0.05);
52 | outline: none;
53 | }
54 |
55 |
56 | .app-root canvas.overlay {
57 | position: absolute;
58 | top: 0;
59 | left: 0;
60 | right: 0;
61 | bottom: 0;
62 | display: block;
63 | width: 100%;
64 | height: 100%;
65 | pointer-events: none;
66 | }
67 |
68 |
69 | div.foo {
70 | width: 180px;
71 | height: 300px;
72 | border-radius: 4px;
73 | background: white;
74 | box-shadow: 0 2px 3px 1px hsla(0, 0%, 0%, 0.2);
75 | }
76 |
--------------------------------------------------------------------------------
/index.dev.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 | import Root from './containers/Root';
5 |
6 |
7 | render(
8 |
9 |
10 | ,
11 | document.body
12 | );
13 |
14 |
15 | if(module.hot)
16 | {
17 | module.hot.accept('./containers/Root', () => {
18 | var NewRoot = require('./containers/Root').default;
19 | render(
20 |
21 |
22 | ,
23 | document.body
24 | );
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Web Animations API Tool – Editor
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/index.prod.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import Root from './containers/Root';
4 |
5 |
6 | render(
7 |
,
8 | document.body
9 | );
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "waapi-transforms",
3 | "version": "0.0.0",
4 | "description": "Web Animations API Transforms Visualisation Experiment",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node server.js",
8 | "build": "NODE_ENV=production webpack -p"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/waapi/transforms.git"
13 | },
14 | "keywords": [
15 | "waapi",
16 | "web animations",
17 | "animations",
18 | "transforms",
19 | "sandbox",
20 | "tool"
21 | ],
22 | "license": "MIT",
23 | "dependencies": {
24 | "classnames": "^2.1.2",
25 | "gl-mat4": "^1.1.4",
26 | "gl-quat": "^1.0.0",
27 | "gl-vec3": "^1.0.3",
28 | "localforage": "^1.4.2",
29 | "mat4-decompose": "^1.0.4",
30 | "react": "^15.0.1",
31 | "react-codemirror": "^0.2.6",
32 | "react-dom": "^15.0.1",
33 | "react-hot-loader": "^3.0.0-beta.1",
34 | "react-input-autosize": "^1.0.0",
35 | "react-notification-center": "^1.2.1"
36 | },
37 | "devDependencies": {
38 | "babel-cli": "^6.3.17",
39 | "babel-core": "^6.3.17",
40 | "babel-loader": "^6.2.0",
41 | "babel-preset-es2015-loose": "^6.1.3",
42 | "babel-preset-react": "6.3.13",
43 | "babel-preset-stage-0": "^6.3.13",
44 | "file-loader": "^0.8.5",
45 | "node-libs-browser": "^0.5.2",
46 | "raw-loader": "^0.5.1",
47 | "style-loader": "^0.12.3",
48 | "url-loader": "^0.5.7",
49 | "webpack": "^1.9.11",
50 | "webpack-dev-server": "^1.9.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/polyfills/canvas.js:
--------------------------------------------------------------------------------
1 | CanvasRenderingContext2D.prototype.dashedLine = function (x0, y0, x1, y1, dash, gap) {
2 | if(dash == undefined) dash = 2;
3 | if(gap == undefined) gap = 2;
4 | var dashgap = dash + gap;
5 |
6 | var dx = x1 - x0;
7 | var dy = y1 - y0;
8 | var dist = Math.sqrt(dx * dx + dy * dy);
9 | var nx = dx / dist;
10 | var ny = dy / dist;
11 | var dashX = nx * dash;
12 | var dashY = ny * dash;
13 | var gapX = nx * gap;
14 | var gapY = ny * gap;
15 |
16 | var approx = dist / dashgap;
17 | var total = Math.floor(approx);
18 | var expose = approx - total;
19 |
20 | x0 += nx * dashgap * expose * 0.5;
21 | y0 += ny * dashgap * expose * 0.5;
22 |
23 | x0 -= gapX * 0.5;
24 | y0 -= gapY * 0.5;
25 |
26 | while (total-->0)
27 | {
28 | x0 += gapX;
29 | y0 += gapY;
30 | this.moveTo(x0, y0);
31 | x0 += dashX;
32 | y0 += dashY;
33 | this.lineTo(x0, y0);
34 | }
35 | };
36 |
37 | CanvasRenderingContext2D.prototype.roundedRect = function (x, y, w, h, r) {
38 | this.moveTo(x + r, y);
39 | this.arcTo(x + w, y, x + w, y + r, r);
40 | this.arcTo(x + w, y + h, x + w - r, y + h, r);
41 | this.arcTo(x, y + h, x, y + h - r, r);
42 | this.arcTo(x, y, x + r, y, r);
43 | };
44 |
--------------------------------------------------------------------------------
/polyfills/css-parser.js:
--------------------------------------------------------------------------------
1 |
2 | class CSSParser {
3 | static transform(string) {
4 | if(!string) return null;
5 |
6 | var number = '[+-]?(?:\\d+\\.\\d+|\\.?\\d+)(?:e\\d+)?';
7 |
8 | var length = `${number}(px|em|ex|rem|vw|vh|vmin|vmax|in|cm|mm|pt|pc)`;
9 | var percent = `${number}(%)`;
10 | var dimension = `${number}(%|px|em|ex|rem|vw|vh|vmin|vmax|in|cm|mm|pt|pc)`;
11 | var angle = `${number}(deg|rad|turn)`;
12 |
13 | var translate = new RegExp(`\\s*(${dimension}|0)\\s*(?:,\\s*(${dimension}|0)\\s*)?`);
14 | var translateXYZ = new RegExp(`\\s*(${dimension}|0)\\s*`);
15 | var translate3d = new RegExp(`\\s*(${dimension}|0)\\s*,\\s*(${dimension}|0)\\s*,\\s*(${dimension}|0)\\s*`);
16 |
17 | var rotate = new RegExp(`\\s*(${angle}|0)\\s*`);
18 | var rotate3d = new RegExp(`\\s*${number}\\s*,\\s*${number}\\s*,\\s*${number}\\s*,\\s*(${angle}|0)\\s*`);
19 |
20 | var skew = new RegExp(`\\s*(${angle}|0)\\s*(?:,\\s*(${angle}|0)\\s*)?`);
21 | var skewXY = new RegExp(`\\s*(${angle}|0)\\s*`);
22 |
23 | var perspective = new RegExp(`\\s*(${length}|0)\\s*`);
24 |
25 | var functions = /\b((?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|Z|3d)?)\((.*?)\)/g;
26 |
27 | var results = [];
28 | var result;
29 | while(result = functions.exec(string))
30 | {
31 | var name = result[1];
32 | var args = result[2];
33 | var values;
34 | switch(name) {
35 | case 'matrix': values = args.split(','); if(values.length !== 6) return null; break;
36 | case 'matrix3d': values = args.split(','); if(values.length !== 16) return null; break;
37 | case 'translate': values = args.match(translate); break;
38 | case 'translateX':
39 | case 'translateY':
40 | case 'translateZ': values = args.match(translateXYZ); break;
41 | case 'translate3d': values = args.match(translate3d); break;
42 | case 'scale':
43 | case 'scaleX':
44 | case 'scaleY':
45 | case 'scaleZ':
46 | case 'scale3d': values = args.split(','); break;
47 | case 'rotate':
48 | case 'rotateX':
49 | case 'rotateY':
50 | case 'rotateZ': values = args.match(rotate); break;
51 | case 'rotate3d': values = args.match(rotate3d); break;
52 | case 'skew': values = args.match(skew); break;
53 | case 'skewX':
54 | case 'skewY': values = args.match(skewXY); break;
55 | case 'perspective': values = args.match(perspective); break;
56 | default: return null; // An invalid or unimplemented transform function? (E.g. skewZ, perspectiveX)
57 | }
58 |
59 | // Parse values into floats
60 | if(/^(matrix|scale)/.test(name)) // unit-less
61 | {
62 | values = values.map(value => parseFloat(value));
63 | }
64 |
65 | else // Some functions have tuples of [value, unit]
66 | {
67 | values = (values => {
68 | var temp = [];
69 | for(var iter = 0, total = values.length; iter < total; iter += 2)
70 | {
71 | var value = values[iter];
72 | var unit = values[iter + 1];
73 | if(typeof value === 'string')
74 | {
75 | var pair = [parseFloat(value), unit];
76 | temp.push(pair);
77 | }
78 | else return temp;
79 | }
80 | return temp;
81 | })(values.slice(1));
82 |
83 | switch(name) {
84 | case 'translate':
85 | values[0].push('width');
86 | if(values[1]) values[1].push('height');
87 | break;
88 | case 'translateX': values[0].push('width'); break;
89 | case 'translateY': values[0].push('height'); break;
90 | case 'translate3d':
91 | values[0].push('width');
92 | values[1].push('height');
93 | break;
94 | }
95 | }
96 |
97 | results.push({
98 | name,
99 | values
100 | });
101 | }
102 |
103 | return results;
104 | }
105 |
106 | static value(input) {
107 | if(!input || !input.length) return null;
108 | var cardinal = input[2] || 'width';
109 |
110 | if(typeof input === 'string')
111 | {
112 | input = input.match(/([+-]?\d+?(?:\.\d+)?)(px|em|rem|ex|%|vw|vh|vmin|vmax|in|cm|mm|pt|pc|deg|rad|turn)?/);
113 | var value = parseFloat(input[1]);
114 | var unit = input[2];
115 | }
116 |
117 | else
118 | {
119 | var [value, unit] = input;
120 | }
121 |
122 | switch (unit) {
123 | case 'em': return value * CSSParser.base.fontSize;
124 | case 'rem': return value * parseFloat(getComputedStyle(document.documentElement).fontSize);
125 | case 'ch': return value * CSSParser.base.fontSize * 0.5;
126 | case 'ex': return value * CSSParser.base.fontSize * 0.45;
127 | case '%': return value * CSSParser.base[cardinal] / 100;
128 | case 'vw': return value * window.innerWidth / 100;
129 | case 'vh': return value * window.innerHeight / 100;
130 | case 'vmin': return value * Math.min(window.innerWidth, window.innerHeight) / 100;
131 | case 'vmax': return value * Math.max(window.innerWidth, window.innerHeight) / 100;
132 | case 'in': return value * 72;
133 | case 'cm': return value / 2.54 * 96;
134 | case 'mm': return value / 2.54 * 96 / 10;
135 | case 'pt': return value * 96 / 72;
136 | case 'pc': return value * CSSParser.base[cardinal];
137 | case 'deg': return value * 0.017453292519943295;
138 | case 'rad': return value;
139 | case 'turn': return value * 6.283185307179586;
140 | default: return value;
141 | }
142 | }
143 | }
144 |
145 | CSSParser.base = { width: 1, height: 1, fontSize: 16 };
146 |
147 | export default CSSParser;
148 |
--------------------------------------------------------------------------------
/polyfills/vector.js:
--------------------------------------------------------------------------------
1 |
2 | export default class Vector {
3 | constructor(a, b) {
4 | if(a instanceof Vector) { this.x = a.x; this.y = a.y }
5 | else { this.x = a || 0; this.y = b || 0 }
6 | }
7 |
8 | add(a, b) {
9 | if(a instanceof Vector) { this.x += a.x; this.y += a.y }
10 | else { this.x += a; this.y += b }
11 | return this;
12 | }
13 |
14 | sub(a, b) {
15 | if(a instanceof Vector) { this.x -= a.x; this.y -= a.y }
16 | else { this.x -= a; this.y -= b }
17 | return this;
18 | }
19 |
20 | scl(a, b) {
21 | if(a instanceof Vector) { this.x *= a.x; this.y *= a.y }
22 | else if(typeof a === 'number' && typeof b === 'number') { this.x *= a; this.y *= b }
23 | else { this.x *= a; this.y *= a }
24 | return this;
25 | }
26 |
27 | div(a, b) {
28 | if(a instanceof Vector) { this.x /= a.x; this.y /= a.y }
29 | else if(typeof a === 'number' && typeof b === 'number') { this.x /= a; this.y /= b }
30 | else { this.x /= a; this.y /= a }
31 | return this;
32 | }
33 |
34 | rot(a) {
35 | var sin = Math.sin(a), cos = Math.cos(a);
36 | var x = this.x * cos - this.y * sin;
37 | var y = this.x * sin + this.y * cos;
38 | this.x = x; this.y = y;
39 | return this;
40 | }
41 |
42 | get mag() { return Math.sqrt(this.x * this.x + this.y * this.y) }
43 | get clone() { return new Vector(this) }
44 | get perp() { return new Vector(this.y, this.x) }
45 | get angle() { return Math.atan2(this.y, this.x) }
46 | }
47 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('./webpack.config');
4 |
5 | config.devtool = 'eval';
6 | config.entry.splice(config.entry.indexOf('./index.prod'), 1, './index.dev');
7 | config.entry.unshift(
8 | 'webpack-dev-server/client?http://localhost:6756',
9 | 'webpack/hot/only-dev-server',
10 | 'react-hot-loader/patch'
11 | );
12 | config.plugins.push(
13 | new webpack.HotModuleReplacementPlugin()
14 | );
15 |
16 | new WebpackDevServer(webpack(config), {
17 | publicPath: config.output.publicPath,
18 | hot: true,
19 | historyApiFallback: true,
20 | stats: {
21 | colors: true
22 | }
23 | }).listen(6756, 'localhost', function (err) {
24 | if(err) console.log(err);
25 |
26 | console.log('Listening at localhost:6756');
27 | });
28 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: [
6 | './index.prod'
7 | ],
8 | output: {
9 | path: path.join(__dirname, 'dist'),
10 | filename: 'bundle.js',
11 | publicPath: '/'
12 | },
13 | plugins: [
14 | new webpack.DefinePlugin({
15 | 'process.env': {
16 | NODE_ENV: JSON.stringify('production')
17 | }
18 | }),
19 | new webpack.optimize.UglifyJsPlugin()
20 | ],
21 | resolve: {
22 | root: [
23 | __dirname
24 | ],
25 | alias: {
26 | 'react': path.join(__dirname, 'node_modules', 'react')
27 | },
28 | extensions: ['', '.js']
29 | },
30 | resolveLoader: {
31 | 'fallback': path.join(__dirname, 'node_modules')
32 | },
33 | watchOptions: {
34 | poll: true,
35 | ignore: /node_modules/
36 | },
37 | module: {
38 | loaders: [{
39 | test: /\.js$/,
40 | loaders: ['babel'],
41 | exclude: /node_modules/,
42 | include: __dirname
43 | }, {
44 | test: /\.css?$/,
45 | loaders: ['style', 'raw'],
46 | include: __dirname
47 | }, {
48 | test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$/,
49 | loader: ['file'],
50 | include: __dirname
51 | }, {
52 | test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$/,
53 | loader: ['url'],
54 | include: __dirname
55 | }]
56 | }
57 | };
58 |
--------------------------------------------------------------------------------