Countdown-Ring Carousel — HTML CSS JS Snippet

Countdown-Ring Carousel · Heroes · Plain HTML, CSS & JS · Live preview

What's included

Features

SVG stroke-dasharray/stroke-dashoffset ring, the same technique behind any circular progress indicator
Driven by requestAnimationFrame and real timestamps, not a fire-and-forget CSS transition
Hover-to-pause freezes the ring at its exact drain position, not a rounded or estimated one
Resuming continues the countdown (and the auto-advance timer) from that same paused point
The ring itself is directly clickable to skip to the next slide immediately
Crossfade slide transition, timer duration and ring configured from two constants

About this UI Snippet

Countdown-Ring Carousel — An SVG Stroke as a Literal Timer

Screenshot of the Countdown-Ring Carousel snippet rendered live

Instead of a linear progress bar, this carousel's autoplay timer is a small circular ring in the corner that drains like an actual countdown — because it's driven by exactly the same trick real progress rings use: an SVG circle with stroke-dasharray set to its own circumference, and stroke-dashoffset animated from 0 up to that same circumference to make the visible stroke shrink away.

requestAnimationFrame instead of a CSS transition

Unlike a bar carousel where a CSS width transition can do all the work, this ring needs to be *readable mid-drain* if a user hovers to pause — so the fill is driven by requestAnimationFrame, computing progress = elapsed / DURATION on every frame from a real performance.now() timestamp, not from a fire-and-forget transition. That's what makes an accurate pause possible: the loop just stops updating strokeDashoffset the instant paused becomes true, freezing the ring at its exact current drain state.

The circumference constant, and why it can't be a guess

stroke-dasharray and the dashoffset math both use 119.4 — the circle's actual circumference, 2 × π × r for a radius of 19. Get this number wrong and the ring either shows a gap at rest (dasharray too large) or never fully hides its stroke (too small); it has to match the SVG circle's real r attribute exactly.

A clickable timer, not just a display

The ring itself is a click target that calls next() directly — letting an impatient visitor skip ahead without hunting for separate arrow buttons, while the ring's decorative countdown role and its functional skip role stay the same element.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how stroke-dasharray and stroke-dashoffset combine to make an SVG circle appear to drain, and why the 119.4 circumference constant has to match the circle's actual radius precisely. It's also worth asking the assistant to add a numeric countdown label inside the ring showing seconds remaining, or to convert the ring into a proper accessible button with an aria-label and prefers-reduced-motion handling that disables autoplay entirely for users who've requested reduced motion.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a hero carousel in plain HTML, CSS, and vanilla JavaScript with a small circular SVG countdown ring (not a linear bar) that visibly drains to time each slide's autoplay duration — no library.

Requirements:
- Several full-bleed hero slides stacked on top of each other, crossfading via opacity, only one active at a time.
- A small SVG circle positioned in a corner of the carousel, using the stroke-dasharray/stroke-dashoffset technique to visually represent a countdown: at the start of each slide's timer the ring shows a full stroke, and it visually drains away (the stroke shrinking) as time elapses, reaching a fully empty stroke exactly when the slide's time is up.
- The countdown must be driven by requestAnimationFrame using real timestamps (not a CSS transition and not a fixed-interval setInterval), calculating elapsed time on every frame and deriving the stroke-dashoffset from that elapsed time divided by the total duration.
- When the ring finishes draining, automatically advance to the next slide (wrapping to the first after the last) and restart the ring's drain from full.
- On mouse hover over the carousel, the ring's drain must freeze at its exact current position rather than continuing or resetting.
- On mouse leave, the ring must resume draining smoothly from that exact frozen position toward empty, correctly timed so it still completes the full original duration relative to when the countdown actually began.
- The ring itself must be directly clickable, immediately advancing to the next slide and resetting the ring to a full stroke when clicked.
- The circle's stroke-dasharray value must be calculated from and match the circle's actual radius (2 × π × radius), not an arbitrary approximation.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="crc-wrap" id="crcWrap">
  <div class="crc-track" id="crcTrack">
    <div class="crc-slide" style="background:linear-gradient(160deg,#6366f1,#4338ca)"><h2>Ship faster</h2><p>Deploy in seconds, not sprints.</p></div>
    <div class="crc-slide" style="background:linear-gradient(160deg,#ec4899,#9d174d)"><h2>Stay in sync</h2><p>Real-time collaboration, built in.</p></div>
    <div class="crc-slide" style="background:linear-gradient(160deg,#10b981,#047857)"><h2>Scale freely</h2><p>Infra that grows without a rewrite.</p></div>
  </div>
  <svg class="crc-ring" viewBox="0 0 44 44" width="44" height="44">
    <circle class="crc-track-circle" cx="22" cy="22" r="19"></circle>
    <circle class="crc-fill-circle" id="crcFillCircle" cx="22" cy="22" r="19"></circle>
  </svg>
</div>

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA hero carousel starts playing, with a small ring in the corner beginning to drain.
  2. 2
    Watch the ring drainWhen it empties completely, the hero crossfades to the next slide and the ring refills.
  3. 3
    Hover over the heroThe ring freezes exactly where it is — the countdown pauses mid-drain.
  4. 4
    Move the mouse awayThe ring resumes draining from that exact frozen point, not from full again.
  5. 5
    Click the ringSkip straight to the next slide immediately, resetting the ring to full.

Real-world uses

Common Use Cases

Marketing hero carousels
A compact, elegant autoplay indicator that fits in a corner rather than spanning the full width.
Announcement or promo rotators
Give visitors a precise, glanceable sense of when the next message will appear.
Kiosk or signage displays
A countdown ring reads clearly at a distance, useful for unattended rotating displays.
Event countdown-style content rotators
Pair the ring metaphor with time-sensitive content like limited offers or session schedules.

Got questions?

Frequently Asked Questions

Change the DURATION constant (milliseconds) — it drives both how long the ring takes to drain and when auto-advance fires, since both come from the same elapsed/DURATION calculation.

Change the SVG's width/height and the circle's r attribute together, then recalculate CIRC as 2 * Math.PI * r and update both stroke-dasharray in the CSS and the CIRC constant in the JS to match — they must agree exactly or the ring will show a gap or never fully empty.

A CSS transition can't be queried mid-flight for a precise "how much progress was made" value in every browser reliably — requestAnimationFrame with a real timestamp gives an exact, readable progress value at any instant, which the pause/resume logic depends on.

Yes — next() uses modulo against slides.length, so any number of .crc-slide elements works without other changes.

The ring is a real clickable element with cursor: pointer; for full accessibility, convert it to a <button> wrapping the SVG with an aria-label like "Skip to next slide," and add prefers-reduced-motion handling to disable autoplay for users who've requested it.