Skip to content

01Web design · Transition

v1.0.0

Ripple Reveal

Slideshow transition using CSS mask + radial-gradient to expand slides outward from a configurable origin point with a soft liquid ripple edge

Section
Web design
Published

Published Jul 22, 2026.

02How to use

Three ways
  1. 01Customize it

    Tune every value in the app until it looks right.

  2. 02Copy the embed

    Settings are data-* attributes on the element, in kebab-case.

    <div data-ripple-reveal style="height:480px"></div>
    <script src="https://vanilla-motion.com/embed/ripple-reveal/1.0.0/ripple-reveal.js"></script>
  3. 03Paste it on your site

    Anywhere that takes HTML: an Embed element in Webflow or Framer, a Custom HTML block in WordPress, or straight into your page.

03Case study

2 min read

CSS mask + radial-gradient expands the incoming slide outward from a configurable origin point. A single @property --p drives the radius; a feather zone between the two gradient stops creates a soft, liquid ripple edge.

Ripple reveal

Circle expands from a configurable origin, like a drop hitting still water. The slideshow auto-advances every 5 seconds with a 1.2s reveal animation.

How it works

The key is @property --p — a registered CSS custom property that can be animated with @keyframes. Without @property, animating --p inside radial-gradient() doesn’t work because the browser can’t interpolate unregistered custom properties.

@property --p {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@keyframes ripple-reveal {
  from { --p: 0; }
  to   { --p: 1; }
}

The active slide computes a radius from --p, then derives two edge stops for a feather zone:

.slideshow-ripple-reveal > .active {
  --radius: calc(var(--p) * 150%);     /* 150% ensures circle covers corners */
  --edge-in:  calc(var(--radius) - var(--feather) / 2);
  --edge-out: calc(var(--radius) + var(--feather) / 2);

  mask: radial-gradient(
    circle at var(--origin-x) var(--origin-y),
    #000 var(--edge-in),
    transparent var(--edge-out)
  );
  animation: ripple-reveal var(--duration) var(--ease) forwards;
}

The --feather gap between --edge-in and --edge-out softens the expanding ring. Larger values → blurrier edge; 0 → hard circle wipe.

The origin and timing are params (data-* attributes on the embed, options in code). The class writes them to custom properties on the slideshow container:

.vm-ripple-reveal {
  --vm-rr-x: 50%;        /* originX: 0 = left edge, 100 = right edge */
  --vm-rr-y: 50%;        /* originY */
  --vm-rr-feather: 20%;  /* feather */
  --vm-rr-duration: 1000ms; /* duration */
  --vm-rr-ease: cubic-bezier(0.4, 0, 1, 1); /* ease-in: slow start → fast expand */
}

The time between slides (delay, in ms) stays in JS.

150% for the max radius guarantees the circle covers all four corners regardless of origin position. If you set originX: 0, the farthest corner is the top-right; 150% of the diagonal still clears it.

Slide z-index management

CSS animation doesn’t handle stacking — JS does. Each transition bumps a monotonically increasing zCounter so the incoming slide always paints above everything:

slides[idx].style.zIndex = String(++zCounter)
slides[idx].classList.remove('active') // force animation restart
slides[idx].classList.add('active')

The remove + add in sequence forces the browser to restart the @keyframes animation even if the slide was previously .active. After animDuration + 200ms (the duration param), the previous slide drops its .active class and falls to z-index: 0.

Ken Burns effect

Active slides get a slow scale(1.1) via a long transition:

.slideshow-ripple-reveal img {
  transform: scale(1);
  transition: transform 10s linear;
}

.slideshow-ripple-reveal > .active img {
  transform: scale(1.1);
}

The 10s duration means the zoom is barely perceptible within a 5s slide hold, but visible as slow drift — the Ken Burns effect without JS.

Accessibility

if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  mount(el, { images })
}

When prefers-reduced-motion is set, skip mounting the slideshow entirely so slides don’t auto-advance and no animation runs.

Top-left origin

Set originX: 0 and originY: 0 (data-origin-x="0" data-origin-y="0" on the embed) — ripple expands from the top-left corner.

MobileDesktop
Open Demo

04More effects

Web design