├── icons ├── circle.svg ├── triangle.svg ├── rectangle.svg ├── eraser.svg └── brush.svg ├── README.md ├── index.html ├── css └── style.css └── js └── script.js /icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icons/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icons/rectangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icons/eraser.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icons/brush.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Paint Clone with HTML CSS and JavaScript

2 | 3 |

project-image

4 | 5 |

Unlock your creativity and enhance your web development skills with our comprehensive course on building a Paint Clone using HTML CSS and JavaScript. This hands-on course is designed for both beginners and intermediate developers who want to delve into the world of web application development while creating a fun and interactive project.

6 | 7 |

🚀 Demo

8 | 9 | [https://sammi.ac/projects/paint](https://sammi.ac/projects/paint) 10 | 11 | 12 | 13 |

🧐 Features

14 | 15 | Here're some of the project's best features: 16 | 17 | * HTML Canvas Fundamentals 18 | * CSS Styling for a Seamless User Interface 19 | * JavaScript Interactivity 20 | * Drawing Tools and Features 21 | * Undo and Redo Functionality 22 | * Saving and Exporting Artwork 23 | * Responsive Design 24 | 25 | 26 | 27 |

💻 Built with

28 | 29 | Technologies used in the project: 30 | 31 | * HTML 32 | * CSS 33 | * JavaScript 34 | * Canvas 35 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sammi | Paint 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 | 19 |
    20 |
  • 21 | rectangle 22 | To'rtburchak 23 |
  • 24 |
  • 25 | circle 26 | Doira 27 |
  • 28 |
  • 29 | triangle 30 | Uchburchak 31 |
  • 32 |
  • 33 | 34 | 35 |
  • 36 |
37 |
38 |
39 | 40 |
    41 |
  • 42 | brush 43 | Brush 44 |
  • 45 |
  • 46 | eraser 47 | O'chirg'ich 48 |
  • 49 |
  • 50 | 51 |
  • 52 |
53 |
54 |
55 | 56 |
    57 |
  • 58 |
  • 59 |
  • 60 |
  • 61 |
  • 62 | 63 |
  • 64 |
65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap'); 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | font-family: 'Poppins', sans-serif; 7 | } 8 | body { 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | min-height: 100vh; 13 | background: rgb(2, 0, 36); 14 | background: linear-gradient( 15 | 90deg, 16 | rgba(2, 0, 36, 1) 0%, 17 | rgba(9, 9, 121, 1) 35%, 18 | rgba(0, 212, 255, 1) 100% 19 | ); 20 | } 21 | .container { 22 | display: flex; 23 | width: 100%; 24 | gap: 10px; 25 | padding: 10px; 26 | max-width: 1050px; 27 | } 28 | section { 29 | background: #fff; 30 | } 31 | .title { 32 | text-transform: uppercase; 33 | } 34 | .tools-board { 35 | width: 210px; 36 | padding: 15px 22px 0; 37 | } 38 | .tools-board .row { 39 | margin-bottom: 20px; 40 | } 41 | .row .options { 42 | list-style: none; 43 | margin: 10px 0 0 5px; 44 | } 45 | .row .options .option { 46 | display: flex; 47 | cursor: pointer; 48 | align-items: center; 49 | margin-bottom: 10px; 50 | } 51 | .option:is(:hover, .active) img { 52 | filter: invert(17%) sepia(90%) saturate(3000%) hue-rotate(900deg) brightness(100%) contrast(100%); 53 | } 54 | .option :where(span, label) { 55 | color: #5a6168; 56 | cursor: pointer; 57 | padding-left: 10px; 58 | } 59 | .option:is(:hover, .active) :where(span, label) { 60 | color: #4a98f7; 61 | } 62 | .option #fill-color { 63 | cursor: pointer; 64 | height: 14px; 65 | width: 14px; 66 | } 67 | #fill-color:checked ~ label { 68 | color: #4a98f7; 69 | } 70 | .option #size-slider { 71 | width: 100%; 72 | height: 5px; 73 | margin-top: 10px; 74 | } 75 | .colors .options { 76 | display: flex; 77 | justify-content: space-between; 78 | } 79 | .colors .option { 80 | height: 20px; 81 | width: 20px; 82 | border-radius: 50%; 83 | margin-top: 3px; 84 | position: relative; 85 | } 86 | .colors .option:nth-child(1) { 87 | background-color: #fff; 88 | border: 1px solid #bfbfbf; 89 | } 90 | .colors .option:nth-child(2) { 91 | background-color: #000; 92 | } 93 | .colors .option:nth-child(3) { 94 | background-color: #e02020; 95 | } 96 | .colors .option:nth-child(4) { 97 | background-color: #6dd400; 98 | } 99 | .colors .option:nth-child(5) { 100 | background-color: #4a98f7; 101 | } 102 | .colors .option.selected::before { 103 | position: absolute; 104 | content: ''; 105 | top: 50%; 106 | left: 50%; 107 | height: 12px; 108 | width: 12px; 109 | background: inherit; 110 | border-radius: inherit; 111 | border: 2px solid #fff; 112 | transform: translate(-50%, -50%); 113 | } 114 | .colors .option:first-child.selected::before { 115 | border-color: #ccc; 116 | } 117 | .option #color-picker { 118 | opacity: 0; 119 | cursor: pointer; 120 | } 121 | .buttons button { 122 | width: 100%; 123 | color: #fff; 124 | border: none; 125 | outline: none; 126 | padding: 11px 0; 127 | font-size: 0.9rem; 128 | margin-bottom: 13px; 129 | background: none; 130 | border-radius: 4px; 131 | cursor: pointer; 132 | } 133 | .buttons .clear-canvas { 134 | color: #6c757d; 135 | border: 1px solid #6c757d; 136 | transition: all 0.3s ease; 137 | } 138 | .clear-canvas:hover { 139 | color: #fff; 140 | background: #6c757d; 141 | } 142 | .buttons .save-img { 143 | background: #4a98f7; 144 | border: 1px solid #4a98f7; 145 | } 146 | .drawing-board { 147 | flex: 1; 148 | overflow: hidden; 149 | } 150 | .drawing-board canvas { 151 | width: 100%; 152 | height: 100%; 153 | } 154 | -------------------------------------------------------------------------------- /js/script.js: -------------------------------------------------------------------------------- 1 | // GLOBAL VARIABLES 2 | const canvas = document.querySelector('canvas'), 3 | toolBtns = document.querySelectorAll('.tool'), 4 | fillColor = document.querySelector('#fill-color'), 5 | sizeSlider = document.querySelector('#size-slider'), 6 | colorBtns = document.querySelectorAll('.colors .option'), 7 | colorPicker = document.querySelector('#color-picker'), 8 | clearCanvasBtn = document.querySelector('.clear-canvas'), 9 | saveImageBtn = document.querySelector('.save-img') 10 | 11 | // VARIABLE WITH DEFAULT VALUE 12 | let ctx = canvas.getContext('2d'), 13 | isDrawing = false, 14 | brushWidth = 5, 15 | selectedTool = 'brush', 16 | selectedColor = '#000', 17 | prevMouseX, 18 | prevMouseY, 19 | snapshot 20 | 21 | // SET CANVAS BACKGROUND 22 | const setCannvasBackground = () => { 23 | ctx.fillStyle = '#fff' 24 | ctx.fillRect(0, 0, canvas.width, canvas.height) 25 | ctx.fillStyle = selectedColor 26 | } 27 | 28 | // SET CANVAS WIDTH AND HEIGHT 29 | window.addEventListener('load', () => { 30 | canvas.width = canvas.offsetWidth 31 | canvas.height = canvas.offsetHeight 32 | setCannvasBackground() 33 | }) 34 | 35 | // START DRAWING 36 | const startDraw = e => { 37 | isDrawing = true 38 | prevMouseX = e.offsetX 39 | prevMouseY = e.offsetY 40 | ctx.beginPath() 41 | ctx.lineWidth = brushWidth 42 | ctx.strokeStyle = selectedColor 43 | ctx.fillStyle = selectedColor 44 | snapshot = ctx.getImageData(0, 0, canvas.width, canvas.height) 45 | } 46 | 47 | // DRAW RECTANGLE 48 | const drawRectangle = e => { 49 | fillColor.checked 50 | ? ctx.fillRect(e.offsetX, e.offsetY, prevMouseX - e.offsetX, prevMouseY - e.offsetY) 51 | : ctx.strokeRect(e.offsetX, e.offsetY, prevMouseX - e.offsetX, prevMouseY - e.offsetY) 52 | } 53 | 54 | // DRAW CIRCLE 55 | const drawCircle = e => { 56 | ctx.beginPath() 57 | const radius = 58 | Math.sqrt(Math.pow(prevMouseX - e.offsetX, 2)) + Math.pow(prevMouseY - e.offsetY, 2) 59 | ctx.arc(prevMouseX, prevMouseY, radius, 0, 2 * Math.PI) 60 | fillColor.checked ? ctx.fill() : ctx.stroke() 61 | } 62 | 63 | // DRAW TRIANGLE 64 | const drawTriangle = e => { 65 | ctx.beginPath() 66 | ctx.moveTo(prevMouseX, prevMouseY) 67 | ctx.lineTo(e.offsetX, e.offsetY) 68 | ctx.lineTo(prevMouseX * 2 - e.offsetX, e.offsetY) 69 | ctx.closePath() 70 | fillColor.checked ? ctx.fill() : ctx.stroke() 71 | } 72 | 73 | // DRAWING 74 | const drawing = e => { 75 | if (!isDrawing) return 76 | ctx.putImageData(snapshot, 0, 0) 77 | 78 | switch (selectedTool) { 79 | case 'brush': 80 | ctx.lineTo(e.offsetX, e.offsetY) 81 | ctx.stroke() 82 | break 83 | case 'rectangle': 84 | drawRectangle(e) 85 | break 86 | case 'circle': 87 | drawCircle(e) 88 | break 89 | case 'triangle': 90 | drawTriangle(e) 91 | break 92 | case 'eraser': 93 | ctx.strokeStyle = '#fff' 94 | ctx.lineTo(e.offsetX, e.offsetY) 95 | ctx.stroke() 96 | break 97 | default: 98 | break 99 | } 100 | } 101 | 102 | // TOOLS BTN AND SET TO VARIABLES SELECTED TOOL 103 | toolBtns.forEach(btn => { 104 | btn.addEventListener('click', () => { 105 | document.querySelector('.options .active').classList.remove('active') 106 | btn.classList.add('active') 107 | selectedTool = btn.id 108 | }) 109 | }) 110 | 111 | // CHANGE BRUSH WITH 112 | sizeSlider.addEventListener('change', () => (brushWidth = sizeSlider.value)) 113 | 114 | // SET COLOR TO SHAPES 115 | colorBtns.forEach(btn => { 116 | btn.addEventListener('click', e => { 117 | document.querySelector('.options .selected').classList.remove('selected') 118 | btn.classList.add('selected') 119 | const bgColor = window.getComputedStyle(btn).getPropertyValue('background-color') 120 | selectedColor = bgColor 121 | }) 122 | }) 123 | 124 | // SET COLOR FROM COLOR PICKER 125 | colorPicker.addEventListener('change', () => { 126 | colorPicker.parentElement.style.background = colorPicker.value 127 | colorPicker.parentElement.click() 128 | }) 129 | 130 | // CLEAR CANVAS BUTTON 131 | clearCanvasBtn.addEventListener('click', () => { 132 | ctx.clearRect(0, 0, canvas.width, canvas.height) 133 | setCannvasBackground() 134 | }) 135 | 136 | // SAVE LIKE IMAGE OUR PAINT 137 | saveImageBtn.addEventListener('click', () => { 138 | const link = document.createElement('a') 139 | link.download = `Sammi-paint${Date.now()}.jpg` 140 | link.href = canvas.toDataURL() 141 | link.click() 142 | }) 143 | 144 | // STOP DRAWING 145 | const stopDraw = () => { 146 | isDrawing = false 147 | } 148 | 149 | canvas.addEventListener('mousedown', startDraw) 150 | canvas.addEventListener('mousemove', drawing) 151 | canvas.addEventListener('mouseup', stopDraw) 152 | --------------------------------------------------------------------------------