Randomised block dissolve

Each rectangular block is assigned a random dissolve threshold. During the transition, blocks flip from the outgoing slide to the incoming one when progress crosses their threshold — producing a stochastic, pixel-art-style dissolve. blockSize controls the granularity: 8–16 px reads as organic noise, 32 px is clearly blocky, 64 px gives a retro 8-bit look. A longer speed (900–1200 ms) lets the full pattern develop.

View code
<div class="wgl-slider" id="my-slider">
  <div class="c--slider-a__wrapper">
    <!-- Slides are empty — WebGL renders the images -->
    <div class="wgl-slide" data-slide></div>
    <div class="wgl-slide" data-slide></div>
    <div class="wgl-slide" data-slide></div>
    <div class="wgl-slide" data-slide></div>
  </div>
</div>
<div id="my-pag" class="c--slider-a__pagination"></div>
.wgl-slider { position: relative; height: 400px; overflow: hidden; background: #000; }
.wgl-slider .c--slider-a__wrapper { display: flex; }
.wgl-slide { flex-shrink: 0; width: 100%; height: 100%; }
.wgl-slider canvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.wgl-slider { position: relative; height: 400px; overflow: hidden; background: #000; }
.wgl-slider .c--slider-a__wrapper { display: flex; }
.wgl-slide { flex-shrink: 0; width: 100%; height: 100%; }
.wgl-slider canvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
import { Slider } from '@andresclua/sliderkit'
import { arrows, pagination } from '@andresclua/sliderkit-plugins'

const IMAGES = [
  'https://picsum.photos/seed/px1/900/400',
  'https://picsum.photos/seed/px2/900/400',
  'https://picsum.photos/seed/px3/900/400',
  'https://picsum.photos/seed/px4/900/400',
]

const VERT = `
  attribute vec2 a_pos; attribute vec2 a_uv; varying vec2 vUv;
  void main() { vUv = a_uv; gl_Position = vec4(a_pos, 0.0, 1.0); }
`
const FRAG = `
  precision highp float;
  uniform sampler2D uFrom; uniform sampler2D uTo;
  uniform float uProgress;
  uniform vec2  uBlocks;   // (canvasW / blockPx, canvasH / blockPx)
  varying vec2 vUv;

  float rand(vec2 n) {
    return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
  }
  void main() {
    vec2  blockId  = floor(vUv * uBlocks);
    float thresh   = rand(blockId);
    float amt      = step(thresh, uProgress);
    gl_FragColor   = mix(texture2D(uFrom, vUv), texture2D(uTo, vUv), amt);
  }
`

// blockSize in CSS pixels
const BLOCK = 32

const container = document.querySelector('#my-slider')
const canvas = document.createElement('canvas')
container.insertBefore(canvas, container.firstChild)
const gl = canvas.getContext('webgl')

let blocksX = 1, blocksY = 1
function resize() {
  const w = container.offsetWidth, h = container.offsetHeight
  canvas.width  = w * Math.min(devicePixelRatio, 2)
  canvas.height = h * Math.min(devicePixelRatio, 2)
  canvas.style.width = w + 'px'; canvas.style.height = h + 'px'
  gl.viewport(0, 0, canvas.width, canvas.height)
  blocksX = canvas.width  / BLOCK
  blocksY = canvas.height / BLOCK
}
resize(); window.addEventListener('resize', resize)

// ... compile prog, create quad buffers, loadTex same as displacement demo ...

Promise.all(IMAGES.map(loadTex)).then(textures => {
  draw(textures[0], textures[0], 0)

  const slider = new Slider('#my-slider', {
    loop: true, speed: 1,
    plugins: [arrows(), pagination({ el: '#my-pag', type: 'dots', clickable: true })],
  })

  slider.on('afterSlideChange', ({ index, previousIndex }) => {
    animateTransition(textures[previousIndex], textures[index], 900)
  })
})

Chunky 8-bit dissolve

The same shader with blockSize set to 56 px, producing large retro blocks. Combining a longer speed (1100 ms) with big blocks lets each one pop visibly — the effect reads as deliberate, almost game-over-screen pixelation rather than noise.

View code
<div class="wgl-slider" id="my-slider">
  <div class="c--slider-a__wrapper">
    <div class="wgl-slide" data-slide></div>
    <div class="wgl-slide" data-slide></div>
    <div class="wgl-slide" data-slide></div>
  </div>
</div>
<div id="my-pag" class="c--slider-a__pagination"></div>
.wgl-slider { position: relative; height: 400px; overflow: hidden; background: #000; }
.wgl-slider .c--slider-a__wrapper { display: flex; }
.wgl-slide { flex-shrink: 0; width: 100%; height: 100%; }
.wgl-slider canvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.wgl-slider { position: relative; height: 400px; overflow: hidden; background: #000; }
.wgl-slider .c--slider-a__wrapper { display: flex; }
.wgl-slide { flex-shrink: 0; width: 100%; height: 100%; }
.wgl-slider canvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
// Same setup — only blockSize changes
const BLOCK = 56   // large blocks → retro 8-bit look

new Slider('#my-slider', {
  loop: true, speed: 1,
  plugins: [arrows(), pagination({ el: '#my-pag', type: 'dots', clickable: true })],
})