Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active Aug 10, 2021
Embed
What would you like to do?
Electric Pond
license: gpl-3.0

Drag your mouse across the pond to add your own rippling waves.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/d3-color@3"></script>
<script>
class Pond {
constructor(options) {
Object.assign(
this,
{
width: innerWidth,
height: innerHeight,
ripples: [],
i: 0
},
options);
}
add(Ripple){
Ripple.i = this.i++;
this.ripples.push(Ripple);
return this;
}
remove(Ripple){
this.ripples.splice(this.ripples.findIndex(d => d.i === Ripple.i), 1);
return this;
}
each(fn){
for (let i = 0, l = this.ripples.length; i < l; i++){
if (this.ripples[i]) fn(this.ripples[i], i, this.ripples);
}
return this;
}
tick(){
this.each(ripple => {
ripple.each(wave => {
// Add wave
if (wave.r === ripple.rNewWave && wave.i <= ripple.wavesMax){
ripple.add(1 - wave.i / ripple.wavesMax);
}
// Expand wave
if (wave.r < ripple.rMax){
wave.r++;
wave.a = wave.dampen - wave.r / (ripple.rMax * wave.dampen);
}
// Remove ripple
if (wave.i === ripple.wavesMax && wave.r === ripple.rMax){
this.remove(ripple);
}
});
});
return this;
}
}
class Ripple {
constructor(options){
const x = options && options.x ? options.x : options.pond.width * Math.random();
const y = options && options.y ? options.y : options.pond.height * Math.random();
Object.assign(
this,
{
x,
y,
rMax: 80,
wavesMax: 5,
rNewWave: 8,
waves: [{ r: 0, x, y, a: 1, i: 1, dampen: 1 }]
},
options
);
}
add(dampen = 1){
this.waves.push({
dampen,
a: dampen,
r: 0,
x: this.x,
y: this.y,
i: this.waves[this.waves.length - 1].i + 1
});
return this;
}
each(fn){
for (let i = 0, l = this.waves.length; i < l; i++){
fn(this.waves[i], i, this.waves);
}
return this;
}
}
const pond = new Pond();
setInterval(_ => {
pond.add(new Ripple({ pond }));
}, 50);
const canvas = document.querySelector("body").appendChild(document.createElement("canvas"));
canvas.style.background = "#000";
const context = canvas.getContext("2d");
resize();
let lastRippleAdded = new Date().getTime();
const minDiff = 10;
canvas.addEventListener("mousemove", ev => {
const t = new Date().getTime();
if (t - lastRippleAdded > minDiff){
pond.add(new Ripple({
x: ev.offsetX,
y: ev.offsetY,
pond
}));
lastRippleAdded = t;
}
});
addEventListener("resize", _ => {
pond.width = innerWidth;
pond.height = innerHeight;
resize();
});
function resize(){
canvas.width = pond.width;
canvas.height = pond.height;
context.lineWidth = 6;
}
function tick(){
requestAnimationFrame(tick);
context.clearRect(0, 0, pond.width, pond.height);
pond
.tick()
.each(ripple => {
const c = d3.rgb(d3.hsl(ripple.i % 360, 1, .5));
ripple.each(wave => {
context.beginPath();
context.strokeStyle = `rgba(${c.r}, ${c.g}, ${c.b}, ${wave.a})`;
context.arc(wave.x, wave.y, wave.r, 0, Math.PI * 2);
context.stroke();
});
});
}
tick();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment