WebGL Displacement Slideshow
A WebGL canvas slideshow: on every change, noise warps the outgoing and incoming images for 0.9 s. Your own images, pause on hover.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Gallery has 44 effects, including 6 free. Explore the category →
3 usage examples



How it works
The visuals are the .wds-slide elements written in the HTML: either an <img> (your photos) or two colours in data-wds-colors="#6d8bff,#5cffd8" (a diagonal gradient). On load the script copies each image into a WebGL texture, cropped like object-fit: cover to fill the 16/9 frame, and draws the first one on the .wds-canvas that covers the slides.
On every change, a shader reads value noise (a smoothed 4 × 4 grid) and uses it to shift both images in opposite directions, the old one more and more, the new one less and less, while they blend (smoothstep between 25% and 75% of the animation). The transition lasts 0.9 s, computed from elapsed time. The canvas follows its frame's size (ResizeObserver, pixel density capped at 2) and redraws immediately after a resize.
Autoplay advances every 3.5 s (data-wds-interval) and stops on mouse hover, while keyboard focus is inside, off screen, or with the pause button at the top right. ‹ › arrows, dots (one per slide, created by the script) and a horizontal swipe change slide. Without WebGL, with a cross-origin image served without CORS, or if the WebGL context is lost, the canvas steps aside and the HTML slides crossfade in 0.7 s.
Accessibility
- Pause: a button named "Pause automatic playback" / "Resume automatic playback", plus a stop on hover, on keyboard focus and off screen.
data-wds-autoplay="false"turns autoplay off (the button can then be hidden). - What gets read: the canvas is
aria-hidden; the HTML slides are what screen readers read, with your<img>alt text. Aaria-roledescription="carousel"region,role="group"slides "2 of 5", hidden onesinert. - Dots: real buttons named after their slide's
aria-label; the current one carriesaria-current="true". Arrows named "Previous" / "Next". - Reduced motion: under
prefers-reduced-motion: reduce, no autoplay at start and no warping: the image changes at once. Everything stays controllable. - Touch: Pointer Events and
touch-action: pan-y: a horizontal gesture changes image, the page scrolls vertically.
Browser compatibility
WebGL 1 (every browser since 2014), ResizeObserver, aspect-ratio and inert (Chrome 102, Firefox 112, Safari 15.5 according to MDN). Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): two slideshows with only the second one driven, 5 dots even with the script pasted twice, autoplay that advances without hover and stops on hover, image still drawn after a resize, real images in <img>, reduced motion, 390 px, finger swipe (Chromium), 0 errors. Crossfade fallback checked by forcing a WebGL context loss.
Without WebGL or with a cross-origin image without CORS: CSS crossfade between the HTML slides, same buttons. Without JavaScript: the first slide stays visible (an image; a colour slide stays on the dark background).
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0801-root">
<div class="wds-scene" data-wds data-wds-interval="3500" role="region" aria-roledescription="carousel" aria-label="…">
<canvas class="wds-canvas" aria-hidden="true"></canvas>
<div class="wds-slides" aria-live="off">
<div class="wds-slide wds-on" role="group" aria-roledescription="slide" aria-label="1 of 5"><img src="…" alt="…"></div>
<div class="wds-slide" role="group" aria-roledescription="slide" aria-label="2 of 5" data-wds-colors="#ff7eb6,#a777ff"></div>
…
</div>
<button class="wds-btn wds-prev" type="button" aria-label="…" data-wds-prev>‹</button>
<button class="wds-btn wds-next" type="button" aria-label="…" data-wds-next>›</button>
<button class="wds-toggle" type="button" data-wds-toggle data-wds-label-pause="…" data-wds-label-play="…"><svg class="wds-icon-pause">…</svg><svg class="wds-icon-play">…</svg></button>
<div class="wds-dots" data-wds-dots></div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Images (HTML — <img> in .wds-slide) |
none | Your photos, cropped to fill the frame. Same domain as the page, or a server sending CORS headers (and crossorigin="anonymous" on the image); otherwise the slideshow falls back to a CSS crossfade. |
Colour slide (HTML — data-wds-colors) |
"#6d8bff,#5cffd8" … (5 gradients) | Two colours separated by a comma: a diagonal gradient, no image. |
Interval (HTML — data-wds-interval on [data-wds]) |
3500 (ms) | Time between two automatic changes; 1500 ms minimum. |
Autoplay (HTML — data-wds-autoplay="false") |
on (except reduced motion) | Turns automatic advance off: only arrows, dots and swipe change the image. |
Proportions (CSS — --wds-ratio) |
16 / 9 | Frame format; auto if you give it a height (full-screen background). |
Pause button labels (HTML — data-wds-label-pause / data-wds-label-play) |
"Pause automatic playback" / "Resume automatic playback" | Names read depending on the state; translate them. |
Active dot (CSS — .wds-dot[aria-current="true"]) |
#5cffd8 | Colour of the dot of the image shown. |
FAQ
crossorigin="anonymous" to the tag and the Access-Control-Allow-Origin header on the server. The crossfade is the planned fallback in that case.wds-on class change on the .wds-slide elements (a MutationObserver is enough) to update a title placed above.