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