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 适配要点
- 获取
devicePixelRatio(通常为 2 或 3) - 将 canvas 缓冲区大小设为 CSS 尺寸乘以 DPI
- 使用
ctx.scale(dpr, dpr)缩放上下文 - 之后按 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 绘图操作