Playing a Tiled Image as an Animation in JavaScript

The JavaScript code below, when run, prompts the user to specify an image file, that image’s size in frames, and the number of frames per second. When the “Play” button is clicked, the frames within the main image are displayed one after the other to create an animation.

I have also added a line (which is currently commented out) that shows how this functionality might be used “inline” in a web page, without going through a user interface. This works by invoking the ImageAsAnimation constructor from the onload event of a hidden “img” element nested inside a “div” element, and appending the returned canvas to the parent div.

To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript.

NumberSequence3x2

ImageAsAnimation


<html>
<body>

<!-- inline usage demo -->
<!-- <div><img style="display:none" src="NumberSequence3x2.png" onload="this.parentElement.appendChild(new ImageAsAnimation(this, new Coords(3, 2), 1))" /></div> -->

<!-- user interface -->

	<div>
		<label>Image:</label>
		<input id="inputImage" type="file" onchange="inputImageChanged(this);" />
	</div>

	<img id="imgPreview"/>

	<div>
		<label>Size in Frames:</label>
		<input id="inputSizeInFramesX" type="number" value="3" />
		x
		<input id="inputSizeInFramesY" type="number" value="2" />
	</div>

	<div>
		<label>Frames per Second:</label>
		<input id="inputFramesPerSecond" type="number" value="1" />
	</div>

	<button id="buttonPlay" onclick="buttonPlayClicked();">Play</button>

<script type="text/javascript">

function inputImageChanged(inputImage)
{
	var filesSelected = inputImage.files;
	if (filesSelected.length > 0)
	{
		var fileToLoad = filesSelected[0];

		if (fileToLoad.type.match("image.*"))
		{
			var fileReader = new FileReader();
			fileReader.onload = function(fileLoadedEvent) 
			{
				var imageLoaded = document.getElementById("imgPreview");
				imageLoaded.src = fileLoadedEvent.target.result;
			};
			fileReader.readAsDataURL(fileToLoad);
		}
	}	
}

function buttonPlayClicked()
{
	var image = document.getElementById("imgPreview");
	var inputSizeInFramesX = document.getElementById("inputSizeInFramesX");
	var inputSizeInFramesY = document.getElementById("inputSizeInFramesY");

	sizeInFrames = new Coords
	(
		parseInt(inputSizeInFramesX.value),
		parseInt(inputSizeInFramesY.value)
	);
	var framesPerSecond = parseInt(inputFramesPerSecond.value);

	document.body.appendChild
	(	
		new ImageAsAnimation(image, sizeInFrames, framesPerSecond)
	);
}

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

	Coords.prototype.divide = function(other)
	{
		this.x /= other.x;
		this.y /= other.y;
		return this;
	}

	Coords.prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;
		return this;
	}
}



function ImageAsAnimation(image, sizeInFrames, framesPerSecond)
{
	this.image = image;
	this.sizeInFrames = sizeInFrames;
	var millisecondsPerFrame = 1000 / framesPerSecond;

	var imageSize = new Coords
	(
		image.width,
		image.height
	);

	this.frameSize = imageSize.clone().divide
	(
		this.sizeInFrames
	);

	this.numberOfImages = 
		this.sizeInFrames.x
		* this.sizeInFrames.y;

	this.canvas = document.createElement("canvas");
	this.canvas.width = this.frameSize.x;
	this.canvas.height = this.frameSize.y;
	this.graphics = this.canvas.getContext("2d");

	this.frameIndexCurrent = 0;	

	this.showFrameNext();
	setInterval(this.showFrameNext.bind(this), millisecondsPerFrame);

	return this.canvas;
}
{
	ImageAsAnimation.prototype.showFrameNext = function()
	{
		var frameOffset = new Coords
		(
			this.frameIndexCurrent % this.sizeInFrames.x,
			Math.floor(this.frameIndexCurrent / this.sizeInFrames.x)
		);

		var framePos = frameOffset.multiply
		(
			this.frameSize
		);

		this.graphics.drawImage
		(
			this.image, 
			framePos.x, framePos.y, // source pos
			this.frameSize.x, this.frameSize.y, // source size
			0, 0, // destination pos
			this.frameSize.x, this.frameSize.y // destination size
		);

		this.frameIndexCurrent++;
		if (this.frameIndexCurrent >= this.numberOfImages)
		{
			this.frameIndexCurrent = 0;
		}
	}
}

</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