← Blurr Motion cta-shader-orb-kinetic
Categorie cta Tier 3 Techniek #36 Deps ogl, gsap

Motion Lab / CTA / shader orb / kinetic

GO LIVE

Launch now →
1. Mechanisme — kopieer 1-op-1, geen styling-keuzes
// Mechanisme: cta-shader-orb-kinetic
// OGL fullscreen-quad shader met snel-pulserende noise + chromatic shift.
// uTime via rAF, uMouse drijft distortion. GSAP intro op CTA-text.
// Fallback: CSS conic-gradient pulse als WebGL faalt of reduced-motion.
import gsap from 'https://esm.sh/gsap@3.12.5';
import { Renderer, Program, Mesh, Triangle } from 'https://esm.sh/ogl@1.0.10';

const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const root = document.querySelector('.shader-orb-kinetic');
if (root) {
  const canvas = root.querySelector('canvas');
  const txt = root.querySelectorAll('.kinetic-word');
  // Intro
  if (!reduce) gsap.from(txt, { yPercent: 110, autoAlpha: 0, duration: 0.8, stagger: 0.04, ease: 'back.out(1.4)' });
  let useGL = false;
  try {
    const renderer = new Renderer({ canvas, dpr: Math.min(window.devicePixelRatio, 2), alpha: true });
    const gl = renderer.gl;
    useGL = !!gl;
    const geom = new Triangle(gl);
    const program = new Program(gl, {
      vertex: `attribute vec2 position; void main(){ gl_Position = vec4(position,0.0,1.0); }`,
      fragment: `precision highp float;
        uniform float uTime; uniform vec2 uRes; uniform vec2 uMouse;
        // simplex-ish hash noise
        float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453); }
        float noise(vec2 p){ vec2 i=floor(p),f=fract(p); float a=hash(i),b=hash(i+vec2(1,0)),c=hash(i+vec2(0,1)),d=hash(i+vec2(1,1)); vec2 u=f*f*(3.0-2.0*f); return mix(a,b,u.x)+(c-a)*u.y*(1.0-u.x)+(d-b)*u.x*u.y; }
        void main(){
          vec2 uv = (gl_FragCoord.xy - 0.5*uRes) / min(uRes.x,uRes.y);
          vec2 m = (uMouse - 0.5*uRes) / min(uRes.x,uRes.y);
          float d = length(uv - m*0.4);
          float pulse = 0.5 + 0.5*sin(uTime*3.2);
          float n = noise(uv*3.0 + uTime*0.9);
          float ring = smoothstep(0.55,0.0, abs(d - 0.32 - 0.04*pulse - 0.06*n));
          float core = smoothstep(0.45,0.0, d - 0.05*n);
          // chromatic shift
          float r = smoothstep(0.5,0.0, length(uv - m*0.4 + vec2(0.012*pulse,0.0)) - 0.05*n);
          float g = core;
          float b = smoothstep(0.5,0.0, length(uv - m*0.4 - vec2(0.012*pulse,0.0)) - 0.05*n);
          vec3 cream = vec3(0.957,0.945,0.922);
          vec3 orange = vec3(1.0,0.290,0.110);
          vec3 ink = vec3(0.039,0.039,0.039);
          vec3 col = mix(ink, orange, r*0.9 + ring*0.6);
          col = mix(col, cream, g*0.35);
          col += vec3(b*0.15, 0.0, 0.0);
          col = mix(ink, col, smoothstep(0.0,0.05, core+ring*0.5));
          gl_FragColor = vec4(col, 1.0);
        }`,
      uniforms: { uTime:{value:0}, uRes:{value:[1,1]}, uMouse:{value:[0,0]} }
    });
    const mesh = new Mesh(gl, { geometry: geom, program });
    function resize(){ const r = canvas.getBoundingClientRect(); renderer.setSize(r.width, r.height); program.uniforms.uRes.value = [gl.canvas.width, gl.canvas.height]; }
    resize(); addEventListener('resize', resize);
    canvas.addEventListener('pointermove', e=>{ const r=canvas.getBoundingClientRect(); program.uniforms.uMouse.value = [(e.clientX-r.left)*renderer.dpr, (r.height-(e.clientY-r.top))*renderer.dpr]; });
    let raf; const start = performance.now();
    function loop(t){ program.uniforms.uTime.value = (t-start)/1000; renderer.render({ scene: mesh }); raf = requestAnimationFrame(loop); }
    if (!reduce) raf = requestAnimationFrame(loop); else renderer.render({ scene: mesh });
  } catch(e){ useGL = false; }
  if (!useGL) root.classList.add('no-webgl');
}
2. Skeleton — DOM + class-namen, mag herschikken
<!-- Skeleton: cta-shader-orb-kinetic -->
<section class="shader-orb-kinetic">
  <canvas class="shader-canvas"></canvas>
  <div class="kinetic-overlay">
    <h2 class="kinetic-headline">
      <span class="kinetic-word">GO</span>
      <span class="kinetic-word">LIVE</span>
    </h2>
    <a href="#" class="kinetic-cta">Launch now →</a>
  </div>
</section>
3. Styling-template — verplicht eigen invulling per merk
/* Styling: cta-shader-orb-kinetic */
:root { --block-bg:#0A0A0A; --block-fg:#F4F1EB; --block-accent:#FF4A1C; }
.shader-orb-kinetic{position:relative;min-height:80vh;background:var(--block-bg);overflow:hidden}
.shader-orb-kinetic canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.shader-orb-kinetic.no-webgl canvas{display:none}
.shader-orb-kinetic.no-webgl::before{content:"";position:absolute;inset:0;background:conic-gradient(from 0deg at 50% 50%,#0A0A0A,#FF4A1C,#F4F1EB,#FF4A1C,#0A0A0A);filter:blur(40px);animation:orbPulse 2.4s ease-in-out infinite}
@keyframes orbPulse{0%,100%{transform:scale(.92);opacity:.7}50%{transform:scale(1.06);opacity:1}}
@media (prefers-reduced-motion: reduce){.shader-orb-kinetic.no-webgl::before{animation:none}}