Etch a Sketch

Objectives

This week, I implemented the Etch-A-Sketch project per the requirements in The Odin Project. So far, I’ve implemented a couple small projects following tutorials I’ve been watching. This was my first project I’ve implemented that is completely open ended aside from the requirements provided.

Here is the final product! All of the code for the project can be found on GitHub.

I had a few goals going into this project.

  • Practice vanilla JavaScript
  • Create a skeleton HTML file and create elements and manipulate classes as required with JavaScript
  • Write CSS! Aside from some small things here and there, I haven’t created much with CSS and this is the perfect opportunity to dive in and get creative. I wanted my end project to look mostly like a real etch-a-sketch.

Things I Learned

I spent a lot of time implementing this project to explore various functionalities of both JavaScript and CSS. I’m still struggling to understand how all the CSS components fit together and interact, but I made a ton of progress.

I learned how to use CSS Grid. I thought this would be a good choice for the etch-a-sketch since I envisioned the elements laid out in a 3x3 grid with the header at the top, the screen in the middle, and the dials at the bottom.

Implementation

HTML

I sketched out my design then started with the HTML. I just needed a few things: the header, the main screen, and the two dials (buttons) at the bottom.

<body>
  <main>
    <h1>Etch-A-Sketch</h1>
    <div class="sketch-grid"></div>
    <button class="size" name="small"></button>
    <button class="clear">Clear</button>
  </main>
</body>

CSS

Below are snippets for some of the key CSS from this project. Some of the styling is not shown below.

Body with Flex and Repeated Background

I found an image that I thought looked like a wooden desk that would make a cute background. I repeated the image across the background. I made a flex display so the etch-a-sketch could be centered on the page.

body {
    background-image: url("https://files.1-background.com/images/nature/images/wood-maple-background-repeat.jpg");
    background-repeat: repeat;
    display: flex;
    height: 100vh;
    padding: 0;
    margin: 0 20px;
    justify-content: center;
    align-items: center;

Main with CSS Grid

The entire etch-a-sketch resides in the main element. I made this a grid component. I struggled a lot with figuring out the best way to lay this out, but ultimately defined the grid template in 3 columns and rows with a specified fractional unit of the page. I specified names for the grid template areas and used periods in the areas that aren’t used.

The remaining CSS specifies the look and feel - I played around with the sizing and border, box-shadow, and sizing until I was happy with the results. I discovered the border style outset which I thought made a nice sloped look to give a 3D effect to the edges. Considering this is just meant to be viewed in a browser, I hardcoded the width and height as pixels. In the future, I need to learn a bit more about how to dynamically size the elements to the window.

main {
    display: grid;
    grid-template-columns: 1fr 6fr 1fr;
    grid-template-rows: 1fr 5fr 1fr;
    grid-template-areas:
		    ". header . "
		    ". sketch-grid ."
		    "size-btn slider clear-btn";
    justify-content: center;
    height: 800px;
    width: 1100px;
    padding: 10px;
    background-color: red;
    border-radius: 2%;
    border: 20px outset rgba(207, 2, 2, 0.322);
    box-shadow: 10px 10px 5px rgb(66, 51, 51);}
}

I added the grid area to each of the appropriate elements.

.size {
    grid-area: size-btn;
}

.clear {
    grid-area: clear-btn;
}

JavaScript

Once I was content with the look and feel of the etch-a-sketch, I started writing the JavaScipt.

The core learning objective of this project was using JavaScript to create each “pixel” of the main screen. The screen has 3 potential pixel densities based on the user’s selection. I envisioned handling this by creating another CSS Grid inside the screen area which would dynamically change rows and columns based on the selected size.

Creating the Screen

I wrote a createGrid function that is called when the DOM content is loaded to generate the screen. It selects the section of the page that should contain the screen which is a div with the class .sketch-grid. First, I set the innerHTML for this element to an empty string so any existing child elements would be removed.

function createGrid () {
  const sketchGrid = document.querySelector('.sketch-grid')
  sketchGrid.innerHTML = ''
  ...
}
Button Sizing

The screen has 3 potential sizes. I decided to control this by through one of the “dials” on the etch-a-sketch. I made the element a button with a name of either “small”, “medium”, or “large”. The createGrid function references this name to determine how may child elements to create.

function createGrid () {
  ...
  const size = document.querySelector('.size').name
  sketchGrid.className = `sketch-grid ${size}`
  ...
}

Here is the function that controls the size when the button when it is clicked:

function changeSize (event) {
  const button = event.target
  if (button.name === 'small') {
    button.name = 'medium'
  } else if (button.name === 'medium') {
    button.name = 'large'
  } else if (button.name === 'large') {
    button.name = 'small'
  }
  button.innerText = button.name
  clearScreen()
  createGrid()
}

I appended a class to the sketch grid container to reference this size. I created an object containing the quantity of pixels to be created for one side of the etch-a-sketch.

function createGrid () {
  ...
  const range = {
    small: 60,
    medium: 30,
    large: 10
  }
  document.documentElement.style.setProperty('--sketch-grid-item-size', range[size])
  ...
}
Screen Grid

I created the second CSS Grid inside the screen element to control the placement of the new child elements that will be created. It is driven by a variable set by the changeGrid function.

:root {
    --sketch-grid-item-size: 60;
}

.sketch-grid {
    grid-area: sketch-grid;
    display: grid;
    grid-template-columns: repeat(var(--sketch-grid-item-size), 1fr);
    grid-template-rows: repeat(var(--sketch-grid-item-size), 1fr);
}

Now that the grid size had been established, I was able to add the final touches to the createGrid function. I created a new div based on the size value, added the sketch-grid item class, and appended it to the parent sketch grid container.

function createGrid () {
  ...
  for (let i = 0; i < range[size] ** 2; i++) {
    const gridDiv = document.createElement('div')
    gridDiv.className = 'sketch-grid-item'
    sketchGrid.appendChild(gridDiv)
  }
  ...
}

Drawing

The draw feature works when the mouse hovers into the screen area.

I set the below event listener for the entire window that calls the draw function. I wasn’t able to get this to work by selecting the sketch grid only, so I applied it to the whole window and added the logic to check if it was inside of the sketch grid inside the draw function. I’m sure there’s a way to handle it, but it works for now!

window.addEventListener('mouseover', draw)

Later in the project, I decided to implement a slider so the color could be changed depending on the user selection. The color changes by adding a class with the color name to the sketch grid items. When the class is added, the grid becomes colored in that area. I handled the color change and drawing in the function below.

The slider changes color when a new color is selected, but this only seems to work in Firefox for now. I’ll have to learn how to handle this in Chrome at some point.

function draw (event) {
  const slider = document.querySelector('.slider')
  const colors = {
    1: 'purple',
    2: 'red',
    3: 'orange',
    4: 'green',
    5: 'blue'
  }
  slider.className = `slider ${colors[slider.value]}`
  if (event.target.classList.contains('sketch-grid-item')) {
    event.target.className = `sketch-grid-item ${colors[slider.value]}`
  }
}

Clear Screen

Because of the color implementation adding a class to the sketch grid items to “draw”, clearing the screen is super simple - just reset the class name to remove the color class!

function clearScreen () {
  const gridItems = document.querySelectorAll('.sketch-grid-item')
  gridItems.forEach((item) => {
    item.className = 'sketch-grid-item'
  })
}

Event Listeners

Tying it all together, here is the event listener I wrote when the DOM content is loaded. It generates the grid and some preliminary classes and text when the DOM content is loaded. It also contains the event listeners for clearing the screen, changing the grid size, and changing the draw color.

document.addEventListener('DOMContentLoaded', () => {
  const clearButton = document.querySelector('.clear')
  const sizeButton = document.querySelector('.size')
  const slider = document.querySelector('.slider')
  sizeButton.innerText = sizeButton.name
  createGrid()
  clearButton.addEventListener('click', clearScreen)
  sizeButton.addEventListener('click', changeSize)
  slider.value = 1
  slider.className = 'slider purple'
  slider.addEventListener('click', draw)
})

Conclusion

Overall, I’m pretty proud of the design of the etch-a-sketch and I learned a ton during this project. I had some sticking points that I’ve marked as learning opportunities in future projects.