A neat way to do circular timer bars in SVG
OK so for a thing, I wanted to make a rotating timer/progress bar in SVG. Something like this:
...with the progress bar sweeping around the circle as time goes on.
Now, I made that in my SVG editor Inkscape, in which I have two handles which I can slide around the circle as I like. So I would just need to extract the relevant SVG from Inkscape and animate one of those handles around the circle, right?
Unfortunately, Inkscape's handles are just an intuitive wrapper over a basic SVG Path object. The arc is actually defined by its start point, end point and radius, with the centre and angle swept calculated automatically. So I can't simply animate the angle swept - I'd have to move the endpoint, which is much harder.
Based on this Stack Overflow post, standard ways to draw rotating progress bars in SVG all depend on setting up a function in JavaScript to calculate the endpoint position by converting from polar to Cartesian coordinates. It felt like there should be a way to do something like this in pure SVG, though.
I ended up with this:
<svg width="400" height="400"> <defs> <clipPath id="clipRightAnim"> <rect width="200" height="400" x="0" y="0" > <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 200 200" to="180 200 200" dur="2s" fill="freeze" begin="0s" id="anim1"/> </rect> </clipPath> <clipPath id="clipRightStatic"> <rect width="200" height="400" x="200" y="0" fill="white"/> </clipPath> <clipPath id="clipLeftAnim"> <rect width="200" height="400" x="200" y="0" > <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 200 200" to="180 200 200" dur="2s" fill="freeze" begin="anim1.end"/> </rect> </clipPath> <clipPath id="clipLeftStatic"> <rect width="200" height="400" x="0" y="0" fill="white"/> </clipPath> </defs> <g clip-path="url(#clipRightAnim)"> <circle cx="200" cy="200" r="80" stroke="#004fec" stroke-width="20" fill="none" clip-path="url(#clipRightStatic)"/> </g> <g clip-path="url(#clipLeftAnim)"> <circle cx="200" cy="200" r="80" stroke="#004fec" stroke-width="20" fill="none" clip-path="url(#clipLeftStatic)"/> </g> </svg>
This works using clipping paths. The circle is clipped by a rect, turning it into a semicircle:
The circle, with its clipping path, is placed in a group. The group has a second clipping mask, initially a rect in the opposite position.
Because the two clipping paths don't overlap at all, the circle isn't visible.
The left clipping rect, which applies to the group containing the circle, is animated to rotate around the centre of the circle. As it overlaps more of the other rectangle, more of the circle is revealed (everything in the pale green area is visible).
Eventually the rectangles completely overlap, and a semi-circle is revealed.
As soon as this first animation finishes, the corresponding animation for a mirrored semicircle begins. The transition should seem seamless.
This worked perfectly in Chrome, but in Firefox at time of writing there seems to be something funny going on with SVG animation which means the SVG doesn't update its appearance until clicked on.
Anyway, here's the above example. I'm going to work on putting the rate of rotation under external control.












