Custom Code Experiment •
Chromatophores 3
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html>
<head>
<style>
html, body {
overflow: hidden;
background: #000;
padding: 0px;
margin: 0px;
width: 100%;
height: 100%;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var w = canvas.width = window.innerWidth;
var h = canvas.height = window.innerHeight;
window.onresize = function() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
};
var pixels = [];
var emitterCount = 8;
var maxPixels = 280;
var pixelSize = 18;
var gap = 6;
var lineWidth = 2;
var maxLifetime = 4500;
var minLifetime = 2000;
var spawnRate = 250;
var opacity = 0.28;
var hue = 0;
var hueRange = 60;
var fadeAlpha = 0.018;
var mouse = { x: -9999, y: -9999, down: false };
var mouseAttractRadius = 160;
var mouseRepelRadius = 80;
var mouseForce = 3.5;
canvas.addEventListener("mousemove", function(e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
canvas.addEventListener("mousedown", function(e) {
mouse.down = true;
mouse.button = e.button;
e.preventDefault();
});
canvas.addEventListener("mouseup", function() { mouse.down = false; });
canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); });
canvas.addEventListener("mouseleave", function() { mouse.x = -9999; mouse.y = -9999; });
canvas.addEventListener("touchmove", function(e) {
mouse.x = e.touches[0].clientX;
mouse.y = e.touches[0].clientY;
e.preventDefault();
}, { passive: false });
canvas.addEventListener("touchstart", function(e) {
mouse.down = true; mouse.button = 0;
mouse.x = e.touches[0].clientX;
mouse.y = e.touches[0].clientY;
});
canvas.addEventListener("touchend", function() { mouse.down = false; });
var attractRadius = 180;
var repelRadius = 35;
var alignRadius = 100;
var attractForce = 0.75;
var repelForce = 2.2;
var alignForce = 0.15;
var maxSpeed = 4.0;
var friction = 0.84;
var shapes = ["rect", "arc", "triangle", "diamond", "cross", "star"];
function drawShape(shape, x, y, size, pulse) {
var s = size * pulse;
ctx.beginPath();
switch(shape) {
case "rect":
ctx.rect(x - s/2, y - s/2, s, s);
break;
case "arc":
ctx.arc(x, y, s / 2, 0, 2 * Math.PI);
break;
case "triangle":
ctx.moveTo(x, y - s / 2);
ctx.lineTo(x + s / 2, y + s / 2);
ctx.lineTo(x - s / 2, y + s / 2);
ctx.closePath();
break;
case "diamond":
ctx.moveTo(x, y - s / 2);
ctx.lineTo(x + s / 2, y);
ctx.lineTo(x, y + s / 2);
ctx.lineTo(x - s / 2, y);
ctx.closePath();
break;
case "cross":
var t = s / 4;
ctx.rect(x - t/2, y - s/2, t, s);
ctx.rect(x - s/2, y - t/2, s, t);
break;
case "star":
var spikes = 5;
var outerR = s / 2;
var innerR = s / 4.5;
var rot = (Math.PI / 2) * 3;
var step = Math.PI / spikes;
ctx.moveTo(x, y - outerR);
for (var k = 0; k < spikes; k++) {
ctx.lineTo(x + Math.cos(rot) * outerR, y + Math.sin(rot) * outerR);
rot += step;
ctx.lineTo(x + Math.cos(rot) * innerR, y + Math.sin(rot) * innerR);
rot += step;
}
ctx.lineTo(x, y - outerR);
ctx.closePath();
break;
}
}
function emit() {
for (var i = 0; i < emitterCount; i++) {
if (pixels.length >= maxPixels) {
pixels.splice(0, 1);
}
pixels.push({
x: Math.random() * w,
y: Math.random() * h,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
t: Math.random() * (maxLifetime - minLifetime) + minLifetime,
s: Date.now(),
h: Math.random() * ((hue + hueRange) - (hue - hueRange)) + (hue - hueRange),
shape: shapes[Math.floor(Math.random() * shapes.length)],
pulseOffset: Math.random() * Math.PI * 2,
pulseSpeed: 2 + Math.random() * 3
});
}
}
function draw() {
var now = Date.now();
for (var i = 0; i < pixels.length; i++) {
var p = pixels[i];
var diff = (now - p.s) / p.t;
var perc = Math.round(diff * 100);
var lifeAlpha = Math.sin(diff * Math.PI);
var pulse = 1 + 0.4 * Math.sin(p.pulseOffset + (now / 1000) * p.pulseSpeed);
ctx.save();
ctx.globalAlpha = opacity * lifeAlpha;
ctx.lineWidth = lineWidth;
ctx.strokeStyle = "hsl(" + p.h + "," + perc + "%,55%)";
ctx.fillStyle = "hsl(" + p.h + "," + perc + "%,55%)";
drawShape(p.shape, p.x, p.y, pixelSize, pulse);
ctx.stroke();
ctx.restore();
}
}
function animate() {
var now = Date.now();
for (var i = pixels.length - 1; i >= 0; i--) {
var p = pixels[i];
if (p.s + p.t < now) {
pixels.splice(i, 1);
continue;
}
var ax = 0, ay = 0;
var sampleStep = Math.max(1, Math.floor(pixels.length / 60));
var avgVx = 0, avgVy = 0, alignCount = 0;
for (var j = 0; j < pixels.length; j += sampleStep) {
if (i === j) continue;
var q = pixels[j];
var dx = q.x - p.x;
var dy = q.y - p.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 0.001;
var nx = dx / dist;
var ny = dy / dist;
if (dist < repelRadius) {
ax -= nx * repelForce * (1 - dist / repelRadius);
ay -= ny * repelForce * (1 - dist / repelRadius);
} else if (dist < attractRadius) {
ax += nx * attractForce * (1 - dist / attractRadius);
ay += ny * attractForce * (1 - dist / attractRadius);
}
if (dist < alignRadius) {
avgVx += q.vx;
avgVy += q.vy;
alignCount++;
}
}
if (alignCount > 0) {
ax += (avgVx / alignCount - p.vx) * alignForce;
ay += (avgVy / alignCount - p.vy) * alignForce;
}
var mdx = p.x - mouse.x;
var mdy = p.y - mouse.y;
var mdist = Math.sqrt(mdx * mdx + mdy * mdy) || 0.001;
var mnx = mdx / mdist;
var mny = mdy / mdist;
if (mouse.down && mouse.button === 0 && mdist < mouseAttractRadius) {
ax -= mnx * mouseForce * (1 - mdist / mouseAttractRadius) * 2.5;
ay -= mny * mouseForce * (1 - mdist / mouseAttractRadius) * 2.5;
} else if (mouse.down && mouse.button === 2 && mdist < mouseAttractRadius) {
ax += mnx * mouseForce * (1 - mdist / mouseAttractRadius) * 3.0;
ay += mny * mouseForce * (1 - mdist / mouseAttractRadius) * 3.0;
} else if (!mouse.down && mdist < mouseRepelRadius) {
ax += mnx * mouseForce * (1 - mdist / mouseRepelRadius);
ay += mny * mouseForce * (1 - mdist / mouseRepelRadius);
}
ax += (Math.random() - 0.5) * 0.4;
ay += (Math.random() - 0.5) * 0.4;
p.vx = (p.vx + ax) * friction;
p.vy = (p.vy + ay) * friction;
var speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
if (speed > maxSpeed) {
p.vx = (p.vx / speed) * maxSpeed;
p.vy = (p.vy / speed) * maxSpeed;
}
p.x += p.vx;
p.y += p.vy;
if (p.x < 0) p.x += w;
if (p.x > w) p.x -= w;
if (p.y < 0) p.y += h;
if (p.y > h) p.y -= h;
}
}
function render() {
ctx.globalAlpha = fadeAlpha;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
ctx.globalAlpha = 1;
draw();
animate();
requestAnimationFrame(render);
}
emit();
setInterval(emit, spawnRate);
requestAnimationFrame(render);
</script>
</body>
</html>