Mouse-driven depth parallax

The slide image is rendered at 110% scale so the edges never show during movement. As the mouse moves over the slider, the UV coordinates are shifted proportionally — foreground appears to move more than the background, simulating depth. intensity controls how many UV units shift per unit of mouse travel (0.04 is subtle, 0.08 is dramatic). Transitions between slides are a simple cross-fade while preserving the live parallax offset.

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>
</div>
.wgl-slider { position: relative; height: 400px; overflow: hidden; background: #000; cursor: none; }
.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; cursor: none; }
.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 } from '@andresclua/sliderkit-plugins'

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 uOffset;
  varying vec2 vUv;
  void main() {
    // Zoom in by ~10% and shift — edges never clip
    vec2 uv = (vUv - 0.5) / 1.1 + 0.5 + uOffset;
    gl_FragColor = mix(texture2D(uFrom, uv), texture2D(uTo, uv), uProgress);
  }
`

const container = document.querySelector('#my-slider')
// ... WebGL setup, load textures ...

// Track mouse, lerp offset
let targetX = 0, targetY = 0
let currX   = 0, currY   = 0

container.addEventListener('mousemove', e => {
  const r = container.getBoundingClientRect()
  targetX = ((e.clientX - r.left) / r.width  - 0.5) * -0.06
  targetY = ((e.clientY - r.top)  / r.height - 0.5) * -0.06
})

// RAF loop: lerp currX/Y toward targetX/Y, redraw every frame
const loop = () => {
  currX += (targetX - currX) * 0.08
  currY += (targetY - currY) * 0.08
  draw(currentTex, currentTex, 0, currX, currY)
  requestAnimationFrame(loop)
}

Standalone tilt card

A single hero image with mouse-driven tilt — no slider required. The image floats within its container, shifting based on cursor position relative to centre. intensity 0.05 gives a gentle card-tilt feel; go up to 0.12 for a dramatic 3D pop. The lerp factor (0.06) controls how snappily the image tracks the cursor.

View code
<div class="tilt-card" id="my-tilt">
  <!-- Canvas injected by JS -->
</div>
.tilt-card {
  width: 100%;
  height: 340px;
  position: relative;
  overflow: hidden;
  background: #000;
  cursor: crosshair;
}
.tilt-card canvas { position: absolute; inset: 0; display: block; }
.tilt-card {
  width: 100%;
  height: 340px;
  position: relative;
  overflow: hidden;
  background: #000;
  cursor: crosshair;
}
.tilt-card canvas { position: absolute; inset: 0; display: block; }
const el = document.getElementById('my-tilt')
// ... WebGL setup, single texture loaded ...

let tx = 0, ty = 0, cx = 0, cy = 0
el.addEventListener('mousemove', e => {
  const r = el.getBoundingClientRect()
  tx = ((e.clientX - r.left) / r.width  - 0.5) * -0.08
  ty = ((e.clientY - r.top)  / r.height - 0.5) * -0.08
})
el.addEventListener('mouseleave', () => { tx = 0; ty = 0 })

// Continuous render loop with lerp
;(function loop() {
  cx += (tx - cx) * 0.06
  cy += (ty - cy) * 0.06
  draw(tex, tex, 0, cx, cy)
  requestAnimationFrame(loop)
})()