Skip to content

canvas 绑定与上下文

在 canvas 上绘图之前,需要先获取绘图上下文(Drawing Context)。本节将介绍如何获取 2D 上下文、理解 canvas 的坐标系统,以及处理高 DPI 屏幕的适配问题。

前置知识

阅读本节前,建议先了解:canvas 画布基础

基础概念

Canvas 的绘图操作通过上下文对象(Context) 来执行。最常用的是 2D 上下文(CanvasRenderingContext2D),通过 canvas.getContext('2d') 获取。上下文对象提供了所有绘图方法:绘制矩形、路径、文字、图像等。

理解 canvas 的坐标系统也很重要——canvas 使用左上角为原点 (0,0) 的直角坐标系,x 轴向右延伸,y 轴向下延伸。

语法

获取 2D 上下文

javascript
// 获取 canvas 元素
var canvas = document.getElementById('myCanvas');

// 获取 2D 绘图上下文
var ctx = canvas.getContext('2d');

if (ctx) {
  // 成功获取上下文,可以开始绘图
  ctx.fillStyle = '#4285f4';
  ctx.fillRect(10, 10, 200, 100);
}
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>canvas 上下文</title>
</head>
<body>
  <h1>获取 canvas 上下文</h1>

  <canvas id="myCanvas" width="400" height="300"
          style="border: 1px solid #ccc;"></canvas>

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

    if (!ctx) {
      console.log('浏览器不支持 2D canvas');
    } else {
      // 坐标系说明
      ctx.fillStyle = '#eee';
      ctx.fillRect(0, 0, 400, 300);

      // 原点 (0,0) 在左上角
      ctx.fillStyle = '#e74c3c';
      ctx.fillRect(0, 0, 10, 10); // 左上角红色小方块

      // x 轴向右
      ctx.fillStyle = '#3498db';
      ctx.fillRect(100, 0, 10, 10);

      // y 轴向下
      ctx.fillStyle = '#2ecc71';
      ctx.fillRect(0, 100, 10, 10);

      // 坐标标签
      ctx.fillStyle = '#333';
      ctx.font = '14px sans-serif';
      ctx.fillText('(0, 0) 原点', 20, 20);
      ctx.fillText('x 轴 →', 200, 25);
      ctx.save();
      ctx.translate(25, 150);
      ctx.rotate(-Math.PI / 2);
      ctx.fillText('y 轴 →', 0, 0);
      ctx.restore();
    }
  </script>
</body>
</html>

详细说明

Canvas 坐标系统

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>canvas 坐标系统</title>
</head>
<body>
  <h1>Canvas 坐标系统</h1>

  <canvas id="coordCanvas" width="400" height="400"
          style="border: 1px solid #ccc;"></canvas>

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

    // 绘制网格
    ctx.strokeStyle = '#ddd';
    ctx.lineWidth = 0.5;
    for (var x = 0; x <= 400; x += 50) {
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, 400);
      ctx.stroke();
    }
    for (var y = 0; y <= 400; y += 50) {
      ctx.beginPath();
      ctx.moveTo(0, y);
      ctx.lineTo(400, y);
      ctx.stroke();
    }

    // 标注坐标
    ctx.fillStyle = '#666';
    ctx.font = '11px sans-serif';
    for (var x = 50; x < 400; x += 50) {
      ctx.fillText(x, x - 8, 12);
    }
    for (var y = 50; y < 400; y += 50) {
      ctx.fillText(y, 2, y + 4);
    }

    // 原点标记
    ctx.fillStyle = '#e74c3c';
    ctx.beginPath();
    ctx.arc(0, 0, 5, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#e74c3c';
    ctx.fillText('原点 (0,0)', 10, 20);
  </script>
</body>
</html>

高 DPI 屏幕适配

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>高 DPI 适配</title>
</head>
<body>
  <h1>高 DPI 屏幕 canvas 适配</h1>

  <canvas id="hdpiCanvas" width="400" height="300"
          style="border: 1px solid #ccc; width: 400px; height: 300px;">
  </canvas>

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

    // 获取设备像素比
    var dpr = window.devicePixelRatio || 1;

    // 设置 canvas 缓冲区大小(乘以 DPI)
    var displayWidth = 400;
    var displayHeight = 300;
    canvas.width = displayWidth * dpr;
    canvas.height = displayHeight * dpr;

    // 缩放上下文以匹配 DPI
    ctx.scale(dpr, dpr);

    // 之后按 CSS 像素坐标绘图即可
    ctx.fillStyle = '#4285f4';
    ctx.fillRect(50, 50, 100, 80);

    ctx.fillStyle = '#333';
    ctx.font = '16px sans-serif';
    ctx.fillText('高 DPI 适配后的清晰文字', 50, 170);
  </script>
</body>
</html>

DPI 适配要点

  1. 获取 devicePixelRatio(通常为 2 或 3)
  2. 将 canvas 缓冲区大小设为 CSS 尺寸乘以 DPI
  3. 使用 ctx.scale(dpr, dpr) 缩放上下文
  4. 之后按 CSS 像素坐标绘图,无需手动换算

getContext 方法

javascript
var canvas = document.getElementById('myCanvas');

// 2D 上下文
var ctx2d = canvas.getContext('2d');

// WebGL 上下文
var ctxGL = canvas.getContext('webgl');

// WebGL 2 上下文
var ctxGL2 = canvas.getContext('webgl2');

// BitmapRenderer(简单图像处理)
var ctxBitmap = canvas.getContext('bitmaprenderer');

// 上下文类型对比
console.log('2D:', ctx2d);        // CanvasRenderingContext2D
console.log('WebGL:', ctxGL);     // WebGLRenderingContext
console.log('WebGL2:', ctxGL2);   // WebGL2RenderingContext
上下文类型字符串参数用途
2D'2d'2D 图形绘制(最常用)
WebGL'webgl'3D 图形(OpenGL ES)
WebGL 2'webgl2'WebGL 下一代
BitmapRenderer'bitmaprenderer'简单图像渲染

实战示例

高 DPI 适配的完整示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>高 DPI canvas</title>
  <style>
    canvas {
      border: 2px solid #ddd;
      border-radius: 8px;
    }
  </style>
</head>
<body>
  <h2>左侧:未适配(模糊) | 右侧:已适配(清晰)</h2>

  <canvas id="normalCanvas" width="300" height="200"
          style="width: 300px; height: 200px;"></canvas>

  <canvas id="hdpiCanvas" width="300" height="200"
          style="width: 300px; height: 200px;"></canvas>

  <script>
    // 普通方式(在高 DPI 上会模糊)
    var normalCanvas = document.getElementById('normalCanvas');
    var normalCtx = normalCanvas.getContext('2d');
    normalCtx.fillStyle = '#f0f0f0';
    normalCtx.fillRect(0, 0, 300, 200);
    normalCtx.fillStyle = '#333';
    normalCtx.font = '14px sans-serif';
    normalCtx.fillText('普通 canvas(模糊)', 80, 100);

    // 高 DPI 适配方式
    var hdpiCanvas = document.getElementById('hdpiCanvas');
    var hdpiCtx = hdpiCanvas.getContext('2d');
    var dpr = window.devicePixelRatio || 1;
    hdpiCanvas.width = 300 * dpr;
    hdpiCanvas.height = 200 * dpr;
    hdpiCtx.scale(dpr, dpr);
    hdpiCtx.fillStyle = '#f0f0f0';
    hdpiCtx.fillRect(0, 0, 300, 200);
    hdpiCtx.fillStyle = '#333';
    hdpiCtx.font = '14px sans-serif';
    hdpiCtx.fillText('高 DPI 适配 canvas(清晰)', 60, 100);
  </script>
</body>
</html>

注意事项

  • getContext('2d') 在同一 canvas 上只能调用一次
  • 传入不支持的上下文类型会返回 null
  • canvas 坐标系的原点在左上角,y 轴向下(与数学坐标系相反)
  • 高 DPI 适配需要在 JavaScript 中动态设置 canvas 尺寸
  • 不要在 CSS 中使用 transform: scale() 代替 DPI 适配

最佳实践

  • 获取上下文后检查是否为 null
  • 处理高 DPI 屏幕以提高绘制质量
  • 封装 DPI 适配为可复用的工具函数
  • 使用 save()/restore() 管理绘图状态
  • 在动画循环中避免频繁创建新对象

下一节

继续学习:canvas 绘图操作

参考链接