Displacement

A displacement map warps both frames at the peak of the transition, producing a fluid liquid-ink wipe.

View code
<div class="wgl-slide" data-slide-index="0"></div>
<div class="wgl-slide" data-slide-index="1"></div>
<div class="wgl-slide" data-slide-index="2"></div>
<div class="wgl-slide" data-slide-index="3"></div>
<div class="wgl-slide" data-slide-index="4"></div>
.wgl-slide { height: 420px; background: #111; }
.wgl-slide { height: 420px; background: #111; }
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { webgl, preloadWebGL } from '@andresclua/sliderkit-webgl'

const assets = await preloadWebGL({
  slides:       [1,2,3,4,5].map(i => `/webgl/slide-${i}.jpg`),
  displacement: '/webgl/displacement.png',
})

new Slider('#my-slider', {
  items: 1, loop: true, speed: 0,
  plugins: [arrows(), webgl({ effect: 'displacement', assets })],
})

RGB Shift

Chromatic aberration splits the R and B channels horizontally at mid-transition, then snaps back clean.

View code
<div class="wgl-slide" data-slide-index="0"></div>
<div class="wgl-slide" data-slide-index="1"></div>
<div class="wgl-slide" data-slide-index="2"></div>
<div class="wgl-slide" data-slide-index="3"></div>
<div class="wgl-slide" data-slide-index="4"></div>
.wgl-slide { height: 420px; background: #111; }
.wgl-slide { height: 420px; background: #111; }
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { webgl, preloadWebGL } from '@andresclua/sliderkit-webgl'

const assets = await preloadWebGL({
  slides:       [1,2,3,4,5].map(i => `/webgl/slide-${i}.jpg`),
  displacement: '/webgl/displacement.png',
})

new Slider('#my-slider', {
  items: 1, loop: true, speed: 0,
  plugins: [arrows(), webgl({ effect: 'rgb-shift', assets, intensity: 0.022 })],
})

Radial Reveal

Custom shader: the incoming image grows from the centre outward with a soft edge, like a spotlight opening.

View code
<div class="wgl-slide" data-slide-index="0"></div>
<div class="wgl-slide" data-slide-index="1"></div>
<div class="wgl-slide" data-slide-index="2"></div>
<div class="wgl-slide" data-slide-index="3"></div>
<div class="wgl-slide" data-slide-index="4"></div>
.wgl-slide { height: 420px; background: #111; }
.wgl-slide { height: 420px; background: #111; }
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { webgl, preloadWebGL } from '@andresclua/sliderkit-webgl'

const assets = await preloadWebGL({
  slides:       [1,2,3,4,5].map(i => `/webgl/slide-${i}.jpg`),
  displacement: '/webgl/displacement.png',
})

new Slider('#my-slider', {
  items: 1, loop: true, speed: 0,
  plugins: [arrows(), webgl({ effect: 'radial', assets })],
})

Wave Wipe (custom shader)

A sine-wave curtain sweeps left-to-right; the wave amplitude peaks at mid-transition and dies away cleanly. Written as a custom GLSL fragment shader passed directly to the plugin.

View code
<div class="wgl-slide" data-slide-index="0"></div>
<div class="wgl-slide" data-slide-index="1"></div>
<div class="wgl-slide" data-slide-index="2"></div>
<div class="wgl-slide" data-slide-index="3"></div>
<div class="wgl-slide" data-slide-index="4"></div>
.wgl-slide { height: 420px; background: #111; }
.wgl-slide { height: 420px; background: #111; }
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { webgl, preloadWebGL } from '@andresclua/sliderkit-webgl'

const assets = await preloadWebGL({
  slides: [1,2,3,4,5].map(i => `/webgl/slide-${i}.jpg`),
})

const FRAG_WAVE = `precision mediump float;
varying vec2 vUv;
uniform sampler2D u_from, u_to;
uniform float u_progress, u_ar;
vec2 cover(vec2 uv) {
  float a = u_ar / 1.6;
  vec2 r = vec2(min(a, 1.0), min(1.0 / a, 1.0));
  return uv * r + (1.0 - r) * 0.5;
}
void main() {
  float p = 1.04 - u_progress * 1.08;
  float envelope = sin(u_progress * 3.14159);
  float wave = p + sin(vUv.y * 18.0 + u_progress * 6.283) * 0.07 * envelope;
  float t = smoothstep(wave - 0.04, wave + 0.04, vUv.x);
  gl_FragColor = mix(texture2D(u_from, cover(vUv)), texture2D(u_to, cover(vUv)), t);
}`

new Slider('#my-slider', {
  items: 1, loop: true, speed: 0,
  plugins: [arrows(), webgl({ effect: 'custom', assets, frag: FRAG_WAVE })],
})

Text Overlay + GSAP

HTML lives above the WebGL canvas (z-index). On each slide change, GSAP animates the caption out, swaps the text, then animates it back in.

01 / 05

Iceland Church

View code
<div class="wgl-hero">
  <div id="my-slider">
    <div class="wgl-slide" data-slide-index="0"></div>
    <div class="wgl-slide" data-slide-index="1"></div>
    <div class="wgl-slide" data-slide-index="2"></div>
    <div class="wgl-slide" data-slide-index="3"></div>
    <div class="wgl-slide" data-slide-index="4"></div>
  </div>
  <div class="wgl-caption">
    <p class="wgl-caption__label">01 / 05</p>
    <h2 class="wgl-caption__title">Slide title</h2>
  </div>
</div>
.wgl-slide { height: 420px; background: #111; }
.wgl-hero { position: relative; }
.wgl-caption {
  position: absolute; bottom: 0; left: 0; right: 0;
  z-index: 10; pointer-events: none;
  padding: 1.75rem 2rem;
  background: linear-gradient(to top, rgba(0,0,0,.6) 0%, transparent 100%);
  color: #fff;
}
.wgl-caption__label {
  font-size: .7rem; text-transform: uppercase;
  letter-spacing: .12em; opacity: .65; margin: 0 0 .3rem;
}
.wgl-caption__title { font-size: 1.6rem; font-weight: 700; margin: 0; }
.wgl-slide {
  height: 420px;
  background: #111;
}

.wgl-hero {
  position: relative;
}

.wgl-caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 10;
  pointer-events: none;
  padding: 1.75rem 2rem;
  background: linear-gradient(to top, rgba(0,0,0,.6) 0%, transparent 100%);
  color: #fff;

  &__label {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    opacity: .65;
    margin: 0 0 .3rem;
  }

  &__title {
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0;
  }
}
import gsap from 'gsap'
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { webgl, preloadWebGL } from '@andresclua/sliderkit-webgl'

const assets = await preloadWebGL({
  slides:       [1,2,3,4,5].map(i => `/webgl/slide-${i}.jpg`),
  displacement: '/webgl/displacement.png',
})

const SLIDES = [
  { label: '01 / 05', title: 'Iceland Church' },
  { label: '02 / 05', title: 'Mountain Road' },
  { label: '03 / 05', title: 'Forest Path' },
  { label: '04 / 05', title: 'Car Interior' },
  { label: '05 / 05', title: 'Aerial View' },
]

const label = document.querySelector('.wgl-caption__label')
const title = document.querySelector('.wgl-caption__title')

const slider = new Slider('#my-slider', {
  items: 1, loop: true, speed: 0,
  plugins: [arrows(), webgl({ effect: 'displacement', assets })],
})

slider.on('indexChanged', ({ displayIndex }) => {
  const s = SLIDES[(displayIndex - 1) % SLIDES.length]
  gsap.timeline()
    .to([label, title], { y: -10, opacity: 0, duration: 0.18, stagger: 0.04 })
    .call(() => { label.textContent = s.label; title.textContent = s.title })
    .fromTo([label, title],
      { y: 14, opacity: 0 },
      { y: 0, opacity: 1, duration: 0.32, stagger: 0.07, ease: 'power2.out' })
})

HTML-driven slides (accessible)

Images and text live in the HTML: data-image feeds the WebGL preloader, img provides the a11y fallback, and the text spans are sr-only so screen readers read them while GSAP drives the visual overlay.

Iceland church with mountains in the background 01 / 05 Iceland Church
Mountain road through a valley 02 / 05 Mountain Road
Forest path in autumn 03 / 05 Forest Path
Car interior dashboard view 04 / 05 Car Interior
Aerial landscape view 05 / 05 Aerial View

01 / 05

Iceland Church

View code
<div class="wgl-hero">
  <div id="my-slider">
    <div class="wgl-slide" data-slide-index="0" data-image="/webgl/slide-1.jpg">
      <img src="/webgl/slide-1.jpg" alt="Iceland church with mountains" class="wgl-slide__img" />
      <span class="wgl-slide__label">01 / 05</span>
      <span class="wgl-slide__title">Iceland Church</span>
    </div>
    <div class="wgl-slide" data-slide-index="1" data-image="/webgl/slide-2.jpg">
      <img src="/webgl/slide-2.jpg" alt="Mountain road" class="wgl-slide__img" />
      <span class="wgl-slide__label">02 / 05</span>
      <span class="wgl-slide__title">Mountain Road</span>
    </div>
    <!-- … repeat for remaining slides … -->
  </div>
  <div class="wgl-caption" aria-live="polite">
    <p class="wgl-caption__label">01 / 05</p>
    <h2 class="wgl-caption__title">Iceland Church</h2>
  </div>
</div>
.wgl-slide {
  height: 420px; background: #111;
  position: relative; overflow: hidden;
}
.wgl-slide__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
/* sr-only: text lives in DOM for a11y, canvas covers it visually */
.wgl-slide__label,
.wgl-slide__title {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap;
}
.wgl-hero { position: relative; }
.wgl-caption {
  position: absolute; bottom: 0; left: 0; right: 0;
  z-index: 10; pointer-events: none;
  padding: 1.75rem 2rem;
  background: linear-gradient(to top, rgba(0,0,0,.6) 0%, transparent 100%);
  color: #fff;
}
.wgl-caption__label {
  font-size: .7rem; text-transform: uppercase;
  letter-spacing: .12em; opacity: .65; margin: 0 0 .3rem;
}
.wgl-caption__title { font-size: 1.6rem; font-weight: 700; margin: 0; }
.wgl-hero {
  position: relative;
}

.wgl-slide {
  height: 420px;
  background: #111;
  position: relative;
  overflow: hidden;

  &__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  &__label,
.wgl-slide__title {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
  }
}

.wgl-caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 10;
  pointer-events: none;
  padding: 1.75rem 2rem;
  background: linear-gradient(to top, rgba(0,0,0,.6) 0%, transparent 100%);
  color: #fff;

  &__label {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    opacity: .65;
    margin: 0 0 .3rem;
  }

  &__title {
    font-size: 1.6rem;
    font-weight: 700;
    margin: 0;
  }
}
import gsap from 'gsap'
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { webgl, preloadWebGL } from '@andresclua/sliderkit-webgl'

// images and text come from the DOM — no hardcoded arrays
const slideEls = [...document.querySelectorAll('#my-slider .wgl-slide')]

const assets = await preloadWebGL({
  slides:       slideEls.map(el => el.dataset.image),
  displacement: '/webgl/displacement.png',
})

const wrap  = document.querySelector('#my-slider').closest('.wgl-hero')
const label = wrap.querySelector('.wgl-caption__label')
const title = wrap.querySelector('.wgl-caption__title')

const slider = new Slider('#my-slider', {
  items: 1, loop: true, speed: 0,
  plugins: [arrows(), webgl({ effect: 'radial', assets })],
})

let prev = 1
slider.on('indexChanged', ({ displayIndex }) => {
  const el    = slideEls[(displayIndex - 1) % slideEls.length]
  const total = slideEls.length
  const diff  = displayIndex - prev
  const dir   = Math.abs(diff) > total / 2 ? -Math.sign(diff) : Math.sign(diff)
  prev = displayIndex

  gsap.timeline()
    .to(label, { x: -20 * dir, opacity: 0, duration: 0.15 })
    .to(title, { y: -16 * dir, opacity: 0, duration: 0.2 }, '<0.04')
    .call(() => {
      label.textContent = el.querySelector('.wgl-slide__label').textContent
      title.textContent = el.querySelector('.wgl-slide__title').textContent
    })
    .fromTo(label, { x: 20 * dir, opacity: 0 }, { x: 0, opacity: 1, duration: 0.28, ease: 'power2.out' })
    .fromTo(title, { y: 18 * dir, opacity: 0 }, { y: 0, opacity: 1, duration: 0.38, ease: 'expo.out' }, '<0.06')
})