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 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());
}
✍ For more posts:
https://yossiabramov.com/
Top comments (2)
Nice done!
(only a little advice, try to do it with canvas, it reduces very well performance requests :D, Merry Xmas 🎁 )
Thanks for the advice - will try that👌