Canvas 的基本使用及动画效果

基本使用

在 HTML 文件中创建一个 <canvas> 标签

html 复制代码
<canvas id="myCanvas" width="500" height="400"></canvas>

获取<canvas>元素,然后调用getContext('2d')获得绘图的2D上下文对象:

js 复制代码
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

示例:绘制一个红色矩形:

js 复制代码
// 设置填充颜色
ctx.fillStyle = 'red';
// 绘制矩形(x, y, 宽, 高)<- 左上角的点(x, y)
ctx.fillRect(50, 50, 200, 100);

示例:绘制线条:

js 复制代码
ctx.strokeStyle = 'blue';
ctx.lineWidth = 5;
ctx.beginPath();// 开始路径
ctx.moveTo(50, 50);// 起点
ctx.lineTo(250, 150);// 终点
ctx.stroke();// 绘制线条/多边形边框要用stroke

示例:绘制圆形(利用arc):

js 复制代码
ctx.beginPath();
ctx.arc(150, 150, 50, 0, Math.PI * 2);
ctx.fillStyle = 'green';
ctx.fill();

实例:绘制三角形

js 复制代码
<canvas id="myCanvas" width="500" height="400"></canvas>
const canvas = document.getElementById('myConvas')
const ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(30, 30)  // 第一个点
ctx.lineTo(40, 40)  // 第二个点
ctx.lineTo(20, 40)  // 第三个点
ctx.closePath()     // 封闭路径
ctx.fillStyle = 'red'
ctx.fill()

状态保存与恢复

  • ctx.save(): 将当前的绘图状态压入栈中。
  • ctx.restore(): 从栈中弹出状态,恢复到上一个保存的状态。
js 复制代码
ctx.save(); // 保存状态 A 
ctx.fillStyle = 'blue'; 
ctx.fillRect(100, 500, 50, 50); 
ctx.save(); // 保存状态 B (此时包含蓝色填充) ctx.fillStyle = 'yellow'; 
ctx.fillRect(150, 500, 50, 50); 
ctx.restore(); // 恢复到状态 B (此时颜色又变回蓝色,因为之前保存的是蓝色填充) 
ctx.restore(); // 恢复到状态 A (此时颜色是默认的黑色,或者之前设置的其他值)

几何变化

平移(Translate)

js 复制代码
// 绘制一个未进行平移的红色矩形 
context.fillStyle = 'red'; 
context.fillRect(10, 10, 50, 50); 
// 保存当前状态 
context.save(); 
// 将原点向下移动 100px,向右移动 100px 
context.translate(100, 100); 
// 这个矩形现在将相对于新的原点 (100, 100) 绘制
context.fillStyle = 'blue'; 
context.fillRect(10, 10, 50, 50); 
// 实际会绘制在 (110, 110) 的位置 
// 恢复状态 
context.restore();

旋转(Rotate)

js 复制代码
context.rotate(Math.PI / 4);
  • 正值表示顺时针旋转。
  • 负值表示逆时针旋转。

缩放(Scale)

js 复制代码
context.scale(x, y)
  • x: 水平方向上的缩放因子。
  • y: 垂直方向上的缩放因子。

requestAnimationFrame

使用 setTimeoutsetInterval 来实现 Canvas 动画 和 requestAnimationFrame的对比:

使用定时器动画干活,实际上是可以的,但是存在一个最大的问题,就是动画会抖动,体验效果不是非常好。

而使用requestAnimationFrame去做动画,就不会出现抖动的现象。

requestAnimationFramejs中的setTimeout定时器函数基本一致,不过setTimeout可以自由设置间隔时间,而requestAnimationFrame的间隔时间是由浏览器自身决定的,大约是17毫秒左右

那为什么定时器会卡顿呢?

1. 为什么定时器会卡

  • 我们在手机或者电脑显示屏上看东西时,显示屏会默默的不停地干活(刷新画面)
  • 这个刷新值得是每秒钟刷新次数,普通显示器的刷新率约为60Hz(每秒刷新60次),高档的有75Hz、90Hz、120Hz、144Hz等等
  • 刷新率次数越高,显示器显示的图像越清晰、越流畅、越丝滑
  • 不刷新就是静态的画面,刷新比较低就是卡了PPT的感觉
  • 动画想要丝滑流畅,需要卡住时间点进行代码操作(代码语句赋值、浏览器重绘)
  • 所以只需要每隔1000毫秒的60分之一(60HZ)即约为17毫秒,进行一次动画操作即可
  • 只要卡住这个17毫秒,每隔17毫秒进行操作,就能确保动画丝滑
  • 但是定时器的回调函数,会受到js的事件队列宏任务、微任务影响,可能设定的是17毫秒执行一次,但是实际上这次是17毫秒、下次21毫秒、再下次13毫秒执行,所以并不是严格的卡住了这个60HZ的时间
  • 没有在合适的时间点操作,就会出现:类似这样的情况:不变不变不变...
  • 于是就出现了,绘制不及时的情况,就会有抖动的出现(以上述案例,位置和时间没有线性对应更新变化导致看起来抖动)

2. 为何requestAnimationFrame不会卡

setTimeoutsetInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。

window.requestAnimationFrame(callback)能够做到,精准严格的卡住显示器刷新的时间,比如普通显示器60HZ它会自动对应17ms执行一次,高级显示器120HZ,它会自动对应9ms执行一次。callback 函数的执行频率会与屏幕刷新同步。

requestAnimationFrame只会执行一次,想要使其多次执行,要写成递归的形式。

所以,这就是requestAnimationFrame的好处,window.requestAnimationFrame这个api就是解决了定时器不精准的问题的。

示例:用 requestAnimationFrame() 实现的小球左右移动动画

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>requestAnimationFrame动画示例</title>
</head>
<body>
<canvas id="myCanvas" width="500" height="200" style="border:1px solid #000;"></canvas>

<script>
  const canvas = document.getElementById('myCanvas');
  const ctx = canvas.getContext('2d');

  let x = 50;          // 小球横坐标
  const y = 100;       // 小球纵坐标
  const radius = 20;   // 小球半径
  let speed = 2;       // 每帧移动距离

  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布

    // 绘制小球
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI * 2);
    ctx.fillStyle = 'blue';
    ctx.fill();

    // 更新位置
    x += speed;

    // 反弹边界检测
    if (x + radius > canvas.width || x - radius < 0) {
      speed = -speed; // 改变方向
    }

    // 下一帧动画请求
    requestAnimationFrame(draw);
  }

  // 启动动画
  requestAnimationFrame(draw);
</script>
</body>
</html>

参考链接:juejin.cn/post/720278...

相关推荐
mldong7 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60618 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角9 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!9 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi11 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen11 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马12 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu12 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss