WebGL Background #1

If you don't known how to prepare your page for using WebGL, I recommend you to read this article about how to manage all necessary infrastructure.

The main idea of the effect is using several Perlin noise generators (I used this implementation) with different scales.

vec4 mod289(vec4 x)
{
    return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
vec4 permute(vec4 x)
{
    return mod289(((x*34.0)+1.0)*x);
}
 
vec4 taylorInvSqrt(vec4 r)
{
    return 1.79284291400159 - 0.85373472095314 * r;
}
 
vec2 fade(vec2 t) {
    return t*t*t*(t*(t*6.0-15.0)+10.0);
}
 
// Classic Perlin noise
float cnoise(vec2 P)
{
    vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
    vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
    Pi = mod289(Pi); // To avoid truncation effects in permutation
    vec4 ix = Pi.xzxz;
    vec4 iy = Pi.yyww;
    vec4 fx = Pf.xzxz;
    vec4 fy = Pf.yyww;
     
    vec4 i = permute(permute(ix) + iy);
     
    vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
    vec4 gy = abs(gx) - 0.5 ;
    vec4 tx = floor(gx + 0.5);
    gx = gx - tx;
     
    vec2 g00 = vec2(gx.x,gy.x);
    vec2 g10 = vec2(gx.y,gy.y);
    vec2 g01 = vec2(gx.z,gy.z);
    vec2 g11 = vec2(gx.w,gy.w);
     
    vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
    g00 *= norm.x;  
    g01 *= norm.y;  
    g10 *= norm.z;  
    g11 *= norm.w;  
     
    float n00 = dot(g00, vec2(fx.x, fy.x));
    float n10 = dot(g10, vec2(fx.y, fy.y));
    float n01 = dot(g01, vec2(fx.z, fy.z));
    float n11 = dot(g11, vec2(fx.w, fy.w));
     
    vec2 fade_xy = fade(Pf.xy);
    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
    float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
    return 2.3 * n_xy;
}

                

The source implementation returns a value in range [-1; 1], so I added a wrapper:

float perlin_noise(vec2 x) {
    return cnoise(x * 10.0) * 0.5 + 0.5;
}
                

In the shader we use time and mouse position. They needs to be passed using uniform and some precomputing:

uniform vec2 uResolution;
uniform vec2 uMouse;
uniform float uTime;

//...

vec2 uv = tPos;
vec2 kMouse = uMouse / uResolution - 0.5;
float yRatio = uResolution.y / uResolution.x;

kMouse.y *= yRatio;
uv.y *= yRatio;
                

As Perlin Noise requires a position, we can pass there an offset according to the mouse position. Using an offset coefficient for each mask, we get the parallax effect. smoothstep is used for antialiasing.

float n1 = smoothstep(0.405, 0.4, perlin_noise((uv + kMouse * 0.8) * 0.5));
float n2 = smoothstep(0.31, 0.3, perlin_noise((uv + kMouse * 0.4) * 1.0));
float n3 = smoothstep(0.22, 0.2, perlin_noise((uv + kMouse * 0.2) * 2.0));
float n4 = smoothstep(0.13, 0.1, perlin_noise((uv + kMouse * 0.1) * 3.0));
                

As using raw masks is boring, I added a gradient changed in time.

vec3 time_color(vec2 uv, float time) {
    return vec3(0.1 + 0.9 * abs(sin(cos(time + 3.0 * uv.y) * 2.0 * uv.x + time)),
                0.1 + 0.9 * abs(cos(sin(time + 2.0 * uv.x) * 3.0 * uv.y + time)),
                0.5);
}
                

And at the end of the shader, I implemented a parallax for colours (order is important):

vec3 col = vec3(0.0);
col = mix(col, time_color(uv + kMouse * 0.1, uTime), n4);
col = mix(col, time_color(uv + kMouse * 0.2, uTime), n3);
col = mix(col, time_color(uv + kMouse * 0.4, uTime), n2);
col = mix(col, time_color(uv + kMouse * 0.8, uTime), n1);
                

Below is necessary JS code, that provides mouse position (and as a bonus - emulate mouse position changing according to mobile device's orientation changing).

var mouseX = 0, mouseY = 0;
var time = 0;

document.onmousemove = function (event) {
    mouseX = event.clientX;
    mouseY = event.clientY;
}

function handleOrientation(event) {
  var absolute = event.absolute;
  var alpha    = event.alpha;
  var beta     = event.beta;
  var gamma    = event.gamma;

  mouseY= (beta / 90.0 * 0.5 + 0.5) * glCanvas.height;
  mouseX = (gamma / 90.0 * 0.5 + 0.5) * glCanvas.width;
}
window.addEventListener("deviceorientation", handleOrientation, true);

//...

function animateScene() {
//...
    time += 0.01;
    
    gl.useProgram(shaderProgram.program);

    gl.uniform2fv(shaderProgram.uniforms["uResolution"], [glCanvas.width, glCanvas.height]);
    gl.uniform2fv(shaderProgram.uniforms["uMouse"], [mouseX, mouseY]);
    gl.uniform1f(shaderProgram.uniforms["uTime"], time);

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
//...
}
                

Of course, you can just look at the page code to see the whole implementation of the effect. So below is whole fragment shader code:

#ifdef GL_ES
    precision highp float;
#endif

uniform vec2 uResolution;
uniform vec2 uMouse;
uniform float uTime;

varying vec2 tPos;



vec4 mod289(vec4 x)
{
    return x - floor(x * (1.0 / 289.0)) * 289.0;
}
 
vec4 permute(vec4 x)
{
    return mod289(((x*34.0)+1.0)*x);
}
 
vec4 taylorInvSqrt(vec4 r)
{
    return 1.79284291400159 - 0.85373472095314 * r;
}
 
vec2 fade(vec2 t) {
    return t*t*t*(t*(t*6.0-15.0)+10.0);
}
 
// Classic Perlin noise
float cnoise(vec2 P)
{
    vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
    vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
    Pi = mod289(Pi); // To avoid truncation effects in permutation
    vec4 ix = Pi.xzxz;
    vec4 iy = Pi.yyww;
    vec4 fx = Pf.xzxz;
    vec4 fy = Pf.yyww;
     
    vec4 i = permute(permute(ix) + iy);
     
    vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
    vec4 gy = abs(gx) - 0.5 ;
    vec4 tx = floor(gx + 0.5);
    gx = gx - tx;
     
    vec2 g00 = vec2(gx.x,gy.x);
    vec2 g10 = vec2(gx.y,gy.y);
    vec2 g01 = vec2(gx.z,gy.z);
    vec2 g11 = vec2(gx.w,gy.w);
     
    vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
    g00 *= norm.x;  
    g01 *= norm.y;  
    g10 *= norm.z;  
    g11 *= norm.w;  
     
    float n00 = dot(g00, vec2(fx.x, fy.x));
    float n10 = dot(g10, vec2(fx.y, fy.y));
    float n01 = dot(g01, vec2(fx.z, fy.z));
    float n11 = dot(g11, vec2(fx.w, fy.w));
     
    vec2 fade_xy = fade(Pf.xy);
    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
    float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
    return 2.3 * n_xy;
}


float perlin_noise(vec2 x) {
    return cnoise(x * 10.0) * 0.5 + 0.5;
}


vec3 time_color(vec2 uv, float time) {
    return vec3(0.1 + 0.9 * abs(sin(cos(time + 3.0 * uv.y) * 2.0 * uv.x + time)),
                0.1 + 0.9 * abs(cos(sin(time + 2.0 * uv.x) * 3.0 * uv.y + time)),
                0.5);
}


void main() {
    vec2 uv = tPos;
    vec2 kMouse = uMouse / uResolution - 0.5;
    float yRatio = uResolution.y / uResolution.x;

    kMouse.y *= yRatio;
    uv.y *= yRatio;

    float n1 = smoothstep(0.405, 0.4, perlin_noise((uv + kMouse * 0.8) * 0.5));
    float n2 = smoothstep(0.31, 0.3, perlin_noise((uv + kMouse * 0.4) * 1.0));
    float n3 = smoothstep(0.22, 0.2, perlin_noise((uv + kMouse * 0.2) * 2.0));
    float n4 = smoothstep(0.13, 0.1, perlin_noise((uv + kMouse * 0.1) * 3.0));

    vec3 col = vec3(0.0);
    col = mix(col, time_color(uv + kMouse * 0.1, uTime), n4);
    col = mix(col, time_color(uv + kMouse * 0.2, uTime), n3);
    col = mix(col, time_color(uv + kMouse * 0.4, uTime), n2);
    col = mix(col, time_color(uv + kMouse * 0.8, uTime), n1);

    gl_FragColor = vec4(col, 1.0);
}