<input id="image-selector-input" style="visibility:hidden;" type="file">

<div class="container">
    <canvas></canvas>
</div>


<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="fragShader">
    precision highp float;
    precision highp sampler2D;

    varying vec2 vUv;
    uniform sampler2D u_image_texture;
    uniform vec2 u_pointer;
    uniform vec3 u_dot_color;
    uniform float u_time;
    uniform float u_tile_scale;
    uniform float u_offset;
    
    vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
    vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
    vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
    float snoise(vec2 v) {
        const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
        vec2 i = floor(v + dot(v, C.yy));
        vec2 x0 = v - i + dot(i, C.xx);
        vec2 i1;
        i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
        vec4 x12 = x0.xyxy + C.xxzz;
        x12.xy -= i1;
        i = mod289(i);
        vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
        vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
        m = m*m;
        m = m*m;
        vec3 x = 2.0 * fract(p * C.www) - 1.0;
        vec3 h = abs(x) - 0.5;
        vec3 ox = floor(x + 0.5);
        vec3 a0 = x - ox;
        m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
        vec3 g;
        g.x = a0.x * x0.x + h.x * x0.y;
        g.yz = a0.yz * x12.xz + h.yz * x12.yw;
        return 130.0 * dot(m, g);
    }
    
    void main () {

        vec2 sampling_uv = vUv;
        sampling_uv.y = 1. - sampling_uv.y;
        sampling_uv -= .5;
        sampling_uv /= u_tile_scale;

        vec2 fract_uv = fract(sampling_uv);
        vec2 floor_uv = floor(sampling_uv);

        fract_uv.x += u_offset * snoise(floor_uv + .001 * u_time);

        sampling_uv = (floor_uv + fract_uv);
        sampling_uv *= u_tile_scale;
        sampling_uv += .5;

        vec4 img_shifted = texture2D(u_image_texture, sampling_uv);
        img_shifted.a = step(.1, img_shifted.a);
        img_shifted.a *= step(0., sampling_uv.x);
        img_shifted.a *= (1. - step(1., sampling_uv.x));

        gl_FragColor = img_shifted;
    }
</script>
html, body {
    margin: 0;
    overflow: hidden;
}

.container {
    width: 100%;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

canvas {
    display: block;
    height: 55%;
}

.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 containerEl = document.querySelector(".container")[0];
const canvasEl = document.querySelector("canvas");
const imgInput = document.querySelector("#image-selector-input");
canvasEl.width = 0;

const devicePixelRatio = Math.min(window.devicePixelRatio, 2);

const params = {
    tileSize: Math.floor(.06 * window.innerHeight),
    offset: .5,
    loadMyImage: () => {
        imgInput.click();
    }
};

imgInput.onchange = () => {
    const [file] = imgInput.files;
    if (file) {
        const reader = new FileReader();
        reader.onload = e => {
            loadImage(e.target.result);
        };
        reader.readAsDataURL(file);
    }
};


let image, uniforms;
const gl = initShader();
updateUniforms();
loadImage("https://ksenia-k.com/img/codepen/for-px-distortion-demo-2.jpg");
createControls();
render();
window.addEventListener("resize", resizeCanvas);


function initShader() {
    const vsSource = document.getElementById("vertShader").innerHTML;
    const fsSource = document.getElementById("fragShader").innerHTML;

    const gl = canvasEl.getContext("webgl") || canvasEl.getContext("experimental-webgl");

    if (!gl) {
        alert("WebGL is not supported by your browser.");
    }

    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;
    }

    const vertexShader = createShader(gl, vsSource, gl.VERTEX_SHADER);
    const fragmentShader = createShader(gl, fsSource, gl.FRAGMENT_SHADER);

    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;
    }

    const shaderProgram = createShaderProgram(gl, vertexShader, fragmentShader);
    uniforms = getUniforms(shaderProgram);

    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;
    }

    const vertices = new Float32Array([-1., -1., 1., -1., -1., 1., 1., 1.]);

    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    gl.useProgram(shaderProgram);

    const positionLocation = gl.getAttribLocation(shaderProgram, "a_position");
    gl.enableVertexAttribArray(positionLocation);

    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    return gl;
}

function updateUniforms() {
    gl.uniform1f(uniforms.u_tile_scale, params.tileSize / canvasEl.clientHeight);
    gl.uniform1f(uniforms.u_offset, params.offset);
}

function loadImage(src) {
    image = new Image();
	 image.crossOrigin = "anonymous";
    image.src = src;
    image.onload = () => {
        const imageTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, imageTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        gl.uniform1i(uniforms.u_image_texture, 0);
        resizeCanvas();
    };
}

function render() {
    const currentTime = performance.now();
    gl.uniform1f(uniforms.u_time, currentTime);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame(render);
}

function resizeCanvas() {
    const imgRatio = image.naturalWidth / image.naturalHeight;
    canvasEl.height = canvasEl.clientHeight * devicePixelRatio;
    canvasEl.width = canvasEl.height * imgRatio;
    gl.viewport(0, 0, canvasEl.width, canvasEl.height);
}

function createControls() {
    const gui = new GUI();
	 gui.close();
    gui
        .add(params, "tileSize", 10, 100, 1)
        .onChange(updateUniforms)
        .name("tile size")
    gui
        .add(params, "offset", 0, 1)
        .onChange(updateUniforms)
    gui
        .add(params, "loadMyImage")
        .name("load image")
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.