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

Canvas添加鼠标跟随小球后清除图像,新手求助分层实现交互需求

解决Canvas添加跟随小球后原有图像消失的问题 + 实现你的需求

嘿,我完全懂你遇到的坑——Canvas跟PS那种图层逻辑不一样,它是「即时渲染」的,每帧如果只画新元素,之前的内容要么被clearRect清掉,要么被新绘制内容覆盖,不会自动保留。你加了小球动画后,肯定是只在循环里画了小球,没重新绘制鼠标、奶酪这些原有元素,所以它们就消失啦。

下面给你一套完整的实现方案,一步步满足所有需求:

核心思路

  1. 每帧重新绘制所有元素:动画循环里先清空画布,再依次画出鼠标图、当前奶酪图、小球(不用保留鼠标和奶酪的连线了,因为要让小球自由移动)
  2. 小球平滑跟随鼠标:用缓动算法让小球慢慢靠近鼠标,避免瞬移的生硬感
  3. 检测小球到达奶酪:计算小球与奶酪中心的距离,达标后自动替换奶酪图像
  4. 无限制移动:小球完全跟随鼠标路径,不受任何固定连线约束

完整代码示例

<canvas id="gameCanvas" width="800" height="600"></canvas>

<script>
// 获取Canvas和绘图上下文
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

// 图像资源定义
const mouseImg = new Image();
const cheeseImg = new Image();
const newCheeseImg = new Image();

// 标记所有图像是否加载完成
let imagesLoaded = false;
let loadedCount = 0;

// 图像加载完成计数,全部加载后启动动画
function checkImagesLoaded() {
  loadedCount++;
  if (loadedCount === 3) {
    imagesLoaded = true;
    requestAnimationFrame(animate);
  }
}

// 替换成你的实际图像路径
mouseImg.src = 'mouse.png';
mouseImg.onload = checkImagesLoaded;

cheeseImg.src = 'cheese.png';
cheeseImg.onload = checkImagesLoaded;

newCheeseImg.src = 'new-cheese.png';
newCheeseImg.onload = checkImagesLoaded;

// 鼠标位置存储
let mouse = { x: canvas.width/2, y: canvas.height/2 };

// 小球属性
let ball = {
  x: canvas.width/2,
  y: canvas.height/2,
  radius: 15,
  speed: 0.1 // 缓动系数,越小移动越平滑缓慢
};

// 当前显示的奶酪图像
let currentCheese = cheeseImg;
// 奶酪的固定位置(可自行调整)
const cheesePos = { x: 600, y: 400 };

// 监听鼠标移动,更新鼠标坐标
canvas.addEventListener('mousemove', (e) => {
  // 转换为Canvas内部相对坐标
  const rect = canvas.getBoundingClientRect();
  mouse.x = e.clientX - rect.left;
  mouse.y = e.clientY - rect.top;
});

// 检测小球是否到达奶酪区域
function isBallReachedCheese() {
  // 计算小球中心到奶酪中心的直线距离
  const dx = ball.x - (cheesePos.x + cheeseImg.width/2);
  const dy = ball.y - (cheesePos.y + cheeseImg.height/2);
  const distance = Math.sqrt(dx*dx + dy*dy);
  // 阈值:小球半径 + 奶酪半宽(可根据实际图像调整)
  return distance < ball.radius + cheeseImg.width/2;
}

// 动画循环函数
function animate() {
  if (!imagesLoaded) return;

  // 1. 清空整个画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 2. 绘制鼠标图像(这里设为固定左上角,想跟随鼠标的话看下面注释)
  ctx.drawImage(mouseImg, 50, 50);
  // 如果想让鼠标图跟随真实鼠标:
  // ctx.drawImage(mouseImg, mouse.x - mouseImg.width/2, mouse.y - mouseImg.height/2);

  // 3. 绘制当前显示的奶酪图像
  ctx.drawImage(currentCheese, cheesePos.x, cheesePos.y);

  // 4. 更新小球位置:缓动跟随鼠标
  ball.x += (mouse.x - ball.x) * ball.speed;
  ball.y += (mouse.y - ball.y) * ball.speed;

  // 5. 绘制小球(这里用圆形,换成图像的话看下面注释)
  ctx.fillStyle = '#ff6b6b';
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
  ctx.fill();
  ctx.strokeStyle = '#333';
  ctx.stroke();
  // 如果换成小球图像:
  // ctx.drawImage(ballImg, ball.x - ball.radius, ball.y - ball.radius, ball.radius*2, ball.radius*2);

  // 6. 检测是否到达奶酪,切换为新奶酪图像
  if (currentCheese === cheeseImg && isBallReachedCheese()) {
    currentCheese = newCheeseImg;
    console.log('奶酪已更新!');
  }

  // 循环调用动画
  requestAnimationFrame(animate);
}
</script>

关键细节说明

  • 图像加载处理:必须等所有图像加载完成再启动动画,否则会出现图像不显示的问题,这里用计数法实现加载完成检测
  • 缓动跟随逻辑:ball.x += (mouse.x - ball.x) * ball.speed这个公式让小球每次移动当前与鼠标距离的10%(speed=0.1),移动更自然流畅
  • 碰撞检测:用勾股定理计算距离,小于设定阈值就判定小球到达奶酪,自动切换图像
  • 自由移动支持:小球完全跟随鼠标路径,没有任何固定路线限制,你可以随意拖动到任何位置

自定义调整点

  1. 替换小球为自定义图像:创建ballImg并加载,把绘制圆形的代码换成drawImage
  2. 调整鼠标图像位置:注释里有跟随真实鼠标的代码,直接替换即可
  3. 修改奶酪初始位置:调整cheesePos的x、y值
  4. 改变小球跟随速度:修改ball.speed,数值越大移动越快

这样修改后,你的鼠标、奶酪图像会正常显示,小球平滑跟随鼠标移动,到达奶酪后自动切换新图像,而且小球可以自由移动到任意位置~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:13:35