Eclipse-
Eclipse-

Reputation: 71

How to get canvas animation code to work with multiple elements?

I have a loading animation module which animates some loading elements in my project, the code:

const loadingAnimation = ( function () {
    //private methods
    let ctx;
    let canvasWidth;
    function initializeCanvas (id) {
        const canvas = document.querySelector(id);
        ctx = canvas.getContext('2d');
        canvasWidth = $(canvas).outerWidth();
    }
    class loadingCircle {
        constructor (x, y, r, start, end, v, width) {
            this.x = x;
            this.y = y;
            this.r = r;
            this.color = '#A065FF';
            this.start = start;
            this.end = end;
            this.offset = this.end - this.start;
            this.v = v;
            this.vi = this.v;
            this.width = width;
            this.expand = true;
        }
        render () {
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.r, this.start, this.end, false);
            ctx.strokeStyle = this.color;
            ctx.lineWidth = this.width;
            ctx.lineCap = 'round';
            ctx.stroke();
        }
        update () {
            this.render();
            if (this.expand) {
                this.end = (this.end - this.start < Math.PI * 2 - .5) ? this.end + this.v + 2 * ( (this.end - this.start - this.offset) / (Math.PI * 2 - this.offset * 2) ) * this.v : this.end;
                this.expand = (this.end - this.start < Math.PI * 2 - .5);
            } else {
                this.start = (this.end - this.start > .5) ? this.start + this.v + 2 * ( (this.end - this.start - this.offset) / (Math.PI * 2 - this.offset * 2) ) * this.v : this.start;
                this.expand = !(this.end - this.start > .5);
            }
            this.start += this.v;
            this.end += this.v;
        }
    }
    function initiateLoading () {
        let circlesArray = [];
        function addFromClass (n) {
            circlesArray = [];
            for (let i = 0; i < n; i++) {
                let r = .4 * canvasWidth;
                let x = .5 * canvasWidth;
                let y = .5 * canvasWidth;
                let start = 0;
                let end = 1;
                let v = .07;
                let width = 3;
                circlesArray.push(new loadingCircle(x, y, r, start, end, v, width));
            }
        }
        function animate() {
            ctx.clearRect(0, 0, innerWidth, innerHeight);
            for (let i = 0; i < circlesArray.length; i++) {
                circlesArray[i].update();
            }
            requestAnimationFrame(animate);
        }
        addFromClass(1);
        animate();
    }
    //public methods
    function init (id) {
        if ( $(id).length ) {
            initializeCanvas(id);
            initiateLoading();
        }
    }
    //export public methods
    return {
        init: init
    };
} () );

loadingAnimation.init('#c1');
loadingAnimation.init('#c2');
canvas { border: 1px solid black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="c1"></canvas>
<canvas id="c2"></canvas>

I create two canvas elements with another method then I call loadingAnimation.init(id) with the id of both canvas elements, the problem is, as soon as the second one starts to animate, the first one stops, if I call the loadingAnimation.init(id) command for the first one in console, it works fine, but now the second one stops animating. Any ideas? cheers.

Upvotes: 0

Views: 49

Answers (2)

user128511
user128511

Reputation:

The issue is you have there 2 shared variables at the top

let ctx;
let canvasWidth;

They can only track one canvas context and one width.

You need so some how refractor your code so they are not shared. How you do that is up to you. Track them in loadingCircle or closure or something. Eg.

const loadingAnimation = ( function () {
    function initializeCanvas (id) {
        const canvas = document.querySelector(id);
        return canvas.getContext('2d');
    }

    //private methods
    class loadingCircle {
        constructor (x, y, r, start, end, v, width) {
            this.x = x;
            this.y = y;
            this.r = r;
            this.color = '#A065FF';
            this.start = start;
            this.end = end;
            this.offset = this.end - this.start;
            this.v = v;
            this.vi = this.v;
            this.width = width;
            this.expand = true;
        }
        render (ctx) {
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.r, this.start, this.end, false);
            ctx.strokeStyle = this.color;
            ctx.lineWidth = this.width;
            ctx.lineCap = 'round';
            ctx.stroke();
        }
        update (ctx) {
            this.render(ctx);
            if (this.expand) {
                this.end = (this.end - this.start < Math.PI * 2 - .5) ? this.end + this.v + 2 * ( (this.end - this.start - this.offset) / (Math.PI * 2 - this.offset * 2) ) * this.v : this.end;
                this.expand = (this.end - this.start < Math.PI * 2 - .5);
            } else {
                this.start = (this.end - this.start > .5) ? this.start + this.v + 2 * ( (this.end - this.start - this.offset) / (Math.PI * 2 - this.offset * 2) ) * this.v : this.start;
                this.expand = !(this.end - this.start > .5);
            }
            this.start += this.v;
            this.end += this.v;
        }
    }
    function initiateLoading (ctx) {
        const canvasWidth = $(ctx.canvas).outerWidth();
        let circlesArray = [];
        function addFromClass (n) {
            circlesArray = [];
            for (let i = 0; i < n; i++) {
                let r = .4 * canvasWidth;
                let x = .5 * canvasWidth;
                let y = .5 * canvasWidth;
                let start = 0;
                let end = 1;
                let v = .07;
                let width = 3;
                circlesArray.push(new loadingCircle(x, y, r, start, end, v, width));
            }
        }
        function animate() {
            ctx.clearRect(0, 0, innerWidth, innerHeight);
            for (let i = 0; i < circlesArray.length; i++) {
                circlesArray[i].update(ctx);
            }
            requestAnimationFrame(animate);
        }
        addFromClass(1);
        animate();
    }
    //public methods
    function init (id) {
        if ( $(id).length ) {
            const ctx = initializeCanvas(id);
            initiateLoading(ctx);
        }
    }
    //export public methods
    return {
        init: init
    };
} () );

loadingAnimation.init('#c1');
loadingAnimation.init('#c2');
canvas { border: 1px solid black; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="c1"></canvas>
<canvas id="c2"></canvas>

Upvotes: 1

kamprasad
kamprasad

Reputation: 648

That's because both canvas access same loadingAnimation object, so you need to have two different object of loadingAnimation and call loadingAnimation.init methods, That will work.

More Simplest way -> try to make more instance of function used as IIFE.

Upvotes: 1

Related Questions