2
votes

I am trying to progressively fill an ellipse behind a rotating radius, really, a clock-like timer that fills behind the sweeping hand. I am close to getting it right but the arc of the filled area moves with each recalculation.

The CodePen version is here but to summarize, I am using the following HTML to start:

<svg version="1.1" baseProfile="full" width="50%" height="50%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 3.2">
  <path id="pie" stroke="none" stroke-width=".01" fill="rgb(204, 50, 50)"
          d="M 1.5 1.7 V 1.5 .3 
             A 1.5 1.3 0 0 1 1.5 .2
             z"/>
  <line id="hand" stroke="black" stroke-width=".02"
           x1="1.5" y1="1.7" x2="1.5" y2=".2"/>
</svg>

and the following JavaScript to rotate the hand and draw/fill behind it:

var sweep = document.getElementById("hand"),
    fill = document.getElementById("pie"),
    degrees = 0;
const Torads = Math.PI/180;

function rotateHand() {
  degrees += 45;
  sweep.setAttribute("transform", "rotate(" + degrees + " 1.5 1.7)");
  if (degrees <= 180) {
  fill.setAttribute("d", "M 1.5 1.7 V .4 A 1.4 1.3 0 0 1 " + ellipticalXcoords(Math.cos((degrees-90) * Torads)) + " " + ellipticalYcoords(Math.sin((degrees-90) * Torads)) + " z");
  } else {
  fill.setAttribute("d", "M 1.5 1.7 V .4 A 1.4 1.3 0 1 1 " + ellipticalXcoords(Math.cos((degrees - 90) * Torads)) + " " + ellipticalYcoords(Math.sin((degrees - 90) * Torads)) + " z");
  }
1

1 Answers

4
votes

You're making life quite difficult for yourself here. Instead of trying to calculate the incremental changes in the shape of the filled region, you could use the same shape as a clip mask applied to a regular circle that can be animated much more easily using the stroke-dasharray trick.

Here's how I would do it. Notice that the circular fill is rotated by -90° so that the animation starts at the top of the circle instead of the side. A corresponding +90° rotation is applied to the clip mask to account for this.

var sweep = document.getElementById("hand"),
  fill = document.getElementById("apple-fill"),
  degrees = 0;
const Torads = Math.PI / 180;
var animating = false;

function rotateHand() {
  degrees += 4;
  if (degrees >= 360) {
    clearInterval(animating);
    animating = false;
    degrees = 360;
  }
  sweep.setAttribute("transform", "rotate(" + degrees + " 1.5 1.7)");
  fill.setAttribute("stroke-dasharray", degrees * 0.01309 + ", 20");
}

function startAnimation() {
  if (!animating) {
    degrees = 0;
    animating = setInterval(rotateHand, 30);
  }
}
<button onclick="startAnimation(); return 0">Animate</button>
<svg width="200" height="200" viewBox="0 0 3 3.2">
  <defs>
    <clipPath id="apple">
      <path d="M1.5.5A.2.2 0 0 1 1.7.315A1.4 1.3 0 1 1 1.3.315A.2.2 0 0 1 1.5.5z" transform="rotate(90,1.5,1.7)" />
    </clipPath>
  </defs>
  <circle cx="1.5" cy="1.7" r=".75" id="apple-fill" fill="none" stroke="rgb(204, 50, 50)" stroke-width="1.5" stroke-dasharray="0,20" transform="rotate(-90,1.5,1.7)" clip-path="url(#apple)" />
  <path id="pomodoro" stroke-width=".01" fill="none" stroke="#000" d="M1.5.5A.2.2 0 0 1 1.7.315A1.4 1.3 0 1 1 1.3.315A.2.2 0 0 1 1.5.5" />
  <path id="leaf" stroke-width=".01" stroke-linejoin="arc" fill="green" d="M1.5.6A.4.4 0 0 1 2 .1A.5.5 0 0 1 1.5.6" />
  <line id="hand" stroke="black" stroke-width=".02" x1="1.5" y1="1.7" x2="1.5" y2=".2" />
</svg>