Search code examples
javascripthtmlcss

How to prevent the right and bottom edges of my grid from being white on certain grid sizes?


I have an etch-a-sketch program which works fine apart from one small issue. When I click the "change grid size" button and enter certain numbers (such as 51, 44, 45), the right and bottom edges of my grid are white instead of black. Some numbers like 10, 16, 25, don't give this issue.

This issue happens in chrome browser. When I use firefox browser, my grid becomes even worse and goes all wonky with a column outside of the grid.

  • I tried resizing the grid itself
  • I tried rounding (up or down) gridSize / gridDimensions to various different values
  • I tried removing the borders of each square
  • I changed border on .squares to outline, just causes the issue on different numbers instead (such as 88)

Here is my code:

let container = document.querySelector(".container");

createGrid(16);

function createGrid(gridDimensions) {  
  let gridSize = 800;
  let gridPixelSize = gridSize / gridDimensions;

  for(row = 1; row < (gridDimensions + 1); row++) {
    let column = 1;
    let square = document.createElement("div");
    square.id = "square-" + row + "-" + column;
    square.className = "squares";
    square.style.height = `${gridPixelSize}px`;
    square.style.width = `${gridPixelSize}px`;
    container.appendChild(square);
    for(column = 2; column < (gridDimensions + 1); column++) {
      let square = document.createElement("div");
      square.id = "square-" + row + "-" + column;
      square.className = "squares";
      square.style.height = `${gridPixelSize}px`;
      square.style.width = `${gridPixelSize}px`;
      container.appendChild(square);
    }
  }
  drawOnGrid(gridPixelSize);
}

function drawOnGrid(gridPixelSize) {
  
  let squares = document.querySelectorAll(".squares");

  squares.forEach((square) => {
    let squareDark = document.createElement("div");
    squareDark.style.height = `${gridPixelSize}px`;
    squareDark.style.width = `${gridPixelSize}px`;
    squareDark.style.backgroundColor = "black";
    let squareDarkOpacity = 0;
    squareDark.style.opacity = `${squareDarkOpacity}`;
    square.appendChild(squareDark);

    square.addEventListener("mouseover", (e) => {
      if(!hasColorBeenSet.includes(square)) {
        hasColorBeenSet.push(square);
        let randomColor = Math.floor(Math.random() * 16777215).toString(16);
        square.style.backgroundColor = `#${randomColor}`;
      }

      squareDarkOpacity += 0.1;
      squareDark.style.opacity = `${squareDarkOpacity}`;
    });
  });
  let hasColorBeenSet = [];
}

function changeGridSize() {
  let validAnswer = false;
  while(!validAnswer) {
    let userInput = prompt("How big should the grid be? Enter a number between 8 to 100.");
    userInput = Number(userInput);
    userInput = Math.round(userInput);

    if(userInput >= 8 && userInput <= 100) {
      validAnswer = true;
      container.innerHTML = "";
      createGrid(userInput);
    }
  }
}
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  background-color: rgb(90, 101, 110);
  display: flex;
  flex-direction: column;
  gap: 50px;
  justify-content: center;
  align-items: center;
}

.container-border {
  border: 1px solid black;
}

.container {
  height: 800px;
  width: 800px;
  background-color: white;
  display: flex;
  flex-wrap: wrap;
}

.squares {
  border: 1px solid black;
}

button {
  font-size: 26px;
  padding: 10px 20px;
  border-radius: 5px;
}

button:hover {
  transition: 0.2s;
  background-color: orange;
  cursor: pointer;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="./style.css">
    <script src="main.js" defer></script>
    <title>Etch-a-sketch</title>
  </head>
  <body>
    <button onclick="changeGridSize()">Change grid size...</button>
    <div class="container-border">
      <div class="container"></div>
    </div>
  </body>
</html>


Solution

  • This is because of how browsers handle subpixel rendering. The problem occurs when 800 is not divisible by userInput. For example, 800 / 20 = 40, so it renders fine, but 800 / 14 = 57.143, and due to inaccuracies, that gap appears. They can appear not only on edges but between cells as well.

    I came up with two methods to solve this:

    Change background color

    If you change the background-color of the container to black and squares to white, the white gap will become black. As a side effect, the border may become a little thicker:

    .container {
        height: 800px;
        width: 800px;
        background-color: black;
        display: flex;
        flex-wrap: wrap;
    }
    
    .squares {
        border: 1px solid black;
        background-color: white;
    }
    

    Adjust the size of the container

    Another method, which I believe works better, is to first round the gridPixelSize to the nearest integer in the createGrid function:

    let gridPixelSize = Math.round(gridSize / gridDimensions);
    

    And then calculate the container size based on that:

    let containerSize = gridDimensions * gridPixelSize;
    

    Then we can resize the container with the newly calculated size:

    container.style.height = `${containerSize}px`;
    container.style.width = `${containerSize}px`;
    

    As a side effect, the container size will change slightly, but the difference is negligible.

    This is the final result:

    let container = document.querySelector('.container');
    
    createGrid(16);
    
    function createGrid(gridDimensions) {
        let gridSize = 800;
        let gridPixelSize = Math.round(gridSize / gridDimensions);
        let containerSize = gridDimensions * gridPixelSize;
    
        for (let row = 1; row < (gridDimensions + 1); row++) {
            for (let column = 1; column < (gridDimensions + 1); column++) {
                let square = document.createElement('div');
                square.id = 'square-' + row + '-' + column;
                square.className = 'squares';
                square.style.height = `${gridPixelSize}px`;
                square.style.width = `${gridPixelSize}px`;
                container.appendChild(square);
            }
        }
        drawOnGrid(gridPixelSize);
    
        container.style.height = `${containerSize}px`;
        container.style.width = `${containerSize}px`;
    }
    
    function drawOnGrid(gridPixelSize) {
    
        let squares = document.querySelectorAll(".squares");
    
        squares.forEach((square) => {
            let squareDark = document.createElement("div");
            squareDark.style.height = `${gridPixelSize}px`;
            squareDark.style.width = `${gridPixelSize}px`;
            squareDark.style.backgroundColor = "black";
            let squareDarkOpacity = 0;
            squareDark.style.opacity = `${squareDarkOpacity}`;
            square.appendChild(squareDark);
    
            square.addEventListener("mouseover", (e) => {
                if(!hasColorBeenSet.includes(square)) {
                    hasColorBeenSet.push(square);
                    let randomColor = Math.floor(Math.random() * 16777215).toString(16);
                    square.style.backgroundColor = `#${randomColor}`;
                }
    
                squareDarkOpacity += 0.1;
                squareDark.style.opacity = `${squareDarkOpacity}`;
            });
        });
        let hasColorBeenSet = [];
    }
    
    function changeGridSize() {
        let validAnswer = false;
        while (!validAnswer) {
            let userInput = prompt('How big should the grid be? Enter a number between 8 to 100.');
            userInput = Number(userInput);
            userInput = Math.round(userInput);
    
            if (userInput >= 8 && userInput <= 100) {
                validAnswer = true;
                container.innerHTML = '';
                createGrid(userInput);
            }
        }
    }
    * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }
    
    body {
        height: 100vh;
        background-color: rgb(90, 101, 110);
        display: flex;
        flex-direction: column;
        gap: 50px;
        justify-content: center;
        align-items: center;
    }
    
    .container-border {
        border: 1px solid black;
    }
    
    .container {
        height: 800px;
        width: 800px;
        background-color: white;
        display: flex;
        flex-wrap: wrap;
    }
    
    .squares {
        border: 1px solid black;
    }
    
    button {
        font-size: 26px;
        padding: 10px 20px;
        border-radius: 5px;
    }
    
    button:hover {
        transition: 0.2s;
        background-color: orange;
        cursor: pointer;
    }
    <button onclick="changeGridSize()">Change grid size...</button>
    <div class="container-border">
        <div class="container"></div>
    </div>