Vissza az animációkhoz
Háttéranimációk

Konstellációs háló

Élő előnézet – az animáció teljes méretben.

Élő előnézet
HTML · CSS · JS
HTML
<div class="we-bg-constellation">

    <canvas class="we-bg-constellation-canvas"></canvas>

    <div class="we-bg-constellation-content">
        <span>WEBELEMENTS</span>

        <h2>Konstelláció</h2>

        <p>
            Egy élő hálózat, amely folyamatosan változik a háttérben.
        </p>
    </div>

</div>
CSS
.we-bg-constellation {
    position: relative;
    width: 100%;
    min-height: 520px;
    overflow: hidden;
    background:
        radial-gradient(
            circle at 50% 45%,
            rgba(63, 159, 99, .08),
            transparent 42%
        ),
        #080b11;
    display: flex;
    align-items: center;
    justify-content: center;
    isolation: isolate;
}

.we-bg-constellation-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    pointer-events: none;
    z-index: 1;
}

.we-bg-constellation-content {
    position: relative;
    z-index: 3;
    width: min(720px, 90%);
    padding: 48px 44px;
    text-align: center;
    pointer-events: none;
}

.we-bg-constellation-content span {
    display: block;
    margin-bottom: 16px;
    color: #7ac98e;
    font-family: Inter, system-ui, sans-serif;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .24em;
}

.we-bg-constellation-content h2 {
    margin: 0 0 18px;
    color: #f4f1e9;
    font-family: "Bodoni Moda", Georgia, serif;
    font-size: clamp(42px, 7vw, 78px);
    font-weight: 500;
    line-height: 1;
    letter-spacing: -.02em;
}

.we-bg-constellation-content p {
    max-width: 560px;
    margin: 0 auto;
    color: rgba(244, 241, 233, .62);
    font-family: Inter, system-ui, sans-serif;
    font-size: 14px;
    line-height: 1.8;
}

@media (max-width: 700px) {

    .we-bg-constellation {
        min-height: 460px;
    }

    .we-bg-constellation-content {
        padding: 35px 22px;
    }

    .we-bg-constellation-content h2 {
        font-size: clamp(38px, 12vw, 58px);
    }
}
JavaScript
document.querySelectorAll(".we-bg-constellation").forEach(container => {

    const canvas = container.querySelector(
        ".we-bg-constellation-canvas"
    );

    const ctx = canvas.getContext("2d");

    const settings = {
        desktop: 115,
        mobile: 52,
        maxDistance: 155,
        mouseRadius: 230,
        minSpeed: 0.12,
        maxSpeed: 0.55,
        mouseForce: 0.035
    };

    let particles = [];
    let width = 0;
    let height = 0;
    let dpr = 1;

    const pointer = {
        x: null,
        y: null,
        active: false
    };

    function resizeCanvas() {

        const rect = container.getBoundingClientRect();

        width = rect.width;
        height = rect.height;

        dpr = Math.min(window.devicePixelRatio || 1, 2);

        canvas.width = width * dpr;
        canvas.height = height * dpr;

        canvas.style.width = width + "px";
        canvas.style.height = height + "px";

        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

        createParticles();
    }

    function createParticles() {

        const amount =
            window.innerWidth <= 700
                ? settings.mobile
                : settings.desktop;

        particles = [];

        for (let i = 0; i < amount; i++) {

            const angle = Math.random() * Math.PI * 2;

            const speed =
                settings.minSpeed +
                Math.random() *
                (settings.maxSpeed - settings.minSpeed);

            particles.push({
                x: Math.random() * width,
                y: Math.random() * height,

                vx: Math.cos(angle) * speed,
                vy: Math.sin(angle) * speed,

                radius: Math.random() * 1.35 + .65,

                phase: Math.random() * Math.PI * 2,

                waveX: Math.random() * Math.PI * 2,
                waveY: Math.random() * Math.PI * 2,

                waveSpeedX:
                    .0008 + Math.random() * .0014,

                waveSpeedY:
                    .0007 + Math.random() * .0013,

                waveAmountX:
                    .12 + Math.random() * .34,

                waveAmountY:
                    .12 + Math.random() * .34
            });
        }
    }

    function updateParticle(particle, time) {

        particle.waveX += particle.waveSpeedX;
        particle.waveY += particle.waveSpeedY;

        particle.x +=
            particle.vx +
            Math.sin(particle.waveX + particle.phase) *
            particle.waveAmountX;

        particle.y +=
            particle.vy +
            Math.cos(particle.waveY + particle.phase) *
            particle.waveAmountY;

        if (
            pointer.active &&
            pointer.x !== null &&
            pointer.y !== null
        ) {

            const dx = particle.x - pointer.x;
            const dy = particle.y - pointer.y;

            const distance = Math.sqrt(
                dx * dx + dy * dy
            );

            if (
                distance > 0 &&
                distance < settings.mouseRadius
            ) {

                const force =
                    (1 - distance / settings.mouseRadius) *
                    settings.mouseForce;

                particle.vx +=
                    (dx / distance) * force;

                particle.vy +=
                    (dy / distance) * force;
            }
        }

        const velocity = Math.sqrt(
            particle.vx * particle.vx +
            particle.vy * particle.vy
        );

        if (velocity > settings.maxSpeed) {

            particle.vx =
                (particle.vx / velocity) *
                settings.maxSpeed;

            particle.vy =
                (particle.vy / velocity) *
                settings.maxSpeed;
        }

        if (velocity < settings.minSpeed) {

            const safeVelocity =
                velocity || settings.minSpeed;

            particle.vx =
                (particle.vx / safeVelocity) *
                settings.minSpeed;

            particle.vy =
                (particle.vy / safeVelocity) *
                settings.minSpeed;
        }

        const margin = 30;

        if (particle.x < -margin) {
            particle.x = width + margin;
        }

        if (particle.x > width + margin) {
            particle.x = -margin;
        }

        if (particle.y < -margin) {
            particle.y = height + margin;
        }

        if (particle.y > height + margin) {
            particle.y = -margin;
        }
    }

    function drawConnections() {

        for (let i = 0; i < particles.length; i++) {

            for (let j = i + 1; j < particles.length; j++) {

                const first = particles[i];
                const second = particles[j];

                const dx = first.x - second.x;
                const dy = first.y - second.y;

                const distance =
                    Math.sqrt(dx * dx + dy * dy);

                if (distance > settings.maxDistance) {
                    continue;
                }

                let opacity =
                    1 - distance / settings.maxDistance;

                opacity *= .42;

                let lineColor =
                    `rgba(95, 189, 125, ${opacity})`;

                let lineWidth = .65;

                if (
                    pointer.active &&
                    pointer.x !== null &&
                    pointer.y !== null
                ) {

                    const firstMouseDistance =
                        Math.hypot(
                            first.x - pointer.x,
                            first.y - pointer.y
                        );

                    const secondMouseDistance =
                        Math.hypot(
                            second.x - pointer.x,
                            second.y - pointer.y
                        );

                    if (
                        firstMouseDistance < settings.mouseRadius ||
                        secondMouseDistance < settings.mouseRadius
                    ) {

                        opacity =
                            Math.min(
                                .82,
                                opacity + .24
                            );

                        lineColor =
                            `rgba(122, 201, 142, ${opacity})`;

                        lineWidth = .9;
                    }
                }

                ctx.beginPath();

                ctx.moveTo(first.x, first.y);
                ctx.lineTo(second.x, second.y);

                ctx.strokeStyle = lineColor;
                ctx.lineWidth = lineWidth;

                ctx.stroke();
            }
        }
    }

    function drawPointerConnections() {

        if (
            !pointer.active ||
            pointer.x === null ||
            pointer.y === null
        ) {
            return;
        }

        for (const particle of particles) {

            const dx = particle.x - pointer.x;
            const dy = particle.y - pointer.y;

            const distance =
                Math.sqrt(dx * dx + dy * dy);

            if (distance > settings.mouseRadius) {
                continue;
            }

            const opacity =
                (1 - distance / settings.mouseRadius) *
                .62;

            ctx.beginPath();

            ctx.moveTo(pointer.x, pointer.y);
            ctx.lineTo(particle.x, particle.y);

            ctx.strokeStyle =
                `rgba(122, 201, 142, ${opacity})`;

            ctx.lineWidth = .85;

            ctx.stroke();
        }

        const glow = ctx.createRadialGradient(
            pointer.x,
            pointer.y,
            0,
            pointer.x,
            pointer.y,
            90
        );

        glow.addColorStop(
            0,
            "rgba(95, 189, 125, .10)"
        );

        glow.addColorStop(
            1,
            "rgba(95, 189, 125, 0)"
        );

        ctx.beginPath();

        ctx.arc(
            pointer.x,
            pointer.y,
            90,
            0,
            Math.PI * 2
        );

        ctx.fillStyle = glow;
        ctx.fill();
    }

    function drawParticles() {

        for (const particle of particles) {

            const glow = ctx.createRadialGradient(
                particle.x,
                particle.y,
                0,
                particle.x,
                particle.y,
                7
            );

            glow.addColorStop(
                0,
                "rgba(244, 241, 233, .78)"
            );

            glow.addColorStop(
                .22,
                "rgba(122, 201, 142, .32)"
            );

            glow.addColorStop(
                1,
                "rgba(122, 201, 142, 0)"
            );

            ctx.beginPath();

            ctx.arc(
                particle.x,
                particle.y,
                7,
                0,
                Math.PI * 2
            );

            ctx.fillStyle = glow;
            ctx.fill();

            ctx.beginPath();

            ctx.arc(
                particle.x,
                particle.y,
                particle.radius,
                0,
                Math.PI * 2
            );

            ctx.fillStyle =
                "rgba(244, 241, 233, .72)";

            ctx.fill();
        }
    }

    function render(time) {

        ctx.clearRect(
            0,
            0,
            width,
            height
        );

        for (const particle of particles) {
            updateParticle(particle, time);
        }

        drawConnections();
        drawPointerConnections();
        drawParticles();

        requestAnimationFrame(render);
    }

    container.addEventListener(
        "mousemove",
        event => {

            const rect =
                container.getBoundingClientRect();

            pointer.x =
                event.clientX - rect.left;

            pointer.y =
                event.clientY - rect.top;

            pointer.active = true;
        }
    );

    container.addEventListener(
        "mouseleave",
        () => {

            pointer.x = null;
            pointer.y = null;
            pointer.active = false;
        }
    );

    window.addEventListener(
        "resize",
        resizeCanvas
    );

    resizeCanvas();

    requestAnimationFrame(render);

});