skip to Main Content

I have made a simple to-do list, which works as intended; however, I am experiencing an issue where the background, which is a random gradient calculated and applied to the of my html on load through JS, sometimes does not apply at all. If you reload the codepen page (linked below) a few times you should see the issue, where instead of a gradient background there is no background at all. It sometimes takes 15+ reloads before it appears but it always inevitably does.

The code works as intended for the most part, but I would like to eliminate this issue altogether.

// Get elements
const inputBox = document.getElementById("input-box");
const listContainer = document.getElementById("list-container");

// Function to add a task
function addTask() {
    if (inputBox.value === '') {
        alert("You need to write something you silly goose.");
    } else {
        const li = document.createElement("li");
        li.innerHTML = inputBox.value;

        const span = document.createElement("span");
        span.innerHTML = "u00d7";
        li.appendChild(span);

        listContainer.appendChild(li);
    }

    inputBox.value = "";
    saveData();
}

// Event listener for listContainer
listContainer.addEventListener("click", function (e) {
    if (e.target.tagName === "LI") {
        e.target.classList.toggle("checked");
        saveData();
    } else if (e.target.tagName === "SPAN") {
        e.target.parentElement.remove();
        saveData();
    }
});

// Event listener for Enter key in inputBox
inputBox.addEventListener("keypress", function (event) {
    if (event.key === "Enter") {
        event.preventDefault();
        addTask();
    }
});

// Function to save data to local storage
function saveData() {
    localStorage.setItem("data", listContainer.innerHTML);
}

// Function to show tasks from local storage
function showTask() {
    listContainer.innerHTML = localStorage.getItem("data");
}

document.addEventListener("DOMContentLoaded", function () {
    requestAnimationFrame(function () {
        backgroundColor();
    });
});


const calculateContrast = (color1, color2) => {
    const luminance1 = calculateLuminance(color1);
    const luminance2 = calculateLuminance(color2);

    const lighterLuminance = Math.max(luminance1, luminance2);
    const darkerLuminance = Math.min(luminance1, luminance2);

    return (lighterLuminance + 0.05) / (darkerLuminance + 0.05);
};

const calculateLuminance = (color) => {
    const rgb = parseInt(color, 16);
    const r = (rgb >> 16) / 255;
    const g = ((rgb >> 8) & 0xff) / 255;
    const b = (rgb & 0xff) / 255;

    const gammaCorrect = (value) => value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);

    const sRGB = gammaCorrect(r) * 0.2126 + gammaCorrect(g) * 0.7152 + gammaCorrect(b) * 0.0722;

    return sRGB;
};

function backgroundColor() {
    const getRandomColor = () => Math.floor(Math.random() * 0xffffff).toString(16);

    const calculateAndSetBackground = () => {
        let color1, color2;

        do {
            color1 = getRandomColor();
            color2 = getRandomColor();
        } while (calculateContrast(color1, color2) < 4.5);

        document.body.style.background = `linear-gradient(to left top, #${color1}, #${color2})`;
    };

    // Ensure that showTask function is complete before setting the background
    showTask();

    // Call calculateAndSetBackground after showTask is complete
    calculateAndSetBackground();
}

I have gone through the syntax and couldn’t find any errors. I thought it might be a timing issue so I have tried modifing my JS code to ensure that the background is set only after the contrast calculation is complete but it doesn’t seem to help.

I tried timing fixes using:

  • a combination of the ‘window.onload’ event and ‘setTimeout’ to give the browser some extra time to fully render the page before applying the background

  • the defer attribute on the tag of my html in conjunction with the DOMContentLoaded event and requestAnimationFrame to ensure that the script is executed after the HTML has been parsed:

document.addEventListener("DOMContentLoaded", function () {
    requestAnimationFrame(function () {
        backgroundColor();
    });
});
  • Separating the background calculation and setting into a new function (calculateAndSetBackground). Also ensuring that the showTask function is called before setting the background, addressing any potential timing issues.

None of which have worked, making me think that it may not be a timing issue but some other issue I can’t place, though I can’t be sure. Possibly something to do with caching?

Here’s a link to Codepen

2

Answers


  1. Here your problem:

    Bug

    Sometime randomColor start with zero*. Then hex code pass to css is incorrect. You need some padding function to make sure color code is 6 digit hex color.

    // old
    document.body.style.background = `linear-gradient(to left top, #${color1}, #${color2})`;
    
    // new
    function pad(num, size) {
        num = num.toString();
        while (num.length < size) num = "0" + num;
        return num;
    }
    color1 = pad(color1, 6);
    color2 = pad(color2, 6);
    document.body.style.background = `linear-gradient(to left top, #${color1}, #${color2})`;
    
    Login or Signup to reply.
  2. The Problem

    You should have tested your colors’ output on the console!

    If you added the code below…

    function backgroundColor() {
      //...
      console.log(color1, color2);
      document.body.style.background = `linear-gradient(to left top, #${color1}, #${color2})`;
      //...
    }
    

    You will see the output once the bug happens. One of the hex colors is going to be 5 or 4 digits. That is, the console will log something like 90f67. So, the timing is the cause of your bug.

    Possible Solutions

    You have many solutions, by the way!

    First, you can do what @takid1412 suggested, add a padding function to make sure color code is 6 digit hex color.

    Second, you may use HSL colors as they really work will once trying to generated random colors. I always use this strategy since you have three spaces. The first one is a 360° dice-like color value called Hue, and the other two are Saturation and Lightness which are a % digit 0%-100%.

    function generateRandomColor() {
      return `hsl(${Math.random() * 360}deg, 100%, 50%)`;
    }
    

    Then, use the generateRandomColor() where needed!

    Minor Tip?

    I noticed that your code uses multiple ways of writing functions, the function name(){} and the most recent and better one, the const name = () => {}.

    I can’t recommend enough how useful the arrow functions can get later, once working with modules, so I heavily recommend that you stick with at all times, unless you have a good reason not to use it!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search