You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在JavaScript Canvas实现火柴人挥手动画?

实现Canvas火柴人挥手动画的完整方案

当然可以在Canvas上实现火柴人肢体动画啦!而且完全不需要依赖setTimeout——浏览器原生的requestAnimationFrame才是做Canvas动画的最佳选择,它能和浏览器的重绘节奏同步,动画更流畅,还不会像定时器那样容易出现卡顿或跳帧问题。

下面直接上一个可运行的挥手动画示例,一步步拆解实现逻辑:

1. 基础Canvas准备

先在HTML里添加Canvas元素,再在JS中获取绘图上下文:

<canvas id="stickmanCanvas" width="400" height="400"></canvas>
const canvas = document.getElementById('stickmanCanvas');
const ctx = canvas.getContext('2d');

2. 定义动画状态变量

我们需要几个变量来控制手臂的摆动节奏,实现自然的挥手效果:

// 手臂摆动的角度(弧度),初始为0(自然下垂状态)
let armAngle = 0;
// 摆动方向:1代表向上挥,-1代表摆回原位
let swingDirection = 1;
// 手臂摆动的最大角度(60度,转成弧度方便计算)
const maxSwingAngle = Math.PI / 3;

3. 绘制火柴人的核心函数

这个函数会根据当前的armAngle值,绘制出对应姿态的火柴人:

function drawStickman() {
  // 清空画布,准备绘制下一帧
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // 绘制头部
  ctx.beginPath();
  ctx.arc(200, 120, 30, 0, Math.PI * 2);
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 3;
  ctx.stroke();
  
  // 绘制身体
  ctx.beginPath();
  ctx.moveTo(200, 150);
  ctx.lineTo(200, 250);
  ctx.stroke();
  
  // 绘制腿部(固定姿态)
  ctx.beginPath();
  ctx.moveTo(200, 250);
  ctx.lineTo(170, 350);
  ctx.moveTo(200, 250);
  ctx.lineTo(230, 350);
  ctx.stroke();
  
  // 绘制静止的手臂
  ctx.beginPath();
  ctx.moveTo(200, 170);
  ctx.lineTo(140, 220);
  ctx.stroke();
  
  // 绘制挥手的手臂(根据角度动态计算位置)
  ctx.beginPath();
  const shoulderX = 200;
  const shoulderY = 170;
  const armLength = 60;
  // 用三角函数计算手臂末端的坐标
  const armEndX = shoulderX + Math.cos(armAngle) * armLength;
  const armEndY = shoulderY + Math.sin(armAngle) * armLength;
  ctx.moveTo(shoulderX, shoulderY);
  ctx.lineTo(armEndX, armEndY);
  ctx.stroke();
}

4. 启动动画循环

requestAnimationFrame驱动动画,实现连续的帧更新:

function animate() {
  // 更新手臂角度:根据摆动方向增减角度值
  armAngle += swingDirection * 0.05;
  
  // 当角度达到最大值时,反转摆动方向,实现往返挥手
  if (armAngle >= maxSwingAngle || armAngle <= 0) {
    swingDirection *= -1;
  }
  
  // 绘制当前帧的火柴人
  drawStickman();
  
  // 请求浏览器绘制下一帧,形成循环
  requestAnimationFrame(animate);
}

// 启动动画
animate();

为什么不用setTimeout

  • setTimeout的时间间隔是固定的,但浏览器的重绘周期不一定和它同步,容易出现卡顿、跳帧的情况。
  • requestAnimationFrame会自动对齐浏览器的刷新频率(通常60Hz),只在浏览器准备好重绘时执行,动画更流畅;而且当页面处于后台时会自动暂停,节省系统资源。

扩展优化思路

如果想要更复杂的肢体动作,可以尝试:

  • 给每个肢体部位(比如另一只手臂、腿部)设置独立的角度变量和动画逻辑,实现多部位联动。
  • Math.sin函数替代线性角度变化,模拟更自然的弹性摆动效果。
  • 加入状态切换逻辑,比如挥手3次后切换到抬手、走路等其他动作。

这样就能轻松实现灵活可控的火柴人肢体动画啦,比CSS Animation更适合做这种需要精细控制的动态效果~

内容的提问来源于stack exchange,提问作者I'm just a coder for fun

火山引擎 最新活动