如何实现更真实的粒子漂浮动画?

Rap*_*nah 2 html javascript animation canvas

我是画布新手,正在尝试创建看起来逼真的浮动粒子动画。

目前,我正在创建 400 个粒子,它们随机分布在 400x400 的画布上。

然后,在每个 中,我都像这样requestAnimationFrame调整x和坐标:y

this.particles[i].x += Math.random() * 0.3 - 0.15;
this.particles[i].y += Math.random() * 0.3 - 0.15;
Run Code Online (Sandbox Code Playgroud)

现在,我正在尝试弄清楚如何使粒子看起来更真实地漂浮。

让他们看起来更现实的一种方法是他们是否会旅行更远的距离。现在,它们看起来只是在振动,而不是漂浮。实现漂移动画以使粒子移动明显距离但不会完全从画布上消失的一种方法是什么?

this.particles[i].x += Math.random() * 0.3 - 0.15;
this.particles[i].y += Math.random() * 0.3 - 0.15;
Run Code Online (Sandbox Code Playgroud)
class ParticleCanvas {
  constructor(canvas, width, height) {
    this.main = {
      element: canvas
    };

    this.buffer = {
      element: document.createElement("canvas")
    };

    this.main.context = this.main.element.getContext("2d");
    this.buffer.context = this.buffer.element.getContext("2d");

    this.main.element.width = this.buffer.element.width = this.width = width;
    this.main.element.height = this.buffer.element.height = this.height = height;

    this.makeParticles(400);
    this.animate();
  }

  makeParticles(n) {
    this.particles = [];

    for (let i = 0; i < n; i++) {
      this.particles.push(
        new Particle(
          this.width / 2 + Math.random() * this.width - this.width / 2,
          this.height / 2 + Math.random() * this.height - this.height / 2,
          Math.random() * 3 + 2,
          "#919191",
          "circle"
        )
      );
    }
  }

  animate() {
    this.main.context.clearRect(0, 0, this.width, this.height);
    for (var i = 0; i < this.particles.length; i++) {
      this.particles[i].x += Math.random() * 0.3 - 0.15;
      this.particles[i].y += Math.random() * 0.3 - 0.15;
      this.particles[i].draw(this.main.context);
    }
    requestAnimationFrame(this.animate.bind(this));
  }
}

class Particle {
  constructor(x, y, radius, dotColor, particleShape) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.dotColor = dotColor;
    this.particleShape = particleShape;
  }

  draw(context) {
    context.save();
    context.translate(this.x, this.y);
    context.fillStyle = this.dotColor;
    switch (this.particleShape) {
      case "circle":
        context.beginPath();
        context.arc(0, 0, this.radius, 0, 2 * Math.PI);
        context.fill();
        break;
      case "square":
        context.fillRect(0, 0, this.radius * 2, this.radius * 2);
        break;
    }
    context.restore();
  }
}


new ParticleCanvas(document.querySelector("canvas"), 400, 400);
Run Code Online (Sandbox Code Playgroud)
body {
  margin: 0;
}
Run Code Online (Sandbox Code Playgroud)

Bli*_*n67 5

漩涡和漂浮介质。

正弦波产生可用于激活粒子的简单运动。

使粒子看起来好像漂浮的方法之一是将它们视为漂浮在介质中。介质的运动使单个粒子运动。这倾向于使彼此靠近的粒子以相似的方式移动。

模拟空气。

在空气中,运动往往呈圆形,如涡流。

为了实现这一点,我们可以创建一组代表空气涡流中心的不可见涡流粒子。我们在画布周围移动漩涡。每个漩涡都有一个位置、大小和相关的强度。它们移动的区域是画布的两倍大。

然后每个粒子被每个涡流移动。粒子移动的量取决于粒子的质量(与其尺寸有关)以及它距涡中心的距离。

每个粒子还具有动量(继续移动的倾向)和一个抽象的浮力值,该浮力值随着时间的推移上下移动粒子(以阻止它们聚集在一个地方)。为了给运动提供一点扩散深度(远离眼睛),会为运动增加一点(非常微小的)视差。

动画有 4 个步骤。

  • 更新沿大的大致圆形路径移动的漩涡。如果它们离开两侧,请将它们移动到另一侧(又名小行星空间)
  • 对于每个涡流,更新每个粒子,沿涡流的切线移动粒子。
  • 对于每个粒子进行阻力和浮力,并检查边界。
  • 画出粒子

神奇数字

就许多神奇数字而言,这些类型的模拟非常棘手。在示例的顶部,我添加了一些常量来控制模拟。

您可以使用这些值,可以实现多种效果,模拟空气、水、粘液等中的粒子。

例子

看起来最好的整页。

// request first frame (this starts the whole shebang)
requestAnimationFrame(update);


const doFor =(c,C)=>{var i=0;while(i<c && C(i++)!==true);return i};
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand  = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const ctx = canvas.getContext("2d");

//==================================================================
// WARNING next two control CPU load. 
// Number of particles is width * height / density
const density = 1000;  // particles per pixel squared WARNING Small numbers bad
const airVortexCount = 8;  // number of vortex Warning big number bad
//==================================================================

const airVortexSpeed = 10;  // speed of vortex movement in pixels
const vortexTurn = 0.02; // bigger values and vortex movement more circular
var airList;
const airForceCof = 1;   // amount of force airVortex applys to circle
const circleDrag = 0.99; // drag is 1-circleDrag Must be less than 1
                         // the smaller the number the less movement in circles
const buoyancySpeed  = 0.01; //  Particle buoyancy  speed
var circleList;
const circleMinSize = 2;  // size is radius in pixels
const circleMaxSize = 4;
const floatPhaseCycleMax = 0.02; // Particle buoyancy phase shift. Bigger numbers 
                                 // create faster updown movement
var airWidth,airHeight;
var cirWidth,cirHeight;

function createParticles(){
    airList = {...list};
    circleList = {...list};
    airList.init();
    circleList.init();
    doFor(airVortexCount, () => airList.add({...particle, ...air}).init());
    var circleCount = (canvas.width * canvas.height) / density;
    doFor(circleCount, () => circleList.add({...particle, ...circle}).init());
    airWidth = canvas.width * 2;
    airHeight = canvas.height * 2;
    cirWidth = canvas.width + circleMaxSize * 2;
    cirHeight = canvas.height + circleMaxSize * 2;    
}

function updateAll(){
    airList.update();
    airList.each(circleList.update.bind(circleList));
    circleList.update();
    ctx.beginPath();
    circleList.draw();
    ctx.fill();
}
            


var w,h,cw,ch;
function update(timer){
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    if(w !== innerWidth || h !== innerHeight){  // resize canvas if needed
        cw = (w = canvas.width = innerWidth) / 2;
        ch = (h = canvas.height = innerHeight) / 2;
        createParticles();
    }
    ctx.fillStyle = "black";
    ctx.fillRect(0,0,w,h);
    ctx.fillStyle = "rgba(255,200,100,0.5)";
    updateAll();
    requestAnimationFrame(update);
}



// air is a Vortex (bad naming when I started)
const air = {
    update(){
        this.x += Math.cos(this.phase) * airVortexSpeed;
        this.y += Math.sin(this.phase) * airVortexSpeed;
        this.phase += this.phaseShift;
        
        this.x = ((this.x % airWidth) + airWidth) % airWidth;
        this.y = ((this.y % airHeight) + airHeight) % airHeight;
    },
    init(){
        this.x = randI(canvas.width);
        this.y = randI(canvas.height);
        this.size2 = randI(canvas.height * canvas.width);
        this.size = Math.sqrt(this.size2);
        this.phase = rand(Math.PI * 2);
        this.phaseShift = rand(-vortexTurn,vortexTurn);
        return this;
    },
}
// the things that float in air
const circle = {
    init(){
        this.x = randI(canvas.width);
        this.y = randI(canvas.height);
        this.dx = 0;
        this.dy = 0;
        this.size = rand(circleMinSize,circleMaxSize);
        this.mass = this.size ** 3;
        this.depth = rand(0.5,1);
        this.floatPhase = rand(Math.PI * 2);
        this.floatPhaseCycle = rand(floatPhaseCycleMax );
        return this;
    },    
    update(air){  // if air is undefined then do momentum and bouyancy
        if(air === undefined){
            this.dy += Math.sin(this.floatPhase)*buoyancySpeed;
            this.floatPhase += this.floatPhaseCycle;
            this.x += this.dx * this.depth;
            this.y += this.dy * this.depth;
            this.dx *= circleDrag;
            this.dy *= circleDrag;
            this.x = ((this.x % cirWidth) + cirWidth) % cirWidth;
            this.y = ((this.y % cirHeight) + cirHeight) % cirHeight;
            return;
        }
        var x = this.x - (air.x - airWidth / 4);
        var y = this.y - (air.y - airHeight / 4);
        var dist = x * x + y * y;
        if(dist < air.size2){
            dist = Math.sqrt(dist);
            x /= dist;
            y /= dist;
            var force = (1 - dist / air.size) * airForceCof;
            
            // using f = ma  (force = mass * acceleration)
            var a = (force / this.mass) * Math.sign(air.phaseShift);
            this.dx += -y * a;
            this.dy += x * a;
        }
    },
    draw(){
        var x = this.x - circleMaxSize;
        var y = this.y - circleMaxSize;
        ctx.moveTo(x + this.size,y);
        ctx.arc(x,y,this.size,0,Math.PI * 2);
    }
}
// basic particle
const particle = { x : 0,  y : 0, size : 0}
// basic list handles all particle arrays
const list = {
    items : [],
    init(){ this.items = [] },
    add(item) {
        this.items.push(item);
        return item;
    },
    each(callback) { for(const item of this.items) { callback(item) } },
    update(data) { for(const item of this.items) { item.update(data) } },
    draw() { for(const item of this.items) { item.draw() } },
}
Run Code Online (Sandbox Code Playgroud)
canvas { position : absolute; top : 0px; left : 0px; }
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)