Skip to content

canvas 绘图操作

Canvas 2D 上下文提供了丰富的绘图方法,可以绘制矩形、线条、弧线、文字,以及更复杂的路径和形状。本节将通过实际示例介绍 canvas 的基础绘图操作。

前置知识

阅读本节前,建议先了解:canvas 绑定与上下文

基础概念

Canvas 2D 绘图基于状态机模型:通过设置填充颜色、描边颜色、线宽等状态,然后调用绘图方法在画布上绘制内容。所有绘图操作都通过 CanvasRenderingContext2D 对象的方法完成。

基本绘图类型包括:矩形(最简单)、路径(线条、弧线、曲线)、文字图像像素操作

语法

绘制矩形

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>绘制矩形</title>
</head>
<body>
  <h1>Canvas 矩形绘制</h1>
  <canvas id="rectCanvas" width="500" height="300"
          style="border: 1px solid #ccc;"></canvas>

  <script>
    var ctx = document.getElementById('rectCanvas').getContext('2d');

    // fillRect(x, y, width, height):填充矩形
    ctx.fillStyle = '#4285f4';
    ctx.fillRect(20, 20, 150, 100);

    // strokeRect(x, y, width, height):描边矩形
    ctx.strokeStyle = '#e74c3c';
    ctx.lineWidth = 3;
    ctx.strokeRect(200, 20, 150, 100);

    // clearRect(x, y, width, height):清除区域
    ctx.fillStyle = '#2ecc71';
    ctx.fillRect(380, 20, 100, 100);
    ctx.clearRect(400, 40, 60, 60); // 清除中间部分

    // 半透明矩形
    ctx.fillStyle = 'rgba(255, 165, 0, 0.6)';
    ctx.fillRect(50, 150, 120, 80);

    ctx.fillStyle = 'rgba(0, 128, 255, 0.6)';
    ctx.fillRect(100, 180, 120, 80);

    // 圆角矩形(使用 roundRect)
    ctx.beginPath();
    ctx.roundRect(280, 150, 150, 80, [15]);
    ctx.fillStyle = '#9b59b6';
    ctx.fill();
  </script>
</body>
</html>

详细说明

绘制线条和路径

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>线条与路径</title>
</head>
<body>
  <h1>线条与路径</h1>
  <canvas id="lineCanvas" width="500" height="300"
          style="border: 1px solid #ccc;"></canvas>

  <script>
    var ctx = document.getElementById('lineCanvas').getContext('2d');

    // 直线
    ctx.beginPath();
    ctx.moveTo(20, 20);    // 起点
    ctx.lineTo(200, 100);  // 终点
    ctx.strokeStyle = '#4285f4';
    ctx.lineWidth = 3;
    ctx.stroke();

    // 折线
    ctx.beginPath();
    ctx.moveTo(20, 150);
    ctx.lineTo(100, 50);
    ctx.lineTo(180, 150);
    ctx.lineTo(260, 50);
    ctx.strokeStyle = '#e74c3c';
    ctx.lineWidth = 2;
    ctx.stroke();

    // 三角形(闭合路径)
    ctx.beginPath();
    ctx.moveTo(300, 200);
    ctx.lineTo(400, 50);
    ctx.lineTo(500, 200);
    ctx.closePath();  // 自动闭合路径
    ctx.fillStyle = '#2ecc71';
    ctx.fill();
    ctx.strokeStyle = '#27ae60';
    ctx.lineWidth = 2;
    ctx.stroke();

    // 虚线
    ctx.beginPath();
    ctx.setLineDash([10, 5]); // 10px 实线,5px 间隔
    ctx.moveTo(20, 250);
    ctx.lineTo(480, 250);
    ctx.strokeStyle = '#666';
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.setLineDash([]); // 重置虚线
  </script>
</body>
</html>

绘制弧线和圆形

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>弧线与圆形</title>
</head>
<body>
  <h1>弧线与圆形</h1>
  <canvas id="arcCanvas" width="500" height="300"
          style="border: 1px solid #ccc;"></canvas>

  <script>
    var ctx = document.getElementById('arcCanvas').getContext('2d');

    // 完整圆形:arc(x, y, radius, startAngle, endAngle)
    ctx.beginPath();
    ctx.arc(80, 80, 50, 0, Math.PI * 2);
    ctx.fillStyle = '#4285f4';
    ctx.fill();

    // 描边圆形
    ctx.beginPath();
    ctx.arc(220, 80, 50, 0, Math.PI * 2);
    ctx.strokeStyle = '#e74c3c';
    ctx.lineWidth = 3;
    ctx.stroke();

    // 半圆弧
    ctx.beginPath();
    ctx.arc(360, 80, 50, 0, Math.PI);
    ctx.strokeStyle = '#2ecc71';
    ctx.lineWidth = 3;
    ctx.stroke();

    // 扇形
    ctx.beginPath();
    ctx.moveTo(150, 200); // 圆心
    ctx.arc(150, 200, 60, -Math.PI / 4, Math.PI / 2);
    ctx.closePath();
    ctx.fillStyle = '#f39c12';
    ctx.fill();

    // 椭圆 ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle)
    ctx.beginPath();
    ctx.ellipse(350, 200, 80, 40, 0, 0, Math.PI * 2);
    ctx.fillStyle = '#9b59b6';
    ctx.fill();
  </script>
</body>
</html>

绘制文字

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>绘制文字</title>
</head>
<body>
  <h1>Canvas 文字</h1>
  <canvas id="textCanvas" width="500" height="300"
          style="border: 1px solid #ccc;"></canvas>

  <script>
    var ctx = document.getElementById('textCanvas').getContext('2d');

    // fillText(text, x, y):填充文字
    ctx.fillStyle = '#333';
    ctx.font = '32px sans-serif';
    ctx.fillText('Hello Canvas!', 20, 50);

    // strokeText(text, x, y):描边文字
    ctx.strokeStyle = '#4285f4';
    ctx.lineWidth = 2;
    ctx.font = 'bold 40px serif';
    ctx.strokeText('描边文字', 20, 110);

    // 文字对齐
    ctx.fillStyle = '#e74c3c';
    ctx.font = '18px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('居中对齐', 250, 170);

    ctx.textAlign = 'right';
    ctx.fillText('右对齐', 480, 200);

    ctx.textAlign = 'left';
    ctx.fillText('左对齐(默认)', 20, 230);

    // 文字基线
    ctx.fillStyle = '#2ecc71';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('垂直居中', 250, 270);
  </script>
</body>
</html>

颜色与样式

javascript
var ctx = canvas.getContext('2d');

// 填充颜色
ctx.fillStyle = '#ff0000';         // 十六进制
ctx.fillStyle = 'rgb(255, 0, 0)';   // RGB
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // RGBA(半透明)
ctx.fillStyle = 'red';              // 颜色名称

// 描边颜色
ctx.strokeStyle = '#0000ff';

// 线宽
ctx.lineWidth = 3;

// 线条端点
ctx.lineCap = 'round';  // round, butt, square

// 线条连接
ctx.lineJoin = 'round'; // round, bevel, miter

// 渐变填充
var gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(1, '#0000ff');
ctx.fillStyle = gradient;

实战示例

绘制简单图表

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Canvas 柱状图</title>
  <style>
    canvas { border-radius: 8px; }
  </style>
</head>
<body>
  <h1>Canvas 柱状图</h1>
  <canvas id="chartCanvas" width="500" height="350"
          style="border: 1px solid #ddd;"></canvas>

  <script>
    var canvas = document.getElementById('chartCanvas');
    var ctx = canvas.getContext('2d');
    var data = [
      { label: '一月', value: 120, color: '#4285f4' },
      { label: '二月', value: 200, color: '#34a853' },
      { label: '三月', value: 150, color: '#fbbc05' },
      { label: '四月', value: 280, color: '#ea4335' },
      { label: '五月', value: 220, color: '#673ab7' },
    ];
    var maxVal = 300;
    var barWidth = 50;
    var startX = 80;
    var startY = 280;
    var chartHeight = 230;

    // 背景
    ctx.fillStyle = '#fafafa';
    ctx.fillRect(0, 0, 500, 350);

    // 网格线
    ctx.strokeStyle = '#eee';
    ctx.lineWidth = 1;
    for (var i = 0; i <= 3; i++) {
      var y = startY - (chartHeight / 3) * i;
      ctx.beginPath();
      ctx.moveTo(startX, y);
      ctx.lineTo(460, y);
      ctx.stroke();
      ctx.fillStyle = '#999';
      ctx.font = '12px sans-serif';
      ctx.textAlign = 'right';
      ctx.fillText(Math.round(maxVal / 3 * i), startX - 10, y + 4);
    }

    // 柱状
    data.forEach(function(item, index) {
      var x = startX + index * 75;
      var height = (item.value / maxVal) * chartHeight;
      var y = startY - height;

      // 柱子
      ctx.fillStyle = item.color;
      ctx.fillRect(x, y, barWidth, height);

      // 数值标签
      ctx.fillStyle = '#333';
      ctx.font = 'bold 14px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText(item.value, x + barWidth / 2, y - 8);

      // X 轴标签
      ctx.fillStyle = '#666';
      ctx.font = '13px sans-serif';
      ctx.fillText(item.label, x + barWidth / 2, startY + 25);
    });
  </script>
</body>
</html>

注意事项

  • canvas 绘图是立即模式(Immediate Mode),绘制后无法修改(只能重新绘制)
  • 路径操作必须以 beginPath() 开始
  • closePath() 会自动连接起点和终点
  • 文字绘制不支持自动换行
  • 颜色值使用 CSS 标准格式

最佳实践

  • 使用 save()/restore() 保存和恢复绘图状态
  • 复杂场景使用离屏 canvas 进行缓存
  • 对于动画,使用 requestAnimationFrame
  • 封装常用绘图操作为可复用函数
  • 避免在循环中创建渐变对象

下一节

继续学习:SVG 内联矢量图

参考链接