skip to Main Content

I was trying to make an animated spiral that should spin around, but when i tried it out it did not start spining but no errors occurred. If anyone could help me, thanks

My code i have right now:

CanvasRenderingContext2D.prototype.drawArchimedeanSpiral =
  CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ||
  function(centerX, centerY, stepCount, loopCount,
    innerDistance, loopSpacing, rotation) {
    this.beginPath();

    var stepSize = 2 * Math.PI / stepCount,
      endAngle = 2 * Math.PI * loopCount,
      finished = false;

    for (var angle = 0; !finished; angle += stepSize) {
      // Ensure that the spiral finishes at the correct place,
      // avoiding any drift introduced by cumulative errors from
      // repeatedly adding floating point numbers.
      if (angle > endAngle) {
        angle = endAngle;
        finished = true;
      }

      var scalar = innerDistance + loopSpacing * angle,
        rotatedAngle = angle + rotation,
        x = centerX + scalar * Math.cos(rotatedAngle),
        y = centerY + scalar * Math.sin(rotatedAngle);

      this.lineTo(x, y);
    }

    this.stroke();
  }
  
const canvasEle = document.querySelector("canvas")
const ctx = canvasEle.getContext("2d")

ctx.drawArchimedeanSpiral(200, 200, 100, 10, 1, 1, 2*Math.PI);
<canvas width="400px" height="400px" />

When i tried it it did not work at all, it just showed a spiral, i would like an animated spiral that i could change the color to.

2

Answers


  1. Your code as-is will already render a nice looking spiral, so all that is left is to get the animation working. When using the canvas element, you can animate it by re-rendering the canvas on each frame, setting the content to the visuals for that frame only, and overwriting it on the next.

    To clear your canvas between frames, you can use ctx.clearRect. To schedule the updates for each frame, you can use setInterval with a small value for the second parameter. Finally, for the animation itself, we only need to change the rotation parameter on each frame to get the image of the rotated spiral.

    So, the basic steps will be to set up a setInterval to execute the draw code 30 times a second (or at whatever FPS you want), then, on each of those frames:

    • Clear the canvas.
    • Calculate the current angle of rotation (based on the current time).
    • Produce a new spiral with that angle.
    • Draw it to the canvas.

    Putting all that together, your snippet might be modified like so:

    CanvasRenderingContext2D.prototype.drawArchimedeanSpiral =
      CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ||
      function(centerX, centerY, stepCount, loopCount,
        innerDistance, loopSpacing, rotation) {
        this.beginPath();
    
        var stepSize = 2 * Math.PI / stepCount,
          endAngle = 2 * Math.PI * loopCount,
          finished = false;
    
        for (var angle = 0; !finished; angle += stepSize) {
          // Ensure that the spiral finishes at the correct place,
          // avoiding any drift introduced by cumulative errors from
          // repeatedly adding floating point numbers.
          if (angle > endAngle) {
            angle = endAngle;
            finished = true;
          }
    
          var scalar = innerDistance + loopSpacing * angle,
            rotatedAngle = angle + rotation,
            x = centerX + scalar * Math.cos(rotatedAngle),
            y = centerY + scalar * Math.sin(rotatedAngle);
    
          this.lineTo(x, y);
        }
    
        this.stroke();
      }
      
    const canvasEle = document.querySelector("canvas")
    const ctx = canvasEle.getContext("2d")
    
    ctx.drawArchimedeanSpiral(200, 200, 100, 10, 1, 1, 0);
    
    const getRotationAngle = (x) => {
      // 0 -> 2pi, every x milliseconds.
      const remainder = Date.now() % x;
      return Math.PI * 2 * (remainder / x);
    }
    
    setInterval(() => {
      ctx.clearRect(0, 0, 400, 400);
      ctx.drawArchimedeanSpiral(200, 200, 100, 10, 1, 1, getRotationAngle(1000)) // rotate once per sec.
    }, 1000/30) // 30 FPS
    <canvas width="400px" height="400px" />

    This seems to produce the animation you were looking for. You can also produce different animations by modifying other parameters of your draw function on each loop.


    P.S. To change color, use ctx.strokeStyle:

    CanvasRenderingContext2D.prototype.drawArchimedeanSpiral =
      CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ||
      function(centerX, centerY, stepCount, loopCount,
        innerDistance, loopSpacing, rotation) {
        this.beginPath();
    
        var stepSize = 2 * Math.PI / stepCount,
          endAngle = 2 * Math.PI * loopCount,
          finished = false;
    
        for (var angle = 0; !finished; angle += stepSize) {
          // Ensure that the spiral finishes at the correct place,
          // avoiding any drift introduced by cumulative errors from
          // repeatedly adding floating point numbers.
          if (angle > endAngle) {
            angle = endAngle;
            finished = true;
          }
    
          var scalar = innerDistance + loopSpacing * angle,
            rotatedAngle = angle + rotation,
            x = centerX + scalar * Math.cos(rotatedAngle),
            y = centerY + scalar * Math.sin(rotatedAngle);
    
          this.lineTo(x, y);
        }
    
        this.stroke();
      }
      
    const canvasEle = document.querySelector("canvas")
    const ctx = canvasEle.getContext("2d")
    
    // Change color!!!
    ctx.strokeStyle = "green";
    
    ctx.drawArchimedeanSpiral(200, 200, 100, 10, 1, 1, 0);
    
    const getRotationAngle = (x) => {
      // 0 -> 2pi, every x milliseconds.
      const remainder = Date.now() % x;
      return Math.PI * 2 * (remainder / x);
    }
    
    setInterval(() => {
      ctx.clearRect(0, 0, 400, 400);
      ctx.drawArchimedeanSpiral(200, 200, 100, 10, 1, 1, getRotationAngle(1000)) // rotate once per sec.
    }, 1000/30) // 30 FPS
    <canvas width="400px" height="400px" />
    Login or Signup to reply.
  2. You need to loop (animate) the canvas.

    I set the FPS to 12 and rotates by Math.PI / 6, so the circle will rotate 260 degrees every second.

    CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ??= function(centerX, centerY, stepCount, loopCount, innerDistance, loopSpacing, rotation) {
      this.beginPath();
      const stepSize = 2 * Math.PI / stepCount, endAngle = 2 * Math.PI * loopCount;
      let finished = false;
      for (let angle = 0; !finished; angle += stepSize) {
        if (angle > endAngle) {
          angle = endAngle;
          finished = true;
        }
        const scalar = innerDistance + loopSpacing * angle, rotatedAngle = angle + rotation, x = centerX + scalar * Math.cos(rotatedAngle), y = centerY + scalar * Math.sin(rotatedAngle);
        this.lineTo(x, y);
      }
      this.stroke();
    };
    
    const ctx = document.querySelector('canvas').getContext('2d'), FPS = 12;
    let rotation = 0;
    const update = () => {
      rotation = (rotation + Math.PI / 6) % (Math.PI * 2);
    };
    const draw = () => {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      ctx.strokeStyle = 'blue';
      ctx.drawArchimedeanSpiral(100, 100, 100, 6, 1, 2, rotation);
    };
    const loop = (timestamp) => {
      setTimeout(() => {
        update();
        draw();
        requestAnimationFrame(loop);
      }, 1000 / FPS);
    };
    loop(); // See https://stackoverflow.com/a/39135659/1762224
    <canvas width="200px" height="200px" />

    If you want a faster rate, adjust the FPS. Now the circle will rotate 720 degrees in 1 second.

    CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ??= function(centerX, centerY, stepCount, loopCount, innerDistance, loopSpacing, rotation) {
      this.beginPath();
      const stepSize = 2 * Math.PI / stepCount, endAngle = 2 * Math.PI * loopCount;
      let finished = false;
      for (let angle = 0; !finished; angle += stepSize) {
        if (angle > endAngle) {
          angle = endAngle;
          finished = true;
        }
        const scalar = innerDistance + loopSpacing * angle, rotatedAngle = angle + rotation, x = centerX + scalar * Math.cos(rotatedAngle), y = centerY + scalar * Math.sin(rotatedAngle);
        this.lineTo(x, y);
      }
      this.stroke();
    };
    
    const ctx = document.querySelector('canvas').getContext('2d'), FPS = 24;
    let rotation = 0;
    const update = () => {
      rotation = (rotation + Math.PI / 6) % (Math.PI * 2);
    };
    const draw = () => {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      ctx.strokeStyle = 'blue';
      ctx.drawArchimedeanSpiral(100, 100, 100, 6, 1, 2, rotation);
    };
    const loop = (timestamp) => {
      setTimeout(() => {
        update();
        draw();
        requestAnimationFrame(loop);
      }, 1000 / FPS);
    };
    loop(); // See https://stackoverflow.com/a/39135659/1762224
    <canvas width="200px" height="200px" />
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search