The Enhanced Animated Refresh Button is a free, copy-paste ready
circular UI button component built with pure HTML, CSS custom
keyframes, and vanilla JavaScript. It is designed as a production-ready
loading state button — drop it into any project as a visual
refresh or reload trigger with a full animation lifecycle. The button is a
90×90px circle with glassmorphism:
background: rgba(255,255,255,0.05),
border: 2px solid rgba(255,255,255,0.15),
backdrop-filter: blur(14px),
box-shadow: 0 10px 30px rgba(0,0,0,0.55).
The background uses a triple radial-gradient layer: purple glow
at 20%/20%, cyan glow at 80%/80%, over --bg: #0f172a deep navy.
Hover state: transform: scale(1.2) rotate(-5deg)
— the button scales up 20% and tilts 5 degrees counter-clockwise.
box-shadow: 0 0 35px 8px rgba(124,58,237,0.8) adds an 8px spread
purple glow that extends well beyond the button boundary. The SVG refresh icon
strokes to #06b6d4 (cyan) and rotates 30 degrees with
scale(1.15). Click state: the SVG icon is hidden
(display:none), the spinner is shown — 70×70px,
border: 5px solid rgba(255,255,255,0.2),
border-top-color: #06b6d4, spinning via
@keyframes spin at 0.9s linear infinite. Simultaneously the
ring becomes visible (opacity 0→1) — 100×100px,
border-top-color: #7c3aed (purple),
border-right-color: #ec4899 (pink), rotating at 1.2s linear
(slower than the inner spinner, creating a layered parallax). A simulated
progress loop fires progress += 15 + Math.random() * 20 every
120ms until ≥100, then restores the icon.
The simulated simulate() function is a placeholder for any real
async operation. Replace it with your actual fetch/axios call:
btn.classList.add('active'); icon.style.display='none';
spinner.style.display='block'; at the start, then in your
.then() or after your await:
spinner.style.display='none'; icon.style.display='block';
btn.classList.remove('active');. The button animation system is
entirely driven by the .active class — add it to start, remove
it to stop. The spinner and ring CSS are already set up to respond to that
single class toggle.
The inner spinner (70px, 0.9s) and outer ring (100px, 1.2s) intentionally
rotate at different speeds. Because 1.2/0.9 = 4/3, they synchronize every
3.6 seconds (LCM of 0.9 and 1.2). Between sync points they create a
continuously shifting angular relationship — the dual-color ring appears to
orbit around the spinner rather than tracking with it. This creates visual
depth and movement complexity from just two simple
@keyframes spin → rotate(360deg) animations.
Released under Creative Commons Attribution 4.0 International (CC BY 4.0). Free for personal and commercial use with attribution to Coodeverse.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Enhanced Animated Refresh Button</title>
</head>
<body>
<div class="refresh-btn" id="refreshBtn">
<svg id="refreshIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M4.93 4.93a10 10 0 0114.14 0l1.41-1.41"/>
<path d="M20.07 19.07a10 10 0 01-14.14 0l-1.41 1.41"/>
</svg>
<div class="spinner" id="spinner"></div>
<div class="ring" id="ring"></div>
</div>
</body>
</html>
Common questions about the free Enhanced Animated Refresh Button project.
Yes — 100% free under Creative Commons Attribution 4.0. No sign-up, no payment. Click Download Project and use it as a loading state button component in any project with attribution to Coodeverse.
Set equal width/height with border-radius:50% for a circle. Then: background:rgba(255,255,255,0.05) (near-transparent fill), border:2px solid rgba(255,255,255,0.15) (subtle white border), backdrop-filter:blur(14px) (frosted glass blur of background), box-shadow:0 10px 30px rgba(0,0,0,0.55) (depth shadow). Add overflow:hidden to contain child elements (spinner, ring) within the circle boundary.
Use box-shadow with spread radius: .refresh-btn:hover { box-shadow: 0 0 35px 8px rgba(124,58,237,0.8); }. The syntax: 0 x-offset, 0 y-offset, 35px blur, 8px spread (extends 8px beyond the element in all directions), rgba(124,58,237,0.8) purple at 80% opacity. Combine with transform:scale(1.2) rotate(-5deg) and transition:0.3s ease for a simultaneous scale + tilt + glow hover effect.
Create a circle div: border:5px solid rgba(255,255,255,0.2); border-top-color:#06b6d4; border-radius:50%. One colored border-top creates a spinning arc. Animate: @keyframes spin { to { transform:rotate(360deg); } } applied as animation:spin 0.9s linear infinite. Use display:none by default and toggle to display:block on click. Position absolute inside the button (relative parent) to layer over the SVG icon.
Create a div slightly larger than the button (100px for a 90px button): border:3px solid transparent; border-top-color:#7c3aed; border-right-color:#ec4899; border-radius:50%. Spin at a different speed: animation:ringSpin 1.2s linear infinite. Control via opacity:0 default → opacity:1 when parent has .active class. The two-speed difference (0.9s inner vs 1.2s ring) creates continuous angular shift — the ring appears to orbit the spinner rather than track with it.
Use recursive setTimeout: let progress = 0; function simulate() { progress += 15 + Math.random() * 20; if (progress < 100) { setTimeout(simulate, 120); } else { /* complete */ } } simulate(). Each call advances 15–35 at random and schedules the next at 120ms. The randomness makes loading feel natural. On completion, reverse all state: hide spinner, show icon, remove .active class. Replace simulate() with your real fetch/.then() for production use.
Browse coodeverse.com/projects — Cute Lamp Character (CSS keyframe character), Gold Ace Poker Cards (SVG filters), Modern Neon Clock, NEXUS 3D CSS Cubes, VORTEX Canvas, and more. All 100% free with complete HTML CSS JavaScript source code.