<html>
<head>
<title>Space Shooter Demo</title>
<style>
canvas {
position: absolute;
top: 0px;
left: 0px;
background: transparent;
}
#background {
z-index: -2;
}
#main {
z-index: -1;
}
#ship {
z-index: 0;
}
.score {
position: absolute;
top: 5px;
left: 480px;
color: #FF7F00;
font-family: Helvetica, sans-serif;
cursor: default;
}
.game-over {
position: absolute;
top: 100px;
left: 210px;
color: #FF7F00;
font-family: Helvetica, sans-serif;
font-size: 30px;
cursor: default;
display: none;
}
.game-over span {
font-size: 20px;
cursor: pointer;
position: relative;
left: 50px;
}
.game-over span:hover {
color: #FFD700;
}
.loading {
position: absolute;
top: 100px;
left: 210px;
color: #FF7F00;
font-family: Helvetica, sans-serif;
font-size: 30px;
cursor: default;
}
</style>
</head>
<body>
<!-- The canvas for the panning background -->
<canvas id="background" width="600" height="360">
Your browser does not support canvas. Please try again with a different browser.
</canvas>
<!-- The canvas for all enemy ships and bullets -->
<canvas id="main" width="600" height="360">
</canvas>
<!-- The canvas the ship uses (can only move up
one forth the screen. -->
<canvas id="ship" width="600" height="360">
</canvas>
<div class="score">SCORE: <span id="score"></span></div>
<div class="game-over" id="game-over">GAME OVER<p><span onclick="game.restart()">Restart</span></p></div>
<div class="loading" id="loading">LOADING...<p>Please wait</p></div>
<script src="space_shooter_part_five.js"></script>
</body>
</html>
/***************
* PART FIVE - Finishing touches
***************/
/* NOTES TO REMEMBER
* Could add
* - hitboxes to all objects to make collision better
* - levels
* - bosses
* - explosions / particles
* - parallax background
* - vectors for movement
* - lirbraries! http://www.createjs.com/#!/CreateJS
*/
/* RESOURCES
* http://www.w3schools.com/html5/html5_ref_av_dom.asp
* http://www.superflashbros.net/as3sfxr/
*/
/**
* Initialize the Game and start it.
*/
var game = new Game();
function init() {
game.init();
}
/**
* Define an object to hold all our images for the game so images
* are only ever created once. This type of object is known as a
* singleton.
*/
var imageRepository = new function() {
// Define images
this.background = new Image();
this.spaceship = new Image();
this.bullet = new Image();
this.enemy = new Image();
this.enemyBullet = new Image();
// Ensure all images have loaded before starting the game
var numImages = 5;
var numLoaded = 0;
function imageLoaded() {
numLoaded++;
if (numLoaded === numImages) {
window.init();
}
}
this.background.onload = function() {
imageLoaded();
};
this.spaceship.onload = function() {
imageLoaded();
};
this.bullet.onload = function() {
imageLoaded();
};
this.enemy.onload = function() {
imageLoaded();
};
this.enemyBullet.onload = function() {
imageLoaded();
};
// Set images src
this.background.src = "http://upic.me/i/1i/wyfbg.png";
this.spaceship.src = "http://s0.uploads.im/OFbcY.png";
this.bullet.src = "http://s0.uploads.im/iM7jY.png";
this.enemy.src = "http://uploads.im/0rkyj.png";
this.enemyBullet.src = "http://s0.uploads.im/xNzET.png";
}
/**
* Creates the Drawable object which will be the base class for
* all drawable objects in the game. Sets up defualt variables
* that all child objects will inherit, as well as the defualt
* functions.
*/
function Drawable() {
this.init = function(x, y, width, height) {
// Defualt variables
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
this.speed = 0;
this.canvasWidth = 0;
this.canvasHeight = 0;
this.collidableWith = "";
this.isColliding = false;
this.type = "";
// Define abstract function to be implemented in child objects
this.draw = function() {
};
this.move = function() {
};
this.isCollidableWith = function(object) {
return (this.collidableWith === object.type);
};
}
/**
* Creates the Background object which will become a child of
* the Drawable object. The background is drawn on the "background"
* canvas and creates the illusion of moving by panning the image.
*/
function Background() {
this.speed = 1; // Redefine speed of the background for panning
// Implement abstract function
this.draw = function() {
// Pan background
this.y += this.speed;
//this.context.clearRect(0,0, this.canvasWidth, this.canvasHeight);
this.context.drawImage(imageRepository.background, this.x, this.y);
// Draw another image at the top edge of the first image
this.context.drawImage(imageRepository.background, this.x, this.y - this.canvasHeight);
// If the image scrolled off the screen, reset
if (this.y >= this.canvasHeight)
this.y = 0;
};
}
// Set Background to inherit properties from Drawable
Background.prototype = new Drawable();
/**
* Creates the Bullet object which the ship fires. The bullets are
* drawn on the "main" canvas.
*/
function Bullet(object) {
this.alive = false; // Is true if the bullet is currently in use
var self = object;
/*
* Sets the bullet values
*/
this.spawn = function(x, y, speed) {
this.x = x;
this.y = y;
this.speed = speed;
this.alive = true;
};
/*
* Uses a "drity rectangle" to erase the bullet and moves it.
* Returns true if the bullet moved of the screen, indicating that
* the bullet is ready to be cleared by the pool, otherwise draws
* the bullet.
*/
this.draw = function() {
this.context.clearRect(this.x-1, this.y-1, this.width+2, this.height+2);
this.y -= this.speed;
if (this.isColliding) {
return true;
}
else if (self === "bullet" && this.y <= 0 - this.height) {
return true;
}
else if (self === "enemyBullet" && this.y >= this.canvasHeight) {
return true;
}
else {
if (self === "bullet") {
this.context.drawImage(imageRepository.bullet, this.x, this.y);
}
else if (self === "enemyBullet") {
this.context.drawImage(imageRepository.enemyBullet, this.x, this.y);
}
return false;
}
};
/*
* Resets the bullet values
*/
this.clear = function() {
this.x = 0;
this.y = 0;
this.speed = 0;
this.alive = false;
this.isColliding = false;
};
}
Bullet.prototype = new Drawable();
/**
* QuadTree object.
*
* The quadrant indexes are numbered as below:
* |
* 1 | 0
* ----+----
* 2 | 3
* |
*/
function QuadTree(boundBox, lvl) {
var maxObjects = 10;
this.bounds = boundBox || {
x: 0,
y: 0,
width: 0,
height: 0
};
var objects = [];
this.nodes = [];
var level = lvl || 0;
var maxLevels = 5;
/*
* Clears the quadTree and all nodes of objects
*/
this.clear = function() {
objects = [];
for (var i = 0; i < this.nodes.length; i++) {
this.nodes[i].clear();
}
this.nodes = [];
};
/*
* Get all objects in the quadTree
*/
this.getAllObjects = function(returnedObjects) {
for (var i = 0; i < this.nodes.length; i++) {
this.nodes[i].getAllObjects(returnedObjects);
}
for (var i = 0, len = objects.length; i < len; i++) {
returnedObjects.push(objects[i]);
}
return returnedObjects;
};
/*
* Return all objects that the object could collide with
*/
this.findObjects = function(returnedObjects, obj) {
if (typeof obj === "undefined") {
console.log("UNDEFINED OBJECT");
return;
}
var index = this.getIndex(obj);
if (index != -1 && this.nodes.length) {
this.nodes[index].findObjects(returnedObjects, obj);
}
for (var i = 0, len = objects.length; i < len; i++) {
returnedObjects.push(objects[i]);
}
return returnedObjects;
};
/*
* Insert the object into the quadTree. If the tree
* excedes the capacity, it will split and add all
* objects to their corresponding nodes.
*/
this.insert = function(obj) {
if (typeof obj === "undefined") {
return;
}
if (obj instanceof Array) {
for (var i = 0, len = obj.length; i < len; i++) {
this.insert(obj[i]);
}
return;
}
if (this.nodes.length) {
var index = this.getIndex(obj);
// Only add the object to a subnode if it can fit completely
// within one
if (index != -1) {
this.nodes[index].insert(obj);
return;
}
}
objects.push(obj);
// Prevent infinite splitting
if (objects.length > maxObjects && level < maxLevels) {
if (this.nodes[0] == null) {
this.split();
}
var i = 0;
while (i < objects.length) {
var index = this.getIndex(objects[i]);
if (index != -1) {
this.nodes[index].insert((objects.splice(i,1))[0]);
}
else {
i++;
}
}
}
};
/*
* Determine which node the object belongs to. -1 means
* object cannot completely fit within a node and is part
* of the current node
*/
this.getIndex = function(obj) {
var index = -1;
var verticalMidpoint = this.bounds.x + this.bounds.width / 2;
var horizontalMidpoint = this.bounds.y + this.bounds.height / 2;
// Object can fit completely within the top quadrant
var topQuadrant = (obj.y < horizontalMidpoint && obj.y + obj.height < horizontalMidpoint);
// Object can fit completely within the bottom quandrant
var bottomQuadrant = (obj.y > horizontalMidpoint);
// Object can fit completely within the left quadrants
if (obj.x < verticalMidpoint &&
obj.x + obj.width < verticalMidpoint) {
if (topQuadrant) {
index = 1;
}
else if (bottomQuadrant) {
index = 2;
}
}
// Object can fix completely within the right quandrants
else if (obj.x > verticalMidpoint) {
if (topQuadrant) {
index = 0;
}
else if (bottomQuadrant) {
index = 3;
}
}
return index;
};
/*
* Splits the node into 4 subnodes
*/
this.split = function() {
// Bitwise or [html5rocks]
var subWidth = (this.bounds.width / 2) | 0;
var subHeight = (this.bounds.height / 2) | 0;
this.nodes[0] = new QuadTree({
x: this.bounds.x + subWidth,
y: this.bounds.y,
width: subWidth,
height: subHeight
}, level+1);
this.nodes[1] = new QuadTree({
x: this.bounds.x,
y: this.bounds.y,
width: subWidth,
height: subHeight
}, level+1);
this.nodes[2] = new QuadTree({
x: this.bounds.x,
y: this.bounds.y + subHeight,
width: subWidth,
height: subHeight
}, level+1);
this.nodes[3] = new QuadTree({
x: this.bounds.x + subWidth,
y: this.bounds.y + subHeight,
width: subWidth,
height: subHeight
}, level+1);
};
}
/**
* Custom Pool object. Holds Bullet objects to be managed to prevent
* garbage collection.
* The pool works as follows:
* - When the pool is initialized, it popoulates an array with
* Bullet objects.
* - When the pool needs to create a new object for use, it looks at
* the last item in the array and checks to see if it is currently
* in use or not. If it is in use, the pool is full. If it is
* not in use, the pool "spawns" the last item in the array and
* then pops it from the end and pushed it back onto the front of
* the array. This makes the pool have free objects on the back
* and used objects in the front.
* - When the pool animates its objects, it checks to see if the
* object is in use (no need to draw unused objects) and if it is,
* draws it. If the draw() function returns true, the object is
* ready to be cleaned so it "clears" the object and uses the
* array function splice() to remove the item from the array and
* pushes it to the back.
* Doing this makes creating/destroying objects in the pool
* constant.
*/
function Pool(maxSize) {
var size = maxSize; // Max bullets allowed in the pool
var pool = [];
this.getPool = function() {
var obj = [];
for (var i = 0; i < size; i++) {
if (pool[i].alive) {
obj.push(pool[i]);
}
}
return obj;
}
/*
* Populates the pool array with the given object
*/
this.init = function(object) {
if (object == "bullet") {
for (var i = 0; i < size; i++) {
// Initalize the object
var bullet = new Bullet("bullet");
bullet.init(0,0, imageRepository.bullet.width,
imageRepository.bullet.height);
bullet.collidableWith = "enemy";
bullet.type = "bullet";
pool[i] = bullet;
}
}
else if (object == "enemy") {
for (var i = 0; i < size; i++) {
var enemy = new Enemy();
enemy.init(0,0, imageRepository.enemy.width,
imageRepository.enemy.height);
pool[i] = enemy;
}
}
else if (object == "enemyBullet") {
for (var i = 0; i < size; i++) {
var bullet = new Bullet("enemyBullet");
bullet.init(0,0, imageRepository.enemyBullet.width,
imageRepository.enemyBullet.height);
bullet.collidableWith = "ship";
bullet.type = "enemyBullet";
pool[i] = bullet;
}
}
};
/*
* Grabs the last item in the list and initializes it and
* pushes it to the front of the array.
*/
this.get = function(x, y, speed) {
if(!pool[size - 1].alive) {
pool[size - 1].spawn(x, y, speed);
pool.unshift(pool.pop());
}
};
/*
* Used for the ship to be able to get two bullets at once. If
* only the get() function is used twice, the ship is able to
* fire and only have 1 bullet spawn instead of 2.
*/
this.getTwo = function(x1, y1, speed1, x2, y2, speed2) {
if(!pool[size - 1].alive && !pool[size - 2].alive) {
this.get(x1, y1, speed1);
this.get(x2, y2, speed2);
}
};
/*
* Draws any in use Bullets. If a bullet goes off the screen,
* clears it and pushes it to the front of the array.
*/
this.animate = function() {
for (var i = 0; i < size; i++) {
// Only draw until we find a bullet that is not alive
if (pool[i].alive) {
if (pool[i].draw()) {
pool[i].clear();
pool.push((pool.splice(i,1))[0]);
}
}
else
break;
}
};
}
/**
* Create the Ship object that the player controls. The ship is
* drawn on the "ship" canvas and uses dirty rectangles to move
* around the screen.
*/
function Ship() {
this.speed = 3;
this.bulletPool = new Pool(30);
var fireRate = 15;
var counter = 0;
this.collidableWith = "enemyBullet";
this.type = "ship";
this.init = function(x, y, width, height) {
// Defualt variables
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.alive = true;
this.isColliding = false;
this.bulletPool.init("bullet");
}
this.draw = function() {
this.context.drawImage(imageRepository.spaceship, this.x, this.y);
};
this.move = function() {
counter++;
// Determine if the action is move action
if (KEY_STATUS.left || KEY_STATUS.right ||
KEY_STATUS.down || KEY_STATUS.up) {
// The ship moved, so erase it's current image so it can
// be redrawn in it's new location
this.context.clearRect(this.x, this.y, this.width, this.height);
// Update x and y according to the direction to move and
// redraw the ship. Change the else if's to if statements
// to have diagonal movement.
if (KEY_STATUS.left) {
this.x -= this.speed
if (this.x <= 0) // Kep player within the screen
this.x = 0;
} else if (KEY_STATUS.right) {
this.x += this.speed
if (this.x >= this.canvasWidth - this.width)
this.x = this.canvasWidth - this.width;
} else if (KEY_STATUS.up) {
this.y -= this.speed
if (this.y <= this.canvasHeight/4*3)
this.y = this.canvasHeight/4*3;
} else if (KEY_STATUS.down) {
this.y += this.speed
if (this.y >= this.canvasHeight - this.height)
this.y = this.canvasHeight - this.height;
}
}
// Redraw the ship
if (!this.isColliding) {
this.draw();
}
else {
this.alive = false;
game.gameOver();
}
if (KEY_STATUS.space && counter >= fireRate && !this.isColliding) {
this.fire();
counter = 0;
}
};
/*
* Fires two bullets
*/
this.fire = function() {
this.bulletPool.getTwo(this.x+6, this.y, 3,
this.x+33, this.y, 3);
game.laser.get();
};
}
Ship.prototype = new Drawable();
/**
* Create the Enemy ship object.
*/
function Enemy() {
var percentFire = .01;
var chance = 0;
this.alive = false;
this.collidableWith = "bullet";
this.type = "enemy";
/*
* Sets the Enemy values
*/
this.spawn = function(x, y, speed) {
this.x = x;
this.y = y;
this.speed = speed;
this.speedX = 0;
this.speedY = speed;
this.alive = true;
this.leftEdge = this.x - 90;
this.rightEdge = this.x + 90;
this.bottomEdge = this.y + 140;
};
/*
* Move the enemy
*/
this.draw = function() {
this.context.clearRect(this.x-1, this.y, this.width+1, this.height);
this.x += this.speedX;
this.y += this.speedY;
if (this.x <= this.leftEdge) {
this.speedX = this.speed;
}
else if (this.x >= this.rightEdge + this.width) {
this.speedX = -this.speed;
}
else if (this.y >= this.bottomEdge) {
this.speed = 1.5;
this.speedY = 0;
this.y -= 5;
this.speedX = -this.speed;
}
if (!this.isColliding) {
this.context.drawImage(imageRepository.enemy, this.x, this.y);
// Enemy has a chance to shoot every movement
chance = Math.floor(Math.random()*101);
if (chance/100 < percentFire) {
this.fire();
}
return false;
}
else {
game.playerScore += 10;
game.explosion.get();
return true;
}
};
/*
* Fires a bullet
*/
this.fire = function() {
game.enemyBulletPool.get(this.x+this.width/2, this.y+this.height, -2.5);
};
/*
* Resets the enemy values
*/
this.clear = function() {
this.x = 0;
this.y = 0;
this.speed = 0;
this.speedX = 0;
this.speedY = 0;
this.alive = false;
this.isColliding = false;
};
}
Enemy.prototype = new Drawable();
/**
* Creates the Game object which will hold all objects and data for
* the game.
*/
function Game() {
/*
* Gets canvas information and context and sets up all game
* objects.
* Returns true if the canvas is supported and false if it
* is not. This is to stop the animation script from constantly
* running on browsers that do not support the canvas.
*/
this.init = function() {
// Get the canvas elements
this.bgCanvas = document.getElementById('background');
this.shipCanvas = document.getElementById('ship');
this.mainCanvas = document.getElementById('main');
// Test to see if canvas is supported. Only need to
// check one canvas
if (this.bgCanvas.getContext) {
this.bgContext = this.bgCanvas.getContext('2d');
this.shipContext = this.shipCanvas.getContext('2d');
this.mainContext = this.mainCanvas.getContext('2d');
// Initialize objects to contain their context and canvas
// information
Background.prototype.context = this.bgContext;
Background.prototype.canvasWidth = this.bgCanvas.width;
Background.prototype.canvasHeight = this.bgCanvas.height;
Ship.prototype.context = this.shipContext;
Ship.prototype.canvasWidth = this.shipCanvas.width;
Ship.prototype.canvasHeight = this.shipCanvas.height;
Bullet.prototype.context = this.mainContext;
Bullet.prototype.canvasWidth = this.mainCanvas.width;
Bullet.prototype.canvasHeight = this.mainCanvas.height;
Enemy.prototype.context = this.mainContext;
Enemy.prototype.canvasWidth = this.mainCanvas.width;
Enemy.prototype.canvasHeight = this.mainCanvas.height;
// Initialize the background object
this.background = new Background();
this.background.init(0,0); // Set draw point to 0,0
// Initialize the ship object
this.ship = new Ship();
// Set the ship to start near the bottom middle of the canvas
this.shipStartX = this.shipCanvas.width/2 - imageRepository.spaceship.width;
this.shipStartY = this.shipCanvas.height/4*3 + imageRepository.spaceship.height*2;
this.ship.init(this.shipStartX, this.shipStartY,
imageRepository.spaceship.width, imageRepository.spaceship.height);
// Initialize the enemy pool object
this.enemyPool = new Pool(30);
this.enemyPool.init("enemy");
this.spawnWave();
this.enemyBulletPool = new Pool(50);
this.enemyBulletPool.init("enemyBullet");
// Start QuadTree
this.quadTree = new QuadTree({x:0,y:0,width:this.mainCanvas.width,height:this.mainCanvas.height});
this.playerScore = 0;
// Audio files
this.laser = new SoundPool(10);
this.laser.init("laser");
this.explosion = new SoundPool(20);
this.explosion.init("explosion");
this.backgroundAudio = new Audio("sounds/kick_shock.wav");
this.backgroundAudio.loop = true;
this.backgroundAudio.volume = .25;
this.backgroundAudio.load();
this.gameOverAudio = new Audio("sounds/game_over.wav");
this.gameOverAudio.loop = true;
this.gameOverAudio.volume = .25;
this.gameOverAudio.load();
this.checkAudio = window.setInterval(function(){checkReadyState()},1000);
}
};
// Spawn a new wave of enemies
this.spawnWave = function() {
var height = imageRepository.enemy.height;
var width = imageRepository.enemy.width;
var x = 100;
var y = -height;
var spacer = y * 1.5;
for (var i = 1; i <= 18; i++) {
this.enemyPool.get(x,y,2);
x += width + 25;
if (i % 6 == 0) {
x = 100;
y += spacer
}
}
}
// Start the animation loop
this.start = function() {
this.ship.draw();
this.backgroundAudio.play();
animate();
};
// Restart the game
this.restart = function() {
this.gameOverAudio.pause();
document.getElementById('game-over').style.display = "none";
this.bgContext.clearRect(0, 0, this.bgCanvas.width, this.bgCanvas.height);
this.shipContext.clearRect(0, 0, this.shipCanvas.width, this.shipCanvas.height);
this.mainContext.clearRect(0, 0, this.mainCanvas.width, this.mainCanvas.height);
this.quadTree.clear();
this.background.init(0,0);
this.ship.init(this.shipStartX, this.shipStartY,
imageRepository.spaceship.width, imageRepository.spaceship.height);
this.enemyPool.init("enemy");
this.spawnWave();
this.enemyBulletPool.init("enemyBullet");
this.playerScore = 0;
this.backgroundAudio.currentTime = 0;
this.backgroundAudio.play();
this.start();
};
// Game over
this.gameOver = function() {
this.backgroundAudio.pause();
this.gameOverAudio.currentTime = 0;
this.gameOverAudio.play();
document.getElementById('game-over').style.display = "block";
};
}
/**
* Ensure the game sound has loaded before starting the game
*/
function checkReadyState() {
if (game.gameOverAudio.readyState === 4 && game.backgroundAudio.readyState === 4) {
window.clearInterval(game.checkAudio);
document.getElementById('loading').style.display = "none";
game.start();
}
}
/**
* A sound pool to use for the sound effects
*/
function SoundPool(maxSize) {
var size = maxSize; // Max bullets allowed in the pool
var pool = [];
this.pool = pool;
var currSound = 0;
/*
* Populates the pool array with the given object
*/
this.init = function(object) {
if (object == "laser") {
for (var i = 0; i < size; i++) {
// Initalize the object
laser = new Audio("sounds/laser.wav");
laser.volume = .12;
laser.load();
pool[i] = laser;
}
}
else if (object == "explosion") {
for (var i = 0; i < size; i++) {
var explosion = new Audio("sounds/explosion.wav");
explosion.volume = .1;
explosion.load();
pool[i] = explosion;
}
}
};
/*
* Plays a sound
*/
this.get = function() {
if(pool[currSound].currentTime == 0 || pool[currSound].ended) {
pool[currSound].play();
}
currSound = (currSound + 1) % size;
};
}
/**
* The animation loop. Calls the requestAnimationFrame shim to
* optimize the game loop and draws all game objects. This
* function must be a gobal function and cannot be within an
* object.
*/
function animate() {
document.getElementById('score').innerHTML = game.playerScore;
// Insert objects into quadtree
game.quadTree.clear();
game.quadTree.insert(game.ship);
game.quadTree.insert(game.ship.bulletPool.getPool());
game.quadTree.insert(game.enemyPool.getPool());
game.quadTree.insert(game.enemyBulletPool.getPool());
detectCollision();
// No more enemies
if (game.enemyPool.getPool().length === 0) {
game.spawnWave();
}
// Animate game objects
if (game.ship.alive) {
requestAnimFrame( animate );
game.background.draw();
game.ship.move();
game.ship.bulletPool.animate();
game.enemyPool.animate();
game.enemyBulletPool.animate();
}
}
function detectCollision() {
var objects = [];
game.quadTree.getAllObjects(objects);
for (var x = 0, len = objects.length; x < len; x++) {
game.quadTree.findObjects(obj = [], objects[x]);
for (y = 0, length = obj.length; y < length; y++) {
// DETECT COLLISION ALGORITHM
if (objects[x].collidableWith === obj[y].type &&
(objects[x].x < obj[y].x + obj[y].width &&
objects[x].x + objects[x].width > obj[y].x &&
objects[x].y < obj[y].y + obj[y].height &&
objects[x].y + objects[y].height > obj[y].y)) {
objects[x].isColliding = true;
obj[y].isColliding = true;
}
}
}
};
// The keycodes that will be mapped when a user presses a button.
// Original code by Doug McInnes
KEY_CODES = {
32: 'space',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
}
// Creates the array to hold the KEY_CODES and sets all their values
// to true. Checking true/flase is the quickest way to check status
// of a key press and which one was pressed when determining
// when to move and which direction.
KEY_STATUS = {};
for (code in KEY_CODES) {
KEY_STATUS[KEY_CODES[code]] = false;
}
/**
* Sets up the document to listen to onkeydown events (fired when
* any key on the keyboard is pressed down). When a key is pressed,
* it sets the appropriate direction to true to let us know which
* key it was.
*/
document.onkeydown = function(e) {
// Firefox and opera use charCode instead of keyCode to
// return which key was pressed.
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if (KEY_CODES[keyCode]) {
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = true;
}
}
/**
* Sets up the document to listen to ownkeyup events (fired when
* any key on the keyboard is released). When a key is released,
* it sets teh appropriate direction to false to let us know which
* key it was.
*/
document.onkeyup = function(e) {
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if (KEY_CODES[keyCode]) {
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = false;
}
}
/**
* requestAnim shim layer by Paul Irish
* Finds the first API that works to optimize the animation loop,
* otherwise defaults to setTimeout().
*/
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
Output
This bin was created anonymously and its free preview time has expired (learn why). — Get a free unrestricted account
Dismiss xKeyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |