## DEV Community # Hyperspace travel in JavaScript Canvas

We have seen cool hyperspace visuals in Starwars. Here we are going mimic the same using Javascript canvas.

So, first we create a canvas element in our html file and also a script tag to hold our javascript.

<body>
<canvas></canvas>
<script></script>
</body>


then in our script tag, we get the canvas element.

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");


## Stars Now lets define a blueprint for a Star. Each star has a location (x,y), radius, color and a draw function that draws the star on the canvas.

function Star(x,y,radius,color){
this.x = x;
this.y = y;
this.color = color;
this.draw = function (){
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fill();
}
}


lets create an array to hold the star objects and const to specify how many stars we create.

const NUMBER_OF_STARS = 1000;
let stars = [];


Lets write a function to create the stars, we use random locations so lets use a simple random number generator.


function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}

function createStars() {
for (let counter = 0; counter < NUMBER_OF_STARS; counter++) {
let color = "lightblue";
stars.push(
);
}
}


Another quick loop to draw them on the screen.

createStars();
stars.forEach(star => star.draw());


Creating stars with different radius makes it feel like few are far and few are near.

## Movement

By movie scenes we can see that when we travel at lightspeed, each star moves from it's location to the edge of the screen, but we need to calculate the angle or linepath.

To simplify this, we can assume a center point and if we draw a line from center to the star, that essentially is the path that star will follow until it reaches the edge of the screen. like below Now, we have two point for each line, 1st is the center of the screen and second point is the Star's location. Using the following formula, we can calculate the slope of the line, which we can use to move the star later.

$m = \frac {(y_2-y_1)} {(x_2-x_1)}$

so, lets add slope attribute to Star

function Star(x,y,radius,color, slope){
this.x = x;
this.y = y;
this.color = color;
this.slope = slope;
this.draw = function (){
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fill();
}
}


and define a function as follows:

function slopeFromCenter(x, y) {
let x1 = x;
let y1 = y;
let x2 = centerX;
let y2 = centerY;
return (y2 - y1) / (x2 - x1);
}


Now change the Star Object creation as follows.

stars.push(new Star(x, y, radius, color, slopeFromCenter(x, y)));


## Animation Loop

For the animation, we use requestAnimationFrame method which will be called once every frame, and to start & stop the travel using spacebar key, we will create a variable travel as follows.

let travel = null;


lets create a basic jump method too, will make changes to it later.

function jump() {

for (let i = 0; i < stars.length; i++) {
const star = stars[i];
ctx.beginPath();
ctx.moveTo(star.x, star.y);
// we will work these in a minute
const nextXY = [star.x + 1, star.y + 1];
const nextX = nextXY;
const nextY = nextXY;
ctx.lineTo(nextX, nextY);
ctx.strokeStyle = star.color;
ctx.stroke();
ctx.closePath();
star.x = nextX;
star.y = nextY;
}
}


All we are doing is simply adding 1 to star's xy coordinates.

let travel = null;
function animate() {
travel = requestAnimationFrame(animate);
jump();
}

if (event.key === " ") {
if (travel == null) {
travel = window.requestAnimationFrame(animate);
} else {
window.cancelAnimationFrame(travel);
travel = null;
}
}
}); We can see that stars are going to right bottom cornor as we are incrementing xy values by 1. Now lets do the actual movement code by defining a nextStop function as follows.

function nextStop(currentX, currentY, m, velocity) {
let s = velocity / Math.sqrt(1 + m * m);
let nextX = currentX + s;
let nextY = currentY + m * s;
return [Math.ceil(nextX), Math.ceil(nextY)];
}


this function simply takes (x,y), slope and velocity and returns a new coordinates, by adjusting the velocity we can adjust the travel speed too.

lets update the jump function to use nextStop function as follows.

function jump() {

for (let i = 0; i < stars.length; i++) {
const star = stars[i];
ctx.beginPath();
ctx.moveTo(star.x, star.y);
// update the following line
const nextXY = nextStop(star.x, star.y, star.slope, 20);
const nextX = nextXY;
const nextY = nextXY;
ctx.lineTo(nextX, nextY);
ctx.strokeStyle = star.color;
ctx.stroke();
ctx.closePath();
star.x = nextX;
star.y = nextY;
}
} ## but...

We can see, we got right half right but left half of the screen is in reverse... This is because the slope we calculated. To fix that update the slopeFromCenter function as follows.

function slopeFromCenter(x, y) {

let x1 = x;
let y1 = y;
let x2 = centerX;
let y2 = centerY;
let q = null; // Quadrant
let direction = 1;

if (x > centerX && y <= centerY) {
q = "Q1";
} else if (x <= centerX && y <= centerY) {
q = "Q2";
} else if (x <= centerX && y > centerY) {
q = "Q3";
} else if (x > centerX && y > centerY) {
q = "Q4";
}

if (q == "Q2" || q == "Q3") {
direction = -1;
}

return [(y2 - y1) / (x2 - x1), direction];
}


And update the createStars method as follows

function createStars() {
for (let counter = 0; counter < NUMBER_OF_STARS; counter++) {
let color = "white";
let slopeAndDirection = slopeFromCenter(x, y);
stars.push(
new Star(x, y, radius, slopeAndDirection, slopeAndDirection, color)
);
}
}


Also update the jump function to use the direction as well re-creating the stars at random location if they reach the edge of the screen. Instead of creating the new object we are simply changing it's location so it would feel like it's new star. It's better for memory & performance.

function jump() {
for (let i = 0; i < stars.length; i++) {
const star = stars[i];
if (
star.x <= 0 ||
star.x > canvas.width ||
star.y <= 0 ||
star.y > canvas.height
) {
star.x = Math.ceil(
);
star.y = Math.ceil(
);
const slopeAndDirection = slopeFromCenter(star.x, star.y);
star.slope = slopeAndDirection;
star.direction = slopeAndDirection;
}
ctx.beginPath();
ctx.moveTo(star.x, star.y);
const nextXY = nextStop(
star.x * star.direction,
star.y * star.direction,
star.slope,
20
);
const nextX = nextXY;
const nextY = nextXY;
ctx.lineTo(nextX * star.direction, nextY * star.direction);
ctx.strokeStyle = star.color;
ctx.stroke();
ctx.closePath();
star.x = nextX * star.direction;
star.y = nextY * star.direction;
}
}


Now, it will be working fine... but over the time screen will be filled complete... to have nice animation, we modify the animate function. Here using ctx.fillRect we are filling the screen with black transparent color, which prevents the screen from getting completely filled.

function animate() {
travel = requestAnimationFrame(animate);
ctx.fillStyle = "rgba(0,0,0,0.1)";
ctx.fillRect(0, 0, window.innerWidth, innerHeight);
jump();
}


The final result can be accessed here.
Hit Spacebar to start or stop,
+ key to increase speed,
- key to slowdown and enjoy the journey :)

The complete code with few other improvements are linked here

Thank you...