├── .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 | --------------------------------------------------------------------------------