Here is another experiment in my Canvas series. This time, I have 2 sets of circles being generated, one set increasing in radius and the other one decreasing. I won’t go into maths details or break down the code.
This article is part of the JavaScript Canvas series where I post experiments, JavaScript generative art, visualizations, Canvas animations, code snippets and how-to’s.
document.addEventListener('DOMContentLoaded', () => {
let last_t = Date.now(),
timer = 0,
PI_BY_180 = Math.PI / 180,
PI2 = 2 * Math.PI,
c = document.querySelector('#canvas'),
ctx = c.getContext('2d'),
items = [],
cx = 200,
cy = 200,
start_radius_outer = 100,
end_radius_outer = 300,
outer_radius_diff = end_radius_outer - start_radius_outer,
start_radius_inner = start_radius_outer,
end_radius_inner = 0,
inner_radius_diff = start_radius_inner - end_radius_inner,
base_line_width = 1,
base_speed = 10,
variable_speed = 50,
interval = 0.3;
function Circle () {
this.enabled = 0;
this.line_width = base_line_width;
}
Circle.prototype.draw = function () {
ctx.strokeStyle = 'black';
ctx.lineWidth = this.line_width;
ctx.beginPath();
ctx.arc(cx, cy, this.radius, 0, PI2, false);
ctx.stroke();
ctx.closePath();
return this;
};
Circle.prototype.update = function (dt) {
let distance = Math.abs(start_radius_inner - this.radius);
if (this.dir === 1) {
angle = (distance / outer_radius_diff) * 180 * PI_BY_180;
sin = Math.sin(angle);
this.radius += (variable_speed * sin + base_speed) * dt;
this.line_width = clamp(base_line_width + sin * 9, base_line_width, 9);
if (this.radius > end_radius_outer) {
this.enabled = 0;
return false;
}
} else {
angle = (distance / inner_radius_diff) * 90 * PI_BY_180;
sin = Math.sin(angle);
this.radius -= (variable_speed * sin + base_speed) * dt;
this.line_width = clamp(base_line_width + sin * 9, base_line_width, 9);
if (this.radius < end_radius_inner) {
this.enabled = 0;
return false;
}
}
return this;
};
function animate() {
let now = Date.now(),
circle = null,
i,
dt = (now - last_t) / 1000;
timer += dt;
last_t = now;
ctx.clearRect (0, 0, 600, 600);
if (timer > interval) {
timer = 0;
// outer circle
for (i = 0; i < items.length; i++) {
if (!items[i].enabled) break;
}
circle = items[i];
circle.radius = start_radius_inner;
circle.dir = 1;
circle.enabled = 1;
// inner circle
for (i = i + 1; i < items.length; i++) {
if (!items[i].enabled) break;
}
circle = items[i];
circle.radius = start_radius_inner;
circle.dir = -1;
circle.enabled = 1;
}
for (i = 0; i < items.length; i++) {
circle = items[i];
circle.enabled && circle.update(dt) && circle.draw();
}
window.requestAnimationFrame ? requestAnimationFrame(animate) : mozRequestAnimationFrame(animate);
}
function clamp(val, clamp_val_lower, clamp_val_upper) {
if (val < clamp_val_lower) {
return clamp_val_lower;
} else if (val > clamp_val_upper) {
return clamp_val_upper;
}
return val;
}
function start() {
let i;
items = [];
ctx.clearRect (0, 0, 400, 400);
i = 0;
while (++i < 60) {
items.push(new Circle());
}
animate();
}
start();
});