Canvas 何尝不是亮点呢?(一)

一、Canvas 是什么?

Canvas 是高性能渲染引擎。

  • <canvas> 是一个位图渲染容器
  • 所有内容都通过 JS API 绘制
  • 绘制完成后,浏览器只认识「像素」,不再关心你画了什么
  • canvans 支持绘制的图形,总结起来有四种:
    • 直线(矩形)
    • 曲线(圆形)
    • 文字
    • 图片

这与 DOM(结构化、可访问)完全不同。

Canvas 和 DOM / SVG 的区别:

使用场景:

  • 动画动效
  • 编辑器(时间轴 / 画布)
  • 数据 / 大屏可视化
  • 大数据量下的表格(性能优化)
  • 游戏
  • 音视频波形、字幕

二、Canvas 基础

Canvas 是 命令式 + 无状态渲染

  • 每一帧都要重新画完整画面
  • Canvas 不会保存任何结构
  1. 简单示例

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <script>
      // 获取 canvas 元素​
      const canvas = document.querySelector("#canvas");
      // 获取 canvas 上下文
      const ctx = canvas.getContext("2d");

      // 初始化
      function init() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
      }
      init();

      // ==设置初始值==
      // 设置路径
      ctx.beginPath();
      // 起始路径点:从画布左上角开始移动到某个位置
      ctx.moveTo(100, 200);
      // 画线中途连接点
      ctx.lineTo(200, 300);
      // 画线下一个点
      // ctx.lineTo(300, 350);
      // 最后闭合,终点连接起点(可选)
      // ctx.closePath();

      // ==绘制行为==
      // 示例一:画线
      ctx.strokeStyle = "red";
      ctx.stroke();
      // 示例二:画圆(小的填充圆就是点)
      ctx.beginPath();
      ctx.arc(200, 300, 15, 0, 2 * Math.PI, true); // 圆心坐标x,圆心坐标y,半径,起始角度,结束角度(默认顺指针),是否逆时针
      ctx.fillStyle = "green";
      ctx.fill();
      // 填充
      // ctx.fillStyle = "blue";
      // ctx.fill();
      // 实例三:文字
      // ctx.font = "32px sans-serif";
      // ctx.fillText("123", 300, 400);
    </script>
  </body>
</html>
  1. 坐标系统

  • 左上角 (0,0)
  • 向右:x 正方向
  • 向下:y 正方向
  1. 常见 API

这里如果想要练手 canvas,我推荐两个项目:第一个是 vue3-particles(现在可能不叫这个名字了,总之就是粒子库/星空图的类似效果,第二个是代码雨/文字雨(canvas 版本),完全可以模仿着使用 canvas 复刻一下,能复刻出来,canvas 就算真正入门了。

彩蛋(😜):如果后续有时间,我还会分享一些我之前做过的“花里胡哨”的动效。

进阶同学可以研究下 gsap 这个动画库,配合 canvas 会非常好用,就可以 GSAP 管时间,Canvas 管空间。

/********************** 获取 Canvas & 上下文 **********************/
const canvas = document.getElementById('canvas'); // 获取 canvas DOM
const ctx = canvas.getContext('2d');              // 获取 2D 渲染上下文(核心对象)

/********************** 状态管理 **********************/
ctx.save();       // 保存当前绘图状态(样式、变换、裁剪区)
ctx.restore();    // 恢复到最近一次 save 的状态
ctx.resetTransform(); // 重置 transform(现代浏览器)
ctx.globalAlpha = 1;  // 全局透明度(0~1)
ctx.globalCompositeOperation = 'source-over'; // 图层混合模式

/********************** 路径相关 **********************/
ctx.beginPath();  // 开启新路径
ctx.closePath();  // 闭合路径
ctx.moveTo(x, y);         // 移动到指定点(不画线)
ctx.lineTo(x, y);         // 从当前点画线到指定点
ctx.arc(x, y, r, start, end, ccw); // 画圆/圆弧
ctx.arcTo(x1, y1, x2, y2, r);      // 圆角连接线
ctx.rect(x, y, w, h);     // 画矩形路径
ctx.quadraticCurveTo(cpx, cpy, x, y); // 二次贝塞尔
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); // 三次贝塞尔
ctx.stroke(); // 描边
ctx.fill();   // 填充
ctx.clip();   // 将当前路径设为裁剪区域(遮罩核心)

/********************** 样式设置 **********************/
ctx.strokeStyle = '#fff';     // 描边颜色
ctx.fillStyle = '#000';       // 填充颜色
ctx.lineWidth = 2;            // 线宽
ctx.lineCap = 'round';        // 线帽(butt | round | square)
ctx.lineJoin = 'round';       // 连接处样式
ctx.miterLimit = 10;          // 尖角限制
ctx.shadowColor = 'rgba(0,0,0,0.3)'; // 阴影颜色
ctx.shadowBlur = 10;                // 阴影模糊
ctx.shadowOffsetX = 5;              // 阴影偏移
ctx.shadowOffsetY = 5;

/********************** 变换 **********************/
ctx.translate(x, y); // 平移
ctx.rotate(rad);     // 旋转(弧度)
ctx.scale(sx, sy);   // 缩放
ctx.transform(a, b, c, d, e, f);    // 矩阵变换
ctx.setTransform(a, b, c, d, e, f); // 直接设置变换矩阵

/********************** 清屏 & 擦除 **********************/
ctx.clearRect(0, 0, canvas.width, canvas.height); // 每次重新 draw 的时候通常会清空指定区域


/********************** 文本 **********************/
ctx.font = 'bold 48px sans-serif'; // 字体
ctx.textAlign = 'center';          // 对齐方式
ctx.textBaseline = 'middle';       // 基线
ctx.fillText('Hello', x, y);   // 填充文字
ctx.strokeText('Hello', x, y); // 描边文字
ctx.measureText('Hello');      // 测量文字宽度(字母动画必用)

/********************** 图片 **********************/
const img = new Image();
img.src = 'image.png';
ctx.drawImage(img, x, y);                         // 原尺寸绘制
ctx.drawImage(img, x, y, w, h);                   // 指定尺寸
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); // 裁剪绘制(精灵图)

/********************** 渐变 & 图案 **********************/
const linearGradient = ctx.createLinearGradient(x0, y0, x1, y1);
linearGradient.addColorStop(0, '#ff0');
linearGradient.addColorStop(1, '#f00');
ctx.fillStyle = linearGradient;
const radialGradient = ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
radialGradient.addColorStop(0, '#fff');
radialGradient.addColorStop(1, '#000');
const pattern = ctx.createPattern(img, 'repeat'); // 平铺图案

/********************** 像素操作(性能敏感) **********************/
const imageData = ctx.getImageData(0, 0, w, h); // 读取像素
const data = imageData.data;                   // RGBA 数组
ctx.putImageData(imageData, x, y);              // 写回像素
// data[i]     -> R
// data[i + 1] -> G
// data[i + 2] -> B
// data[i + 3] -> A

/********************** 动画循环 **********************/
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // draw...
  requestAnimationFrame(render);
  // 不能使用 setInterval,因为 1. 不跟随屏幕刷新 2. 掉帧严重 3. 能耗高
}
render();

三、其他注意事项

  1. 分层思想

  • 时间层(GSAP / Ticker)
  • 逻辑层(业务逻辑、状态计算)
  • 渲染层(Canvas draw)
  1. canvas / 图片清晰度问题

样式尺寸:el.style.width

原始尺寸:el.naturalWidth(canvas.width)

页面缩放倍率:devicePixelRatio

☝️ 保证图片(canvas)清晰可以总结为一个等式:原始尺寸 = 样式尺寸 * 缩放倍率

所以在项目中,我们一般为初始化的 canvas 设置宽高为:

const dpr = window.devicePixelRatio || 1;
canvas.width = project.width * dpr;
canvas.height = project.height * dpr;
  1. 必须使用 requestAnimationFrame

function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // draw...
  requestAnimationFrame(render);
  // 
}
render();

不能使用 setInterval,因为:

  1. 不跟随屏幕刷新
  2. 掉帧严重
  3. 能耗高
posted @ 2026-01-25 22:06  秀秀不只会前端  阅读(3)  评论(0)    收藏  举报