Home > front end >  SVG semi-arc clockwise animation over 180 degrees (half of a circle) with pure CSS/JS
SVG semi-arc clockwise animation over 180 degrees (half of a circle) with pure CSS/JS

Time:05-20

I need some help with my code. My goal is to make a 180 degrees progressive animation that goes clockwise for the upper half of a circle. My ultimate goal is to have two animations playing at the same time: upper half goes clockwise, bottom half goes counter-clockwise, both in the very same fashion.

So far I have this:

// Circle functions
  function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
    const angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

    return {
      x: centerX   (radius * Math.cos(angleInRadians)),
      y: centerY   (radius * Math.sin(angleInRadians))
    };
  }

  function describeArc(x, y, radius, startAngle, endAngle){
    const start = polarToCartesian(x, y, radius, endAngle);
    const end = polarToCartesian(x, y, radius, startAngle);

    const arcSweep = endAngle - startAngle <= 180 ? "0" : "1";

    const d = [
        "M", start.x, start.y, 
        "A", radius, radius, 0, arcSweep, 0, end.x, end.y,
        "L", x,y,
        "L", start.x, start.y
    ].join(" ");

    return d;       
  }
.arc {
  animation: moveForward 5s linear forwards;
}

@keyframes moveForward {
  to {
    d: path("M 450 150 A 300 300 0 0 0 450 150 L 150 150 L 450 150");
  }
}
<svg viewBox="0 0 300 300" height="250" width="250" style="background-color: black;">
  <path fill="white" stroke-width="0" d="M 450 150 A 300 300 0 0 0 -150 149.99999999999997 L 150 150 L 450 150" />
  <path  fill="black" stroke-width="0" d="M 450 150 A 300 300 0 0 0 -145.44232590366238 97.90554669992092 L 150 150 L 450 150" />
</svg>

Don't mind the extrapolated radius, I want to confide the animation within the SVG area for this animation. As you can see, it goes along the path but something is missing because it starts to shrink over the course of the animation. I'm not an expert, but maybe something is missing in the shape interpolation.

I'm using formulas to generate the end points based on this answer from another somewhat related question. It basically translates my arc's angle to cartesian coordinates.

I appreciate any input and thank you so much.

CodePudding user response:

A possible solution would be animating a very thick stroke (the double of the radius of the circle).

In this case the radius of the circle is 20 so the stroke-width="40"

In the next example I'm animating the stroke-dasharray of the path from: 62.84,0 (stroke length = 62.84, gap = 0) to 0, 62.4 (stroke length = 0, gap = 62.84) where 62.84 is the length of the path.

Please read about how How SVG Line Animation Works

path {
  stroke-dasharray: 62.84,0;
  animation: anim 5s linear infinite;
}

@keyframes anim {
  to {
    stroke-dasharray: 0, 62.84;
  }
}
<svg viewBox="-50 -50 100 100" width="90vh">
  <path stroke-width="40" fill="none" stroke="black" d="M20,0A20,20 0 0 0 -20,0"/> 
</svg>

As you can see I'm using only css. You may need to use JS in order to calculate the length of the path. You can do it using the getTotalLength() method.

UPDATE

The OP is commenting:

if I were to expand the radius so it covers the SVG area (the square container) entirely, without any padding, what's the factor between this enlarged radius and your solution? What should I take into account?

In this case the visible circle (radius stroke-width/2) should be at least 70.71 but it can be bigger than that. 70.71 represents the distance between the center and one of the corners of the svg elements. It's the hypotenuse of a right-angled triangle with both legs = 50. Since the viewBox="-50 -50 100 100" the leg = 100/2.

This means stroke-width="70.71" and you need to rewrite the d attribute like so:

d="M35.355,0A35.355,35.355 0 0 0 -35.355,0"

where 35.355 = 70.71/2

Also the length of the path need to be recalculated (I'm using the p.getTotalLength() method) and in this case is 111.09. I'm using this value for the stroke-dasharray in the CSS.

console.log(p.getTotalLength())
svg{border:solid}

path {
  stroke-dasharray: 111.09,0;
  animation: anim 5s linear infinite;
}

@keyframes anim {
  to {
    stroke-dasharray: 0, 111.09;
  }
}
<svg viewBox="-50 -50 100 100" width="90vh">
  <path id="p" stroke-width="70.71" fill="none" stroke="black" 
  d="M35.355,0A35.355,35.355 0 0 0 -35.355,0"/> 
</svg>

  • Related