How to Implement Infinite Parallax Scrolling Backgrounds in JavaScript
Using The Kaplay.js Game Library
Infinite parallax scrolling backgrounds are nice to look at because they add depth to a 2D scene. In this tutorial, I’ll teach you how to implement them in JavaScript with the Kaplay library.
This post assumes that you have basic knowledge of both JavaScript and the Kaplay game making library (Formerly known as Kaboom). If you’re interested in learning the library, you can head over to my YouTube channel where I have many project based tutorials or you can check the official docs.
Getting The Assets
You’ll find the assets we’re going to use here. Once downloaded, you’ll find a sub folder called “nature_5” which will contain all the layers needed. Import them into your Kaplay project like this :
const k = kaplay({
width: 576,
height: 324,
letterbox: true,
global: false,
});
k.loadSprite("sky", "./1.png");
k.loadSprite("clouds", "./2.png");
k.loadSprite("far-field", "./3.png");
k.loadSprite("near-field", "./4.png");
Each of the layers included in the asset pack has a width of 576px and a height of 324px. Since we want the background to take the whole canvas, I have made the width and height of our game 576x324. Using the letterbox property, the canvas will scale while retaining its aspect ratio at different screen sizes.
Let’s display the sky, but before that, let’s create a constant that will hold the width of our background. This will come in handy later when we will need to duplicate layers to achieve infinite scrolling. Add the following :
const IMAGE_WIDTH = 576;
k.add([k.sprite("sky"), k.pos(0, 0)]);
You should get the following result :
Implementing Infinite Scrolling
Let’s do it with only the “clouds” layer. Once done, adding other layers will become easy.
To make our code modular, let’s create an array of objects where each object represents a layer and will contain all the data needed to achieve infinite scrolling for that layer.
Add the following code :
const layers = [
{
speed: -20,
parts: [
k.add([k.sprite("clouds"), k.pos(0,0)]),
k.add([k.sprite("clouds"), k.pos(IMAGE_WIDTH, 0)]),
],
}
]
The “parts” property will contain two game objects. They display the same image. One starts at coordinates (0,0) while the other is placed right after the first one at coordinates (IMAGE_WIDTH, 0). Here is a schematic illustrating the situation.
The trick to implement infinite scrolling is to reposition the first image behind the second one once it leaves the screen. The second image becomes the first and the first becomes the second. The same logic is repeated indefinitely. Here is a schematic illustrating the situation.
Adding the following code will achieve infinite scrolling.
k.onUpdate(() => {
for (const layer of layers) {
if (layer.parts[1].pos.x < 0) {
layer.parts[0].moveTo(layer.parts[1].pos.x + IMAGE_WIDTH, 0);
layer.parts.push(layer.parts.shift());
}
layer.parts[0].move(layer.speed, 0);
layer.parts[1].move(layer.speed, 0);
}
});
We iterate through each layer. Currently we only have one. For each layer, we check if the first image has an x coordinate < 0 implying it’s no longer visible due to exceeding the left canvas bound. (In this example, we take for granted that our layers will move to the left. As an exercise, you could try implementing scrolling to the right instead.) Finally, if the first image is no longer visible, we reposition it behind the second image.
The second image will now be considered the first. That’s why we change the order of the images in the “parts” array by extracting the first element of the array by using shift() and then, adding it to the end of that array by using push().
Finally, we move both parts using the speed set in our layer object. Here is the result.
Implementing Parallax
Parallax is just multiple layers moving at different speeds. Modify your “layers” array to contain the remaining layers :
const layers = [
{
speed: -20,
parts: [
k.add([k.sprite("clouds"), k.pos(0, 0)]),
k.add([k.sprite("clouds"), k.pos(IMAGE_WIDTH, 0)]),
],
},
{
speed: -100,
parts: [
k.add([k.sprite("far-field"), k.pos(0, 0)]),
k.add([k.sprite("far-field"), k.pos(IMAGE_WIDTH, 0)]),
],
},
{
speed: -300,
parts: [
k.add([k.sprite("near-field"), k.pos(0, 0)]),
k.add([k.sprite("near-field"), k.pos(IMAGE_WIDTH, 0)]),
],
},
];
Tweak the speed properties of each layer to your taste. Here is the final result.
Next Steps
Thanks for reading! Hope this was useful. If you want more content like this. Consider subscribing.
Here are other posts you can read next :