Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Pen by Jason Brown.
<canvas id="canvas"></canvas>
'use strict';
// requestanimation polyfill
(function () {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
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);
};
}());
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var Segment = function (settings) {
settings = settings || {};
this.angle = settings.angle || 0;
this.x = settings.x || 0;
this.y = settings.y || 0;
this.width = settings.width || 10;
this.height = settings.height || 2;
this.color = settings.color || {
r: 255,
g: 50,
b: 100
};
}
Segment.prototype.getJoint = function () {
var x = this.x + Math.cos(this.angle) * this.width,
y = this.y + Math.sin(this.angle) * this.width;
return {
x: x,
y: y
};
};
Segment.prototype.render = function (ctx) {
ctx.fillStyle = "rgb(" + this.color.r + "," + this.color.g + "," + this.color.b + ")";
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.fillRect(-this.height / 2, -this.height / 2, this.width + this.height, this.height);
ctx.restore();
};
var Snake = function (settings) {
settings = settings || {};
this.x = settings.x || 0;
this.y = settings.y || 0;
this.color = settings.color || {
r: ~~ (Math.random() * 255),
g: ~~ (Math.random() * 255),
b: ~~ (Math.random() * 255)
};
this.vX = 0;
this.vY = 0;
this.wiggle = 0;
this.bounds = settings.bounds || {
x: 0,
y: 0,
width: 100,
height: 100
};
this.segNum = settings.segNum || 10;
this.angle = (Math.random() * 360 - 180) * Math.PI / 180;
this.setTarget(settings.target);
this.segments = [];
this.segments.push(new Segment({
x: this.x,
y: this.y,
angle: this.angle,
color: this.color
}));
for (var s = 1; s < this.segNum; s++) {
this.segments.push(new Segment({
angle: (Math.random() * 360 - 180) * Math.PI / 180,
color: this.color
}));
}
}
Snake.prototype.setTarget = function (target) {
var tX = Math.random() * this.bounds.width,
tY = Math.random() * this.bounds.height;
if (target) {
tX = target.x;
tY = target.y;
}
this.target = {
x: tX,
y: tY
};
};
Snake.prototype.move = function (segment, x, y) {
var dx = x - segment.x,
dy = y - segment.y;
segment.angle = Math.atan2(dy, dx);
var w = segment.getJoint().x - segment.x,
h = segment.getJoint().y - segment.y;
segment.x = x - w;
segment.y = y - h;
}
Snake.prototype.update = function () {
this.move(this.segments[0], this.x, this.y);
var move = this.move,
color = this.color;
this.segments.forEach(function (e, i, arr) {
e.color = color;
if (i !== 0) {
move(e, arr[i - 1].x, arr[i - 1].y);
}
});
};
Snake.prototype.render = function (ctx) {
this.segments.forEach(function (e) {
e.render(ctx);
});
};
var Light = function (settings) {
this.ease = settings.ease || 20;
this.bounds = settings.bounds || {
x: 0,
y: 0,
width: 100,
height: 100
};
this.angle = 0;
this.x = Math.random() * this.bounds.width/2;
this.y = Math.random() * this.bounds.height/2;
this.setTarget();
};
Light.prototype.update = function () {
var dX = this.target.x - this.x,
dY = this.target.y - this.y,
dist = Math.sqrt(dX * dX - dY * dY);
if (dist > this.ease) {
this.x += dX / this.ease;
this.y += dY / this.ease;
} else {
this.setTarget();
}
};
Light.prototype.setTarget = function () {
var tX = Math.random() * this.bounds.width,
tY = Math.random() * this.bounds.height;
this.target = {
x: tX,
y: tY
};
};
function Demo(canvas) {
var that = this;
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.ctx.fillStyle = "#000";
this.req = null;
this.settings = {
brightness: 120,
lightness: 120,
snakeSpread: 30,
mouseFollow: false,
snakeCount: 100,
snakeLength: 8,
snakeSpeed: 5,
restart: function () {
that.init();
}
};
this.mouseX = 0;
this.mouseY = 0;
this.canvas.addEventListener("mousemove", function (e) {
that.mouseX = e.pageX;
that.mouseY = e.pageY;
});
this.update = function () {
this.brightness = this.settings.brightness;
this.lightness = this.settings.lightness;
this.ctx.fillStyle = "rgba(0,0,0,0.5)";
this.ctx.fillRect(0, 0, canvas.width, canvas.height);
this.light.update();
for (var s = 0, sLen = this.snakes.length; s < sLen; s++) {
var snake = this.snakes[s],
dX = snake.x - snake.target.x,
dY = snake.y - snake.target.y,
heading = Math.atan2(dY, dX),
dist = Math.sqrt(dX * dX + dY * dY);
snake.angle = heading + Math.cos(snake.wiggle += 0.1);
snake.vX = Math.cos(snake.angle) * this.settings.snakeSpeed;
snake.vY = Math.sin(snake.angle) * this.settings.snakeSpeed;
snake.color = this.colorCycle(this.cycle, this.brightness, this.lightness);
if (dist > 10) {
snake.x -= snake.vX;
snake.y -= snake.vY;
} else {
var x = this.light.x + this.snakeSpread - Math.random() * this.snakeSpread,
y = this.light.y + this.snakeSpread - Math.random() * this.snakeSpread;
if (this.settings.mouseFollow) {
x = this.mouseX - this.snakeSpread*2 + Math.random() * this.snakeSpread*5;
y = this.mouseY - this.snakeSpread*2 + Math.random() * this.snakeSpread*5;
}
snake.setTarget({
x: x,
y: y
});
}
snake.update();
snake.render(this.ctx);
}
this.cycle += 0.4;
this.req = requestAnimationFrame(this.update.bind(this));
}
this.colorCycle = function (cycle, bright, light) {
bright = bright || 255;
light = light || 0;
cycle *= .1;
var r = ~~ (Math.sin(.3 * cycle + 0) * bright + light),
g = ~~ (Math.sin(.3 * cycle + 2) * bright + light),
b = ~~ (Math.sin(.3 * cycle + 4) * bright + light);
return {
r: Math.min(r, 255),
g: Math.min(g, 255),
b: Math.min(b, 255)
};
}
this.init = function () {
cancelAnimationFrame(this.req);
this.snakes = [];
this.light = null;
this.cycle = 0;
this.brightness = this.settings.brightness;
this.lightness = this.settings.lightness;
this.snakeSpread = this.settings.snakeSpread;
this.snakeCount = this.settings.snakeCount;
this.light = new Light({
bounds: {
x: 0,
y: 0,
width: this.canvas.width,
height: this.canvas.height,
ease: 25 - this.settings.snakeSpeed
}
});
for (var s = 0; s < this.snakeCount; s++) {
var x = canvas.width/2 - Math.random() * canvas.width/4,
y = canvas.height/2 - Math.random() * canvas.height/4;
this.snakes.push(new Snake({
x: x,
y: y,
segNum: this.settings.snakeLength,
bounds: {
x: 0,
y: 0,
width: this.canvas.width,
height: this.canvas.height
},
target: {
x: this.light.y,
y: this.light.x
}
}));
}
this.update();
}
var gui = new dat.GUI();
gui.add(this.settings, 'mouseFollow');
gui.add(this.settings, 'brightness', 1, 255);
gui.add(this.settings, 'lightness', 1, 255);
var snakeSettings = gui.addFolder('Snake Settings');
snakeSettings.add(this.settings, 'snakeCount', 1, 300);
snakeSettings.add(this.settings, 'snakeLength', 1, 50);
snakeSettings.add(this.settings, 'snakeSpeed', 1, 10);
gui.add(this.settings, 'restart');
this.init();
}
var demo = null;
setTimeout(function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
demo = new Demo(canvas);
}, 100);
body {
margin:0;
padding:0;
overflow:hidden;
}
canvas {
position:absolute;
}

Swimming snakes

Havent done a pen in the longest time, decided to put this demo together after playing around with IK, the effect turned out pretty cool! (in my biased opinion of course)

A Pen by Jason Brown on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment