<section class="demo">
<canvas id="gl"></canvas>
<p>Move to warp the field</p>
</section>.demo {
position: relative;
min-height: 100%;
height: 100%;
background: #08080a;
}
#gl {
display: block;
width: 100%;
height: 100%;
}
.demo p {
position: absolute;
left: 24px;
bottom: 24px;
margin: 0;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
pointer-events: none;
}const canvas = document.querySelector("#gl");
const gl = canvas.getContext("webgl");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function fit() {
const w = Math.max(1, canvas.clientWidth || window.innerWidth);
const h = Math.max(1, canvas.clientHeight || window.innerHeight);
if (canvas.width === w && canvas.height === h) return;
canvas.width = w;
canvas.height = h;
if (gl) gl.viewport(0, 0, w, h);
}
if (!gl) {
canvas.style.background = "linear-gradient(135deg, #1b3350, #3a3123)";
} else {
const vs = "attribute vec2 a; varying vec2 v; void main(){ v = a * 0.5 + 0.5; gl_Position = vec4(a, 0.0, 1.0); }";
const fs = "precision mediump float; varying vec2 v; uniform sampler2D tex; uniform vec2 mouse; uniform float time; void main(){ vec2 uv = v; vec2 d = uv - mouse; float r = length(d); float w = (1.0 - smoothstep(0.0, 0.55, r)) * 0.045; uv += normalize(d + 0.0001) * sin(r * 26.0 - time * 2.4) * w; gl_FragColor = texture2D(tex, uv); }";
function shader(type, src) {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, shader(gl.VERTEX_SHADER, vs));
gl.attachShader(prog, shader(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(prog);
gl.useProgram(prog);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
const loc = gl.getAttribLocation(prog, "a");
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
const texCanvas = document.createElement("canvas");
texCanvas.width = 512;
texCanvas.height = 512;
const tctx = texCanvas.getContext("2d");
const grad = tctx.createLinearGradient(0, 0, 512, 512);
grad.addColorStop(0, "#1b3350");
grad.addColorStop(0.45, "#3a3123");
grad.addColorStop(1, "#4a2422");
tctx.fillStyle = grad;
tctx.fillRect(0, 0, 512, 512);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texCanvas);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const uMouse = gl.getUniformLocation(prog, "mouse");
const uTime = gl.getUniformLocation(prog, "time");
const mouse = { x: 0.5, y: 0.5 };
const start = performance.now();
canvas.addEventListener("pointermove", function (event) {
const rect = canvas.getBoundingClientRect();
gsap.to(mouse, {
x: (event.clientX - rect.left) / rect.width,
y: 1 - (event.clientY - rect.top) / rect.height,
duration: 0.5,
ease: "power3.out",
overwrite: true,
});
});
function draw(now) {
gl.uniform2f(uMouse, mouse.x, mouse.y);
gl.uniform1f(uTime, reduce ? 0 : (now - start) / 1000);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(draw);
}
fit();
requestAnimationFrame(draw);
window.addEventListener("resize", fit);
}