2

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 class="arc" 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.

Paulo Filho
  • 343
  • 2
  • 13

2 Answers2

2

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>
enxaneta
  • 31,608
  • 5
  • 29
  • 42
  • 1
    I like this solution a lot as the movement is exactly what I'm looking for. One question: 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? – Paulo Filho May 19 '22 at 16:59
  • 1
    I've updated my answer. Please take a look – enxaneta May 19 '22 at 17:24
  • This is very helpful. I was tweaking with two circles and the last step is indeed embedding this last update into the animation. This is what I have thus far, the only thing missing is expanding exactly how you did! https://jsfiddle.net/6pek241g/ – Paulo Filho May 19 '22 at 17:48
  • Out of curiosity. This is one of former Pokémon RBY battle transitions which I wanted to reproduce with SVG+CSS. I'll revisit your answer multiple times to better understand how to properly use trigonometry for this effect. Thank you so much for your time and patience. https://jsfiddle.net/6pek241g/1/ – Paulo Filho May 19 '22 at 18:07
  • I'm sorry I'm not into Pokémon – enxaneta May 19 '22 at 18:10
  • What about **not** calculating the path length, and setting ``pathLength="1"`` – Danny '365CSI' Engelman May 20 '22 at 13:36
  • @Danny'365CSI'Engelman feel free to answer the question – enxaneta May 20 '22 at 13:38
  • @enxaneta I added a similar question. If you want and feel like checking it in, it's here: https://stackoverflow.com/questions/72333896/svg-continuous-inwards-square-spiral-animation-with-pure-css-js Thanks! – Paulo Filho May 21 '22 at 23:45
2

As per comments; Enxaneta her answer, without calculating pathLength, instead set pathLength="1"

svg {
  border: solid
}

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

@keyframes anim {
  to {
    stroke-dasharray: 0, 1.1;
  }
}
<svg viewBox="-50 -50 100 100" height="180px">
  <path pathLength="1" 
        stroke-width="70.71" fill="none" stroke="black" 
        d="M35.355,0A35.355,35.355 0 0 0 -35.355,0"/> 
</svg>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • This is incredible! The platform I'm building this upon is not the web so skipping calculating the path's total length is just incredible! – Paulo Filho May 20 '22 at 17:37