Horizontal starfield

Code

Horizontal starfield

  • Nils Thiebosch
  • 02-06-2023 08:24:38
  • 0 Comments

In the old days the most intro’s had a starfield, a logo, a scroller and something happening. Let’s build that starfield we loved so much in those days. .

Web development and browser power goes on and on, once we had to do animations with gif files now we can draw dynamically straight on the screen.
Browsers have an option in the html format, for a while now, called "canvas". The canvas is even fast enough to develop small games in it.

To draw an animation it is important that everything that you draw goes in a smooth transition. This only can be done if you have a trusted stable timer where you can count on. We need a minimum number of frames to be rendered. 

 

 

The Starfield effect,

Lets develop a starfield the same as done in the old commodore 64 and early amiga demos. Back then you developed a sprite that you manipulated. On the amiga I used my pixel draw routine. 

I am going to use a canvas. I request from the DOM its canvas and take the 2d context that I am going to use. On that I add a timer routine so I have a periodic interval to jump to and execute my drawing. I want it with a minimum of 25 frames per second. Why? Lets say its the minimum frame rate for the human eye to see stuff transitioning smoothly. 

Let’s start at the beginning

The timer is nothing more than a javascript: setInterval(timeFunction, timeInterval), that is a number of times called per second. 

I created the next html page.


<!DOCTYPE html>
<html lang="en">;
    <head>
        <meta charset="UTF-8">
        <title>Starfield</title>
        <script type="text/javascript" src="starfield.js"></script>
    
    <body onload="startStarField();">
        <canvas id="canvas" width="1200" height="480"></canvas>;
    </body>
</html>

This page is pretty basic but let me explain it anyway. When everything is loaded the onstart event of the body calls the function startStarField(). There we get the canvas element and take the 2d context from it. We store them in the global variables canvas and ctx.

Also we store the element canvas width and height we need later for setting up the data table. Then we validate if no other timer is running, if so we cancel that one and add our timer, called interruptFunction, to the interval function.

Draw something,

To create an animation effect we need to draw. So each frame we move some stuff around

We create a list, as shown below, with for each horizontal row a dot. Each dot has its own speed and it determines the size and color. The slower the dot goes, the darker it is to create a bit of depth. The faster the dot goes the bigger it is. We have 4 speeds. After each draw we move it with the given speed to the right and if the x position is reached the width of the canvas we reset it to 0.

This routine needs to be called before the timer starts so that the list is ready and we don't get any surprises.



let canvas;
let ctx;
let height;
let width;
let timeHolder = null;
const timeInterval = 40;
const stars = [];
function initStarField() {
    let speed = 1;
    const colors = ['#888888', '#bbbbbb', '#dddddd','#ffffff'];
    for( let i = 0; i < height; i++) {
        const star = [];
        star['x'] = parseInt( Math.random()*width );
        star['y'] = i;
        star['s'] = speed;
        star['c'] = colors[i - 1];
        stars[i] = star;
        speed++;
        if ( speed > 4 ) {
            speed = 1;
        }
    }
}

What happens here? 

It is a loop where we create a list with randomly picked x positions, we pick the color that belongs to the speed, we store the speed value too and we increase the speed + 1 and if reached max we reset it to 1.

The dot function makes it easier to read what is going on. You can decide to place these 2 rows of code into the main function. The function has default values and if called without parameters it will draw a white dot of 1 x 1 on position 1,1. (top left of your canvas)

 


const dot = (x=1, y=1, w=1, h=1, c='#ffffff') => {
    ctx.fillStyle = c;
    ctx.fillRect(x,y,w,h);
}

const clear = () => {
    ctx.fillStyle = '#000000';
    ctx.fillRect(1, 1, width, height);
}

And the interupt function and starter


const interruptFunction = () => {
    clear();
    for ( let i =0; i < stars.length-1; i++) {
        dot(
            stars[i]['x'], stars[i]['y'],
            stars[i]['s'], stars[i]['s'],
            stars[i]['c']
        );
        stars[i]['x'] += stars[i]['s'];
        if ( stars[i]['x'] >= width ) {
            stars[i]['x'] = 0;
        }
    }
}

const startStarField = () => {
    canvas = document.getElementById('canvas');
    width = canvas.width;
    height = canvas.height;
    ctx = canvas.getContext("2d");
    initStarField();

    if (timeHolder !== null) {
        clearInterval( timeHolder );
    }
    timeHolder = setInterval( interruptFunction, timeInterval)
}

What happens here

We start a loop that handles every item in the list.
We draw the “star”
We add the speed of the start to the x position of the same star.
We validate if the star is placed outside of the view, if so we reset the x position to 0;

Each action you do in a loop makes it heavier to draw. You could calculate the colors while drawing but that would have cost you cpu power and reduce the amount of stars you can move. There are always multiple ways to fix or do things, this is my way.

I know you could do different drawing methods with canvas and let canvas decide.

Download source
0 Comments

relevant Stories

ThreeJS logo

ThreeJS Terminology - The basics

  • Nils Thiebosch
  • 21-05-2023 07:54:35
  • 0 Comments

Since the late 80’s or early 90’s I have been fascinated with drawing 3d images and animations. This was in the time that a computer was still working on Mhz and not Ghz. And it was pre-internet so you had to research everything on your own&