Live Vote Bar Race — Free FLIP-Animated Ranking Snippet

Live Vote Bar Race · Charts · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

FLIP-based row reordering: measured positions, an inverted transform, then a played transition — no jump-cut pops
Forced-reflow step between Invert and Play so the browser always has a frame to animate from
Bar width and vote-count label animate with a plain CSS transition, kept separate from the FLIP reorder logic
Crown indicator derived live from the current leader on every tick, with a one-shot pop animation on leadership change only
Simulated live updates via setInterval, structured so real WebSocket/polling data could drive the identical render path
Pause/Resume toggle that cleanly starts and stops the interval without losing current vote state
Reset restores original data and rebuilds all rows, demonstrating the non-animated initial-build path versus the animated update path
Zero dependencies: FLIP measurements via native getBoundingClientRect(), no animation or charting library

About this UI Snippet

Live Vote Bar Race — Animated Ranking Bars That Reorder Smoothly With the FLIP Technique

Screenshot of the Live Vote Bar Race snippet rendered live

The "bar chart race" format — a set of labeled horizontal bars that grow, shrink, and swap vertical positions as their underlying numbers change over time — has become one of the most recognizable data-visualization formats on social media, used constantly for election results, sports standings, and trending topics. This snippet builds a live, interactive version: five candidates with simulated vote counts that update on a timer, bars that resize smoothly, rows that physically glide past each other when their rank changes, and a small crown animation whenever a new leader takes first place.

Why naive re-sorting causes a visual "pop"

The obvious way to reorder a list of DOM rows by rank is to sort the underlying data and re-insert the elements in the new order — container.appendChild(row) for each row in rank order. The problem is that the browser has no concept of "this row used to be third and is now first" once you do that; it simply computes a new layout and paints it. The result is every reordered row instantly jumping to its new position with zero visible motion — a jarring pop that makes it hard to follow which item moved where, exactly the failure mode a bar-chart race must avoid to be legible.

FLIP: First, Last, Invert, Play

The fix is the FLIP technique. First: before touching the DOM order, reorderWithFlip() records every row's current top position with getBoundingClientRect(). Last: the rows are then actually re-inserted into the container in their new sorted order — a real DOM mutation, causing an instant (invisible, un-animated) jump to the new layout. Invert: for each row, the delta between its old top and its new top is computed, and that delta is applied as a transform: translateY(...) — which visually cancels the jump out, making the row *appear* to still be sitting in its old position even though it has already moved in the DOM. Play: a CSS transition is enabled and the transform is reset to translateY(0), so the browser animates from the inverted (old-looking) position to the true (new) position — which the eye reads as the row smoothly gliding to its new rank, exactly the effect a bar race needs.

The forced-reflow step is not optional

Between setting the inverted transform and clearing it, the code calls row.getBoundingClientRect() again purely to force the browser to compute layout synchronously. Without this, both style writes can get batched into a single paint and the transition has no starting frame to animate from — the row would just silently snap to its final position. This one-line detail is the most common way a from-scratch FLIP implementation silently stops animating.

Why bar width still uses a plain CSS transition, not FLIP

Only the *reordering* (vertical position) needs FLIP, because a width change on a single element does not have the "the browser threw away my old position" problem that reordering has — a straightforward transition: width 0.5s on .vbr-fill already animates smoothly between two known widths on the same element. FLIP specifically solves the reordering problem, not general-purpose resizing, so this snippet uses the right tool for each of its two moving parts rather than over-engineering the whole thing through FLIP.

Deriving the crown from the same sorted data, not a separate check

The trophy/crown only appears on the current ranked[0] and is shown or hidden via a plain classList.toggle('show', c.id === leader.id) recomputed every tick — it never has its own independent state. A small pop animation (a scale-and-lift transform, hand-triggered rather than declared as a @keyframes loop) only fires when leader.id !== lastLeaderId, so the celebratory bounce plays exactly once per actual leadership change, not on every tick where the same candidate happens to still be winning.

Simulated data on a timer, structured for a real feed

tickVotes() randomly nudges each candidate's vote count and is called from a plain setInterval; in a production version you would replace the random nudge with data pulled from a WebSocket or polling endpoint and call the same reorderWithFlip() + updateBars() pair whenever new numbers arrive — the animation layer is fully decoupled from where the numbers come from.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's JS into an AI assistant like Claude and ask it to trace exactly what reorderWithFlip() does between the First measurement and the Play transition — walking through why the DOM is reordered before the inverted transform is applied (not after) will make FLIP click in a way the acronym alone doesn't. Good extensions to ask for: animating the crown traveling smoothly from the old leader's row to the new leader's row instead of just popping on the new one, adding a subtle color pulse on any bar that just changed rank, or generalizing reorderWithFlip() into a standalone function that could reorder any list of elements, not just this specific bar race.

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 "bar chart race" style live ranking widget in plain HTML, CSS, and JavaScript where ranked horizontal bars reorder smoothly instead of jump-cutting, no libraries.

Requirements:
- A vertical list of labeled horizontal bars, each showing a name, a fill proportional to its current value relative to the maximum, and the numeric value itself.
- Simulate periodic data updates on a setInterval that randomly adjusts each item's value.
- When values change and cause the rank order to shift, reorder the underlying DOM rows using the FLIP technique: before reordering, measure every row's current position with getBoundingClientRect (First); re-insert the rows into the container in the new sorted order, causing an instant unanimated layout jump (Last); for each row, compute the delta between its old and new position and apply it as an inverted CSS transform so it visually appears unmoved (Invert); force a synchronous layout read; then enable a CSS transition and reset the transform to identity so the row animates smoothly from its old-looking position to its true new position (Play).
- Animate each bar's width and displayed number with a normal CSS transition separately from the FLIP reorder logic, since resizing a single element does not need the FLIP technique.
- Show a small trophy or crown indicator on whichever item currently has the highest value, derived fresh from the sorted data on every update, and play a brief pop/bounce animation on it only at the exact moment the leader changes, not on every update.
- Include Pause/Resume controls that stop and restart the update interval without losing the current data, and a Reset control that restores the original starting values and rebuilds the list.
- Explain in code comments why naive DOM re-sorting without FLIP causes a jarring instant pop instead of a smooth animated reorder.

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.

Step by step

How to Use

  1. 1
    Watch the bars update automaticallyEvery 1.5 seconds each candidate's vote count shifts slightly, and the corresponding bar width and number animate smoothly to the new value.
  2. 2
    Watch rows swap places when rank changesWhen a candidate's votes overtake the row above it, both rows visibly glide past each other into their new vertical order rather than jump-cutting — that's the FLIP reorder in action.
  3. 3
    Watch for the crownA small crown icon appears above whichever row currently has the most votes, and it plays a quick pop animation the moment leadership changes from one candidate to another.
  4. 4
    Click PauseThe update timer stops and the bars freeze at their current values; the button label switches to "Resume".
  5. 5
    Click ResumeUpdates continue from wherever the vote counts currently stand, on the same 1.5-second interval.
  6. 6
    Click ResetAll candidates return to their original starting vote counts and rank order, rebuilding the rows from scratch.

Real-world uses

Common Use Cases

Live polls, elections, and audience voting displays
Drop in real vote totals from a backend or WebSocket feed to show an audience-facing live ranking during a stream, event, or town-hall style poll, alongside a poll widget for the actual voting UI.
Sports league standings and leaderboard widgets
Adapt candidate votes into team points or scores for a live standings widget; compare the FLIP-driven reorder here with the static ranking layout in leaderboard podium and leaderboard table for different presentation styles.
Analytics dashboards showing trending items
Reuse the reorder engine for "trending products," "top search terms," or "most active users" widgets where rank naturally shifts as new data streams in, next to a bar chart for a static comparison view.
Teaching the FLIP animation technique for list reordering
A focused, real-world example of FLIP applied specifically to reordering (as opposed to the position-and-size morph FLIP handles in other UI components), useful for understanding the technique before applying it to drag-and-drop lists.
Social-media style "bar chart race" embeds
Recreate the viral bar-chart-race format as an interactive, embeddable web component instead of a pre-rendered video, letting viewers pause and inspect any moment of the race themselves.
Design system reference for animated ranked lists
Use as a canonical implementation reference anywhere your product needs a ranked list whose order can change live — comment threads by upvotes, leaderboards, or priority queues.

Got questions?

Frequently Asked Questions

When you remove and re-insert DOM elements in a new order, the browser computes and paints the new layout with no awareness that an element used to be somewhere else — from its perspective, it is just laying out elements at their current positions. There is nothing to animate from, so every reordered row instantly appears at its new position in a single frame, which reads as a jarring pop rather than motion.

After the DOM has already been reordered (so every row is sitting at its true new position), Invert computes how far each row moved (new position minus old position) and applies the *opposite* of that as a CSS transform. Because a transform does not affect layout, the row is still occupying its new DOM position for layout purposes, but visually it is offset back to where it used to be. Play then transitions that transform back to zero, which the browser can animate smoothly since it is only animating a transform, not layout.

Browsers batch style writes and only recompute layout/paint when something forces them to, such as reading a layout property like getBoundingClientRect(). Without that forced read in between, both the "apply inverted transform" write and the "clear it back to zero" write can be coalesced into a single paint, meaning the browser never actually renders the inverted (old-looking) frame — the row would just silently snap to its destination with no visible glide.

Yes, with care around where FLIP measurements happen relative to re-renders. In React, measure row positions with refs before updating the sorted state, let React re-render the new order, then in a useLayoutEffect (which runs before paint) apply the inverted transform and immediately trigger the Play transition — useLayoutEffect matters here because a regular useEffect can run after the browser has already painted the un-animated new positions. Clear the setInterval driving simulated updates inside the effect's cleanup function. In Vue, do the same measurement-before/apply-after pattern around nextTick and clear the interval in onUnmounted; in Angular, use ngOnDestroy to clear it.

Replace the random nudge inside tickVotes() with however you receive real data — a WebSocket message handler, a polling fetch() on an interval, or a server-sent event listener — as long as it ends by updating the candidates array with the new vote counts and then calling the same reorderWithFlip() followed by updateBars(false). The animation logic has no dependency on where the numbers came from, only that candidates reflects the latest values before those two functions run.