Home >Web Front-end >JS Tutorial >Flow Field Screen

Flow Field Screen

Susan Sarandon
Susan SarandonOriginal
2024-10-03 20:18:29672browse

Flow Field Screen

Dynamic Flow Fields with Vanilla JS and HTML Canvas

Have you ever been mesmerized by abstract particle animations? These flowing, dynamic visuals can be achieved with surprisingly simple techniques using plain JavaScript and the HTML canvas element. In this article, we will break down the process of creating a flow field that animates thousands of particles, giving them a natural movement.

1. Setting Up the Project

To start, we need three files: an HTML file to set up the canvas, a CSS file for styling, and a JavaScript file for handling the logic.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flow Fields</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <canvas id="canvas1"></canvas>
    <script src="script.js"></script>
</body>
</html>

Explanation:

  • We define a element where all our animations will take place.
  • The styles.css will be linked to style the canvas.
  • The main animation logic is contained in script.js.

2. Styling the Canvas with CSS

Let’s add a simple style to give our canvas a black background and make sure all padding and margins are removed.

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

canvas {
    background-color: black;
}

Explanation:

  • Setting margin and padding to zero ensures that the canvas fills the entire screen.
  • The black background provides a nice contrast for the white particles.

3. Particle Class: Creating the Magic

The Particle class is where the core of the animation lies. Each particle moves across the canvas, leaving a trail of its past locations, creating the flowing effect.

class Particle {
    constructor(effect) {
        this.effect = effect;
        this.x = Math.floor(Math.random() * this.effect.width);
        this.y = Math.floor(Math.random() * this.effect.height);
        this.speedModifier = Math.floor(Math.random() * 5 + 1);
        this.history = [{ x: this.x, y: this.y }];
        this.maxLength = Math.floor(Math.random() * 200 + 10);
        this.timer = this.maxLength * 2;
        this.colors = ['#4C026B', '#8E0E00', '#9D0208', '#BA1A1A', '#730D9E'];
        this.color = this.colors[Math.floor(Math.random() * this.colors.length)];
    }

    draw(context) {
        context.beginPath();
        context.moveTo(this.history[0].x, this.history[0].y);
        for (let i = 1; i < this.history.length; i++) {
            context.lineTo(this.history[i].x, this.history[i].y);
        }
        context.strokeStyle = this.color;
        context.stroke();
    }

    update() {
        this.timer--;
        if (this.timer >= 1) {
            let x = Math.floor(this.x / this.effect.cellSize);
            let y = Math.floor(this.y / this.effect.cellSize);
            let index = y * this.effect.cols + x;
            let angle = this.effect.flowField[index];

            this.speedX = Math.cos(angle);
            this.speedY = Math.sin(angle);
            this.x += this.speedX * this.speedModifier;
            this.y += this.speedY * this.speedModifier;

            this.history.push({ x: this.x, y: this.y });
            if (this.history.length > this.maxLength) {
                this.history.shift();
            }
        } else if (this.history.length > 1) {
            this.history.shift();
        } else {
            this.reset();
        }
    }

    reset() {
        this.x = Math.floor(Math.random() * this.effect.width);
        this.y = Math.floor(Math.random() * this.effect.height);
        this.history = [{ x: this.x, y: this.y }];
        this.timer = this.maxLength * 2;
    }
}

Explanation:

  • Constructor: Each particle is initialized with a random position and movement speed. The history array tracks past positions to create trails.
  • draw(): This function draws the particle’s path based on its history. The particle leaves a colorful trail that adds to the visual effect.
  • update(): Here, the particle's position is updated by calculating the angle from the flow field. The speed and direction are controlled by trigonometric functions.
  • reset(): When the particle finishes its trail, it is reset to a new random location.

4. Effect Class: Organizing the Animation

The Effect class handles the creation of particles and the flow field itself, which controls the movement of the particles.

class Effect {
    constructor(canvas) {
        this.canvas = canvas;
        this.width = this.canvas.width;
        this.height = this.canvas.height;
        this.particles = [];
        this.numberOfParticles = 3000;
        this.cellSize = 20;
        this.flowField = [];
        this.curve = 5;
        this.zoom = 0.12;
        this.debug = true;
        this.init();
    }

    init() {
        this.rows = Math.floor(this.height / this.cellSize);
        this.cols = Math.floor(this.width / this.cellSize);
        for (let y = 0; y < this.rows; y++) {
            for (let x = 0; x < this.cols; x++) {
                let angle = (Math.cos(x * this.zoom) + Math.sin(y * this.zoom)) * this.curve;
                this.flowField.push(angle);
            }
        }
        for (let i = 0; i < this.numberOfParticles; i++) {
            this.particles.push(new Particle(this));
        }
    }

    drawGrid(context) {
        context.save();
        context.strokeStyle = 'white';
        context.lineWidth = 0.3;
        for (let c = 0; c < this.cols; c++) {
            context.beginPath();
            context.moveTo(c * this.cellSize, 0);
            context.lineTo(c * this.cellSize, this.height);
            context.stroke();
        }
        for (let r = 0; r < this.rows; r++) {
            context.beginPath();
            context.moveTo(0, r * this.cellSize);
            context.lineTo(this.width, r * this.cellSize);
            context.stroke();
        }
        context.restore();
    }

    render(context) {
        if (this.debug) this.drawGrid(context);
        this.particles.forEach(particle => {
            particle.draw(context);
            particle.update();
        });
    }
}

Explanation:

  • Constructor: Initializes the canvas dimensions, the number of particles, and the flow field.
  • init(): Calculates the angles for the flow field by combining trigonometric functions for each grid cell. This field influences how particles move.
  • drawGrid(): Draws the grid that divides the canvas into cells, used when debugging.
  • render(): Calls the draw and update methods for each particle to animate the particles across the canvas.

5. Bringing it to Life with the Animation Loop

To make everything work, we need an animation loop that continuously clears the canvas and re-renders the particles:

const effect = new Effect(canvas);

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    effect.render(ctx);
    requestAnimationFrame(animate);
}
animate();

Explanation:

  • clearRect(): Clears the canvas on each frame to avoid drawing over previous frames.
  • requestAnimationFrame: Keeps the animation smooth by recursively calling the animate() function.

Conclusion

By breaking down the Particle and Effect classes, we have created a fluid and dynamic flow field animation using only vanilla JavaScript. The simplicity of the HTML canvas, combined with JavaScript's trigonometric functions, allows us to build these mesmerizing visual effects.

Feel free to play around with the particle count, colors, or the flow field formula to create your own unique effects!

The above is the detailed content of Flow Field Screen. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn