🍦 Vanilla JS Starry Night

🍦 Vanilla JS Starry Night

Published: 12/25/20204 min read
HTML
CSS
JS

In this post I would like to share with you a Vanilla JS demo of twinkling stars on a smooth black canvas. Basically, this kind of challenge involves dealing with random parameters like a star’s width, height, coordinates and twinkling duration. I think it is probably better to start with the relevant portion of the CSS for this small demo and work our way to the JS part. Here is a CodePen for this demo:

Here are some links for this demo:
👉 GitHub repo: https://github.com/yossi-abramov/starry-night
👉 GitHub page: https://yossi-abramov.github.io/starry-night/

CSS

.star {
  --twinkle-duration: "";
  --twinkle-delay: "";
  --star-size: "";
  position: absolute;
  width: var(--star-size);
  height: var(--star-size);
  background: #fff;
  border-radius: 50%;
  animation: twinkle infinite alternate;
  animation-duration: var(--twinkle-duration);
  animation-delay: var(--twinkle-delay);
}

@keyframes twinkle {
    from {
        transform: scale(1);
    }

    to {
        transform: scale(1.5);
        box-shadow: 0 0 5px 0.5px rgba(150, 150, 150, 0.6);
    }
}

I’ve created a .star class for my stars and initialised CSS variables in it. By scoping these variables to the .star class we can make these variables behave like “arguments”. That means we can set a value to our scoped CSS property on a given star element (Just think of all the cool things you can build with CSS variables!). Next, I’ve set a keyframe animation for the “twinkling” effect. As you can see, both animation-duration and animation-delay have a CSS variable assigned to them. As for the twinkle keyframe, it’s pretty straight forward: alternate infinitely between a scale of 1 and 1.5 of the star’s size and add a box-shadow for a soft glow effect.

JS

Now, let’s go over the JS portion of the code. As mentioned above, we need to deal with some random “star” properties. For that reason the first thing we need is a function that will generate a random number between a min and a max. For that purpose, we can use the mighty JS Math Object:

const genRandomNumber = (min, max) => {
    return Math.random() * (max - min) + min;
}

After setting up our genRandomNumber function we can move on to defining the boundaries of our microcosmos/canvas/night sky. Remember, we need to spread our stars randomly across a space. So first we need to select our parent and give our stars coordinates relative to the selected parent. In this case, I’ve selected the body element:

const $el = document.body;

Now, all we need to do is create an element, append a .star class to it and pass the required random CSS properties that we defined in our CSS. After our star element is created, we will simply append it to the parent element - body in this case. We will need to repeat this process x amount of times – so a simple for loop will do!

for (let index = 0; index < 1000; index++) {
    const star = document.createElement("div");
    star.classList.add("star");
    
    // Star coordinates
    let x = genRandomNumber(1, $el.offsetWidth);
    let y = genRandomNumber(1, $el.offsetHeight);

    star.style.left = Math.floor(x) + "px";
    star.style.top = Math.floor(y) + "px";
    
    star.style.setProperty(
        "--star-size",
        genRandomNumber(1, 3) + "px"
    );

    star.style.setProperty(
        "--twinkle-duration",
        Math.ceil(genRandomNumber(1, 5)) + "s"
    );

    star.style.setProperty(
        "--twinkle-delay",
        Math.ceil(genRandomNumber(1, 5)) + "s"
    );

    $el.append(star);
}

Let's refactor this code a bit, here is another suggestion:

// Night Sky element

const $el = document.body;

// Generate a random number between min and max values

const genRandomNumber = (min, max) => {
    return Math.random() * (max - min) + min;
}

// Generate a star <div>

const genStar = () => {
    const star = document.createElement("div");
    star.classList.add("star");

    // Gen star coordinates relative to $el size
    let x = genRandomNumber(1, $el.offsetWidth);
    let y = genRandomNumber(1, $el.offsetHeight);

    const { style } = star;
    
    style.left = Math.floor(x) + "px";
    style.top = Math.floor(y) + "px";

    style.setProperty(
        "--star-size",
        genRandomNumber(1, 3) + "px"
    );

    style.setProperty(
        "--twinkle-duration",
        Math.ceil(genRandomNumber(1, 5)) + "s"
    );

    style.setProperty(
        "--twinkle-delay",
        Math.ceil(genRandomNumber(1, 5)) + "s"
    );

    return star;
}

for (let index = 0; index < 800; index++) {
    $el.append(genStar());
}