How to Animate using Canvas in Angular

Discussão em 'Angular' iniciado por MRichards, Novembro 21, 2025.

  1. MRichards

    MRichards Guest

    I am trying to code an animated progress wheel in Angular 20. My animation works in a vanilla javascript environment, but when ported to Angular, nothing draws on the canvas and I'm not sure why. I've searched online and the only examples I can find of trying to do this don't work (issues with `this`, which is why I had to bind the values I need to the animation method). I've read about `this`, and have tried what I can, but have reached a point where I'm not sure what is going wrong.

    Here is my component. There's a Stackblitz link at the bottom reproducing the issue.

    Component:

    import {
    Component,
    computed,
    input,
    signal,
    OnInit,
    Signal,
    ViewChild,
    ElementRef,
    } from '@angular/core';

    @Component({
    selector: 'app-progress-wheel',
    template: `<canvas #myCanvas id="canvas" width="150" height="150" style="border:1px solid #000000;">
    Unsupported
    </canvas>`,
    })
    export class ProgressWheelComponent implements OnInit {
    public maxProgress = input<number>(1);
    public minProgress = input<number>(1);
    public completed = input<number>(0);

    @ViewChild('myCanvas')
    private canvas: ElementRef<HTMLCanvasElement>;
    private context: CanvasRenderingContext2D;
    private progressAngle: Signal<number>;
    private extraAngle: Signal<number>;
    private twoPi = 2 * Math.PI;
    private maxIterations = 100;
    private animationId: number = 0;

    constructor() {}

    ngOnInit() {
    this.initializeAngles();
    }

    ngAfterViewInit(): void {
    this.context = this.canvas.nativeElement.getContext('2d');
    this.animationId = requestAnimationFrame(
    this.animate.bind(
    this,
    this.maxIterations,
    this.progressAngle(),
    this.extraAngle(),
    this.context
    )
    );
    }

    ngOnDestroy(): void {
    if (this.animationId) {
    cancelAnimationFrame(this.animationId);
    }
    }

    private initializeAngles(): void {
    const extraCompleted = computed(() =>
    Math.max(this.completed() - this.minProgress(), 0)
    );

    const amountCompletedFraction = computed(
    () => (this.completed() - extraCompleted()) / this.minProgress()
    );
    this.progressAngle = computed(() => amountCompletedFraction() * this.twoPi);

    this.extraAngle = computed(() => extraCompleted() * this.twoPi);
    }

    private animate(maxIterations, progressAngle, extraAngle, context): void {
    let i = 0;
    let j = 0;
    const ctx = context;

    if (i <= maxIterations || j <= maxIterations) {
    ctx.save();
    ctx.clearRect(0, 0, 150, 150);
    ctx.translate(75, 75);
    ctx.scale(0.5, 0.5);
    ctx.rotate(-Math.PI / 2);

    if (i <= maxIterations) {
    ctx.beginPath();
    ctx.lineWidth = 8;
    ctx.strokeStyle = '#325FA2';
    ctx.arc(0, 0, 70, 0, progressAngle * (i / maxIterations), false);
    ctx.stroke();
    i++;
    } else {
    ctx.beginPath();
    ctx.lineWidth = 8;
    ctx.strokeStyle = '#325FA2';
    ctx.arc(0, 0, 70, 0, progressAngle * (i / maxIterations), false);
    ctx.stroke();

    ctx.beginPath();
    ctx.strokeStyle = '#FF0000';
    ctx.arc(
    0,
    0,
    70,
    progressAngle,
    progressAngle + extraAngle * (j / maxIterations),
    false
    );
    ctx.stroke();
    j++;
    }

    ctx.restore();
    this.animationId = requestAnimationFrame(
    this.animate.bind(
    this,
    maxIterations,
    progressAngle,
    extraAngle,
    context
    )
    );
    }
    }
    }


    StackBlitz: https://stackblitz.com/edit/stackblitz-starters-2bzndbtw?file=src/progress-wheel.component.ts

    Continue reading...

Compartilhe esta Página