<!-- Quick start shader template: https://codepen.io/pen/?template=ZEyrWNe -->

<div id="main">
  <h1>Wavey Footer</h1>
  <p>A GLSL shader inspired by <a href="https://twitter.com/marioecg/status/1455744243423645696">Mario Carrillo</a>.</p>
</div>

<canvas id="canvas"></canvas>
<footer>Made by <a href="https://twitter.com/jhancock532">@jhancock532</a></footer>

<script id="vertexShader" type="x-shader/x-vertex">
  attribute vec4 position;

  void main() {
    gl_Position = vec4( position );
  }
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
  #ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
  #else
  precision mediump float;
  #endif

  uniform vec2 u_resolution;
  uniform float u_time;
  uniform vec2 u_mouse;
  
  
  //Taken from Book of Shaders
  //https://thebookofshaders.com/edit.php#11/2d-gnoise.frag
  vec2 random2(vec2 st){
    st = vec2( dot(st,vec2(127.1,311.7)),
               dot(st,vec2(269.5,183.3)) );
    return -1.0 + 2.0*fract(sin(st)*43758.5453123);
  }

  // Gradient Noise by Inigo Quilez - iq/2013
  // https://www.shadertoy.com/view/XdXGW8
  float noise(vec2 st) {
      vec2 i = floor(st);
      vec2 f = fract(st);

      vec2 u = f*f*(3.0-2.0*f);

      return mix( mix( dot( random2(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ),
                       dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
                  mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
                       dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);
  }

  void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution;
    
    float waveOffset = 0.3;
    float waveCenter = 1.;
    float waveFocus = 0.25;
    float waveSpeed = 2.; //Lower faster.
    
    float wMin = waveCenter - waveFocus;
    float wMax = waveCenter + waveFocus;
    
    //Smoothly extend on the x axis without stretching the noise
    uv.x *= u_resolution.x/u_resolution.y;
    uv.x += 1787.74328; //random starting seed
    uv.y *= 2.;

    float rn = noise( vec2(uv.x, u_time / waveSpeed));
    float ry = uv.y - rn;
    float r = smoothstep(wMin, wMax, ry);
    
    float bn = noise( vec2(uv.x, u_time / waveSpeed - waveOffset));
    float by = uv.y - bn;
    float b = smoothstep(wMin, wMax, by);
    
    float gn = noise( vec2(uv.x, u_time / waveSpeed + waveOffset));
    float gy = uv.y - gn;
    float g = smoothstep(wMin, wMax, gy);
    
    //float a = (r + g + b) / 3.0;
    
    gl_FragColor = vec4(r, b, g, 1.0);
  }
</script>
body {
 margin: 0;
 font-family: consolas;
}

footer {
  position: absolute;
  font-size: 22px;
  bottom: 0;
  left: 0;
  transform: translate(calc(50vw - 50%), -50%);
  color: white;
}

footer a {
  color: white;
}

#main {
  margin: 80px 80px 0 80px;
}

@media only screen and (max-width: 600px) {
  #main {
    margin: 20px 20px 0 20px;
  }
}

p {
  font-size: 22px;
}

label {
  font-size: 22px;
  font-weight: bold;
  display: block;
}

input {
  display: block;
  margin-bottom: 10px;
  width: 100%;
  max-width: 300px;
}

canvas {
 position: absolute;
 bottom: 0;
 pointer-events: none;
 width: 100%;
 height: 50vh;
}
// Following the twgl tiny tutorial, https://twgljs.org/
// Code adapted from https://github.com/greggman/twgl.js/blob/master/examples/tiny.html

const gl = document.getElementById("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, ["vertexShader", "fragmentShader"]);

const arrays = {
  position: [-1, -1, 0, 1, -1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

let mouseX = 0, mouseY = 0;

document.getElementById("canvas").addEventListener('mousemove', e => {
  mouseX = e.clientX;
  mouseY = e.clientY;
});


function render(time) {
  //Paints only 25% pixels in the screen space, 
  //Slightly lower resolution but much better performance!
  //See also: the zoom feature of https://glslsandbox.com/
  
  twgl.resizeCanvasToDisplaySize(gl.canvas, 0.5); 
  
  //Paints 4x the number of pixels.
  //Very computationally expensive in full screen on desktop devices
  //twgl.resizeCanvasToDisplaySize(gl.canvas, 1.0); 

  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  const uniforms = {
    u_time: (time) * 0.002,
    u_resolution: [gl.canvas.width, gl.canvas.height],
    u_mouse: [mouseX, mouseY],
  };

  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, uniforms);
  twgl.drawBufferInfo(gl, bufferInfo);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://jhancock532.github.io/twgl.min.js