You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决Canvas绘制气泡时全部合并成一条线的问题

问题原因及修复方案

核心问题1:数值类型错误

generateDecimalBetween函数使用toFixed(2)返回的是字符串类型,但气泡的位置、大小等属性需要是数值类型。字符串参与减法运算时会触发错误的类型转换,导致位置计算完全异常,最终所有气泡挤在同一区域形成线条。

修复该函数,返回数值类型:

function generateDecimalBetween(max, min) {
  return parseFloat((Math.random() * (max - min) + min).toFixed(2));
}

核心问题2:未定义的画布尺寸变量

animate方法中调用的this.canvasWidth和this.canvasHeight从未被赋值,导致clearRect无法正确清除画布,残留的绘制内容会让气泡看起来连成线。需要在canvasSize方法中保存这两个值:

canvasSize(){
    this.canvasWidth = this.canvas.offsetWidth;
    this.canvasHeight = this.canvas.offsetHeight;
    this.canvas.width = this.canvasWidth * this.dpr;
    this.canvas.height = this.canvasHeight * this.dpr;
    this.ctx.scale(this.dpr, this.dpr);
}

修复后的完整代码

const COLORS = ["255,108,80", "5,117,18", "29,39,57", "67,189,81"];
const BUBBLE_DENSITY = 100;

function generateDecimalBetween(max, min) {
  return parseFloat((Math.random() * (max - min) + min).toFixed(2));
}
function generateColor(max, min) {
  return Math.floor(Math.random() * (max - min) + min);
}

class Bubble {
  constructor(canvas) {
    this.canvas = canvas;
    this.getCanvasSize();
    this.init();
  }

  getCanvasSize() {
    this.canvasWidth = this.canvas.offsetWidth;
    this.canvasHeight = this.canvas.offsetHeight;
  }

  init() {
    this.color = generateColor(COLORS.length, 0);
    this.size = generateDecimalBetween(3, 1);
    this.alpha = generateDecimalBetween(10, 5) / 10;
    this.translateX = generateDecimalBetween(this.canvasWidth, 0);
    this.translateY = generateDecimalBetween(this.canvasHeight, 0);
    this.velocity = generateDecimalBetween(40, 20);
    this.movementX = generateDecimalBetween(2, -2) / this.velocity;
    this.movementY = generateDecimalBetween(20, 1) / this.velocity;
  }

  move() {
    this.translateX -= this.movementX;
    this.translateY -= this.movementY;
    if (this.translateY < 0 || this.translateX < 0 || this.translateX > this.canvasWidth) {
      this.init();
      this.translateY = this.canvasHeight;
    }
  }
}

class canvasBackground{
  constructor(id){
    this.canvas = document.getElementById(id);
    this.ctx = this.canvas.getContext('2d');
    this.dpr = window.devicePixelRatio || 1;
  }

  start(){
    this.canvasSize();
    this.generateBubbles();
    this.animate();
  }

  canvasSize(){
    this.canvasWidth = this.canvas.offsetWidth;
    this.canvasHeight = this.canvas.offsetHeight;
    this.canvas.width = this.canvasWidth * this.dpr;
    this.canvas.height = this.canvasHeight * this.dpr;
    this.ctx.scale(this.dpr, this.dpr);
  }

  generateBubbles(){
    this.bubblesList = [];
    for (let i = 0; i < BUBBLE_DENSITY; i++) {
      this.bubblesList.push(new Bubble(this.canvas));
    }
  }

  animate() {
    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); 

    this.bubblesList.forEach(bubble => {
      this.ctx.save();
      bubble.move();
      this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
      this.ctx.translate(bubble.translateX, bubble.translateY);
      this.ctx.beginPath();
      this.ctx.arc(0, 0, bubble.size, 0, Math.PI * 2);
      this.ctx.fillStyle = `rgba(${COLORS[bubble.color]},${bubble.alpha})`;
      this.ctx.fill();
      this.ctx.restore();
    });

    requestAnimationFrame(this.animate.bind(this));
  }
}

document.addEventListener('DOMContentLoaded', () => {
  const CanvasBackground = new canvasBackground('orb-canvas'); 
  CanvasBackground.start();
});

内容的提问来源于stack exchange,提问作者Илья

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 13:08:10