webdevpuneet.com

Konva Animated Progress Ring — Canvas Arc Loader Snippet

Konva Animated Progress Ring · Loaders · Plain HTML, CSS & JS · Live preview

CategoryLoaders

Copy & paste

Source Code

Requires
<div class="kpr-stage">
  <div class="kpr-head">
    <span class="kpr-tag">Konva · Animation loop</span>
    <h2>Animated Progress Ring</h2>
    <p>A canvas arc animated frame-by-frame with Konva.Animation, not CSS.</p>
  </div>
  <div id="kprContainer" class="kpr-container"></div>
  <div class="kpr-controls">
    <button class="kpr-btn" data-target="35">35%</button>
    <button class="kpr-btn" data-target="68">68%</button>
    <button class="kpr-btn" data-target="92">92%</button>
    <button class="kpr-btn" data-target="100">100%</button>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Custom sceneFunc arc
A Konva.Shape draws the arc directly with canvas ctx.arc, not a prebuilt Arc node.
Real Konva.Animation loop
A requestAnimationFrame-backed callback drives the arc every frame.
Frame-rate-independent easing
Step size scales by frame.timeDiff so speed is consistent across refresh rates.
Ease-out settle
The step shrinks as the value approaches target, then snaps at a small threshold.
Live, redirectable target
Clicking a new target mid-animation eases from the current value, not zero.
Synced center label
The percentage text reads from the same variable driving the arc angle.
Custom shape attribute
progressAngle is stored on the shape itself and read back inside sceneFunc.
Clean track + arc layering
A static full-circle track sits beneath the animated progress arc.

About this UI Snippet

Konva Animated Progress Ring — A Real Render Loop, Not CSS

Screenshot of the Konva Animated Progress Ring snippet rendered live

Most progress rings on the web are CSS: a conic-gradient or a stroke-dashoffset trick on an SVG circle, eased with transition. Those work, but they hand control of the animation entirely to the browser's CSS engine — you can't easily read the current value mid-animation, ease it non-linearly based on live state, or drive it from anything other than a start/end pair. This snippet instead draws the ring with Konva on a <canvas> and drives it with an actual per-frame JavaScript loop, Konva.Animation.

Drawing an arc with a custom sceneFunc

Konva ships a Konva.Arc shape, but this snippet uses the more general Konva.Shape with a custom sceneFunc to make the underlying mechanism explicit:

ctx.arc(center.x, center.y, RADIUS, -Math.PI / 2, -Math.PI / 2 + angle, false); ctx.strokeShape(shape);

sceneFunc receives the raw 2D canvas context and the shape instance, and Konva calls it on every redraw. ctx.arc starts at -Math.PI / 2 (12 o'clock, since canvas angle 0 is 3 o'clock and increases clockwise) and sweeps to -Math.PI/2 + angle, where angle is stored as a custom attribute on the shape (shape.getAttr('progressAngle')) rather than hardcoded — the arc redraws itself correctly however that attribute changes. ctx.strokeShape(shape) hands the path off to Konva's own stroke/fill pipeline (respecting stroke, strokeWidth, lineCap set on the shape) instead of calling ctx.stroke() directly, so the shape still participates in Konva's caching and hit-detection systems.

The animation loop

Konva.Animation is Konva's wrapper around requestAnimationFrame: you give it a function and a layer, and it calls your function once per frame with a frame object carrying frame.time (elapsed ms since the animation started) and frame.timeDiff (ms since the previous frame), then automatically redraws the layer after your callback returns.

currentPercent += diff * Math.min(1, (frame.timeDiff / 1000) * 4);

Rather than linearly stepping toward the target by a fixed amount per frame (which would run at different speeds on different refresh rates), the step size is scaled by frame.timeDiff, making the animation's real-world duration consistent whether the browser is running at 60fps or 120fps. The diff * factor formula is an ease-out: the step size shrinks as currentPercent approaches targetPercent, so the ring decelerates into its final value rather than stopping abruptly — once the remaining difference drops under 0.15, the loop snaps to the exact target and calls anim.stop().

Restarting mid-animation

Each call to animateTo() stops any animation already in progress (if (anim) anim.stop()) before starting a new one from the *current* value of currentPercent — not from wherever the target buttons think the ring should be. That's why clicking a new percentage button while the ring is still moving smoothly redirects it rather than jumping or restarting from zero.

The label updates from the same loop

label.text(Math.round(currentPercent) + '%') is set inside the same animation callback as the arc angle, so the numeric label and the visual sweep are always perfectly in sync — there's a single source of truth (currentPercent) driving both.

Step by step

How to Use

  1. 1
    Add the Konva CDNInclude konva.min.js from the CDN panel — it attaches a global Konva object.
  2. 2
    Paste HTML, CSS, and JSA ring animates in to 68% on load.
  3. 3
    Click a percentage buttonThe ring eases toward the new target from wherever it currently is.
  4. 4
    Read the live labelThe center percentage updates every frame from the same value driving the arc.
  5. 5
    Adjust the easing feelChange the 4 multiplier in the timeDiff calculation for a faster or slower settle.
  6. 6
    Reuse for other metricsCall animateTo(n) from anywhere — a fetch callback, a form handler, a timer.

Real-world uses

Common Use Cases

Upload and processing progress
Show precise, JavaScript-driven progress for file operations, drawing the arc directly with canvas ctx.arc inside a custom Konva.Shape.
Dashboard KPI rings
Display goal completion or utilisation rings that animate to new targets, with step size scaled by frame.timeDiff for consistent speed.
Canvas animation loop teaching
Use it as a concrete reference for Konva.Animation, a requestAnimationFrame loop you can start, stop and tune.
Onboarding step progress
Show how far through a flow a user is, with the step shrinking as the value nears target and snapping at a small threshold.
Gamified XP meters
Fill experience or completion rings in a game or course that visibly ease toward their targets instead of jumping straight there.

Got questions?

Frequently Asked Questions

Konva.Arc works too, but a custom sceneFunc makes the drawing mechanism explicit and gives full control over exactly how the angle attribute maps to the canvas arc call \u2014 useful when you want non-standard behavior later, like a gapped or dashed progress arc.

Konva.Animation calls its callback with a frame object every animation frame. frame.time is elapsed milliseconds since the animation started; frame.timeDiff is milliseconds since the previous frame. Scaling the per-frame step by timeDiff keeps the animation\u2019s real-world speed consistent regardless of the display\u2019s refresh rate.

The step formula is diff * factor, where diff is the remaining distance to the target. As the ring gets closer, diff shrinks, so the step shrinks too \u2014 a simple ease-out. A small threshold (0.15) then snaps the last fraction of a percent to the exact target and stops the loop.

animateTo() stops the existing Konva.Animation and starts a new one, but currentPercent is a variable outside the animation closure, so the new animation continues from wherever the ring currently is rather than resetting to zero or jumping.

setAttr/getAttr ties the value to the Konva node itself, which keeps sceneFunc self-contained \u2014 it reads everything it needs from the shape it\u2019s drawing, rather than depending on an external closure variable, which matters more once you have multiple progress rings on the same page.

Inside the animation callback, interpolate an RGB color based on currentPercent / 100 (e.g. lerp between two color arrays) and set progressArc.stroke(interpolatedColor) each frame alongside the angle update.