Twinkling night sky with shooting stars made in SVG

Have you seen the stars background effect on our start page? Here a quick post how we built it.


Here the result to get you excited:

See the Pen Shooting Star Base by Georgios (@gkey) on CodePen.

Also look at our startpage as long it's still there.

satellytes-sky

There are three parts you should notice. A night sky full of tiny stars, occasionally a shooting star and a parallax effect when you scroll. I will talk about how we created those effects. We use JavaScript to create them but you can actually follow without writing JavaScript but this will involve some manual labour. The parallax effect won't work without JavaScript as we need to read the scroll position.

Night Sky

The night sky is a set of tiny SVG circles. They are created from an array of random coordinates. We had no need of randomness during runtime so it's a static list that looks like this:

const STAR_COORDS = [
  {
    "x": 1596,
    "y": 578
  },
  {
    "x": 609,
    "y": 379
  },
  //...
]

To create the actuals stars, iterate of this list, create a group (to hold the translation) and your actual circle element inside. Assign the CSS class .star so we can modify it later with CSS. To create any SVG element with JS you need to use document.createElementNS which looks pretty long combined with the involved namespace. This always looks uncomfortable to me — so you're not alone.

const svgElement = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
svgElement.setAttribute('viewbox', `0 0 2000 1000`);
svgElement.setAttribute('preserveaspectratio', `xMinYMin slice`);
svgElement.setAttribute('width', `100%`);
svgElement.setAttribute('height', `100%`);

document.body.appendChild(svgElement);

STAR_COORDS.forEach(({x, y}, index) => {
	//star circle shape
	const star = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
	star.setAttribute('r', 1);
	star.setAttribute('fill', 'white');
	star.classList.add('star');

	// group to hold the translation
	const starTranslate = document.createElementNS("http://www.w3.org/2000/svg", 'g');
	starTranslate.setAttribute('transform', `translate(${x} ${y})`);
	starTranslate.appendChild(star);

	// add it your existing svg element.
   svgElement.appendChild(starTranslate);
})

You can of course create all the stars manually. In the end it will look like this.

<svg id="svgCanvas" viewbox="0 0 2000 1000" preserveaspectratio="xMinYMin slice" width="100%" >
	<g transform="translate(100 200)">
		<circle class="star"  r="1" fill="white"></circle>
	</g>
	<g transform="translate(300 400)">
		<circle class="star" r="1" fill="white"></circle>
	</g>
<svg>
<!-- ... -->

To simulate the twinkle effect that is caused by our atmosphere we use two CSS animations pulseAnimation and glowingAnimation in combination.

Pulse

The pulse animation makes the star larger and smaller by animating its scale3d transformation.

@keyframes pulseAnimation {
  0% {
    transform: scale3d(1, 1, 1);
  }

  30% {
    transform: scale3d(1.2, 1.2, 1.2);
  }

  70% {
    transform: scale3d(0.2, 0.2, 0.2);
  }

  100% {
    transform: scale3d(1, 1, 1);
  }
}

Glow

The glow animation is interesting, because we use CSS variables inside the animation to change the maximum brightness of our stars with JavaScript. That way we maintain a single CSS animation definition while having slightly different characteristics. That's a powerful technique we will use more extensively for other parts of our night sky later.

@keyframes glowingAnimation {
  0% {
    opacity: var(--star-brightness, 1);
  }

  100% {
    opacity: calc(var(--star-brightness, 1) * 0.5);
  }
}

If you don't know CSS variables (actually the standard calls them Custom Properties), Mozilla has a pretty good article about it.

Put everything together and twinkle

Although the star circle shape is a SVG element it can receive CSS properties as any other DOM element. We assign our two animations through CSS and again use some CSS variables to easily change their appearance during runtime with JS. That's important because we need them to randomly glow and scale so they appear more natural.

.star {
	animation:
	    pulseAnimation var(--star-animation-duration, 3000ms) infinite backwards,
	    glowingAnimation var(--star-animation-glow-duration, 5000ms) infinite alternate backwards;

	  animation-delay: var(--star-animation-delay, 0);

}

You can now assign different values for --star-animation-duration when you create your stars with JavaScript.

starElement.style.setProperty('--star-animation-delay', `${delay}ms`);
...

If you are not using JavaScript you can still assign different values to the CSS variables. An idiomatic way would be using the :nth-child() pseudo class to assign different values per star.

.star:nth-child(1) {
 --star-animation-delay: 1s;
 --star-animation-duration: 3s;
 --star-animation-glow-duration: 4s;
}

.star:nth-child(2) {
/*...*/

Randomly created SVG circles, two different animations for glowing and scaling and the power of CSS variables to randomize the animations just made you a beautiful and calming night sky.

Here a preview on CodePen:

See the Pen Night Sky by Georgios (@gkey) on CodePen.

Let's put some action in there by creating shooting stars.

Shooting Stars

We use a simple technique to create our shooting stars. It's a small circle shape visually moving on an ellipsis which can be simplified by using a circle path.

We start with a similiar structure as with the stars. A group to translate and the circle shape itself.

<g transform="translate(300 400)">
	<circle class="shooting-star" r="1" fill="white"/>
</g>

The star should rotate around its center with an CSS animation (that spares us another group). You can't combine both on a single element as the translation would interfere with the rotation settings.

We can now apply a rotation to the star to simulate the required circular path. We only need a small part of a full circle so we animate from 360deg to 270deg (feel free to try other values). We use linear for the animation so the distance travelled is constant — we don't want any easing.

@keyframes orbitAnimation {
  0% {
    transform: rotate3d(0, 0, 1, 360deg);
  }
  100% {
    transform: rotate3d(0, 0, 1, 270deg);
  }
}

.shooting-star {
  animation: 5s orbitAnimation infinite linear;
}

You won't see the circle rotating at this point. But when you offset its center with transform-origin you can make the star rotate on any radius you choose. Here an example with the radius of 125px. The yellow shape is there helping visualizing the radius only.

See the Pen Shooting Star Base by Georgios (@gkey) on CodePen.

To offset the star properly use transform-origin together with a translate. The translate will produce the necessary offset, the negative transform origin of the same distance moves the rotation center back to the original position of the circle (in the very center).

@keyframes orbitAnimation {
  0% {
    transform: translate(-125px, 0) rotate3d(0, 0, 1, 360deg);
  }
  100% {
    transform: translate(-125px, 0) rotate3d(0, 0, 1, 270deg);
  }
}

.shooting-star {
  transform-origin: 125px 0;
  animation: 5s orbitAnimation infinite linear;
}

Now we have the star rotating. Let's add some fading so it can appear and disappear smoothly.

@keyframes shootStarGlow {
  0% {
    opacity: 0
  }

  50%, 50% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

.shooting-star {
  transform-origin: 125px 0;
  animation: 5s orbitAnimation infinite linear, 5s shootStarGlow infinite;
}

See the Pen Shooting Star Base by Georgios (@gkey) on CodePen.

Place that several times in your svg, use different radius, from/to angles in the animation and start the animations randomly. That's your random shooting star collection.

Parallax

You can create a parallax effect with your night sky. Just wrap every star in an additional group. That group can use again the power of CSS variables to calculate its translation dependent on the current scroll position of the page. If you set different depths (to distinguish our parallax layers). Our calculations look like the following code.

.star-parallax {
  transform: translate(0, calc(-1px * var(--translateScrollY) * var(--parallax-intensity, 200) * 1/var(--parallax-depth, 1) ));
transition: transform .1s;
will-change: transition;
}

The transform boils down to this formular: -1px * translateScrollY * parallaxIntensity * 1/parallaxDepth which means:

  • Make it negative and convert everything to pixels (-1px).Tt should move inverse to the scroll movement and we have only numbers in the other variables so we need to tell that we want pixels.
  • Multiply with the normalized scroll position translateScrollY (0 - 1) where 0 is top and 1 is bottom.
  • Multiply again, with the maximum translation parallaxIntensity of a layer (200 for example)
  • Multiply a last time, with the inverted depth 1/parallaxDepth (1/1, 1/2, 1/3). Deeper layers move slower that's why a layer very deep (say 4) will move slower than the foremost layer. The total translation of such a layer would be 1/4 * parallaxIntensity.

We use the project basicScroll to map the current scroll position to the CSS variable translateScrollY.

Result

You can see the full version currently on our start page (try pressing alt + d being there 🤫), on glitch and also on CodePen showing both effects with parallax combined.

See the Pen Shooting Star Base by Georgios (@gkey) on CodePen.

React

Everything you see here can be created in Vanilla JS but as we are using Gatsby with React we created our Night Sky with shooting stars with the help of styled-component.

You can the see the files in this folder on our satellytes github repository.

Thank you

Thank you for reading I hope you had some fun with SVGs.

Aktualisiert: 27.Okt. 2018,  Erstellt: 27.Okt. 2018