Canvas: Circles & Optical Illusions

Ciprian on Monday, March 21, 2022 in Canvas

NEW! Learn JavaScript by example. Code snippets, how-to's and tutorials. Try now!

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 math 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.

JavaScript Canvas Logo
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();
});

Related posts

Leave a Reply

You have to agree to the comment policy.