I am a beginner in javascript and I have made this project as a part of The odin project. Feautres:
- Draw by hovering the mouse on grid
- Erase
- Clear the entire sketch at once
- Reset the grid size
I have created the grid using only flexbox.
Here is my code:
function createGrid(grid, rowSize, colSize) {
for (let i = 0; i < rowSize; i++) {
grid[i] = document.createElement("div");
grid[i].setAttribute("id", "container");
let pixels = [];
for (let j = 0; j < colSize; j++) {
pixels[j] = document.createElement("div");
pixels[j].setAttribute("id", "pixel");
grid[i].appendChild(pixels[j]);
}
}
return grid;
}
function appendArray(pDiv, cDiv) {
cDiv.forEach((div) => pDiv.appendChild(div));
return pDiv;
}
(() => {
const GRID_WIDTH = 16;
const GRID_HEIGHT = 16;
const sketchEl = document.querySelector("#sketch");
const eraseEl = document.querySelector("#erase");
const buttonsEl = document.querySelector(".buttons");
let gridCont = document.querySelector("#grid-container");
let grid = [];
let state;
grid = createGrid(grid, GRID_WIDTH, GRID_HEIGHT);
gridCont = appendArray(gridCont, grid);
buttonsEl.addEventListener("click", changeState);
gridCont.addEventListener("mouseover", eventHandler, false);
function changeState(event) {
state = event.target.id;
switch (state) {
case "reset":
sketchReset();
break;
case "gridSize":
sizeReset();
}
}
function sketchReset() {
const pixels = document.querySelectorAll("#pixel");
pixels.forEach((pxl) =>
pxl.setAttribute("style", "background-color:'transparent';")
);
}
function sizeReset() {
let size = parseInt(prompt("Enter grid size (MAX=100) : "));
if (size <= 100) {
grid.length = 0;
grid = createGrid(grid, size, size);
gridCont.innerHTML = "";
gridCont = appendArray(gridCont, grid);
}
}
function eventHandler(event) {
if (event.target.id !== "pixel") {
return;
}
changeColor(event);
}
function changeColor(event) {
if (state === "eraser") {
event.target.setAttribute("style", "background-color:'transparent';");
return;
}
event.target.setAttribute("style", "background-color:black;");
}
})();
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#header {
font-size: 2.5em;
}
.flex-container {
display: flex;
gap: 2em;
}
.buttons {
display: flex;
flex-direction: column;
justify-content: center;
gap: 2em;
}
.button {
font-size: x-large;
}
.grid-container {
display: flex;
height: 30em;
width: 30em;
border: 1px solid black;
}
.grid-container > div {
flex: 1;
display: flex;
flex-direction: column;
}
.grid-container > div > * {
flex: 1;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Etch-a-Sketch</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<script src="index.js" defer></script>
<h1 id="header">Etch-a-sketch!</h1>
<div class="flex-container">
<div class="buttons">
<button class="button" id="sketch">Sketch</button>
<button class="button" id="eraser">Erase</button>
<button class="button" id="reset">Reset</button>
<button class="button" id="gridSize">Grid Size</button>
</div>
<div id="grid-container" class="grid-container"></div>
</div>
</body>
</html>
1 Answer 1
I'm going to only focus on the Javascript code in my answer.
function createGrid(grid, rowSize, colSize) {
for (let i = 0; i < rowSize; i++) {
grid[i] = document.createElement("div");
grid[i].setAttribute("id", "container");
let pixels = [];
for (let j = 0; j < colSize; j++) {
pixels[j] = document.createElement("div");
pixels[j].setAttribute("id", "pixel");
grid[i].appendChild(pixels[j]);
}
}
return grid;
}
I'm not sure why this takes a grid
argument, given that the name is createGrid. Just create the array within the function.
Secondly, ids are supposed to be unique. For a group of similar elements, you should use a class instead. See this SO question for more info.
function appendArray(pDiv, cDiv) {
cDiv.forEach((div) => pDiv.appendChild(div));
return pDiv;
}
There already is a function to append multiple children to an element: Element#append
. The invocation is a bit different, as you'll need to use spread syntax to pass an array, ie. gridCont.append(...grid)
.
gridCont.addEventListener("mouseover", eventHandler, false);
eventHandler
is an undescriptive name. I would rename it to something like gridMouseover
.
function changeState(event) {
state = event.target.id;
switch (state) {
case "reset":
sketchReset();
break;
case "gridSize":
sizeReset();
}
}
In my opinion, reset and gridSize shouldn't affect the global state, as they are one-time actions. As written, your code works, though restructuring the code will help for future modifications.
function changeState(event) {
switch (event.target.id) {
case "reset":
sketchReset();
break;
case "gridSize":
sizeReset();
break;
case "sketch":
case "reset":
state = event.target.id;
break;
}
}
const pixels = document.querySelectorAll(".pixel");
(code modified to align with the createGrid
change)
There's no need for querySelectorAll
here - just use document.getElementsByClassName("pixel")
for better performance and more code clarity.
function changeColor(event) {
if (state === "eraser") {
event.target.setAttribute("style", "background-color:'transparent';");
return;
}
event.target.setAttribute("style", "background-color:black;");
}
This doesn't need to use the entire event object, just the pixel element that's being hovered, so only pass that as the argument.
Full code:
function createGrid(rowSize, colSize) {
let grid = [];
for (let i = 0; i < rowSize; i++) {
grid[i] = document.createElement("div");
grid[i].setAttribute("class", "container");
let pixels = [];
for (let j = 0; j < colSize; j++) {
pixels[j] = document.createElement("div");
pixels[j].setAttribute("class", "pixel");
}
grid[i].append(...pixels);
}
return grid;
}
(() => {
const GRID_WIDTH = 16;
const GRID_HEIGHT = 16;
const sketchEl = document.querySelector("#sketch");
const eraseEl = document.querySelector("#erase");
const buttonsEl = document.querySelector(".buttons");
let gridCont = document.querySelector("#grid-container");
let state;
let grid = createGrid(GRID_WIDTH, GRID_HEIGHT);
gridCont.append(...grid);
buttonsEl.addEventListener("click", changeState);
gridCont.addEventListener("mouseover", gridMouseover, false);
function changeState(event) {
switch (event.target.id) {
case "reset":
sketchReset();
break;
case "gridSize":
sizeReset();
break;
case "sketch":
case "reset":
state = event.target.id;
break;
}
}
function sketchReset() {
const pixels = document.querySelectorAll(".pixel");
Array.from(pixels).forEach((pxl) =>
pxl.setAttribute("style", "background-color:'transparent';")
);
}
function sizeReset() {
let size = parseInt(prompt("Enter grid size (MAX=100) : "));
if (size <= 100) {
grid = createGrid(size, size);
gridCont.innerHTML = "";
gridCont.append(...grid);
}
}
function gridMouseover(event) {
if (!event.target.classList.contains("pixel")) {
return;
}
changeColor(event.target);
}
function changeColor(pixel) {
if (state === "eraser") {
pixel.setAttribute("style", "background-color:'transparent';");
return;
}
pixel.setAttribute("style", "background-color:black;");
}
})();
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#header {
font-size: 2.5em;
}
.flex-container {
display: flex;
gap: 2em;
}
.buttons {
display: flex;
flex-direction: column;
justify-content: center;
gap: 2em;
}
.button {
font-size: x-large;
}
.grid-container {
display: flex;
height: 30em;
width: 30em;
border: 1px solid black;
}
.grid-container > div {
flex: 1;
display: flex;
flex-direction: column;
}
.grid-container > div > * {
flex: 1;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Etch-a-Sketch</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<script src="index.js" defer></script>
<h1 id="header">Etch-a-sketch!</h1>
<div class="flex-container">
<div class="buttons">
<button class="button" id="sketch">Sketch</button>
<button class="button" id="eraser">Erase</button>
<button class="button" id="reset">Reset</button>
<button class="button" id="gridSize">Grid Size</button>
</div>
<div id="grid-container" class="grid-container"></div>
</div>
</body>
</html>
Some suggestions for future improvements:
- only draw pixels while the mouse is held - check out the
mousedown
andmouseup
events. - display validation errors for
sizeReset
.