服务端阅读 05月28日 02:57
如何获取 Canvas 的 2D 上下文并使用基本绘制方法?
获取 Canvas 2D 上下文通过 canvas.getContext('2d') 获取 2D 渲染上下文,返回 CanvasRenderingContext2D 对象:const canvas = document.getElementById('myCanvas');const ctx = canvas.getContext('2d');实际开发中建议做兼容性检查:const canvas = document.querySelector('canvas');if (!canvas?.getContext) { throw new Error('当前浏览器不支持 Canvas');}const ctx = canvas.getContext('2d');注意:同一个 Canvas 元素多次调用 getContext('2d') 返回的是同一个上下文对象,不会重复创建。基本绘制方法分类Canvas 2D 的绘制方法可以按用途分为以下几类:矩形绘制矩形是 Canvas 中唯一可以直接绘制的图形,不需要路径:| 方法 | 说明 ||------|------|| fillRect(x, y, w, h) | 绘制填充矩形 || strokeRect(x, y, w, h) | 绘制矩形边框 || clearRect(x, y, w, h) | 清除矩形区域(变为透明) |路径绘制所有非矩形图形都需要通过路径来绘制:ctx.beginPath(); // 开始新路径ctx.moveTo(50, 50); // 移动画笔到起点ctx.lineTo(200, 50); // 画直线到 (200, 50)ctx.arc(150, 100, 40, 0, Math.PI * 2); // 画圆弧ctx.closePath(); // 闭合路径ctx.fill(); // 填充ctx.stroke(); // 描边核心路径方法:beginPath() — 开始新路径(不会清除已有路径)moveTo(x, y) / lineTo(x, y) — 移动/画直线arc(x, y, r, startAngle, endAngle) — 画圆弧或圆closePath() — 从当前点回到路径起点fill() / stroke() — 填充或描边当前路径样式设置绘制前设置样式,影响后续所有绘制操作:ctx.fillStyle = '#ff6600'; // 填充颜色ctx.strokeStyle = 'rgba(0,0,255,0.8)'; // 描边颜色ctx.lineWidth = 3; // 线宽ctx.lineCap = 'round'; // 线帽样式:butt | round | squarectx.lineJoin = 'miter'; // 连接样式:miter | round | bevel文本绘制ctx.font = '24px sans-serif';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText('Hello Canvas', 100, 100); // 填充文本ctx.strokeText('Hello Canvas', 100, 100); // 描边文本配合 measureText() 可以精确计算文本宽度:const metrics = ctx.measureText('Hello');console.log(metrics.width); // 文本像素宽度图像绘制drawImage() 支持三种调用方式:// 基础:原尺寸绘制ctx.drawImage(img, dx, dy);// 缩放:指定目标尺寸ctx.drawImage(img, dx, dy, dWidth, dHeight);// 裁剪:从源图裁剪区域绘制到目标区域ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);状态保存与恢复这是面试高频考点。Canvas 通过栈结构管理绘制状态:ctx.save(); // 将当前状态(样式、变换等)压入栈// ... 修改样式、变换 ...ctx.restore(); // 从栈中弹出并恢复最近一次 save 的状态典型场景:绘制多个不同样式的图形时,用 save/restore 避免样式互相污染。一个完整示例把上面的方法组合起来,绘制一个带标题的彩色柱状图:const canvas = document.getElementById('chart');const ctx = canvas.getContext('2d');const data = [ { label: 'A', value: 120, color: '#ff6600' }, { label: 'B', value: 80, color: '#0066ff' }, { label: 'C', value: 150, color: '#00cc66' },];const barWidth = 60;const gap = 30;const baseY = 250;data.forEach((item, i) => { const x = 40 + i * (barWidth + gap); const height = item.value; // 绘制柱子 ctx.fillStyle = item.color; ctx.fillRect(x, baseY - height, barWidth, height); // 绘制标签 ctx.save(); ctx.fillStyle = '#333'; ctx.font = '14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(item.label, x + barWidth / 2, baseY + 20); ctx.fillText(String(item.value), x + barWidth / 2, baseY - height - 8); ctx.restore();});常见追问Q:Canvas 和 SVG 的区别是什么?Canvas 是像素级绘制,适合高频重绘场景(游戏、图表动画);SVG 是矢量图形,通过 DOM 操作,适合交互式静态图形。Canvas 绘制后无法单独操作某个图形元素,SVG 可以。Q:如何实现 Canvas 动画?核心思路是清空画布 + 重绘。用 clearRect 清除上一帧,再绘制新帧,配合 requestAnimationFrame 控制帧率。需要避免在每帧中创建对象,减少 GC 压力。Q:Canvas 绘制模糊怎么解决?这是高 DPI 屏幕的常见问题。需要将 Canvas 的实际像素尺寸设为 CSS 尺寸的 devicePixelRatio 倍,再用 ctx.scale(dpr, dpr) 缩放上下文。