如何用原生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
相关产品推荐
相关产品推荐

