├── .browserslistrc
├── public
├── CNAME
├── image.png
├── icon-42.png
├── book-solid.svg
├── code-solid.svg
├── sliders-h-solid.svg
├── hand-point-up-regular.svg
└── index.html
├── babel.config.js
├── postcss.config.js
├── src
├── shims-vue.d.ts
├── assets
│ └── logo.png
├── main.ts
├── shims-tsx.d.ts
├── App.vue
├── components
│ ├── SvgTarget.vue
│ ├── SvgArrow.vue
│ ├── MatrixCode.vue
│ ├── SvgGrid.vue
│ ├── MatrixTable.vue
│ ├── SvgDragPoint.vue
│ ├── GHeader.vue
│ ├── SvgGraph.vue
│ └── HomeFloatingPanel.vue
├── misc.ts
├── DragHandler.ts
└── pages
│ └── HomePage.vue
├── doc
└── understanding-transform-matrix.gif
├── README.md
├── .editorconfig
├── .gitignore
├── tslint.json
├── package.json
└── tsconfig.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | understanding-transform-matrix.ginpei.info
2 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/public/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginpei/understanding-transform-matrix/HEAD/public/image.png
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue';
3 | export default Vue;
4 | }
5 |
--------------------------------------------------------------------------------
/public/icon-42.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginpei/understanding-transform-matrix/HEAD/public/icon-42.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginpei/understanding-transform-matrix/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/doc/understanding-transform-matrix.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginpei/understanding-transform-matrix/HEAD/doc/understanding-transform-matrix.gif
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 |
4 | Vue.config.productionTip = false;
5 |
6 | new Vue({
7 | render: (h) => h(App),
8 | }).$mount('#app');
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Understanding `transform: matrix()`
2 |
3 | - http://understanding-transform-matrix.ginpei.info/
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_size = 2
6 | indent_style = space
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/src/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue';
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | interface Element extends VNode {}
7 | // tslint:disable no-empty-interface
8 | interface ElementClass extends Vue {}
9 | interface IntrinsicElements {
10 | [elem: string]: any;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "warning",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "linterOptions": {
7 | "exclude": [
8 | "node_modules/**"
9 | ]
10 | },
11 | "rules": {
12 | "quotemark": [true, "single"],
13 | "indent": [true, "spaces", 2],
14 | "interface-name": false,
15 | "ordered-imports": false,
16 | "object-literal-sort-keys": false,
17 | "no-consecutive-blank-lines": false,
18 | "variable-name": [
19 | true,
20 | "ban-keywords",
21 | "check-format",
22 | "allow-leading-underscore"
23 | ]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/public/book-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
33 |
--------------------------------------------------------------------------------
/public/code-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/SvgTarget.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
11 |
12 |
28 |
29 |
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "transform-matrix",
3 | "version": "1.1.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "npm run serve",
7 | "serve": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint",
10 | "release": "npm run lint -s && npm run build -s && push-dir --dir dist --branch gh-pages"
11 | },
12 | "dependencies": {
13 | "vue": "^2.5.17",
14 | "vue-class-component": "^6.0.0",
15 | "vue-property-decorator": "^7.0.0"
16 | },
17 | "devDependencies": {
18 | "@vue/cli-plugin-babel": "^3.1.1",
19 | "@vue/cli-plugin-typescript": "^3.1.1",
20 | "@vue/cli-service": "^3.1.1",
21 | "push-dir": "^0.4.1",
22 | "typescript": "^3.0.0",
23 | "vue-template-compiler": "^2.5.17"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/public/sliders-h-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "experimentalDecorators": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env"
16 | ],
17 | "paths": {
18 | "@/*": [
19 | "src/*"
20 | ]
21 | },
22 | "lib": [
23 | "esnext",
24 | "dom",
25 | "dom.iterable",
26 | "scripthost"
27 | ]
28 | },
29 | "include": [
30 | "src/**/*.ts",
31 | "src/**/*.tsx",
32 | "src/**/*.vue",
33 | "tests/**/*.ts",
34 | "tests/**/*.tsx"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/SvgArrow.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
50 |
--------------------------------------------------------------------------------
/public/hand-point-up-regular.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/misc.ts:
--------------------------------------------------------------------------------
1 | export const noop = () => undefined;
2 |
3 | export enum colors {
4 | i = 'red',
5 | j = 'blue',
6 | translation = 'green',
7 | }
8 |
9 | export interface IPos {
10 | x: number;
11 | y: number;
12 | }
13 |
14 | export const invalidPos: IPos = Object.freeze({ x: NaN, y: NaN });
15 | export const zeroPos: IPos = Object.freeze({ x: 0, y: 0 });
16 |
17 | export const getEventPositions = (event: MouseEvent | TouchEvent) => {
18 | let positions: IPos[];
19 |
20 | if (event instanceof MouseEvent) {
21 | positions = [{ x: event.pageX, y: event.pageY }];
22 | } else {
23 | positions = [...event.touches].map((touch) =>
24 | ({ x: touch.pageX, y: touch.pageY }));
25 | }
26 |
27 | return positions;
28 | };
29 |
30 | export interface IMatrix {
31 | ix: number;
32 | iy: number;
33 | jx: number;
34 | jy: number;
35 | tx: number;
36 | ty: number;
37 | }
38 |
39 | export const mergeMatrix = (m: IMatrix, diff: IMatrix): IMatrix => {
40 | return {
41 | ix: m.ix + diff.ix,
42 | iy: m.iy + diff.iy,
43 | jx: m.jx + diff.jx,
44 | jy: m.jy + diff.jy,
45 | tx: m.tx + diff.tx,
46 | ty: m.ty + diff.ty,
47 | };
48 | };
49 | export const roundMatrix = (m: IMatrix): IMatrix => {
50 | return {
51 | ix: fixMatrixNumber(m.ix),
52 | iy: fixMatrixNumber(m.iy),
53 | jx: fixMatrixNumber(m.jx),
54 | jy: fixMatrixNumber(m.jy),
55 | tx: fixMatrixNumber(m.tx),
56 | ty: fixMatrixNumber(m.ty),
57 | };
58 | };
59 | export const getMatrixStr = (m: IMatrix) => {
60 | const rm = roundMatrix(m);
61 | return `matrix(${[
62 | rm.ix, rm.iy,
63 | rm.jx, rm.jy,
64 | rm.tx, rm.ty,
65 | ].join(', ')})`;
66 | };
67 |
68 | export const fixMatrixNumber = (n: number) => {
69 | return Number(n.toFixed(2));
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/MatrixCode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | .the-target {
5 | transform: matrix({{ ix }},
8 | {{ iy }},
11 | {{ jx }},
14 | {{ jy }},
17 | {{ tx }},
20 | {{ ty }});
23 | }
24 |
25 |
26 |
27 |
28 |
46 |
47 |
64 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Understanding transform:matrix()
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/components/SvgGrid.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
19 |
25 |
31 |
32 |
33 |
34 |
70 |
71 |
80 |
--------------------------------------------------------------------------------
/src/components/MatrixTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | |
6 | (
7 | |
8 | {{ matrix.ix }} |
11 | {{ matrix.jx }} |
14 | {{ matrix.tx }} |
17 |
18 | )
19 | |
20 |
21 |
22 | | {{ matrix.iy }} |
25 | {{ matrix.jy }} |
28 | {{ matrix.ty }} |
31 |
32 |
33 | | 0 |
34 | 0 |
35 | 1 |
36 |
37 |
38 |
39 |
40 |
41 |
52 |
53 |
72 |
--------------------------------------------------------------------------------
/src/components/SvgDragPoint.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 | {{ title }}
8 |
9 |
12 |
13 |
14 |
15 |
71 |
72 |
105 |
--------------------------------------------------------------------------------
/src/components/GHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
44 |
45 |
126 |
--------------------------------------------------------------------------------
/src/DragHandler.ts:
--------------------------------------------------------------------------------
1 | import { getEventPositions, IPos, invalidPos } from './misc';
2 |
3 | export default class DragHandler {
4 | protected _dragging = false;
5 | protected el: HTMLElement | undefined = undefined;
6 | protected startedPos = invalidPos;
7 | protected lastPos = invalidPos;
8 | protected onEnd: ((diff: IPos) => void) | undefined = undefined;
9 | protected onMove: ((diff: IPos) => void) | undefined = undefined;
10 | protected onStart: ((data: { event: MouseEvent | TouchEvent, stop: () => void }) => void) | undefined = undefined;
11 |
12 | public get dragging() {
13 | return this._dragging;
14 | }
15 |
16 | constructor() {
17 | this.onMouseDown = this.onMouseDown.bind(this);
18 | this.onMouseMove = this.onMouseMove.bind(this);
19 | this.onMouseUp = this.onMouseUp.bind(this);
20 | this.onTouchStart = this.onTouchStart.bind(this);
21 | this.onTouchMove = this.onTouchMove.bind(this);
22 | this.onTouchEnd = this.onTouchEnd.bind(this);
23 | }
24 |
25 | public start(options: {
26 | el: HTMLElement,
27 | onEnd?: (diff: IPos) => void,
28 | onMove?: (diff: IPos) => void,
29 | onStart?: (data: { event: MouseEvent | TouchEvent, stop: () => void }) => void,
30 | }) {
31 | this.el = options.el;
32 | this.onEnd = options.onEnd;
33 | this.onMove = options.onMove;
34 | this.onStart = options.onStart;
35 |
36 | this.el.addEventListener('mousedown', this.onMouseDown);
37 | document.addEventListener('mousemove', this.onMouseMove);
38 | document.addEventListener('mouseup', this.onMouseUp);
39 | this.el.addEventListener('touchstart', this.onTouchStart);
40 | document.addEventListener('touchmove', this.onTouchMove);
41 | document.addEventListener('touchend', this.onTouchEnd);
42 | document.addEventListener('touchcancel', this.onTouchEnd);
43 | }
44 |
45 | public stop() {
46 | if (!this.el) {
47 | throw new Error('Unexpected situation: this.el must be set');
48 | }
49 |
50 | this.el.removeEventListener('mousedown', this.onMouseDown);
51 | document.removeEventListener('mousemove', this.onMouseMove);
52 | document.removeEventListener('mouseup', this.onMouseUp);
53 | this.el.removeEventListener('touchstart', this.onTouchStart);
54 | document.removeEventListener('touchmove', this.onTouchMove);
55 | document.removeEventListener('touchend', this.onTouchEnd);
56 | document.removeEventListener('touchcancel', this.onTouchEnd);
57 | }
58 |
59 | public onMouseDown(event: MouseEvent) {
60 | event.preventDefault();
61 |
62 | const [pos] = getEventPositions(event);
63 | this.startDragging(event, pos);
64 | }
65 |
66 | public onMouseMove = (event: MouseEvent) => {
67 | if (this._dragging) {
68 | const [pos] = getEventPositions(event);
69 | this.drag(pos);
70 | }
71 | }
72 |
73 | public onMouseUp = (event: MouseEvent) => {
74 | if (this._dragging) {
75 | const [pos] = getEventPositions(event);
76 | this.endDragging(pos);
77 | }
78 | }
79 |
80 | public onTouchStart(event: TouchEvent) {
81 | if (!this._dragging) {
82 | event.preventDefault();
83 |
84 | const [pos] = getEventPositions(event);
85 | this.startDragging(event, pos);
86 | }
87 | }
88 |
89 | public onTouchMove = (event: TouchEvent) => {
90 | if (this._dragging) {
91 | const [pos] = getEventPositions(event);
92 | this.drag(pos);
93 | }
94 | }
95 |
96 | public onTouchEnd = (event: TouchEvent) => {
97 | if (this._dragging) {
98 | this.endDragging(this.lastPos);
99 | }
100 | }
101 |
102 | protected startDragging(event: MouseEvent | TouchEvent, pos: IPos) {
103 | let stopped = false;
104 | const stop = () => stopped = true;
105 | if (this.onStart) {
106 | this.onStart({ event, stop });
107 | }
108 |
109 | if (stopped) {
110 | return;
111 | }
112 |
113 | this._dragging = true;
114 | this.startedPos = pos;
115 | }
116 |
117 | protected drag(pos: IPos) {
118 | if (!this._dragging) {
119 | return;
120 | }
121 |
122 | const diff: IPos = {
123 | x: pos.x - this.startedPos.x,
124 | y: pos.y - this.startedPos.y,
125 | };
126 |
127 | this.lastPos = pos;
128 |
129 | if (this.onMove) {
130 | this.onMove(diff);
131 | }
132 | }
133 |
134 | protected endDragging(pos: IPos) {
135 | if (!this._dragging) {
136 | return;
137 | }
138 |
139 | const diff: IPos = {
140 | x: pos.x - this.startedPos.x,
141 | y: pos.y - this.startedPos.y,
142 | };
143 |
144 | this.startedPos = invalidPos;
145 | this.lastPos = invalidPos;
146 | this._dragging = false;
147 |
148 | if (this.onEnd) {
149 | this.onEnd(diff);
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/pages/HomePage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
28 |
29 |
35 |
36 |
37 |
38 |
39 |
175 |
176 |
191 |
--------------------------------------------------------------------------------
/src/components/SvgGraph.vue:
--------------------------------------------------------------------------------
1 |
2 |
77 |
78 |
79 |
179 |
180 |
198 |
--------------------------------------------------------------------------------
/src/components/HomeFloatingPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 | Code
10 |
13 |
14 |
15 | Matrix
16 |
17 |
20 |
21 |
22 |
23 | Presets
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | References
32 |
49 |
50 |
51 |
52 |
56 |
57 |
58 |
61 | ()
62 |
63 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
171 |
172 |
247 |
--------------------------------------------------------------------------------