<script src="https://kit.fontawesome.com/48764efa36.js" crossorigin="anonymous"></script>
        <link rel="preconnect" href="https://fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;900&display=swap" rel="stylesheet">
        <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600;900&display=swap" rel="stylesheet">
        <div id="content">
            <h1>Welcome to Dynamic Art</h1>
            <div>  
                <h2>Size</h2>
                <input id="range" type="range" min="-1" max="1" step="0.05" value="1" />
            </div>
            <div>
                <h2>Definition</h2>
                <input id="veins" type="range" min="0.1" max="20" step="0.05" value="20" />
            </div>
            <div>
                <h2>Go Crazy</h2>
                <input id="crazy" type="range" min="1" max="600" step="1" value="1" />
            </div>
            <div>
                <h2>Recolor</h2>
                <div class="color red" data-id="0"></div>
                <div class="color blue" data-id="1"></div>
                <div class="color orange" data-id="2"></div>
            </div>
        </div>
        <div id="art">
            <a target="_top" href="https://fjolt.com/article/webgl-dynamic-art-background">Go to Article</a>
        </div>
        <div id="click">
            <i class="fal fa-hand-pointer"></i> Click Me
        </div>
        <canvas id="canvas"></canvas>

<script id="noise" type="shader">
  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 rand(float n){return fract(sin(n) * 43758.5453123);}
float rand(vec2 n) { 
    return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(float p){
    float fl = floor(p);
  float fc = fract(p);
    return mix(rand(fl), rand(fl + 1.0), fc);
}
float noise (in vec2 n) {
    vec2 i = floor(n);
    vec2 f = fract(n);

    // Four corners in 2D of a tile
    float a = rand(i);
    float b = rand(i + vec2(1.0, 0.0));
    float c = rand(i + vec2(0.0, 1.0));
    float d = rand(i + vec2(1.0, 1.0));

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

float snoise(vec2 v) {
    const vec4 C = vec4(0.211324865405187,  // (3.0-sqrt(3.0))/6.0
                        0.366025403784439,  // 0.5*(sqrt(3.0)-1.0)
                        -0.577350269189626,  // -1.0 + 2.0 * C.x
                        0.024390243902439); // 1.0 / 41.0
    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); // Avoid truncation effects in permutation
    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);
}
const mat2 m2 = mat2(0.8,-0.6,0.6,0.8);

#define NUM_OCTAVES 10

float fbm ( in vec2 _st) {
    float v = 0.0;
    float a = 0.5;
    vec2 shift = vec2(100.0);
    // Rotate to reduce axial bias
    mat2 rot = mat2(cos(0.5), sin(0.5),
                    -sin(0.5), cos(0.50));
    for (int i = 0; i < NUM_OCTAVES; ++i) {
        v += a * noise(_st);
        _st = rot * _st * 2.0 + shift;
        a *= 0.5;
    }
    return v;
}
</script>
<script id="vertex" type="shader">
  uniform float u_time;
uniform float u_height;

varying vec2 vUv;

void main() {
    vUv = uv;
    
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x, position.y, position.z, 0.4) * 20.;
}

</script>
<script id="fragment" type="shader">vec3 rgb(float r, float g, float b) {
    return vec3(r / 255., g / 255., b / 255.);
}
vec3 rgb(float c) {
    return vec3(c / 255., c / 255., c / 255.);
}

uniform vec3 u_lowColor;
uniform vec3 u_highColor;
uniform float u_time;
uniform float u_goCrazy;
uniform float u_veinDefinition;
uniform float u_manipulate;
uniform float u_clickLength;
uniform vec2 u_resolution;
uniform float u_scale;
uniform vec2 u_mouse;
uniform sampler2D u_inputTexture;

varying vec2 vUv;
varying float vDistortion;
varying float xDistortion;

// Main function
void main() {
    // We have to adjust the effect to fit our resolution.
    // Heavily modified FBM function from https://thebookofshaders.com/13/
    vec2 res = (gl_FragCoord.xy + 100.) / (u_resolution.xy * u_scale);
    
    // Next lets get our colors
    vec3 highColor = rgb(u_highColor.r, u_highColor.g, u_highColor.b);
    vec3 lowColor = rgb(u_lowColor.r, u_lowColor.g, u_lowColor.b);
    
    // Set a random color
    vec3 color = vec3(23.0);

    // This is a randomised function based on fbm and some other variables
    // that we can adjust in our Javascript
    vec2 fbm1 = vec2(10.);
    fbm1.x = fbm( res + 0.05 * u_time) * snoise(res) * u_goCrazy;
    fbm1.y = fbm( res + vec2(3.0)) / (u_manipulate - snoise(res)) * 9. * u_goCrazy / u_veinDefinition * u_clickLength * 5.;

    // Next we adjust it all based on mouse position, time, and qfbm1
    vec2 r = vec2(0.);
    r.x = fbm( res + fbm1 * u_time * 0.1 ) + -sin(u_mouse.x) + 600.;
    r.y = fbm( res + fbm1 * u_time * 0.5 ) * -u_mouse.y;

    // And create a float of fbm, for use in the final color
    float f = fbm(res+r) * 1.;

    // Then we mix all our colors together
    color = mix(highColor*2., lowColor, f*3.);
    color = mix(color, lowColor, clamp(length(fbm1),0.0,2.0)); // * snoise(st) * 51.9
    color = mix(color, highColor, clamp(length(r.y),0.0,3.0));

    // And output them for render
    gl_FragColor = vec4((f*f*f*0.9*f*f+.5*f)*color,1.);
}

</script>
body {
    padding: 0;
    margin: 0;
    background-color: #000920;
    background-image: radial-gradient(circle at top left, #161231, #161231);
    font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    padding: 4rem;
}

canvas {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 9;
    transition: all 0.1s ease-out;
}
h1 {
    font-size: 2.5rem;
    color: black;
    font-family: 'Playfair Display', serif;
    font-size: 4.5rem;
    margin: 0;
    font-weight: 900;
    margin-bottom: 2rem;
}

.color {
    float: left;
    width: 25px;
    height: 10px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.05);
    border-radius: 20px;
    padding: 0.5rem;
    margin: 0 1rem 0 0;
    position: relative;
    cursor: pointer;
}

.color:after {
    position: absolute;
    top: 0;
    content: '';
    left: 0;
    width: 100%;
    transition: all 0.1s ease-out;
    height: 100%;
    border-radius: 100px;
    background: transparent;
}
.color:hover:after {
    background: rgba(0,0,0,0.2);
}
.color.red {
    background: #c53a48;
}
.color.orange {
    background: #ffa042;
}
.color.blue {
    background: #2b3565;
}
#art {
    position: absolute;
    padding: 1rem 2rem;
    border-radius: 100px;
    color: rgba(255,255,255,0.6);
    z-index: 999;
    bottom: 2rem;
    font-weight: 600;
    box-shadow: 0 2px 20px rgba(0,0,0,0.1);
    background: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.8));
    right: 2rem;
}
#click {
    position: absolute;
    top: 2rem;
    right: 2rem;
    color: white;
    text-shadow: 0 2px 10px rgba(0,0,0,.1);
    z-index: 999;
    font-size: 1.25rem;
    font-weight: 600;
}
#click i {
    margin: 0 1rem 0 0;
}
h2 {
    font-size: 1rem;
    text-transform: uppercase;
    letter-spacing: 0.25px;
}
#art a {
    color: white;
    text-decoration: none;
    border-bottom: 1px solid #fff;
}
#content {
    width: 50%;
    height: 910px;
    padding: 4rem;
    position: absolute;
    top: 0;
    box-sizing: border-box;
    z-index: 9999;
    left: 0;
    background: linear-gradient(90deg, white 10%, rgba(255,255,255,0.8));
    box-shadow: 0 0 90px rgba(0,0,0,0.5);
}

strong {
    color: rgba(0,0,0,0.7);
}

p {
    font-size: 1.25rem;
    color: rgba(0,0,0,0.5);
    line-height: 2rem;
}
.reveal {
    font-family: 'Playfair Display', serif;
    color: #7ba998;
    border: 3px solid #7ba998;
    padding: 0.5rem 2rem;
    display: block;
    float: left;
    font-size: 1.5rem;
    margin: 2rem 0 0 0;
    position: relative;
    text-decoration: none;
    border-radius: 4px;
}

.reveal:after {
    content: '';
    width: 100%;
    background: #7ba998;
    position: absolute;
    top: 0;
    left: 0;
    transition: all 0.1s ease-out;
    height: 0;
    z-index: -1;
}

.reveal:hover:after {
    height: 100%;
}

.reveal:hover {
    color: white;
    box-shadow: 0 2px 20px rgba(0,0,0,0.05);
}

input[type=range] {
    -webkit-appearance: none;
    width: 100%;
    border-radius: 10px;
    background: rgba(0,0,0,0.1); 
    height: 16px;
}

input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    border: none;
    height: 20px;
    width: 20px;
    border-radius: 50%;
    background: goldenrod;
    margin-top: -1px;
 }

input[type=range]::-moz-range-track,
input[type=range]::-webkit-slider-runnable-track  {
    background: blue;
}

@media screen and (max-width: 1000px) {
    #content {
        width: 100%;
        max-height: 50%;
        overflow: scroll;
        padding-top: 1rem;
    }
    h1 {
        display: none;
    }
    #click {
        top: auto;
        bottom: 2rem;
        left: 2rem;
        right: auto;
    }
}
import * as THREE from 'https://cdn.skypack.dev/three@v0.122.0';

const rgb = function(r, g, b) {
    return new THREE.Vector3(r, g, b);
}
const loader = function(path, texture) {
    return new Promise((resolve, reject) => {
        let loader = new THREE.FileLoader();
        if(typeof texture !== "undefined") {
            loader = new THREE.TextureLoader();
        }
        loader.load(path, (item) => resolve(item));
    })
}
const randomInteger = function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
const config = {
    individualItem: '.button', // class of individual item
    carouselId: '#album-rotator', // carousel selector
    carouselHolderId: '#album-rotator-holder', // carousel should be <div id="carouselId"><div id="carouselHolderId">{items}</div></div>
    colors: [
        { low: rgb(127,68,255), high: rgb(225,68,90) },
        { low: rgb(225, 109, 68), high: rgb(77, 115, 241) },
        { low: rgb(77, 115, 241), high: rgb(225, 109, 68) }
    ]
}

// Async function for generating webGL waves
const createWave = async function(colors) {      
    // Import all the fragment and vertex shaders
    const noise = document.getElementById('noise').textContent
    const fragment = document.getElementById('fragment').textContent
    const vertex = document.getElementById('vertex').textContent
    // For each of the selector elemefnts
    // Create a renderer
    const renderer = new THREE.WebGLRenderer({
        powerPreference: "high-performance",
        antialias: true, 
        alpha: true,
        canvas: canvas
    });
    
    document.querySelectorAll('.color').forEach(function(item) {
        item.addEventListener('click', function(e) {
            let i = parseFloat(this.getAttribute('data-id'));
            mesh.material.uniforms.u_highColor.value = config.colors[i].high;
            mesh.material.uniforms.u_lowColor.value = config.colors[i].low;
        });
    });
    
    // Get el width and height
    let elWidth = window.innerWidth + 500;
    let elHeight = window.innerHeight + 500;
    
    // Set sizes and set scene/camera
    renderer.setSize( elWidth, elHeight );
    document.body.appendChild( renderer.domElement )
    renderer.setPixelRatio( elWidth/elHeight );
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera( 75, elWidth / elHeight, 0.1, 1000 );
    
    let i = 2;
    // Check on colors to use
    let high = config.colors[i].high; 
    let low = config.colors[i].low;

        document.getElementById('content').style.height = `${document.querySelector('canvas').getBoundingClientRect().height}px`;
    // Create a plane, and pass that through to our shaders
    let geometry = new THREE.PlaneGeometry(600, 600, 100, 100);
    let material = new THREE.ShaderMaterial({
        uniforms: {
            u_lowColor: {type: 'v3', value: low },
            u_highColor: {type: 'v3', value: high },
            u_time: {type: 'f', value: 0},
            u_resolution: {type: 'v2', value: new THREE.Vector2(elWidth, elHeight) },
            u_mouse: {type: 'v2', value: new THREE.Vector2(0, 0) },
            u_height: {type: 'f', value: 1},
            u_manipulate: {type: 'f', value: 1 },
            u_veinDefinition: {type: 'f', value: 20 },
            u_goCrazy: { type: 't', value: 1 },
            u_inputTexture: {type: 't', value: ''},
            u_scale: {type: 'f', value: 0.85 },
            u_clickLength: { type: 'f', value: 1},
            u_rand: { type: 'f', value: randomInteger(0, 10) },
            u_rand: {type: 'f', value: new THREE.Vector2(randomInteger(6, 10), randomInteger(8, 10)) }
        },
        fragmentShader: noise + fragment,
        vertexShader: noise + vertex,
    });
    // Create the mesh and position appropriately
    let mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(0, 0, -300);
    scene.add(mesh);
    let reduceVector;
    let increasePressure;
    let reducePressure;
    let prevX = 0;
    let prevY = 0;
    let curValueX = 0;
    let curValueY = 0;
    let mouseEnterX = 0;
    let mouseEnterY = 0;
    let mousePressure = 0;

    document.getElementById('range').addEventListener('input', function(e) {
        mesh.material.uniforms.u_manipulate.value = this.value;
    })
    document.getElementById('veins').addEventListener('input', function(e) {
        mesh.material.uniforms.u_veinDefinition.value = this.value;
    })
    document.getElementById('crazy').addEventListener('input', function(e) {
        mesh.material.uniforms.u_goCrazy.value = this.value;
    })
 document.body.addEventListener('pointerenter', function(e) {
        prevX = curValueX;
        prevY = curValueY;
        mouseEnterX = e.pageX;
        mouseEnterY = e.pageY;
        clearInterval(reduceVector);
    })
    document.body.addEventListener('pointermove', function(e) {
        if(typeof reduceVector !== "undefined") {
            clearInterval(reduceVector);
            curValueX = 0;
            curValueY = 0;
        }
        let mouseMoveX = mouseEnterX - e.pageX;
        let mouseMoveY = mouseEnterY - e.pageY;
        mesh.material.uniforms.u_mouse.value = new THREE.Vector2(prevX + (mouseMoveX / elWidth), prevY + (mouseMoveY / elHeight));
    });
    document.getElementById('canvas').addEventListener('pointerdown', function(e) {
        if(typeof reducePressure !== "undefined") clearInterval(reducePressure);
        increasePressure = setInterval(function() {
            if(mesh.material.uniforms.u_clickLength.value < 3) {
                mesh.material.uniforms.u_clickLength.value += 0.03;
            }
        },1000/60);
    });
    document.getElementById('canvas').addEventListener('pointerup', function(e) {
        if(typeof increasePressure !== "undefined") clearInterval(increasePressure);
        reducePressure = setInterval(function() {
            if(mesh.material.uniforms.u_clickLength.value > 1) {
                mesh.material.uniforms.u_clickLength.value -= 0.03;
            }
        },1000/60);
    });
    document.body.addEventListener('pointerleave', function(e) {
        reduceVector = setInterval(function() {
            let startXNeg, startXPos, startYNeg, startYPos;
            let finishX, finishY;
            if(curValueX == 0 && curValueY == 0) {
                curValueX = mesh.material.uniforms.u_mouse.value.x;
                curValueY = mesh.material.uniforms.u_mouse.value.y;
            }
            if(typeof reduceVector == "function") {
                requestAnimationFrame(reduceVector);
            }
            if(curValueX > 0) {
                if(startXPos !== true) {
                    mesh.material.uniforms.u_mouse.value = new THREE.Vector2(curValueX, curValueY);
                } else { finishX = true; }
                curValueX -= 0.005;
                startXNeg = true;
            }
            else if(curValueX < 0) {
                if(startXNeg !== true) {
                    mesh.material.uniforms.u_mouse.value = new THREE.Vector2(curValueX, curValueY);
                } else { finishX = true; }
                curValueX += 0.005;
                startXPos = true;
            }
            if(curValueY > 0) {
                if(startYNeg !== true) {
                    mesh.material.uniforms.u_mouse.value = new THREE.Vector2(curValueX, curValueY);
                } else { finishY = true; }
                curValueY -= 0.005;
                startYPos = true;
            }
            else if(curValueY < 0) {
                if(startYNeg !== true) {
                    mesh.material.uniforms.u_mouse.value = new THREE.Vector2(curValueX, curValueY);
                } else { finishY = true; }
                curValueY += 0.005;
                startYNeg = true;
            }
            if(finishX == true && finishY == true) {
                clearInterval(reduceVector);
            }
        }, 1000/60);
    });
    // On hover effects for each item
    let enterTimer, exitTimer;
    document.getElementById('canvas').addEventListener('mouseenter', function(e) {
        if(typeof exitTimer !== "undefined") {
            clearTimeout(exitTimer);
        }
        enterTimer = setInterval(function() {
            if(mesh.material.uniforms.u_height.value >= 0.5) {
                mesh.material.uniforms.u_height.value -= 0.05;
            } else {
                clearTimeout(enterTimer);
            }
        }, 1000/60);
    });
    document.getElementById('canvas').addEventListener('mouseleave', function(e) {
        if(typeof enterTimer !== "undefined") {
            clearTimeout(enterTimer);
        }
        exitTimer = setInterval(function() {
            if(mesh.material.uniforms.u_height.value < 1) {
                mesh.material.uniforms.u_height.value += 0.05;
            } else {
                clearTimeout(exitTimer);
            }
        }, 1000/60);
    });
    // Render
    renderer.render( scene, camera );
    let t = 5;
    // Animate
    let backtrack = false;
    const animate = function () {
        requestAnimationFrame( animate );
        renderer.render( scene, camera );
        document.body.appendChild(renderer.domElement);
        mesh.material.uniforms.u_time.value = t;
        if(t < 10 && backtrack == false) {
            t = t + 0.005;
        } else {
            backtrack = true;
            t = t - 0.005;
            if(t < 0) {
                backtrack = false;
            }
        }
        
    };
    animate();
}

document.addEventListener("DOMContentLoaded", function(e) {
    createWave(config.individualItem, config.colors);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.