Skip to content

Instantly share code, notes, and snippets.

@bugpowder
Created May 13, 2012 19:13
Show Gist options
  • Save bugpowder/2689822 to your computer and use it in GitHub Desktop.
Save bugpowder/2689822 to your computer and use it in GitHub Desktop.
Canvas experiment
<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