Created
May 13, 2012 19:13
-
-
Save bugpowder/2689822 to your computer and use it in GitHub Desktop.
Canvas experiment
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
<html> | |
<body style="margin:0px; padding:0px;"> | |
<div style="margin-left: auto; margin-right: auto;"></div> | |
<script> | |
var body = document.getElementsByTagName("body")[0]; | |
body.onload = function(){ | |
// basic settings | |
var is_capable = true; //set to false for slow CPUs | |
var SCENE_LIFECYCLE = 60 * 5; | |
var OBJECT_COUNT = is_capable ? 50 : 30; | |
var INITIAL_RADIUS = is_capable ? 20 : 10; | |
var WIDTH = is_capable ? self.innerWidth : 700; | |
var HEIGHT = is_capable ? self.innerHeight : 500; | |
//create a canvas for showing our drawing | |
var canvas = document.createElement("canvas"); | |
canvas.setAttribute("width", WIDTH); | |
canvas.setAttribute("height", HEIGHT); | |
canvas.setAttribute("id", "canvas"); | |
canvas.setAttribute("style", "margin-left: auto; margin-right: auto"); | |
var div = document.getElementsByTagName("div")[0]; | |
div.setAttribute("width", WIDTH); | |
div.appendChild(canvas); | |
var visibleCtx = canvas.getContext("2d"); | |
//create a second canvas for offline drawing | |
var offscreenCanvas = document.createElement('canvas'); | |
offscreenCanvas.width = WIDTH; | |
offscreenCanvas.height = HEIGHT; | |
var ctx = offscreenCanvas.getContext('2d'); | |
//circle class | |
function Circle(center, radius, color){ | |
this.center = center; | |
this.radius = radius; | |
this.color = color; | |
this.angle = Math.random()*Math.PI*2; | |
this.tics = 0; | |
this.lastDrawTime; | |
this.speed = Math.round(Math.random()*60)+10; | |
} | |
//draw a circle that changes radius based on it's age in "tics" | |
//a count that is increased after every frame | |
Circle.prototype.draw = function(){ | |
ctx.fillStyle = this.translateColor(); | |
var radius = this.radius * Math.sin(Math.PI*this.tics/SCENE_LIFECYCLE); | |
var arcLength = Math.PI*2; | |
ctx.beginPath(); | |
ctx.arc(this.center.x, this.center.y, radius, 0, arcLength, true); | |
ctx.closePath(); | |
ctx.fill(); | |
this.tics++; | |
this.lastDrawTime = new Date().getTime(); | |
return this; | |
} | |
//get the color in HTML readable format | |
Circle.prototype.translateColor = function(){ | |
this.color.a = alphas[this.tics]; | |
return 'rgba(' | |
+ this.color.r +"," | |
+ this.color.g + "," | |
+ this.color.b + "," | |
+ this.color.a + ")"; | |
} | |
//draw a line from one circle to another | |
Circle.prototype.connectTo = function(c2){ | |
ctx.save(); | |
ctx.strokeStyle = "rgba(0,0,0,"+Math.min(0.05, this.color.a)+")"; | |
ctx.beginPath(); | |
ctx.moveTo(this.center.x,this.center.y); | |
ctx.lineTo(c2.center.x,c2.center.y); | |
ctx.stroke(); | |
ctx.restore(); | |
} | |
//calculate the new position for the circle, given it's speed | |
//and the time that passed | |
Circle.prototype.move = function(){ | |
var direction = this.angle; | |
var dx = 0; | |
var dy = 0; | |
if (typeof this.lastDrawTime != "undefined") { | |
var elapsedT = (new Date().getTime() - this.lastDrawTime)/1000; //elapsed secs | |
var movement = this.speed * elapsedT; | |
dx = movement * Math.sin(direction); | |
dy = movement * Math.cos(direction); | |
} | |
this.center.x += dx; | |
this.center.y += dy; | |
return this; | |
} | |
//check circle proximity to other circle | |
Circle.prototype.isCloserThan = function(c2, distance){ | |
return Math.abs(this.center.x - c2.center.x)<distance && Math.abs(this.center.y - c2.center.y)<distance; | |
} | |
//pre-compute some values for different transparencies | |
var alphas = []; | |
for (var i=0; i<SCENE_LIFECYCLE; i++) { | |
var MID_SCENE = SCENE_LIFECYCLE/2; | |
var alpha = (i<MID_SCENE) ? i/MID_SCENE : 1 - Math.abs(1 - i/MID_SCENE); | |
alphas.push(Math.round(alpha*100)/100); | |
} | |
//return a random point on the canvas (and slightly outside it) | |
function getNewPoint(i){ | |
return { x: Math.random()*WIDTH, y: Math.random()*HEIGHT}; | |
} | |
//return a random color | |
function getColor(i){ | |
return { | |
r:Math.floor(Math.random()*255), | |
g:Math.floor(Math.random()*255), | |
b:Math.floor(Math.random()*255), | |
a:1 | |
}; | |
} | |
function drawScene(circles){ | |
//traverse the circles array and draw each pair of circles | |
//connected with a line | |
circles.map(function(cur){ | |
var c1 = cur.first; | |
var c2 = cur.second; | |
c1.connectTo(c2); | |
c1.draw(); | |
c2.draw(); | |
}); | |
// check all the circles and if two are close, make | |
// them blink in random colors. | |
// If they get TOO close, make them (and their pairs) | |
// disappear in the next frame | |
for (var i=0, len=circles.length-1; i<len; i=i+2){ | |
var set1 = circles[i]; | |
var c1 = set1.first; | |
var c2 = set1.second; | |
var set2 = circles[i+1]; | |
var c3 = set2.first; | |
var c4 = set2.second; | |
c1.connectTo(c3); | |
c1.connectTo(c4); | |
c2.connectTo(c4); | |
c2.connectTo(c3); | |
var touchDistance = 200; | |
if (c1 !== c3 && c1.isCloserThan(c3, touchDistance)) { | |
if (repeats%3 == 0) { | |
c1.radius = 10; | |
c1.color.r = repeats%150; | |
c1.color.g = repeats%150; | |
c1.color.b = 0; | |
} else if (repeats % 4 == 0) { | |
c3.radius = 20; | |
c3.color.r = Math.pow(repeats, 3) % 255; | |
c3.color.g = repeats%255; | |
c3.color.b = 255 - repeats%255; | |
} | |
} else if (c1 !== c3 && c1.isCloserThan(c3, (3/2)*touchDistance)) { | |
c1.tics = c2.tics = c3.tics = c4.tics = SCENE_LIFECYCLE; | |
} | |
} | |
} | |
// create OBJECT_COUNT random circle pairs, with random colors | |
// and random radius for each pair | |
function createScene(){ | |
var circles = []; | |
for (var i=0; i<OBJECT_COUNT; i++){ | |
var color = getColor(i); | |
var radius = Math.random()*INITIAL_RADIUS + INITIAL_RADIUS/2; | |
circles.push({first: new Circle (getNewPoint(i), radius, color), second: new Circle (getNewPoint(i), radius, color)}); | |
} | |
return circles; | |
} | |
// traverse all the circle pairs, and update their | |
// positions | |
function updateScene(circles){ | |
circles.map(function(cur){ | |
cur.first.move(); | |
cur.second.move(); | |
}); | |
//remove any circles that have existed for SCENE_LIFECYCLE frames (tics) | |
return circles.filter(function(cur){ | |
return (cur.first.tics < SCENE_LIFECYCLE); | |
});; | |
} | |
var repeats = 0; | |
var circles = createScene(); | |
window.requestAnimFrame = window.requestAnimationFrame | |
|| window.webkitRequestAnimationFrame | |
|| window.mozRequestAnimationFrame; | |
function render() { | |
// clear the canvas | |
visibleCtx.clearRect (0 , 0, WIDTH, HEIGHT); | |
ctx.clearRect (0 , 0, WIDTH, HEIGHT); | |
ctx.fillStyle = "rgba(240,240,240,1)"; | |
ctx.fillRect(0, 0, WIDTH, HEIGHT); | |
ctx.fill(); | |
// when the first OBJECT_COUNT pairs are in their mid-life | |
// add a fresh OBJECT_COUNT pairs of circles to the scene | |
if (repeats % Math.floor(SCENE_LIFECYCLE/2) == 0) { | |
circles = circles.concat(createScene()); | |
} | |
//draw the current scene | |
drawScene(circles); | |
//update the circles for the next scene | |
circles = updateScene(circles); | |
repeats++; | |
//draw the offscreen image to the visible canvas | |
visibleCtx.drawImage(offscreenCanvas, 0, 0); | |
//the browser will call this when he has the time to | |
//draw a new frame | |
requestAnimFrame(render); | |
} | |
render(); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment