Atmosphere✨ Premium

Ripple Effect

Material Design circular wave from the click point — zero rAF, relative positioning, pure CSS scale+opacity animation, auto-cleanup on animationend.

JSMaterial

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →

3 usage examples

Hero / Landing — Interactive welcome area — Ripple Effect example 1

① Hero / Landing — Interactive welcome area

WhenOn a landing page where the user should explore or click anywhere to get started — onboarding, product home, splash screen.
WhyThe wave visually confirms each click on the hero area and reinforces perceived responsiveness without loading the page with self-running animations.
SettingsWave color: rgba(99,102,241,0.35) (brand indigo); final scale: 8×; duration: 0.8s ease-out. Full-width container with indigo gradient background.
Product component — Dashboard action card — Ripple Effect example 2

② Product component — Dashboard action card

WhenOn a dashboard or task list where each card is clickable to open a detail view, filter, or trigger an action.
WhyThe localized wave tells the user exactly which item they activated in a dense list — selection feedback without a jarring color change.
SettingsWave color: rgba(255,255,255,0.18) on dark background; scale: 6×; duration: 0.55s. Card with border-radius: 16px and overflow: hidden.
Conversion micro-interaction — Primary CTA button — Ripple Effect example 3

③ Conversion micro-interaction — Primary CTA button

WhenOn the 'Subscribe', 'Buy', or 'Start trial' button — the wave propagates from the click point before the page redirects.
WhyThe CSS animation (0 rAF) does not block the JS redirect: the wave fires and the <code>setTimeout</code> or navigation runs in the same tick. The feedback precedes network latency.
SettingsWave color: rgba(255,255,255,0.50) on purple button; scale: 5×; duration: 0.5s. Container = the <button> itself with position: relative; overflow: hidden.

How it works

On click, getBoundingClientRect() converts screen coordinates (clientX/Y) into container-local coordinates. Subtracting clientX - rect.left gives the exact position even when the container has CSS transforms or the page is scrolled.

A <div class="ripple"> is created and inserted at those coordinates. Its size is set to 100 × 100 px directly in JS; it is centered on the click point via marginLeft: -50px; marginTop: -50px. The container carries overflow: hidden — the wave is clipped to its edges, creating the illusion that it emerges from inside the component.

The entire animation is driven by a CSS keyframe (scale(0) → scale(N) + opacity 0.4 → 0): no requestAnimationFrame runs, no JS loop is launched. The main thread is not blocked. Multiple rapid clicks create multiple independent .ripple elements in the DOM simultaneously.

The animationend listener removes the element from the DOM as soon as the keyframe ends — no timer, no manual cleanup. Note: the .ripple class and its keyframe are not defined in the provided CSS (they lived on the host page); add them to your stylesheet. .ripple elements also need pointer-events: none to avoid capturing subsequent clicks.

Accessibility

  • prefers-reduced-motion absent: the code does not check the system preference — the animation always runs. Add @media (prefers-reduced-motion: reduce) { .ripple { animation-duration: 0.01ms !important; } } so animationend fires near-instantly without leaving the element stuck in the DOM.
  • The .ripple-container has cursor: pointer but neither tabindex="0" nor role="button" — not keyboard-activatable as provided. Add both attributes and a keydown listener for Enter/Space if the element is interactive.
  • No aria-label on the container in the original code. If the inner text does not describe the action clearly, add an explicit ARIA label.
  • Injected .ripple elements do not carry pointer-events: none in the provided CSS — add it to prevent in-flight waves from capturing subsequent clicks.
  • The default #12121a background with white text exceeds WCAG AA (ratio > 10:1) — no contrast issue on the default configuration.

Browser compatibility

Uses getBoundingClientRect(), addEventListener('animationend'), and DOM manipulation — universally supported since Chrome 43 / Firefox 43 / Safari 9.

Chrome 43+✓ Full
Firefox 43+✓ Full
Safari 9+✓ Full
Edge 79+✓ Full
Mobile iOS 9+✓ Touch OK
Android Chrome 43+✓ Full

If CSS animations are unsupported, <code>animationend</code> never fires and <code>.ripple</code> elements stay in the DOM. Add a fallback <code>setTimeout</code> at 1 s to remove them.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="ripple-container" id="rippleContainer">
  <!-- Component content (text, icon, label) -->
  Click here
  <!-- <div class="ripple"> elements are injected by JS on click and removed on animationend -->
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
background (CSS .ripple) rgba(255,255,255,0.4) Wave color and opacity. Use your brand color with opacity 0.2–0.5 depending on background contrast.
animation-duration (CSS .ripple) ~0.6s Expansion duration. 0.4s = snappy (CTA button), 0.8–1s = organic (hero, large surfaces).
to { transform: scale(N) } (CSS @keyframes) 4 Final expansion factor. Calibrate to container size: N ≈ (2 × container diagonal) ÷ 100 to have the wave cover the entire surface.
ripple.style.width / height (JS lines 7–8) 100px Initial disc size. Larger values soften the wave start; smaller values make it more pinpoint. Always keep marginLeft = marginTop = -(size / 2).
animation-timing-function (CSS .ripple) ease-out Expansion easing. ease-out = fast start, gentle end (Material Design). cubic-bezier(0.4,0,0.2,1) = Material standard curve.
border-radius (CSS .ripple) 50% Wave shape. 50% = perfect circle. Lower values produce a rounded diamond; 0 = square geometric wave.

FAQ

Yes. Each click creates a new independent .ripple element in the container. Multiple rapid clicks generate overlapping waves — each self-removes on its own animationend.
Add to your CSS: @media (prefers-reduced-motion: reduce) { .ripple { animation-duration: 0.01ms !important; } }. The wave ends near-instantly, animationend still fires and cleans up the DOM. No JS changes needed.
Yes. Add position: relative; overflow: hidden to the <button> — native behavior (focus, keyboard, submit) is preserved since the effect only inserts a child element and does not manipulate events.