Skip to content

Commit bec4a17

Browse files
committed
curr(pixel-art-creator): project reivew updates
1 parent 9cc72db commit bec4a17

6 files changed

Lines changed: 53 additions & 52 deletions

File tree

-3.11 KB
Loading
-3.14 KB
Loading
-3.17 KB
Loading
-3.02 KB
Loading
-3.14 KB
Loading

projects/build-a-pixel-art-creator-with-html-css-and-javascript/build-a-pixel-art-creator-with-html-css-and-javascript.mdx

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ courses:
1515
- javascript
1616
tags:
1717
- intermediate
18-
- html
1918
- css
2019
- javascript
2120
---
2221

2322
## Introduction
2423

25-
Ever wanted to make your own pixel art like the sprites in classic video games? What if you could build the entire drawing tool yourself, right in the browser?
24+
Ever wanted to make your own pixel art like the sprites in classic video games like Super Mario and indie games like Stardew Valley? What if you could build the entire drawing tool yourself, right in the browser?
2625

2726
In this project tutorial, we'll build a **Pixel Art Creator**, a fully functional drawing app where you can paint pixel art on a grid canvas, pick colors, fill regions, and export your creation as a PNG image!
2827

@@ -32,7 +31,7 @@ You will learn about the following concepts in this tutorial:
3231
- How to use **2D arrays** to represent a grid of pixels in memory.
3332
- How to handle **mouse events** and map screen coordinates to grid cells.
3433
- How **flood fill** works (the algorithm behind the paint bucket tool).
35-
- How to **export a canvas** as a downloadable PNG image.
34+
- How to **export a canvas** as a downloadable **.png** image.
3635

3736
By the end of this tutorial, we'll have a fully functional pixel art editor that looks like this:
3837

@@ -53,15 +52,16 @@ Before we jump into code, let's understand the core ideas behind any pixel art e
5352

5453
### The Grid as a 2D Array
5554

56-
A pixel art canvas is just a grid of colored squares. In code, we represent this grid as a **2D array**, an array of arrays:
55+
A pixel art canvas is just a grid of colored squares. In code, we represent this grid as a **2D array** which is an array of arrays like so:
5756

5857
```javascript
5958
// A 3x3 grid example
59+
6060
[
6161
["#ffffff", "#ff0000", "#ffffff"],
6262
["#ff0000", "#ff0000", "#ff0000"],
6363
["#ffffff", "#ff0000", "#ffffff"],
64-
];
64+
]
6565
```
6666

6767
Each value is a hex color string. The position in the array corresponds to a cell on the grid: `grid[row][col]` gives us the color at that position.
@@ -70,8 +70,8 @@ Each value is a hex color string. The position in the array corresponds to a cel
7070

7171
The HTML5 `<canvas>` element gives us a drawing surface. We use its 2D rendering context to draw rectangles, one per grid cell:
7272

73-
- `fillRect(x, y, width, height)` draws a filled rectangle.
74-
- `strokeRect(x, y, width, height)` draws a rectangle outline (our grid lines).
73+
- `.fillRect(x, y, width, height)` draws a filled rectangle.
74+
- `.strokeRect(x, y, width, height)` draws a rectangle outline (our grid lines).
7575

7676
### Coordinate Mapping
7777

@@ -86,15 +86,15 @@ Download the starter code from this repository: [Pixel Art Creator Starter Code]
8686
The folder should have the following structure:
8787

8888
```
89-
build-a-pixel-art-creator-with-html-css-and-javascript/
90-
├── starter/ # Start here: has TODOs to fill in
91-
├── index.html
92-
├── style.css
93-
└── script.js
94-
└── completed/ # Reference: fully working code
95-
├── index.html
96-
├── style.css
97-
└── script.js
89+
pixel-art-creator-with-html-css-and-javascript/
90+
├── starter/ # Start here - has TODOs to fill in
91+
├── index.html
92+
├── style.css
93+
└── script.js
94+
└── completed/ # Reference - fully working code
95+
├── index.html
96+
├── style.css
97+
└── script.js
9898
```
9999

100100
**Note:** We'll be working in the **starter** directory. The **completed** directory contains the finished code you can use as reference if you get stuck.
@@ -105,7 +105,7 @@ Let's take a look at what the starter code gives us:
105105
- **style.css** - All the styling is done. Dark theme, flexbox sidebar layout, tool button styles with active/hover states, a CSS Grid color palette, and responsive rules for mobile. No changes needed here.
106106
- **script.js** - This is where we'll spend our time. The starter has variable declarations and function stubs with TODO comments. We'll implement each function step by step.
107107

108-
Open **starter/index.html** in your browser. You should see a dark-themed layout with a toolbar on the left and an empty canvas area:
108+
Open **starter/index.html** in your browser. You should see a dark-themed layout with a toolbar on the left and an empty canvas area. Nothing is interactive yet - that's what JavaScript is for!
109109

110110
<img
111111
src="https://raw.githubusercontent.com/codedex-io/projects/main/projects/build-a-pixel-art-creator-with-html-css-and-javascript/assets/empty-grid.png"
@@ -134,16 +134,19 @@ Find the `init()` function in the starter code and add the following code:
134134
```javascript
135135
function init() {
136136
grid = Array.from({ length: gridSize }, () =>
137-
Array(gridSize).fill("#ffffff"),
137+
Array(gridSize).fill("#ffffff")
138138
);
139+
139140
cellSize = Math.floor(480 / gridSize);
141+
140142
canvas.width = gridSize * cellSize;
141143
canvas.height = gridSize * cellSize;
144+
142145
render();
143146
}
144147
```
145148

146-
**How this works:**
149+
How this works:
147150

148151
- `Array.from({ length: gridSize }, () => Array(gridSize).fill("#ffffff"))` creates a 2D array where every cell starts as white (`#ffffff`). For a 16x16 grid, that's 256 cells.
149152
- `cellSize = Math.floor(480 / gridSize)` calculates the pixel size of each cell. We target roughly 480 pixels total, so for a 16x16 grid each cell is 30px, for a 32x32 grid each cell is 15px, and so on.
@@ -170,6 +173,7 @@ function render() {
170173
if (hoveredCell && !isDrawing) {
171174
const { row, col } = hoveredCell;
172175
const previewColor = currentTool === "eraser" ? "#ffffff" : currentColor;
176+
173177
ctx.fillStyle = previewColor;
174178
ctx.globalAlpha = 0.4;
175179
ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
@@ -181,11 +185,11 @@ function render() {
181185
**How this works:**
182186

183187
- The nested loop goes through every cell in the grid, row by row, column by column.
184-
- For each cell, we set `fillStyle` to that cell's color and draw a filled rectangle at the correct position.
188+
- For each cell, we set `.fillStyle` to that cell's color and draw a filled rectangle at the correct position.
185189
- We then draw a thin gray outline (`strokeRect`) to show the grid lines.
186190
- The `hoveredCell` block draws a semi-transparent preview of the current color on whatever cell the mouse is hovering over. We use `globalAlpha = 0.4` to make it translucent, then reset it to `1.0` so future drawing isn't affected.
187191

188-
**Note:** We don't render the hover preview while actively drawing (`!isDrawing`) - it would be distracting during fast drawing.
192+
**Note:** We don't render the hover preview while actively drawing (`!isDrawing`). It would be distracting during fast drawing.
189193

190194
The `init()` call is already at the bottom of the starter file, so once we implement these functions, they'll work right away!
191195

@@ -195,7 +199,7 @@ Now let's make the canvas interactive. We need to figure out which cell the mous
195199

196200
### Step 2-a: Map Mouse Position to Grid Cell
197201

198-
Find the `getCellFromMouse` function and add this:
202+
Find the `getCellFromMouse` function and add the following code:
199203

200204
```javascript
201205
function getCellFromMouse(e) {
@@ -212,16 +216,16 @@ function getCellFromMouse(e) {
212216
}
213217
```
214218

215-
**How this works:**
219+
How this works:
216220

217-
- `canvas.getBoundingClientRect()` tells us where the canvas is on the page.
218-
- We subtract the canvas position from the mouse position to get coordinates _relative to the canvas_.
221+
- `canvas.getBoundingClientRect()` tells us where the canvas is on the web page.
222+
- We subtract the canvas position from the mouse position to get coordinates relative to the canvas.
219223
- Dividing by `cellSize` and using `Math.floor` converts pixel coordinates to grid coordinates.
220224
- If the position is outside the grid, we return `null` to avoid errors.
221225

222226
### Step 2-b: Paint a Single Cell
223227

224-
Now find `paintCell` and add:
228+
Now find `paintCell` and implement it:
225229

226230
```javascript
227231
function paintCell(row, col) {
@@ -324,7 +328,7 @@ function floodFill(row, col, newColor) {
324328
}
325329
```
326330

327-
**Let's break this down:**
331+
Let's break this down:
328332

329333
- First, we check what color the clicked cell is (`targetColor`). If it's already the new color, we do nothing - this prevents an infinite loop!
330334
- We use a **stack** (a regular JavaScript array used as a stack with `push` and `pop`) to track which cells to check next.
@@ -343,6 +347,8 @@ function floodFill(row, col, newColor) {
343347
alt="Flood fill tool filling a smiley face with yellow"
344348
/>
345349

350+
Phew… that was a little tough to implement! But, we're now through the most difficult part. 😤
351+
346352
## Step 4: Build the Color Palette and Tool Switching
347353

348354
Now let's make the toolbar interactive - generating color swatches, handling color selection, and switching between tools.
@@ -357,59 +363,56 @@ function buildPalette() {
357363
PRESET_COLORS.forEach((color) => {
358364
const swatch = document.createElement("div");
359365
swatch.classList.add("color-swatch");
366+
360367
if (color === currentColor) swatch.classList.add("active");
368+
361369
swatch.style.backgroundColor = color;
370+
362371
swatch.addEventListener("click", () => {
363372
currentColor = color;
364373
document.getElementById("custom-color").value = color;
365-
document
366-
.querySelectorAll(".color-swatch")
367-
.forEach((s) => s.classList.remove("active"));
374+
document.querySelectorAll(".color-swatch").forEach((s) => s.classList.remove("active"));
368375
swatch.classList.add("active");
369376
});
370377
palette.appendChild(swatch);
371378
});
372379
}
373380
```
374381

375-
**How this works:**
382+
How this works:
376383

377384
- We loop through `PRESET_COLORS` and create a `<div>` for each one.
378385
- Each swatch gets its background color set and a click handler.
379386
- When clicked, we update `currentColor`, sync the custom color picker input, and toggle the `.active` class so only the selected swatch has the white border.
380387

381388
### Step 4-b: Custom Color Picker
382389

383-
Find the comment marked `Step 4-b` and add the custom color picker handler:
390+
Find the TODO marked `Step 4-b` and add the custom color picker handler:
384391

385392
```javascript
386393
document.getElementById("custom-color").addEventListener("input", (e) => {
387394
currentColor = e.target.value;
388-
document
389-
.querySelectorAll(".color-swatch")
390-
.forEach((s) => s.classList.remove("active"));
395+
document.querySelectorAll(".color-swatch").forEach((s) => s.classList.remove("active"));
391396
});
392397
```
393398

394399
When the user picks a custom color, we update `currentColor` and deselect all preset swatches (since the active color is now a custom one).
395400

396401
### Step 4-c: Tool Button Switching
397402

398-
Next, go to `Step 4-c` and wire up the tool buttons:
403+
Find the TODO marked `Step 4-c` and wire up the tool buttons:
399404

400405
```javascript
401406
document.querySelectorAll(".tool-btn").forEach((btn) => {
402407
btn.addEventListener("click", () => {
403408
currentTool = btn.dataset.tool;
404-
document
405-
.querySelectorAll(".tool-btn")
406-
.forEach((b) => b.classList.remove("active"));
409+
document.querySelectorAll(".tool-btn").forEach((b) => b.classList.remove("active"));
407410
btn.classList.add("active");
408411
});
409412
});
410413
```
411414

412-
**How this works:**
415+
How this works:
413416

414417
- We select all buttons with class `.tool-btn` and add a click handler to each.
415418
- `btn.dataset.tool` reads the `data-tool` attribute we set in the HTML - this gives us `"pen"`, `"eraser"`, or `"fill"`.
@@ -419,12 +422,12 @@ The `buildPalette()` call is already in the starter code, so save and refresh -
419422

420423
## Step 5: Add Grid Size Switching
421424

422-
For `Step 5`, add the grid size dropdown:
425+
Find the TODO marked `Step 5` and wire up the grid size dropdown:
423426

424427
```javascript
425428
document.getElementById("grid-size").addEventListener("change", (e) => {
426429
const confirmed = confirm(
427-
"Changing grid size will clear your canvas. Continue?",
430+
"Changing grid size will clear your canvas. Continue?"
428431
);
429432
if (confirmed) {
430433
gridSize = parseInt(e.target.value);
@@ -435,7 +438,7 @@ document.getElementById("grid-size").addEventListener("change", (e) => {
435438
});
436439
```
437440

438-
**How this works:**
441+
How this works:
439442

440443
- When the dropdown value changes, we show a confirmation dialog - because changing the grid size resets everything.
441444
- If confirmed, we parse the new value (`"16"`, `"32"`, or `"64"` - these are strings from the HTML, so we need `parseInt`), update `gridSize`, and call `init()` to rebuild the grid from scratch.
@@ -450,7 +453,7 @@ document.getElementById("grid-size").addEventListener("change", (e) => {
450453

451454
## Step 6: Export as PNG
452455

453-
The last feature! Find `Step 6` and add the export handler:
456+
The last feature! Find the TODO marked `Step 6` and add the export handler:
454457

455458
```javascript
456459
document.getElementById("export-btn").addEventListener("click", () => {
@@ -467,7 +470,7 @@ document.getElementById("export-btn").addEventListener("click", () => {
467470
col * exportCellSize,
468471
row * exportCellSize,
469472
exportCellSize,
470-
exportCellSize,
473+
exportCellSize
471474
);
472475
}
473476
}
@@ -479,11 +482,11 @@ document.getElementById("export-btn").addEventListener("click", () => {
479482
});
480483
```
481484

482-
**Why a separate canvas?**
485+
So why a separate canvas? 🤔
483486

484487
We don't export the visible canvas directly because it has grid lines drawn on it. Instead, we create a new, invisible canvas and draw only the colored cells - no grid lines. This gives us a clean exported image.
485488

486-
**How the export works:**
489+
How the export works:
487490

488491
- `Math.max(16, Math.floor(512 / gridSize))` ensures the exported image is at least 512px wide. A 16x16 grid exports at 32px per cell (512px total), while a 64x64 grid exports at 8px per cell.
489492
- We loop through the grid and draw each cell's color using `fillRect`, same as rendering, but without the `strokeRect` grid lines.
@@ -503,12 +506,10 @@ Test everything:
503506

504507
## Conclusion
505508

506-
Congratulations! You've built a pixel art editor from scratch using only HTML, CSS, and JavaScript!
509+
Congratulations! You've built a pixel art editor from scratch using only HTML, CSS, and JavaScript! 🎉
507510

508511
Let's recap what we learned:
509512

510-
### Concepts Covered
511-
512513
- **Canvas API** - Using `fillRect`, `strokeRect`, `toDataURL`, and `getContext("2d")` to draw and export graphics.
513514
- **2D Arrays** - Representing grid state in memory and keeping it in sync with the visual canvas.
514515
- **Event Handling** - Using `mousedown`, `mousemove`, `mouseup`, and `mouseleave` for click-and-drag drawing.
@@ -517,7 +518,7 @@ Let's recap what we learned:
517518
- **File Downloads** - Programmatically creating download links with `toDataURL()`.
518519
- **Responsive CSS** - Using `@media` queries to adapt layouts for different screen sizes.
519520

520-
### Next Steps
521+
### Ideas to Keep Building
521522

522523
Now that you have a working pixel art editor, here are some ways to extend it:
523524

@@ -534,4 +535,4 @@ Now that you have a working pixel art editor, here are some ways to extend it:
534535
- [Mouse Events (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
535536
- [CSS Grid Layout (MDN)](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout)
536537

537-
Share your pixel art creations on Instagram and tag [@gokucodes](https://www.instagram.com/gokucodes/) and [@codedex_io](https://www.instagram.com/codedex.io/)!
538+
Share your pixel art creations on Instagram and LinkedIn and tag [@gokucodes](https://www.instagram.com/gokucodes) and [@codedex_io](https://www.instagram.com/codedex.io/)!

0 commit comments

Comments
 (0)