Parallax Scrolling of Background Images in JavaScript

The code shown below implements animated parallax scrolling of three different levels in JavaScript.

To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript. You’ll also need to download the three 300×300-pixel image files named “level0.png”, “level1.png”, and “level2.png”, and place them in the same directory. Alternatively, you can make your own, but be aware that layers 0 and 1 will need to be mostly transparent.

Or, for an online version, visit http://thiscouldbebetter.neocities.org/parallaxscrolling.html.

ParallaxScrolling

Level0 Level1 Level2

<html>
<body>
<script type="text/javascript">

// main

function ScrollingTest()
{
	this.main = function()
	{
		var venue0 = new Venue
		(
			"Venue0",
			new Coords(300, 300), // viewSize
			new Coords(0, 0), // cameraPosInitial
			new Coords(2, 1), // cameraMovePerTick
			// backgroundLevels 
			[
				new BackgroundLevel
				(
					"level2.png", 
					new Coords(300, 300), 
					4
				),
				new BackgroundLevel
				(
					"level1.png", 
					new Coords(300, 300), 
					2
				),
				new BackgroundLevel
				(
					"level0.png", 
					new Coords(300, 300), 
					1
				),
			]
		);

		Globals.Instance.initialize
		(
			venue0,
			100 // millisecondsPerTick
		);
	}
}

// classes

function BackgroundLevel(imagePath, imageSize, depthDivisor)
{
	this.imagePath = imagePath;
	this.imageSize = imageSize;
	this.depthDivisor = depthDivisor;
}
{
	BackgroundLevel.prototype.toHTMLElement = function()
	{
		var returnValue = document.createElement("img");

		returnValue.src = this.imagePath;

		this.htmlElement = returnValue;

		return returnValue;
	}
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;

		return this;
	}

	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;

		return this;
	}

	Coords.prototype.modulo = function(other)
	{
		this.x = this.x % other.x;
		this.y = this.y % other.y;

		return this;
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;

		return this;
	}

	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;

		return this;
	}
}

function Globals()
{}
{
	Globals.Instance = new Globals();

	Globals.processTick = function()
	{
		Globals.Instance.venue.update();
	}

	Globals.prototype.initialize = function(venue, millisecondsPerTick)
	{
		this.venue = venue;

		document.body.appendChild(this.venue.toHTMLElement());

		setInterval(Globals.processTick, millisecondsPerTick);
	}

}

function Venue(name, viewSize, cameraPosInitial, cameraMovePerTick, backgroundLevels)
{
	this.name = name;
	this.backgroundLevels = backgroundLevels;
	this.viewSize = viewSize;
	this.cameraPos = cameraPosInitial;
	this.cameraMovePerTick = cameraMovePerTick;
}
{
	Venue.prototype.update = function()
	{
		this.cameraPos.add(this.cameraMovePerTick);
		this.updateHTMLElement();
	}

	// html

	Venue.prototype.toHTMLElement = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.viewSize.x;
		canvas.height = this.viewSize.y;
		canvas.graphics = canvas.getContext("2d");

		for (var i = 0; i < this.backgroundLevels.length; i++)
		{
			var backgroundLevel = this.backgroundLevels[i];

			backgroundLevel.toHTMLElement();
		}

		this.htmlElement = canvas;

		return canvas;
	}

	Venue.prototype.updateHTMLElement = function()
	{
		var graphics = this.htmlElement.graphics;

		var splitPos = new Coords(0, 0);
		var sizeMinusSplitPos = new Coords(0, 0);

		for (var i = 0; i < this.backgroundLevels.length; i++)
		{
			var backgroundLevel = this.backgroundLevels[i];

			splitPos.overwriteWith
			(
				this.cameraPos
			).divideScalar
			(
				backgroundLevel.depthDivisor
			).modulo
			(
				this.viewSize
			);

			sizeMinusSplitPos.overwriteWith
			(
				backgroundLevel.imageSize
			).subtract
			(
				splitPos
			);

			// northwest

			graphics.drawImage
			(
				backgroundLevel.htmlElement,
				// source pos
				splitPos.x, splitPos.y, 
				// source size
				sizeMinusSplitPos.x, sizeMinusSplitPos.y, 
				// destination pos
				0, 0, 
				// destination size
				sizeMinusSplitPos.x, sizeMinusSplitPos.y 
			);

			// northeast

			graphics.drawImage
			(
				backgroundLevel.htmlElement,
				// source pos
				0, splitPos.y, 
				// source size
				splitPos.x, sizeMinusSplitPos.y, 
				// destination pos
				sizeMinusSplitPos.x, 0, 
				// destination size
				splitPos.x, sizeMinusSplitPos.y 
			);

			// southwest

			graphics.drawImage
			(
				backgroundLevel.htmlElement,
				// source pos
				splitPos.x, 0, 
				// source size
				sizeMinusSplitPos.x, splitPos.y, 
				// destination pos
				0, sizeMinusSplitPos.y, 
				// destination size
				sizeMinusSplitPos.x, splitPos.y 
			);

			// southeast

			graphics.drawImage
			(
				backgroundLevel.htmlElement,
				// source pos
				0, 0, 
				// source size
				splitPos.x, splitPos.y, 
				// destination pos
				sizeMinusSplitPos.x, sizeMinusSplitPos.y, 
				// destination size
				splitPos.x, splitPos.y 
			);

			graphics.strokeStyle = "Cyan";
			graphics.strokeRect
			(
				0, 0, 
				this.htmlElement.width, 
				this.htmlElement.height
			);
			
		}
	}
}

// run

new ScrollingTest().main();

</script>
</body>
</html>

This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s