4 142 Codepen

Эффект с частицами



CSS

html, body {
    overflow: hidden;
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    background:#000;
    touch-action: none;
    content-zooming: none;
}
canvas {
    position: absolute;
    width: 100%;
    height: 100%;
    background:#000;
    cursor: pointer;
}

JS

"use strict";
{
    // http://mrl.nyu.edu/~perlin/noise/
    const perlin = {
        init (octaves = 1) {
            this.p = new Uint8Array(512);
            this.octaves = octaves;
            this.reset();
        },
        reset() {
            for (let i = 0; i < 512; ++i) {
                this.p[i] = Math.random() * 256;
            }
        },
        lerp(t, a, b) {
            return a + t * (b - a);
        },
        grad2d(i, x, y) {
            const v = (i & 1) === 0 ? x : y;
            return (i & 2) === 0 ? -v : v;
        },
        noise2d(x2d, y2d) {
            const X = Math.floor(x2d) & 255;
            const Y = Math.floor(y2d) & 255;
            const x = x2d - Math.floor(x2d);
            const y = y2d - Math.floor(y2d);
            const fx = (3 - 2 * x) * x * x;
            const fy = (3 - 2 * y) * y * y;
            const p0 = this.p[X] + Y;
            const p1 = this.p[X + 1] + Y;
            return this.lerp(
                fy,
                this.lerp(
                    fx,
                    this.grad2d(this.p[p0], x, y),
                    this.grad2d(this.p[p1], x - 1, y)
                ),
                this.lerp(
                    fx,
                    this.grad2d(this.p[p0 + 1], x, y - 1),
                    this.grad2d(this.p[p1 + 1], x - 1, y - 1)
                )
            );
        },
        noise(x, y) {
            let e = 1,
                k = 1,
                s = 0;
            for (let i = 0; i < this.octaves; ++i) {
                e *= 0.5;
                s += e * (1 + this.noise2d(k * x, k * y)) / 2;
                k *= 2;
            }
            return s;
        }
    };
/////////////////////////////////////////////////////////////////
    const canvas = {
        init() {
            this.elem = document.createElement("canvas");
            document.body.appendChild(this.elem);
            this.width = this.elem.width = this.elem.offsetWidth;
            this.height = this.elem.height = this.elem.offsetHeight;
            return this.elem.getContext("2d");
        }
    };
/////////////////////////////////////////////////////////////////
    const webgl = {
        init(canvas, options) {
            this.elem = document.createElement("canvas");
            this.gl = (
                this.elem.getContext("webgl", options) ||
                this.elem.getContext("experimental-webgl", options)
            );
            if (!this.gl) return false;
            const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
            this.gl.shaderSource(vertexShader, `
                precision highp float;
                attribute vec3 aPosition;
                uniform vec2 uResolution;
                void main() {
                    gl_PointSize = 1.0;
                    gl_Position = vec4(
                        ( aPosition.x / uResolution.x * 2.0) - 1.0, 
                        (-aPosition.y / uResolution.y * 2.0) + 1.0, 
                        0.0,
                        1.0
                    );
                }`
            );
            this.gl.compileShader(vertexShader);
            const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
            this.gl.shaderSource(fragmentShader, `
                precision highp float;
                void main() {
                    gl_FragColor = vec4(0.2, 0.3, 1.0, 1.0);
                }`
            );
            this.gl.compileShader(fragmentShader);
            this.program = this.gl.createProgram();
            this.gl.attachShader(this.program, vertexShader);
            this.gl.attachShader(this.program, fragmentShader);
            this.gl.linkProgram(this.program);
            this.gl.useProgram(this.program);
            this.aPosition = this.gl.getAttribLocation(this.program, "aPosition");
            this.gl.enableVertexAttribArray(this.aPosition);
            this.positionBuffer = this.gl.createBuffer();
            this.resize(canvas);
            return this.gl;
        },
        resize (canvas) {
            this.elem.width = canvas.width;
            this.elem.height = canvas.height;
            const uResolution = this.gl.getUniformLocation(this.program, "uResolution");
            this.gl.enableVertexAttribArray(uResolution);
            this.gl.uniform2f(uResolution, canvas.width, canvas.height);
            this.gl.viewport(
                0,
                0,
                this.gl.drawingBufferWidth,
                this.gl.drawingBufferHeight
            );
        },
        drawBuffer(data, num) {
            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
            this.gl.vertexAttribPointer(this.aPosition, 2, this.gl.FLOAT, false, 0, 0);
            this.gl.bufferData(
                this.gl.ARRAY_BUFFER,
                data,
                this.gl.DYNAMIC_DRAW
            );
            this.gl.drawArrays(this.gl.GL_POINTS, 0, num);
        }
    };
/////////////////////////////////////////////////////////////////
    const ctx = canvas.init();
    const gl = webgl.init(canvas, {
        alpha: false,
        stencil: false,
        antialias: false,
        depth: false,
    });
    perlin.init(2);
    const nParticles = 30000;
    const velocities = new Float32Array(nParticles * 2);
    const particles = new Float32Array(nParticles * 2);
    let frame = 0;
    for (let i = 0; i < nParticles; i++) {
        const p = i * 2;
        particles[p+0] = Math.random() * canvas.width;
        particles[p+1] = Math.random() * canvas.height;
    }
/////////////////////////////////////////////////////////////////
    const run = () => {
        requestAnimationFrame(run);
        frame++;
        gl.clear(gl.COLOR_BUFFER_BIT);
        ctx.globalCompositeOperation = "source-over";
        ctx.fillStyle = "rgba(0, 0, 0, 0.025)";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalCompositeOperation = "lighter";
        for (let i = 0; i < nParticles; i++) {
            const p = i * 2;
            let n = 180 * perlin.noise(particles[p+0] * 0.001, particles[p+1] * 0.001);
            velocities[p+0] += 0.1 * Math.cos(n);
            velocities[p+1] += 0.1 * Math.sin(n);
            particles[p+0]  += (velocities[p+0] *= 0.99);
            particles[p+1]  += (velocities[p+1] *= 0.99);
            particles[p+0]   = (canvas.width  + particles[p+0]) % canvas.width;
            particles[p+1]   = (canvas.height + particles[p+1]) % canvas.height;
        }
        webgl.drawBuffer(particles, nParticles);
        if (frame > 30) ctx.drawImage(webgl.elem, 0, 0);    
    };
    requestAnimationFrame(run);
/////////////////////////////////////////////////////////////////
    ["click", "touchdown"].forEach(event => {
        document.addEventListener(event, e => perlin.reset(), false);
    });
}

Комментарии

  • Facebook
  • Вконтакте

Похожие статьи