如何在HTML Canvas中计算动画方向 实现矩形沿直线路径移动到点击位置
问题根源
- 你当前设置的
deltaX和deltaY固定为±1,x和y方向每帧移动步长完全相同,当水平位移差和垂直位移差不一致时,会先走完较短的边,再单独走较长的边,最终形成折线轨迹,无法沿直线移动 - 原有动画逻辑还存在两个附加问题:没有每帧清空画布会产生拖影,
cancelAnimationFrame未传入动画id无法正确终止动画
修正思路
- 每次触发点击时,先计算当前矩形位置到目标点的水平差
dx、垂直差dy,以及两点的直线距离 - 按位移比例分配x、y方向的每帧步长:
deltaX = 速度 * dx / 总距离,deltaY = 速度 * dy / 总距离,保证两个方向同时到达目标点 - 每帧绘制前清空画布,同时增加精度判断,避免浮点数误差导致无法完全匹配目标坐标的问题
- 正确管理动画帧id,到达目标后终止动画
修正后的完整代码
HTML部分
<canvas id="canvas"></canvas>
JS部分
'use strict' const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') const ratio = Math.ceil(window.devicePixelRatio) let height = window.innerHeight let width = window.innerWidth // 动画帧id,用于终止动画 let animationId = null // 移动速度,可自行调整数值控制快慢 const MOVE_SPEED = 3 canvas.height = height * ratio canvas.width = width * ratio canvas.style.height = `${height}px` canvas.style.width = `${width}px` ctx.setTransform(ratio, 0, 0, ratio, 0, 0) let position = { x: 0, y: 0, deltaX: 0, deltaY: 0, size: 20 } let move = { x: 0, y: 0, } function animate() { // 计算当前位置到目标的剩余距离 const dx = move.x - position.x const dy = move.y - position.y const distance = Math.sqrt(dx * dx + dy * dy) // 剩余距离小于步长时直接定位到目标点,终止动画 if (distance < MOVE_SPEED) { position.x = move.x position.y = move.y cancelAnimationFrame(animationId) animationId = null // 绘制最终位置的矩形 ctx.clearRect(0, 0, width, height) ctx.fillRect(position.x, position.y, position.size, position.size) return } // 清空画布,绘制当前帧矩形 ctx.clearRect(0, 0, width, height) position.x += position.deltaX position.y += position.deltaY ctx.fillRect(position.x, position.y, position.size, position.size) animationId = requestAnimationFrame(animate) } function moveTo(x, y) { // 如果有正在运行的动画,先终止 if (animationId) { cancelAnimationFrame(animationId) } move.x = x move.y = y const dx = move.x - position.x const dy = move.y - position.y const distance = Math.sqrt(dx * dx + dy * dy) // 按比例分配步长 position.deltaX = MOVE_SPEED * dx / distance position.deltaY = MOVE_SPEED * dy / distance animate() } canvas.addEventListener('click', (event) => { moveTo(event.clientX, event.clientY) }) // 初始绘制矩形 ctx.fillRect(position.x, position.y, position.size, position.size)
内容的提问来源于stack exchange,提问作者Mr. lindroid
相关产品推荐
相关产品推荐

