pin_drop当前位置:知识文库 ❯ 图文
多媒体:绘制弧线与圆 - 完整教程与代码示例
一、教程简介
弧线与圆形是 Canvas 中常用的图形元素。Canvas 提供了两个绘制弧线的方法:arc 用于绘制基于圆心和半径的弧线,arcTo 用于绘制基于切线的弧线。通过这两个方法,可以绘制圆形、扇形、圆角矩形等各种弧形图形。本教程将详细讲解弧线的绘制方法和参数含义。
二、核心概念
arc 方法
arc 是最常用的弧线绘制方法,通过指定圆心、半径和角度范围来绘制弧线。
提示:Canvas 中角度以 X 轴正方向为 0 度,顺时针方向为正方向(与数学中的逆时针相反)。
arcTo 方法
arcTo 基于两条切线绘制弧线,常用于绘制圆角。它需要一个当前点、两个控制点和一个半径。
角度单位
Canvas 使用弧度制(radian),不是角度制(degree):
代码示例
// 角度转弧度
const radian = degree * Math.PI / 180;
// 常用角度
// 0 度 = 0
// 90 度 = Math.PI / 2
// 180 度 = Math.PI
// 270 度 = Math.PI * 1.5
// 360 度 = Math.PI * 2三、语法与用法
arc - 绘制弧线
代码示例
ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);arcTo - 绘制切线弧
代码示例
ctx.arcTo(x1, y1, x2, y2, radius);
arcTo 从当前点出发,经过控制点1,到控制点2方向,绘制一个与两条线段相切的圆弧。
四、代码示例
示例1:arc 方法全面演示
以下示例展示了完整圆形、半圆、扇形、逆时针弧线、顺时针与逆时针对比、角度标注以及饼图效果:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>arc 方法全面演示</title>
<style>
body { margin: 0; padding: 20px; background: #0d1117; color: #c9d1d9; font-family: 'Segoe UI', sans-serif; display: flex; flex-direction: column; align-items: center; }
canvas { border: 1px solid #30363d; border-radius: 8px; background: #161b22; }
</style>
</head>
<body>
<h1>arc 方法全面演示</h1>
<canvas id="myCanvas" width="750" height="500"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 1. 完整圆形
ctx.beginPath();
ctx.arc(80, 80, 50, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(88, 166, 255, 0.3)';
ctx.fill();
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 2;
ctx.stroke();
// 2. 半圆
ctx.beginPath();
ctx.arc(230, 80, 50, 0, Math.PI);
ctx.fillStyle = 'rgba(63, 185, 80, 0.3)';
ctx.fill();
ctx.strokeStyle = '#3fb950';
ctx.lineWidth = 2;
ctx.stroke();
// 3. 扇形
ctx.beginPath();
ctx.moveTo(380, 80);
ctx.arc(380, 80, 50, 0, Math.PI * 0.75);
ctx.closePath();
ctx.fillStyle = 'rgba(248, 181, 0, 0.3)';
ctx.fill();
ctx.strokeStyle = '#f8b500';
ctx.lineWidth = 2;
ctx.stroke();
// 4. 逆时针弧线
ctx.beginPath();
ctx.arc(530, 80, 50, 0, Math.PI * 1.5, true);
ctx.strokeStyle = '#f78166';
ctx.lineWidth = 3;
ctx.stroke();
// 5. 饼图
const data = [
{ value: 35, color: '#58a6ff', label: 'JavaScript' },
{ value: 25, color: '#3fb950', label: 'Python' },
{ value: 20, color: '#f8b500', label: 'Java' },
{ value: 12, color: '#e94560', label: 'Go' },
{ value: 8, color: '#d2a8ff', label: 'Other' }
];
const pieX = 650, pieY = 240, pieR = 70;
let startAngle = -Math.PI / 2;
data.forEach(item => {
const sliceAngle = (item.value / 100) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(pieX, pieY);
ctx.arc(pieX, pieY, pieR, startAngle, startAngle + sliceAngle);
ctx.closePath();
ctx.fillStyle = item.color;
ctx.fill();
ctx.strokeStyle = '#161b22';
ctx.lineWidth = 2;
ctx.stroke();
startAngle += sliceAngle;
});
</script>
</body>
</html>示例2:arcTo 与圆角矩形
以下示例展示了 arcTo 的原理、不同圆角半径的矩形以及圆角矩形卡片效果:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>arcTo 与圆角矩形</title>
<style>
body { margin: 0; padding: 20px; background: #1a1a2e; color: #eee; font-family: 'Segoe UI', sans-serif; display: flex; flex-direction: column; align-items: center; }
canvas { border: 1px solid #333; border-radius: 8px; background: #16213e; }
</style>
</head>
<body>
<h1>arcTo 与圆角矩形</h1>
<canvas id="myCanvas" width="700" height="450"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// arcTo 原理演示
const p0 = { x: 50, y: 150 };
const p1 = { x: 200, y: 50 };
const p2 = { x: 300, y: 150 };
const radius = 40;
// 绘制辅助线
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = '#30363d';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.stroke();
ctx.setLineDash([]);
// 绘制 arcTo 弧线
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.arcTo(p1.x, p1.y, p2.x, p2.y, radius);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 3;
ctx.stroke();
// 圆角矩形函数
function roundRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
// 不同圆角半径
const radii = [5, 15, 30, 50];
radii.forEach((r, i) => {
const x = 400 + (i % 2) * 150;
const y = 50 + Math.floor(i / 2) * 120;
roundRect(ctx, x, y, 120, 80, r);
ctx.fillStyle = 'rgba(88, 166, 255, 0.2)';
ctx.fill();
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 2;
ctx.stroke();
});
</script>
</body>
</html>五、浏览器兼容性
两个方法都属于 Canvas 2D API 的基础部分,浏览器支持度非常好。
六、注意事项与最佳实践
1. radius 必须为非负数
代码示例
// 错误:负数半径会抛出异常
ctx.arc(100, 100, -10, 0, Math.PI); // RangeError
// 正确:确保半径非负
const r = Math.max(0, radius);
ctx.arc(100, 100, r, 0, Math.PI);2. arc 会从当前点画线到弧线起点
代码示例
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.arc(150, 50, 30, 0, Math.PI);
ctx.stroke();
// 会从 (50, 50) 画一条线到弧线起点 (180, 50)
// 如果不想要这条连接线,需要先 moveTo 到弧线起点
ctx.beginPath();
ctx.moveTo(180, 50); // 先移动到弧线起点
ctx.arc(150, 50, 30, 0, Math.PI);
ctx.stroke();3. 角度使用弧度制
代码示例
// 工具函数:角度转弧度
function degToRad(degrees) {
return degrees * Math.PI / 180;
}
// 使用
ctx.arc(100, 100, 50, degToRad(0), degToRad(90));4. 绘制扇形需要 moveTo 到圆心
代码示例
// 扇形
ctx.beginPath();
ctx.moveTo(cx, cy); // 先移动到圆心
ctx.arc(cx, cy, r, startAngle, endAngle);
ctx.closePath(); // 闭合回圆心
ctx.fill();5. arcTo 的半径过大时
如果 arcTo 的半径大于两条线段交点到弧线的最短距离,弧线会退化为一个点:
代码示例
// 确保半径合理
const maxRadius = Math.min(
distanceToLine(p0, p1, p2) / 2,
desiredRadius
);
ctx.arcTo(p1.x, p1.y, p2.x, p2.y, maxRadius);七、代码规范示例
代码示例
// 推荐:角度转换工具
function degToRad(deg) {
return deg * Math.PI / 180;
}
function radToDeg(rad) {
return rad * 180 / Math.PI;
}
// 推荐:封装圆形绘制
function drawCircle(ctx, cx, cy, r, options) {
const {
fill = null,
stroke = null,
lineWidth = 1
} = options || {};
r = Math.max(0, r); // 确保半径非负
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
if (fill) {
ctx.fillStyle = fill;
ctx.fill();
}
if (stroke) {
ctx.strokeStyle = stroke;
ctx.lineWidth = lineWidth;
ctx.stroke();
}
}
// 推荐:封装扇形绘制
function drawSector(ctx, cx, cy, r, startAngle, endAngle, options) {
const {
fill = null,
stroke = null,
lineWidth = 1
} = options || {};
r = Math.max(0, r);
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, startAngle, endAngle);
ctx.closePath();
if (fill) {
ctx.fillStyle = fill;
ctx.fill();
}
if (stroke) {
ctx.strokeStyle = stroke;
ctx.lineWidth = lineWidth;
ctx.stroke();
}
}
// 推荐:封装圆角矩形
function roundRect(ctx, x, y, w, h, r) {
r = Math.min(r, w / 2, h / 2); // 确保半径不超过短边一半
r = Math.max(0, r);
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
// 使用示例
drawCircle(ctx, 100, 100, 50, {
fill: 'rgba(88, 166, 255, 0.3)',
stroke: '#58a6ff',
lineWidth: 2
});
drawSector(ctx, 200, 200, 60, degToRad(0), degToRad(120), {
fill: '#3fb950'
});
roundRect(ctx, 300, 50, 200, 100, 15);
ctx.fillStyle = '#0f3460';
ctx.fill();
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 2;
ctx.stroke();八、常见问题与解决方案
arc 绘制时多了一条连接线?
原因:arc 会从当前点画一条直线到弧线的起点。
解决方案:
代码示例
// 方案1:在 arc 前不设置其他路径点
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI);
ctx.stroke();
// 方案2:先 moveTo 到弧线起点
const startX = cx + r * Math.cos(startAngle);
const startY = cy + r * Math.sin(startAngle);
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.arc(cx, cy, r, startAngle, endAngle);
ctx.stroke();如何绘制椭圆?
解决方案:Canvas 2D API 有 ellipse 方法,也可以通过 scale + arc 实现。
代码示例
// 方案1:使用 ellipse 方法(现代浏览器)
ctx.beginPath();
ctx.ellipse(cx, cy, radiusX, radiusY, rotation, 0, Math.PI * 2);
ctx.fill();
// 方案2:使用 scale + arc(兼容性更好)
ctx.save();
ctx.translate(cx, cy);
ctx.scale(1, radiusY / radiusX);
ctx.beginPath();
ctx.arc(0, 0, radiusX, 0, Math.PI * 2);
ctx.restore();
ctx.fill();圆角矩形圆角太大怎么办?
原因:圆角半径超过矩形短边一半时,会出现异常。
解决方案:限制半径不超过短边一半。
代码示例
function roundRect(ctx, x, y, w, h, r) {
r = Math.min(r, w / 2, h / 2);
r = Math.max(0, r);
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}如何绘制环形(甜甜圈)?
解决方案:
代码示例
// 方案1:使用 evenodd 填充规则
ctx.beginPath();
ctx.arc(cx, cy, outerR, 0, Math.PI * 2);
ctx.arc(cx, cy, innerR, 0, Math.PI * 2, true); // 逆时针
ctx.fillStyle = '#58a6ff';
ctx.fill('evenodd');
// 方案2:使用 nonzero 反向
ctx.beginPath();
ctx.arc(cx, cy, outerR, 0, Math.PI * 2, false);
ctx.arc(cx, cy, innerR, 0, Math.PI * 2, true);
ctx.fillStyle = '#58a6ff';
ctx.fill('nonzero');arcTo 绘制的弧线位置不对?
原因:arcTo 依赖当前点位置,如果当前点不在预期位置,弧线会不正确。
解决方案:确保 arcTo 前的当前点位置正确。
代码示例
ctx.beginPath();
ctx.moveTo(startX, startY); // 明确设置起点
ctx.arcTo(cp1x, cp1y, cp2x, cp2y, radius);
ctx.stroke();九、总结
弧线与圆形是 Canvas 绘图的重要部分,关键要点:
-
arc:基于圆心和半径绘制弧线,参数包括圆心、半径、起止角度和方向
-
arcTo:基于切线绘制弧线,常用于圆角矩形
-
角度方向:0 度在 X 轴正方向,顺时针为正方向
-
弧度制:Canvas 使用弧度,需要用
Math.PI或角度转换函数 -
扇形绘制:需要
moveTo到圆心 +arc+closePath -
圆角矩形:使用
arcTo绘制四个圆角,注意限制半径大小 -
环形绘制:使用
evenodd填充规则或反向绘制内圆
下一教程将介绍贝塞尔曲线的绘制。
本文涉及AI创作
内容由AI创作,请仔细甄别