Vanilla MotionVanilla Motion

Ripple Reveal

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

MotionCSSCSS MaskAnimation

Built with CSS Mask, CSS Custom Properties, @property, CSS Animations, Vanilla JS, TypeScript

MobileDesktop
Open Demo

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

Use it on a site

One script, no build step. Every element with data-ripple-reveal starts the effect; settings go in data-* attributes 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>

More

Venetian Blind

Venetian Blind

motioncsscss-maskanimation
Ripple Reveal WebGL

Ripple Reveal WebGL

motionwebglglslanimation
Ice Logo

Ice Logo

webglglslmotion