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

如何用原生JS实现ProcessingJS的frameRate以运行彩弹动画

将ProcessingJS彩弹动画迁移到原生JavaScript

核心问题:替换frameRate(30)

ProcessingJS的frameRate()用于固定动画帧率,原生JS中可通过requestAnimationFrame结合时间差计算实现相同效果,确保动画速度不受浏览器刷新频率影响。

自定义帧率控制实现

我们可以封装一个帧率控制逻辑,替代原代码中的frameRate(30):

const targetFrameRate = 30;
let lastFrameTime = 0;

function animate(timestamp) {
  if (!lastFrameTime) lastFrameTime = timestamp;
  const elapsed = timestamp - lastFrameTime;
  
  // 当累计时间达到目标帧间隔时执行绘制
  if (elapsed >= 1000 / targetFrameRate) {
    draw();
    lastFrameTime = timestamp;
  }
  
  requestAnimationFrame(animate);
}

// 启动动画循环
animate();

完整适配后的代码

原代码依赖ProcessingJS的绘图API,需替换为原生Canvas API,以下是完整可运行代码:

<!DOCTYPE html>
<html>
<head>
  <title>Paintball Painter</title>
  <style>
    canvas { border: 1px solid #000; }
  </style>
</head>
<body>
  <canvas id="canvas" width="800" height="400"></canvas>
  
  <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    let mouseIsPressed = false;
    let mouseX = 0;
    let mouseY = 0;
    
    // 模拟ProcessingJS鼠标变量
    canvas.addEventListener('mousedown', () => mouseIsPressed = true);
    canvas.addEventListener('mouseup', () => mouseIsPressed = false);
    canvas.addEventListener('mousemove', (e) => {
      const rect = canvas.getBoundingClientRect();
      mouseX = e.clientX - rect.left;
      mouseY = e.clientY - rect.top;
    });
    
    // 原代码变量
    let canvasHeight = 400;
    const splatDiameter = 40;
    const numSpots = 6;
    let pcolor = 11;
    let pballFrames = [1234, 1234];
    let pballXs = [1234, 1234];
    let pballYs = [1234, 1234];
    let pballColors = [1234, 1234];
    let spotXs = [1234, 1234];
    let spotYs = [1234, 1234];
    let spotColors = [1234, 1234];
    let spotTrans = [1234, 1234];
    let spotDiameters = [1234, 1234];
    canvasHeight -= 20;
    
    // 替换ProcessingJS绘图API为原生Canvas实现
    function background(r, g, b) {
      ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
    
    function noStroke() {
      ctx.strokeStyle = 'transparent';
    }
    
    function fill(r, g, b, a = 255) {
      ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a / 255})`;
    }
    
    function rect(x, y, w, h) {
      ctx.fillRect(x, y, w, h);
      ctx.strokeRect(x, y, w, h);
    }
    
    function textSize(size) {
      ctx.font = `${size}px Arial`;
    }
    
    function text(text, x, y) {
      ctx.fillText(text, x, y);
    }
    
    function stroke(r, g, b) {
      ctx.strokeStyle = `rgb(${r}, ${g}, ${b})`;
    }
    
    function strokeWeight(weight) {
      ctx.lineWidth = weight;
    }
    
    function ellipse(x, y, w, h) {
      ctx.beginPath();
      ctx.ellipse(x, y, w / 2, h / 2, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.stroke();
    }
    
    function random(min, max) {
      return Math.random() * (max - min) + min;
    }
    
    function floor(num) {
      return Math.floor(num);
    }
    
    // 原代码逻辑函数
    function drawColorSelector() {
      noStroke();
      fill(255, 100, 130);
      rect(0, canvasHeight, 33, 20);
      fill(255, 0, 0);
      rect(33, canvasHeight, 33, 20);
      rect(363, canvasHeight, 37, 3);
      fill(255, 140, 0);
      rect(66, canvasHeight, 33, 20);
      rect(363, canvasHeight + 3, 37, 3);
      fill(255, 255, 0);
      rect(99, canvasHeight, 33, 20);
      rect(363, canvasHeight + 6, 37, 3);
      fill(0, 200, 0);
      rect(132, canvasHeight, 33, 20);
      rect(363, canvasHeight + 9, 37, 3);
      fill(0, 150, 150);
      rect(165, canvasHeight, 33, 20);
      fill(0, 0, 255);
      rect(198, canvasHeight, 33, 20);
      rect(363, canvasHeight + 12, 37, 3);
      fill(150, 0, 150);
      rect(231, canvasHeight, 33, 20);
      rect(363, canvasHeight + 15, 37, 3);
      fill(255, 0, 255);
      rect(264, canvasHeight, 33, 20);
      fill(150, 150, 150);
      rect(297, canvasHeight, 33, 20);
      rect(363, canvasHeight + 18, 37, 3);
      fill(0, 0, 0);
      rect(330, canvasHeight, 33, 20);
      textSize(18);
      text("?", 377, canvasHeight + 15);
      ctx.fillStyle = 'transparent';
      stroke(255, 255, 255);
      strokeWeight(4);
      if (pcolor < 11) {
        rect(33 * pcolor, canvasHeight, 33, 20);
      } else {
        rect(364, canvasHeight, 35, 20);
      }
    }
    
    function selectColor() {
      if (mouseIsPressed) {
        if (mouseY > canvasHeight) {
          if (mouseX < 33) pcolor = 0;
          else if (mouseX < 66) pcolor = 1;
          else if (mouseX < 99) pcolor = 2;
          else if (mouseX < 132) pcolor = 3;
          else if (mouseX < 165) pcolor = 4;
          else if (mouseX < 198) pcolor = 5;
          else if (mouseX < 231) pcolor = 6;
          else if (mouseX < 264) pcolor = 7;
          else if (mouseX < 297) pcolor = 8;
          else if (mouseX < 330) pcolor = 9;
          else if (mouseX < 363) pcolor = 10;
          else pcolor = 11;
        }
      }
    }
    
    function setFill(clr, trans) {
      switch(clr) {
        case 0: fill(255, 100, 130, trans); break;
        case 1: fill(255, 0, 0, trans); break;
        case 2: fill(255, 140, 0, trans); break;
        case 3: fill(255, 255, 0, trans); break;
        case 4: fill(0, 200, 0, trans); break;
        case 5: fill(0, 150, 150, trans); break;
        case 6: fill(0, 0, 255, trans); break;
        case 7: fill(150, 0, 150, trans); break;
        case 8: fill(255, 0, 255, trans); break;
        case 9: fill(150, 150, 150, trans); break;
        case 10: fill(0, 0, 0, trans); break;
        default: fill(random(0,255), random(0,255), random(0,255), trans);
      }
    }
    
    function drawPaintball(x, y, frames) {
      if (frames !== 1234) {
        noStroke();
        ellipse(x, y, (11 - frames) * 3, (11 - frames) * 3);
      }
    }
    
    function spawnPaintball() {
      if (mouseIsPressed && (mouseY < canvasHeight || mouseX > 400)) {
        for (let i = 0; i < pballXs.length + 1; i++) {
          if (pballXs[i] === 1234 || !pballXs[i]) {
            pballXs[i] = mouseX;
            pballYs[i] = mouseY;
            pballColors[i] = pcolor === 11 ? floor(random(0,10)) : pcolor;
            pballFrames[i] = 1;
            break;
          }
        }
      }
    }
    
    function spawnSplat(x, y, dia, num, pclrid) {
      for (let k = 0; k < num; k++) {
        let j = spotXs.length;
        if (!spotXs[j] || spotXs[j] === 1234) {
          if (k === 0) {
            spotXs[j] = x;
            spotYs[j] = y;
            spotDiameters[j] = 30;
            spotColors[j] = pclrid;
            spotTrans[j] = 255;
          } else {
            spotXs[j] = x + random(-dia/2, dia/2);
            spotYs[j] = y + random(-dia/2, dia/2);
            spotDiameters[j] = random(5,40);
            spotColors[j] = pclrid;
            spotTrans[j] = 255;
          }
          break;
        }
      }
    }
    
    function updatePaintball() {
      for (let i = 0; i < pballXs.length; i++) {
        if (pballXs[i] !== 1234 && pballXs[i]) {
          setFill(pballColors[i], 1000);
          drawPaintball(pballXs[i], pballYs[i], pballFrames[i]);
          pballFrames[i] += 0.4;
          
          if (pballFrames[i] > 11) {
            spawnSplat(pballXs[i], pballYs[i], splatDiameter, numSpots, pballColors[i]);
            pballFrames[i] = pballXs[i] = pballYs[i] = pballColors[i] = 1234;
          }
        }
      }
    }
    
    function updateSpots() {
      for (let i = 0; i < spotXs.length; i++) {
        if (spotXs[i] !== 1234 && spotXs[i]) {
          noStroke();
          setFill(spotColors[i], spotTrans[i]);
          spotTrans[i] -= 0.75;
          ellipse(spotXs[i], spotYs[i], spotDiameters[i], spotDiameters[i]);
          if (spotTrans[i] < 0) {
            spotXs[i] = spotYs[i] = spotTrans[i] = spotDiameters[i] = 1234;
          }
        }
      }
    }
    
    function draw() {
      background(255,255,255);
      selectColor();
      spawnPaintball();
      updateSpots();
      updatePaintball();
      drawColorSelector();
    }
    
    // 帧率控制逻辑
    const targetFrameRate = 30;
    let lastFrameTime = 0;
    
    function animate(timestamp) {
      if (!lastFrameTime) lastFrameTime = timestamp;
      const elapsed = timestamp - lastFrameTime;
      
      if (elapsed >= 1000 / targetFrameRate) {
        draw();
        lastFrameTime = timestamp;
      }
      
      requestAnimationFrame(animate);
    }
    
    // 启动动画
    animate();
  </script>
</body>
</html>

关键说明

  • 帧率控制:通过时间戳计算帧间隔,确保每1000/30毫秒执行一次绘制,稳定维持30帧/秒。
  • API替换:将ProcessingJS的绘图函数逐一替换为原生Canvas API,保证原有绘图逻辑正常运行。
  • 事件模拟:添加鼠标事件监听,模拟ProcessingJS中的mouseIsPressed、mouseX、mouseY变量。

内容的提问来源于stack exchange,提问作者user14992713

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 12:15:44