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

JavaScript Canvas使用clearRect()后仍残留绘制轨迹问题求助

问题

我认为自己忽略了某个明显的细节,但始终找不到问题所在:
我编写了两个绘制函数:draw_canvas()用于绘制背景(两个几乎覆盖整个Canvas的矩形),draw_player()通过context.stroke()绘制一个三角形。
这两个函数都在由setInterval(update,10)调用的update函数中执行,该函数内还调用了context.clearRect()。三角形的朝向通过document.onkeydown和onkeyup事件更新。
我理解clearRect()应该清空Canvas所有内容,draw_canvas()会重绘背景,两者都应该清除之前的三角形,但切换朝向时,每一次的三角形轨迹都能被看到,我无法理解原因,希望有人能解释问题所在。

代码

JavaScript 代码

var canvas;
var ctx;
var lx;
var ly;
right = false;
left = false;

var player = {
    x: 0,
    y: 0,
    size: 32,
    angle: 90
}

window.addEventListener("load", function(event) {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
    player.x = canvas.width/2;
    player.y = canvas.height/2;
});

document.onkeydown = function (e) {
    /*
    left = 37
    up = 38
    right = 39
    down = 40
    space = 32
    */
    if(e.keyCode == 37){
        left = true;
        right = false;
    }
    
    if(e.keyCode == 39){
        left = false;
        right = true;
    }

}

document.onkeyup = function (e) {
    if(e.keyCode == 37 || e.keyCode == 39){
        left = false;
        right = false;
    }
}

function draw_canvas(){
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'white';
    ctx.fillRect(1, 1, canvas.width-2, canvas.height-2);
}

function draw_player(){
    
    // draw du player, triangle équilatéral avec angle
    ctx.strokeStyle = 'black';
    ctx.linewidth = 2;
    
    lx = player.x + (player.size/2)*Math.cos(Math.PI*player.angle/180);
    ly = player.y + (player.size/2)*Math.sin(-Math.PI*player.angle/180);
    
    ctx.moveTo(lx,ly);
    ctx.lineTo( lx + player.size*Math.cos(Math.PI*(player.angle-150)/180), 
                ly + player.size*Math.sin(-Math.PI*(player.angle-150)/180));
    ctx.stroke();
    
    lx = lx + player.size*Math.cos(Math.PI*(player.angle-150)/180), 
    ly = ly + player.size*Math.sin(-Math.PI*(player.angle-150)/180);
    
    ctx.moveTo(lx,ly);
    ctx.lineTo( lx + player.size*Math.cos(Math.PI*(90 + player.angle)/180), 
                ly + player.size*Math.sin(-Math.PI*(90 + player.angle)/180));                
    ctx.stroke();

    lx = lx + player.size*Math.cos(Math.PI*(90 + player.angle)/180);
    ly = ly + player.size*Math.sin(-Math.PI*(90 + player.angle)/180);
    
    ctx.moveTo(lx,ly);
    ctx.lineTo( lx + player.size*Math.cos(Math.PI*(player.angle-30)/180), 
                ly + player.size*Math.sin(-Math.PI*(player.angle-30)/180));                
    ctx.stroke();

}

function update_player(){
    if (right){
        player.angle -= 2;
    }
    else if (left){
        player.angle += 2;
    }
}


setInterval(update,10);
function update(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
      draw_canvas();
    draw_player();
    update_player();

}

HTML 代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title>Asteroids</title>
    <script type="text/javascript" src="asteroids.js"></script>
  </head>
  <body>
    <canvas id="canvas" width="512" height="512"></canvas>
  </body>
</html>
问题原因及解决方法

问题出在Canvas的路径状态未重置上。Canvas绘图上下文会持续保留之前的路径记录,每次调用stroke()时,不仅会绘制当前新添加的路径,还会重复绘制所有未关闭的历史路径。哪怕用clearRect()清空了画布,路径状态依然存在,导致每次stroke()都会重绘历史路径,从而留下轨迹。

核心解决方法:在绘制玩家三角形前,调用ctx.beginPath()重置路径状态,确保每次绘制都是全新的路径,不会重复绘制历史轨迹。

同时可以优化两个细节:

  1. linewidth需改为驼峰式的lineWidth,否则宽度设置不生效
  2. 将三次stroke()合并为一次,减少API调用次数,提升性能

修改后的draw_player()函数如下:

function draw_player(){
    // 重置路径,解决轨迹问题的核心操作
    ctx.beginPath();
    
    // draw du player, triangle équilatéral avec angle
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 2; // 修正驼峰式命名
    
    lx = player.x + (player.size/2)*Math.cos(Math.PI*player.angle/180);
    ly = player.y + (player.size/2)*Math.sin(-Math.PI*player.angle/180);
    
    ctx.moveTo(lx,ly);
    ctx.lineTo( lx + player.size*Math.cos(Math.PI*(player.angle-150)/180), 
                ly + player.size*Math.sin(-Math.PI*(player.angle-150)/180));
    
    lx = lx + player.size*Math.cos(Math.PI*(player.angle-150)/180), 
    ly = ly + player.size*Math.sin(-Math.PI*(player.angle-150)/180);
    
    ctx.lineTo( lx + player.size*Math.cos(Math.PI*(90 + player.angle)/180), 
                ly + player.size*Math.sin(-Math.PI*(90 + player.angle)/180));                

    lx = lx + player.size*Math.cos(Math.PI*(90 + player.angle)/180);
    ly = ly + player.size*Math.sin(-Math.PI*(90 + player.angle)/180);
    
    ctx.lineTo( lx + player.size*Math.cos(Math.PI*(player.angle-30)/180), 
                ly + player.size*Math.sin(-Math.PI*(player.angle-30)/180));                
    
    // 一次stroke绘制整个三角形路径
    ctx.stroke();
}

内容的提问来源于Stack Exchange,提问作者Gwaka

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 16:25:30