├── .gitignore
├── README.md
├── app.js
├── green.mp4
├── index.html
├── reset.css
├── styles.css
├── video.css
├── video.html
└── video.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # paintjs
2 | Painting Board made with VanillaJS
3 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const canvas = document.getElementById("jsCanvas");
2 | const ctx = canvas.getContext("2d");
3 | const colors = document.getElementsByClassName("jsColor");
4 | const range = document.getElementById("jsRange");
5 | const mode = document.getElementById("jsMode");
6 | const saveBtn = document.getElementById("jsSave");
7 |
8 | const INITIAL_COLOR = "#2c2c2c";
9 | const CANVAS_SIZE = 700;
10 |
11 | canvas.width = CANVAS_SIZE;
12 | canvas.height = CANVAS_SIZE;
13 |
14 | ctx.fillStyle = "white";
15 | ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
16 | ctx.strokeStyle = INITIAL_COLOR;
17 | ctx.fillStyle = INITIAL_COLOR;
18 | ctx.lineWidth = 2.5;
19 |
20 | let painting = false;
21 | let filling = false;
22 |
23 | function stopPainting() {
24 | painting = false;
25 | }
26 |
27 | function startPainting() {
28 | painting = true;
29 | }
30 |
31 | function onMouseMove(event) {
32 | const x = event.offsetX;
33 | const y = event.offsetY;
34 | if (!painting) {
35 | ctx.beginPath();
36 | ctx.moveTo(x, y);
37 | } else {
38 | ctx.lineTo(x, y);
39 | ctx.stroke();
40 | }
41 | }
42 |
43 | function handleColorClick(event) {
44 | const color = event.target.style.backgroundColor;
45 | ctx.strokeStyle = color;
46 | ctx.fillStyle = color;
47 | }
48 |
49 | function handleRangeChange(event) {
50 | const size = event.target.value;
51 | ctx.lineWidth = size;
52 | }
53 |
54 | function handleModeClick() {
55 | if (filling === true) {
56 | filling = false;
57 | mode.innerText = "Fill";
58 | } else {
59 | filling = true;
60 | mode.innerText = "Paint";
61 | }
62 | }
63 |
64 | function handleCanvasClick() {
65 | if (filling) {
66 | ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
67 | }
68 | }
69 |
70 | function handleCM(event) {
71 | event.preventDefault();
72 | }
73 |
74 | function handleSaveClick() {
75 | const image = canvas.toDataURL();
76 | const link = document.createElement("a");
77 | link.href = image;
78 | link.download = "PaintJS[🎨]";
79 | link.click();
80 | }
81 |
82 | if (canvas) {
83 | canvas.addEventListener("mousemove", onMouseMove);
84 | canvas.addEventListener("mousedown", startPainting);
85 | canvas.addEventListener("mouseup", stopPainting);
86 | canvas.addEventListener("mouseleave", stopPainting);
87 | canvas.addEventListener("click", handleCanvasClick);
88 | canvas.addEventListener("contextmenu", handleCM);
89 | }
90 |
91 | Array.from(colors).forEach(color =>
92 | color.addEventListener("click", handleColorClick)
93 | );
94 |
95 | if (range) {
96 | range.addEventListener("input", handleRangeChange);
97 | }
98 |
99 | if (mode) {
100 | mode.addEventListener("click", handleModeClick);
101 | }
102 |
103 | if (saveBtn) {
104 | saveBtn.addEventListener("click", handleSaveClick);
105 | }
106 |
--------------------------------------------------------------------------------
/green.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nomadcoders/paintjs/dcf6cf31cdd6c4c0fcba2b3a04cbd11d5e9c54f4/green.mp4
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | PaintJS
9 |
10 |
11 |
12 |
13 |
14 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
36 |
40 |
44 |
48 |
52 |
56 |
60 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/reset.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | div,
4 | span,
5 | applet,
6 | object,
7 | iframe,
8 | h1,
9 | h2,
10 | h3,
11 | h4,
12 | h5,
13 | h6,
14 | p,
15 | blockquote,
16 | pre,
17 | a,
18 | abbr,
19 | acronym,
20 | address,
21 | big,
22 | cite,
23 | code,
24 | del,
25 | dfn,
26 | em,
27 | img,
28 | ins,
29 | kbd,
30 | q,
31 | s,
32 | samp,
33 | small,
34 | strike,
35 | strong,
36 | sub,
37 | sup,
38 | tt,
39 | var,
40 | b,
41 | u,
42 | i,
43 | center,
44 | dl,
45 | dt,
46 | dd,
47 | ol,
48 | ul,
49 | li,
50 | fieldset,
51 | form,
52 | label,
53 | legend,
54 | table,
55 | caption,
56 | tbody,
57 | tfoot,
58 | thead,
59 | tr,
60 | th,
61 | td,
62 | article,
63 | aside,
64 | canvas,
65 | details,
66 | embed,
67 | figure,
68 | figcaption,
69 | footer,
70 | header,
71 | hgroup,
72 | menu,
73 | nav,
74 | output,
75 | ruby,
76 | section,
77 | summary,
78 | time,
79 | mark,
80 | audio,
81 | video {
82 | margin: 0;
83 | padding: 0;
84 | border: 0;
85 | font-size: 100%;
86 | font: inherit;
87 | vertical-align: baseline;
88 | }
89 | /* HTML5 display-role reset for older browsers */
90 | article,
91 | aside,
92 | details,
93 | figcaption,
94 | figure,
95 | footer,
96 | header,
97 | hgroup,
98 | menu,
99 | nav,
100 | section {
101 | display: block;
102 | }
103 | body {
104 | line-height: 1;
105 | }
106 | ol,
107 | ul {
108 | list-style: none;
109 | }
110 | blockquote,
111 | q {
112 | quotes: none;
113 | }
114 | blockquote:before,
115 | blockquote:after,
116 | q:before,
117 | q:after {
118 | content: "";
119 | content: none;
120 | }
121 | table {
122 | border-collapse: collapse;
123 | border-spacing: 0;
124 | }
125 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | @import "reset.css";
2 | body {
3 | background-color: #f6f9fc;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
5 | Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | padding: 50px 0px;
10 | }
11 |
12 | .canvas {
13 | width: 700px;
14 | height: 700px;
15 | background-color: white;
16 | border-radius: 15px;
17 | box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
18 | }
19 |
20 | .controls {
21 | margin-top: 80px;
22 | display: flex;
23 | flex-direction: column;
24 | align-items: center;
25 | }
26 |
27 | .controls .controls__btns {
28 | margin-bottom: 30px;
29 | }
30 |
31 | .controls__btns button {
32 | all: unset;
33 | cursor: pointer;
34 | background-color: white;
35 | padding: 5px 0px;
36 | width: 80px;
37 | text-align: center;
38 | border-radius: 5px;
39 | box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
40 | border: 2px solid rgba(0, 0, 0, 0.2);
41 | color: rgba(0, 0, 0, 0.7);
42 | text-transform: uppercase;
43 | font-weight: 800;
44 | font-size: 12px;
45 | }
46 |
47 | .controls__btns button:active {
48 | transform: scale(0.98);
49 | }
50 |
51 | .controls .controls__colors {
52 | display: flex;
53 | }
54 |
55 | .controls__colors .controls__color {
56 | width: 50px;
57 | height: 50px;
58 | border-radius: 25px;
59 | cursor: pointer;
60 | box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
61 | }
62 |
63 | .controls .controls__range {
64 | margin-bottom: 30px;
65 | }
66 |
--------------------------------------------------------------------------------
/video.css:
--------------------------------------------------------------------------------
1 | video,
2 | .result {
3 | width: 640px;
4 | height: 360px;
5 | }
6 |
--------------------------------------------------------------------------------
/video.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Video Process
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Source Video
12 |
13 |
14 |
15 |
Result
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/video.js:
--------------------------------------------------------------------------------
1 | const result = document.getElementById("jsResult");
2 | const video = document.getElementById("jsVideo");
3 |
4 | const WIDTH = 640;
5 | const HEIGHT = 360;
6 |
7 | result.width = WIDTH;
8 | result.height = HEIGHT;
9 |
10 | const ctx = result.getContext("2d");
11 |
12 | function setBg() {
13 | result.style.backgroundImage =
14 | 'url("https://scontent-hkg3-2.xx.fbcdn.net/v/t1.0-9/49336100_1885308894915070_8104276389001166848_n.jpg?_nc_cat=107&_nc_ht=scontent-hkg3-2.xx&oh=e6141acb4b22b4909b056c2dc0ef4279&oe=5D3252EB")';
15 | result.style.backgroundPosition = "center -350px";
16 | }
17 |
18 | function handlePlay(event) {
19 | ctx.drawImage(video, 0, 0, WIDTH, HEIGHT);
20 | let frame = ctx.getImageData(0, 0, WIDTH, HEIGHT);
21 | let pxNumber = frame.data.length / 4;
22 | for (let i = 0; i < pxNumber; i++) {
23 | let r = frame.data[i * 4 + 0];
24 | let g = frame.data[i * 4 + 1];
25 | let b = frame.data[i * 4 + 2];
26 | if (r <= 126 && g >= 80 && b <= 80) {
27 | frame.data[i * 4 + 3] = 0;
28 | }
29 | }
30 | ctx.putImageData(frame, 0, 0);
31 | setBg();
32 | setTimeout(handlePlay, 0);
33 | }
34 |
35 | if (video) {
36 | video.addEventListener("play", handlePlay);
37 | }
38 |
--------------------------------------------------------------------------------