Last active
November 5, 2016 23:27
-
-
Save samliew/fdf0858873fedcdc6a2e14aab8165832 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Source: http://www.kodhus.com/kodity/land/kod/bXCBjh | |
* | |
* The stars in our starfield! | |
* Stars coordinate system is relative to the CENTER of the canvas | |
* @param {number} x | |
* @param {number} y | |
* | |
* Usage: | |
* var starField = new StarField('fullScreen').render(333, 3); | |
*/ | |
var Star = function(x, y, maxSpeed) { | |
this.x = x; | |
this.y = y; | |
this.slope = y / x; // This only works because our origin is always (0,0) | |
this.opacity = 0; | |
this.speed = Math.max(Math.random() * maxSpeed, 1); | |
}; | |
/** | |
* Compute the distance of this star relative to any other point in space. | |
* | |
* @param {int} originX | |
* @param {int} originY | |
* | |
* @return {float} The distance of this star to the given origin | |
*/ | |
Star.prototype.distanceTo = function(originX, originY) { | |
return Math.sqrt(Math.pow(originX - this.x, 2) + Math.pow(originY - this.y, 2)); | |
}; | |
/** | |
* Reinitializes this star's attributes, without re-creating it | |
* | |
* @param {number} x | |
* @param {number} y | |
* | |
* @return {Star} this star | |
*/ | |
Star.prototype.resetPosition = function(x, y, maxSpeed) { | |
Star.apply(this, arguments); | |
return this; | |
}; | |
/** | |
* The BigBang factory creates stars (Should be called StarFactory, but that is | |
* a WAY LESS COOL NAME! | |
* @type {Object} | |
*/ | |
var BigBang = { | |
/** | |
* Returns a random star within a region of the space. | |
* | |
* @param {number} minX minimum X coordinate of the region | |
* @param {number} minY minimum Y coordinate of the region | |
* @param {number} maxX maximum X coordinate of the region | |
* @param {number} maxY maximum Y coordinate of the region | |
* | |
* @return {Star} The random star | |
*/ | |
getRandomStar: function(minX, minY, maxX, maxY, maxSpeed) { | |
var coords = BigBang.getRandomPosition(minX, minY, maxX, maxY); | |
return new Star(coords.x, coords.y, maxSpeed); | |
}, | |
/** | |
* Gets a random (x,y) position within a bounding box | |
* | |
* | |
* @param {number} minX minimum X coordinate of the region | |
* @param {number} minY minimum Y coordinate of the region | |
* @param {number} maxX maximum X coordinate of the region | |
* @param {number} maxY maximum Y coordinate of the region | |
* | |
* @return {Object} An object with random {x, y} positions | |
*/ | |
getRandomPosition: function(minX, minY, maxX, maxY) { | |
return { | |
x: Math.floor((Math.random() * maxX) + minX), | |
y: Math.floor((Math.random() * maxY) + minY) | |
}; | |
} | |
}; | |
/** | |
* Constructor function of our starfield. This just prepares the DOM nodes where | |
* the scene will be rendered. | |
* | |
* @param {string} canvasId The DOM Id of the <div> containing a <canvas> tag | |
*/ | |
var StarField = function(containerId) { | |
this.container = document.getElementById(containerId); | |
if(typeof this.container === 'undefined') { | |
console.log('Could not find target container. Exiting.'); | |
return false; | |
} | |
this.canvasElem = this.container.getElementsByTagName('canvas'); | |
if(typeof this.canvasElem === 'undefined') { | |
var c = document.createElement('canvas'); | |
c.style.position = 'absolute'; | |
c.style.top = '0px'; | |
c.style.left = '0px'; | |
c.style.bottom = '0px'; | |
c.style.right = '0px'; | |
c.style.zIndex = '0'; | |
this.container.appendChild(c); | |
this.canvasElem = c; | |
} | |
else { | |
this.canvasElem = this.canvasElem[0]; | |
} | |
this.canvas = this.canvasElem.getContext('2d'); | |
this.width = this.container.offsetWidth; | |
this.height = this.container.offsetHeight; | |
this.starField = []; | |
}; | |
/** | |
* Updates the properties for every star for the next frame to be rendered | |
*/ | |
StarField.prototype._updateStarField = function() { | |
var i, | |
star, | |
randomLoc, | |
increment; | |
for (i = 0; i < this.numStars; i++) { | |
star = this.starField[i]; | |
increment = Math.min(star.speed, Math.abs(star.speed / star.slope)); | |
star.x += (star.x > 0) ? increment : -increment; | |
star.y = star.slope * star.x; | |
star.opacity += star.speed / 100; | |
// Recycle star obj if it goes out of the frame | |
if ((Math.abs(star.x) > this.width / 2) || | |
(Math.abs(star.y) > this.height / 2)) { | |
//randomLoc = BigBang.getRandomPosition( | |
// -this.width / 2, -this.height / 2, | |
// this.width, this.height | |
//); | |
randomLoc = BigBang.getRandomPosition( | |
-this.width / 10, -this.height / 10, | |
this.width / 5, this.height / 5 | |
); | |
star.resetPosition(randomLoc.x, randomLoc.y, this.maxStarSpeed); | |
} | |
} | |
}; | |
/** | |
* Renders the whole starfield (background + stars) | |
* This method could be made more efficient by just blipping each star, | |
* and not redrawing the whole frame | |
*/ | |
StarField.prototype._renderStarField = function() { | |
var i, | |
star; | |
// Background | |
this.canvas.fillStyle = "rgba(0, 0, 0, .5)"; | |
this.canvas.fillRect(0, 0, this.width, this.height); | |
// Stars | |
for (i = 0; i < this.numStars; i++) { | |
star = this.starField[i]; | |
this.canvas.fillStyle = "rgba(255, 255, 255, " + star.opacity + ")"; | |
this.canvas.fillRect( | |
star.x + this.width / 2, | |
star.y + this.height / 2, | |
2, 2); | |
} | |
}; | |
/** | |
* Function that handles the animation of each frame. Update the starfield | |
* positions and re-render | |
*/ | |
StarField.prototype._renderFrame = function(elapsedTime) { | |
var timeSinceLastFrame = elapsedTime - (this.prevFrameTime || 0); | |
window.requestAnimationFrame(this._renderFrame.bind(this)); | |
// Skip frames unless at least 30ms have passed since the last one | |
// (Cap to ~30fps) | |
if (timeSinceLastFrame >= 30 || !this.prevFrameTime) { | |
this.prevFrameTime = elapsedTime; | |
this._updateStarField(); | |
this._renderStarField(); | |
} | |
}; | |
/** | |
* Makes sure that the canvas size fits the size of its container | |
*/ | |
StarField.prototype._adjustCanvasSize = function(width, height) { | |
// Set the canvas size to match the container ID (and cache values) | |
this.width = this.canvasElem.width = width || this.container.offsetWidth; | |
this.height = this.canvasElem.height = height || this.container.offsetHeight; | |
}; | |
/** | |
* This listener compares the old container size with the new one, and caches | |
* the new values. | |
*/ | |
StarField.prototype._watchCanvasSize = function(elapsedTime) { | |
var timeSinceLastCheck = elapsedTime - (this.prevCheckTime || 0), | |
width, | |
height; | |
window.requestAnimationFrame(this._watchCanvasSize.bind(this)); | |
// Skip frames unless at least 333ms have passed since the last check | |
// (Cap to ~3fps) | |
if (timeSinceLastCheck >= 333 || !this.prevCheckTime) { | |
this.prevCheckTime = elapsedTime; | |
width = this.container.offsetWidth; | |
height = this.container.offsetHeight; | |
if (this.oldWidth !== width || this.oldHeight !== height) { | |
this.oldWidth = width; | |
this.oldHeight = height; | |
this._adjustCanvasSize(width, height); | |
} | |
} | |
}; | |
/** | |
* Initializes the scene by resizing the canvas to the appropiate value, and | |
* sets up the main loop. | |
* @param {int} numStars Number of stars in our starfield | |
*/ | |
StarField.prototype._initScene = function(numStars) { | |
var i; | |
for (i = 0; i < this.numStars; i++) { | |
this.starField.push( | |
BigBang.getRandomStar(-this.width / 2, -this.height / 2, this.width, this.height, this.maxStarSpeed) | |
); | |
} | |
// Intervals not stored because I don't plan to detach them later... | |
window.requestAnimationFrame(this._renderFrame.bind(this)); | |
window.requestAnimationFrame(this._watchCanvasSize.bind(this)); | |
}; | |
/** | |
* Kicks off everything! | |
* @param {int} numStars The number of stars to render | |
* @param {int} maxStarSpeed Maximum speed of the stars (pixels / frame) | |
*/ | |
StarField.prototype.render = function(numStars, maxStarSpeed) { | |
this.numStars = numStars || 100; | |
this.maxStarSpeed = maxStarSpeed || 3; | |
this._initScene(this.numStars); | |
}; | |
/** | |
* requestAnimationFrame shim layer with setTimeout fallback | |
* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating | |
*/ | |
(function() { | |
var lastTime = 0; | |
var vendors = ['ms', 'moz', 'webkit', 'o']; | |
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { | |
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; | |
window.cancelAnimationFrame = | |
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; | |
} | |
if (!window.requestAnimationFrame) | |
window.requestAnimationFrame = function(callback, element) { | |
var currTime = new Date().getTime(); | |
var timeToCall = Math.max(0, 16 - (currTime - lastTime)); | |
var id = window.setTimeout(function() { callback(currTime + timeToCall); }, | |
timeToCall); | |
lastTime = currTime + timeToCall; | |
return id; | |
}; | |
if (!window.cancelAnimationFrame) | |
window.cancelAnimationFrame = function(id) { | |
clearTimeout(id); | |
}; | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment