You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为HTML5 Canvas瀑布粒子效果添加蓝色模糊渐变效果?

如何为HTML5 Canvas瀑布粒子效果添加蓝色模糊渐变效果?

嘿,我看了你用HTML5 Canvas做的瀑布粒子效果,想要实现参考图里那种粒子逐渐淡出并变成蓝色模糊的效果对吧?你提到的快速模糊思路完全可行,我来给你具体讲讲怎么修改现有代码来达成这个效果~

核心思路:颜色渐变 + 动态模糊叠加

参考图里的效果是粒子从接近白色的亮色,随着淡出逐渐过渡为模糊的蓝色。我们可以通过两个关键调整来实现:

  • 让粒子的颜色随透明度(alpha)动态变化,从浅亮色调过渡到深蓝色
  • 给淡出阶段的粒子添加动态模糊,透明度越低,模糊程度越高

具体代码修改步骤

1. 调整粒子颜色渐变逻辑

原来的粒子颜色是固定的蓝色调,我们可以修改fillStyle,让亮度随alpha值变化:当粒子刚生成(alpha=1)时亮度接近白色,随着alpha降低,亮度逐渐回到深蓝。

修改Particle类的draw()方法:

draw() {
  // 根据粒子类型和alpha动态调整模糊程度
  const blurAmount = this.isSplinter ? (3 - (this.alpha * 3)) : (2 - (this.alpha * 2));
  ctx.filter = `blur(${blurAmount}px)`;
  
  // 动态调整亮度,实现从亮到蓝的渐变
  const brightness = 67 + (30 * this.alpha);
  
  ctx.beginPath();
  ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
  ctx.fillStyle = `hsla(199, 100%, ${brightness}%, ${this.alpha})`; // 随alpha变化的颜色
  ctx.fill();
  
  // 重置滤镜,避免影响后续绘制
  ctx.filter = 'none';
}

2. 优化画布的拖尾效果

原来的画布清空用的是rgba(0,0,0,0.2),可以稍微调整透明度,让模糊效果的残留更自然,比如改成rgba(0,0,0,0.15),这样拖尾会更柔和,和模糊效果更搭配:

animate()函数里修改:

ctx.fillStyle = 'rgba(0, 0, 0, 0.15)'; // 更柔和的拖尾,适配模糊效果
ctx.fillRect(0, 0, canvas.width, canvas.height);

修改后的完整代码

<canvas id="fountainCanvas"></canvas>
canvas {
  display: block;
  margin: 0 auto;
  background: #000;
}
const canvas = document.getElementById('fountainCanvas');
const ctx = canvas.getContext('2d');

// Set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const particles = [];
const splinters = [];
const gravity = 0.1; // Gravity constant
const fountainX = canvas.width / 2;
const fountainY = canvas.height / 2;

// Mouse position
let mouseX = fountainX;
let mouseY = fountainY;

// Particle class
class Particle {
  constructor(x, y, angle, isSplinter = false) {
    this.x = x;
    this.y = y;
    const speed = Math.random() * 3 + 2; // Random speed
    const spread = Math.random() * 0.4 - 0.2; // Randomize direction slightly
    this.vx = isSplinter ?
      (Math.random() * 2 - 1) * 3 :
      Math.cos(angle + spread) * speed;
    this.vy = isSplinter ?
      Math.random() * -3 :
      Math.sin(angle + spread) * speed;
    this.alpha = isSplinter ? 1 : 1; // Opacity
    this.radius = isSplinter ? Math.random() : Math.random() + 1; // Size
    this.isSplinter = isSplinter;
  }

  update() {
    this.x += this.vx;
    this.y += this.vy;
    this.vy += gravity; // Apply gravity
    this.alpha -= this.isSplinter ? 0.02 : 0.005; // Fade out

    // Check if main particles reach the bottom of the canvas
    if (!this.isSplinter && this.y >= canvas.height) {
      this.createSplinters(); // Create splinters on impact
      this.alpha = 0; // Make particle invisible
    }
  }

  createSplinters() {
    for (let i = 0; i < 10; i++) {
      splinters.push(new Particle(this.x, canvas.height, 0, true));
    }
  }

  draw() {
    // 根据粒子类型和alpha动态调整模糊程度
    const blurAmount = this.isSplinter ? (3 - (this.alpha * 3)) : (2 - (this.alpha * 2));
    ctx.filter = `blur(${blurAmount}px)`;
    
    // 动态调整亮度,实现从亮到蓝的渐变
    const brightness = 67 + (30 * this.alpha);
    
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = `hsla(199, 100%, ${brightness}%, ${this.alpha})`; // 随alpha变化的颜色
    ctx.fill();
    
    // 重置滤镜,避免影响其他绘制
    ctx.filter = 'none';
  }

  isAlive() {
    return this.alpha > 0; // Check if particle is still visible
  }
}

// Create particles over time
function emitParticles() {
  const angle = Math.atan2(mouseY - fountainY, mouseX - fountainX);
  for (let i = 0; i < 5; i++) { // Emit a few particles per frame
    particles.push(new Particle(fountainX, fountainY, angle));
  }
}

// Animation loop
function animate() {
  ctx.fillStyle = 'rgba(0, 0, 0, 0.15)'; // 更柔和的拖尾,适配模糊效果
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  emitParticles(); // Emit new particles continuously

  // Update and draw particles
  particles.forEach((particle, index) => {
    particle.update();
    if (!particle.isAlive()) {
      particles.splice(index, 1); // Remove dead particles
    } else {
      particle.draw();
    }
  });

  // Update and draw splinters
  splinters.forEach((splinter, index) => {
    splinter.update();
    if (!splinter.isAlive()) {
      splinters.splice(index, 1); // Remove dead splinters
    } else {
      splinter.draw();
    }
  });

  requestAnimationFrame(animate);
}

// Update mouse position on move
canvas.addEventListener('mousemove', (event) => {
  mouseX = event.clientX;
  mouseY = event.clientY;
});

// Initialize animation
animate();

关于Simplex Noise的补充思路

如果想要更自然的流体扰动效果(比如粒子运动时的轻微随机偏移),可以把Simplex Noise的核心函数直接写进代码里,在update()方法里用噪声值调整粒子的vx/vy,让粒子运动更接近真实水流的不规则感。不过对于你想要的淡出模糊效果,上面的颜色+动态模糊已经足够啦~

备注:内容来源于stack exchange,提问作者satyanarayan mishra

火山引擎 最新活动