<canvas></canvas>

<script type="x-shader/x-fragment" id="vertShader">
    precision highp float;

    varying vec2 vUv;
    attribute vec2 a_position;

    void main () {
        vUv = .5 * (a_position + 1.);
        gl_Position = vec4(a_position, 0., 1.);
    }
</script>

<script type="x-shader/x-fragment" id="fragShaderSpeed">
    precision highp float;
    precision highp sampler2D;

    varying vec2 vUv;
    uniform sampler2D u_prev_frame_texture;
    uniform vec2 u_pointer_position;
    uniform float u_pointer_power;
    uniform vec2 u_delta_xy;
    uniform float u_ratio;
    uniform float u_speed_fade;


    #define TWO_PI 6.28318530718
    #define PI 3.14159265358979323846


    void main () {

        vec2 uv = vUv;

        vec2 pointer = u_pointer_position;
        pointer.x *= u_ratio;

        vec2 pointer_uv = uv;
        pointer_uv.x *= u_ratio;

        float pointer_dot = 1. - clamp(length(pointer_uv - pointer), 0., 1.);
        pointer_dot = pow(pointer_dot, 6.);
        pointer_dot *= u_pointer_power;

        vec3 back = texture2D(u_prev_frame_texture, uv).rgb;
        back *= u_speed_fade;
        back = mix(back, vec3(u_delta_xy, 0.), pointer_dot);

        gl_FragColor = vec4(back.xyz, 1.);
    }
</script>

<script type="x-shader/x-fragment" id="fragShaderOutput">
    precision highp float;
    precision highp sampler2D;

    varying vec2 vUv;
    uniform sampler2D u_image_texture;
    uniform sampler2D u_speed_texture;
    uniform float u_ratio;
    uniform float u_img_ratio;
    uniform float u_time;
    uniform float u_pointer_multiplier;

    #define TWO_PI 6.28318530718
    #define PI 3.14159265358979323846

    float random(vec2 co) {
        return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
    }

    float get_dot_shape (vec2 dist, float radius) {
        return 1. - smoothstep(0., radius, dot(dist, dist) * 4.);
    }

    float get_point_shape (vec2 dist, float p) {
        float v = pow(1. - clamp(0., 1., length(dist)), 1.);
        v = smoothstep(0., 1., v);
        v = pow(v, 2.);
        return v;
    }

    void main () {

        float t = .001 * u_time;

        vec2 offset = texture2D(u_speed_texture, vUv).xy;
        offset.x = - offset.x;
        offset *= u_pointer_multiplier;

        float offset_pow = 9.;
        float offset_mult = 1.4;
        offset.x += offset_mult * pow(vUv.x, offset_pow);
        offset.x -= offset_mult * pow(1. - vUv.x, offset_pow);
        offset.y += offset_mult * pow(vUv.y, offset_pow);
        offset.y -= offset_mult * pow(1. - vUv.y, offset_pow);

        vec2 uv = vUv - .5;
        uv.x *= u_ratio;

        float f1, f2, f3, f4;
        float f1_eyes, f2_eyes, f3_eyes, f4_eyes;
        float f1_pupils, f2_pupils, f3_pupils, f4_pupils;

        {
            vec2 f_uv = uv + .9 * offset;
            vec2 f1_traj = .2 * vec2(.8 * sin(.5 * t), .2 + 2.5 * cos(.3 * t));
            vec2 f1_eye_size = vec2(.015, .001);
            float f1_eye_x = .1;
            float f1_eye_y = .1 + .1 * f1_traj.y;

            float f = get_point_shape(f_uv + f1_traj, 5.);
            f1_eyes = get_dot_shape(f_uv - vec2(f1_eye_x, f1_eye_y) + f1_traj, f1_eye_size[0]);
            f1_eyes += get_dot_shape(f_uv - vec2(-f1_eye_x, f1_eye_y) + f1_traj, f1_eye_size[0]);

            f1_pupils = get_dot_shape(f_uv - vec2(f1_eye_x, f1_eye_y - .02) + f1_traj, f1_eye_size[1]);
            f1_pupils += get_dot_shape(f_uv - vec2(-f1_eye_x + .005, f1_eye_y - .03) + f1_traj, f1_eye_size[1]);

            f1 = f;
        }

        {
            vec2 f_uv = uv + 1.3 * offset;
            vec2 f2_traj = .5 * vec2(1.7 * sin(-.5 * t), sin(.8 * t));
            vec2 f1_eye_size = vec2(.01, .001);
            float f1_eye_x = .1;
            float f1_eye_y = .05 + .3 * f2_traj.y;

            float f = get_point_shape(f_uv + f2_traj, 3.);
            f2_eyes = get_dot_shape((f_uv - vec2(f1_eye_x, f1_eye_y) + f2_traj) * vec2(.9, .8), f1_eye_size[0]);
            f2_eyes += get_dot_shape((f_uv - vec2(-f1_eye_x, f1_eye_y) + f2_traj) * vec2(1.2, 1.), f1_eye_size[0]);

            f2_pupils = get_dot_shape(f_uv - vec2(f1_eye_x + .01 * sin(2. * t), f1_eye_y - .014) + f2_traj, f1_eye_size[1]);
            f2_pupils += get_dot_shape(f_uv - vec2(-f1_eye_x + .01 * sin(2. * t) + .005, f1_eye_y - .011) + f2_traj, f1_eye_size[1]);

            f2 = f;
        }

        {
            vec2 f_uv = uv + 1.2 * offset;
            vec2 f3_traj = .45 * vec2(.5 * cos(-.3 * t), cos(-.8 * t));
            vec2 f1_eye_size = vec2(.005, .001);
            float f1_eye_x = .1;
            float f1_eye_y = .1 + .5 * f3_traj.y;

            float f = get_point_shape(f_uv + f3_traj, 3.);
            f3_eyes += get_dot_shape(f_uv - vec2(f1_eye_x, f1_eye_y) + f3_traj, f1_eye_size[0]);
            f3_eyes += get_dot_shape(f_uv - vec2(-f1_eye_x, f1_eye_y) + f3_traj, f1_eye_size[0]);

            f3_pupils = get_dot_shape(f_uv - vec2(f1_eye_x, f1_eye_y - .014) + f3_traj, f1_eye_size[1]);
            f3_pupils += get_dot_shape(f_uv - vec2(-f1_eye_x + .005, f1_eye_y - .011) + f3_traj, f1_eye_size[1]);

            f3 = f;
        }

        f1 -= f2;
        f3 -= f2;
        f2 -= f1;
        f2 -= f3;
        f1 -= f3;
        f3 -= f1;
        f3 -= f1;


        f1_eyes *= smoothstep(.1, .9, f1);
        f1_pupils *= smoothstep(.1, .9, f1);

        f2_eyes *= smoothstep(.3, .9, f2);
        f2_pupils *= smoothstep(.3, .9, f2);

        f3_eyes *= smoothstep(.3, .9, f3);
        f3_pupils *= smoothstep(.3, .9, f3);

        f1 = step(.3, f1);
        f2 = step(.3, f2);
        f3 = step(.3, f3);
        f1_eyes = step(.2, f1_eyes);
        f2_eyes = step(.2, f2_eyes);
        f3_eyes = step(.2, f3_eyes);
        f1_pupils = step(.2, f1_pupils);
        f2_pupils = step(.2, f2_pupils);
        f3_pupils = step(.2, f3_pupils);

        vec3 color1 = vec3(0.8549, 0.7059, 0.1961);
        vec3 color2 = vec3(0.7569, 0.2863, 0.1765);
        vec3 color3 = vec3(0.2627, 0.4627, 0.6706);

        float opacity = 0.;
        opacity += f1;
        opacity -= f1_eyes;
        opacity += f1_pupils;

        opacity += .9 * f2;
        opacity *= (1. - f2_eyes);
        opacity += f2_pupils;

        opacity += .95 * f3;
        opacity *= (1. - f3_eyes);
        opacity += f3_pupils;

        vec3 color = vec3(0.);
        color = f1 * color1;
        color -= f1_eyes;
        color = mix(color, color2, f2);
        color *= (1. - f2_eyes);
        color = mix(color, color3, f3);
        color = mix(color, vec3(0.), f3_pupils);

        float noise = random(uv + sin(t));
        color.rgb += noise * .1;

        gl_FragColor = vec4(color, opacity);
    }
</script>
html, body {
    overflow: hidden;
    margin: 0;
    background-color: #f3f2f1;
}

canvas {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.lil-gui {
    --width: 450px;
    max-width: 90%;
    --widget-height: 20px;
    font-size: 15px;
    --input-font-size: 15px;
    --padding: 10px;
    --spacing: 10px;
    --slider-knob-width: 5px;
    --background-color: rgba(5, 0, 15, .8);
    --widget-color: rgba(255, 255, 255, .3);
    --focus-color: rgba(255, 255, 255, .4);
    --hover-color: rgba(255, 255, 255, .5);

    --font-family: monospace;
}
import GUI from "https://cdn.jsdelivr.net/npm/lil-gui@0.18.2/+esm"

const canvasEl = document.getElementsByTagName("canvas")[0];
const devicePixelRatio = Math.min(window.devicePixelRatio, 2);

const params = {
    pointerMultiplier: .05,
    pointerPower: .35,
    deltaThreshold: .1,
    pointerFadeSpeed: .75,
    speedTextureFadeSpeed: .95,
};

const pointer = {
    x: 0,
    y: 0,
    moving: 0,
    dx: 0,
    dy: 0,
    dxTarget: 0,
    dyTarget: 0,
};
setupEvents();

const inputImageCanvasEl = document.createElement("canvas");
let inputImageTexture;

const vsSource = document.getElementById("vertShader").innerHTML;
const fsSpeedSource = document.getElementById("fragShaderSpeed").innerHTML;
const fsOutputSource = document.getElementById("fragShaderOutput").innerHTML;

const gl = canvasEl.getContext("webgl");
gl.getExtension("OES_texture_float");

const vertexShader = createShader(gl, vsSource, gl.VERTEX_SHADER);
const fragmentSpeedShader = createShader(gl, fsSpeedSource, gl.FRAGMENT_SHADER);
const fragmentOutputShader = createShader(gl, fsOutputSource, gl.FRAGMENT_SHADER);

const speedShaderProgram = createShaderProgram(gl, vertexShader, fragmentSpeedShader);
const speedShaderUniforms = getUniforms(speedShaderProgram);
const outputShaderProgram = createShaderProgram(gl, vertexShader, fragmentOutputShader);
const outputShaderUniforms = getUniforms(outputShaderProgram);

let velocity = createDoubleFBO(canvasEl.width, canvasEl.height);

uploadImageTexture();
createControls();

render();
window.addEventListener("resize", resizeCanvas);
resizeCanvas();


function createShaderProgram(gl, vertexShader, fragmentShader) {
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);

    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(program));
        return null;
    }

    return program;
}

function getUniforms(program) {
    let uniforms = [];
    let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
    for (let i = 0; i < uniformCount; i++) {
        let uniformName = gl.getActiveUniform(program, i).name;
        uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
    }
    return uniforms;
}

function createShader(gl, sourceCode, type) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, sourceCode);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }

    return shader;
}

function blit(target) {
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        -1, -1,
        -1, 1,
        1, 1,
        1, -1
    ]), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
    gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(0);

    if (target == null) {
        gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    } else {
        gl.viewport(0, 0, target.w, target.h);
        gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
    }
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

    gl.uniform1f(outputShaderUniforms.u_pointer_multiplier, params.pointerMultiplier);
}

function createFBO(w, h) {
    gl.activeTexture(gl.TEXTURE0);

    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    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);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.FLOAT, null);

    const fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
    gl.viewport(0, 0, w, h);
    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    return {fbo, texture, w, h};
}

function createDoubleFBO(w, h) {
    let fbo1 = createFBO(w, h);
    let fbo2 = createFBO(w, h);

    return {
        read: () => {
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, fbo1.texture);
            return fbo1;
        },
        write: () => fbo2,
        swap() {
            let temp = fbo1;
            fbo1 = fbo2;
            fbo2 = temp;
        }
    }
}

function render() {

    const currentTime = performance.now();
    gl.uniform1f(outputShaderUniforms.u_time, currentTime);

    pointer.dx += (pointer.dxTarget - pointer.dx) * params.deltaThreshold;
    pointer.dy += (pointer.dyTarget - pointer.dy) * params.deltaThreshold;
    pointer.moving *= params.pointerFadeSpeed;
    if (pointer.moving < .05) pointer.moving = 0;

    gl.useProgram(speedShaderProgram);
    gl.uniform1i(speedShaderUniforms.u_prev_frame_texture, velocity.read());
    gl.uniform2f(speedShaderUniforms.u_pointer_position, pointer.x / window.innerWidth, 1. - pointer.y / window.innerHeight);
    gl.uniform1f(speedShaderUniforms.u_pointer_power, pointer.moving);
    gl.uniform2f(speedShaderUniforms.u_delta_xy, pointer.dx, pointer.dy);
    gl.uniform1f(speedShaderUniforms.u_ratio, canvasEl.width / canvasEl.height);
    gl.uniform1f(speedShaderUniforms.u_speed_fade, params.speedTextureFadeSpeed);

    blit(velocity.write());
    velocity.swap();

    gl.useProgram(outputShaderProgram);
    gl.uniform1i(outputShaderUniforms.u_speed_texture, velocity.read());
    gl.uniform1f(outputShaderUniforms.u_ratio, canvasEl.width / canvasEl.height);

    blit();

    requestAnimationFrame(render);
}

function uploadImageTexture() {

    const image = new Image();
    image.src = "./bw-text.png";
    image.onload = () => {

        inputImageTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, inputImageTexture);
        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);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, inputImageCanvasEl);

        gl.uniform1f(outputShaderUniforms.u_img_ratio, image.naturalWidth / image.naturalHeight);
        gl.uniform1i(outputShaderUniforms.u_image_texture, 2);

        gl.activeTexture(gl.TEXTURE0 + 2);
        gl.bindTexture(gl.TEXTURE_2D, inputImageTexture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    };
}

function resizeCanvas() {
    canvasEl.width = window.innerWidth * devicePixelRatio;
    canvasEl.height = window.innerHeight * devicePixelRatio;
    velocity = createDoubleFBO(canvasEl.width, canvasEl.height);
}

function setupEvents() {
    window.addEventListener("pointermove", e => {
        updateMousePosition(e.pageX, e.pageY);
    });
    window.addEventListener("touchmove", e => {
        updateMousePosition(e.targetTouches[0].pageX, e.targetTouches[0].pageY);
    });
    window.addEventListener("click", e => {
        updateMousePosition(e.pageX, e.pageY);
    });

    function updateMousePosition(eX, eY) {

        pointer.dxTarget = eX - pointer.x;
        pointer.dyTarget = eY - pointer.y;

        pointer.dxTarget = Math.sign(pointer.dxTarget) * Math.pow(Math.abs(pointer.dxTarget), params.pointerPower);
        pointer.dyTarget = Math.sign(pointer.dyTarget) * Math.pow(Math.abs(pointer.dyTarget), params.pointerPower);

        pointer.x = eX;
        pointer.y = eY;

        pointer.moving = 1;
    }
}


function createControls() {
    const gui = new GUI();
    gui
        .add(params, "pointerMultiplier", 0, .3)
        .onChange(() => {
            gl.uniform1f(outputShaderUniforms.u_pointer_multiplier, params.pointerMultiplier);
        })
        .name("cursor disturbing power")

}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.