pin_drop当前位置:知识文库 ❯ 图文
多媒体:绘制线条 - 完整教程与代码示例
一、教程简介
线条是 Canvas 绘图中最基本的元素之一。通过 moveTo 和 lineTo 可以定义线段的路径,而 lineWidth、lineCap、lineJoin 等属性则控制线条的视觉表现。本教程将详细讲解线条的绘制方法和样式设置,帮助你掌握从简单直线到复杂线框图形的绘制技巧。
二、核心概念
线条绘制流程
beginPath()- 开始新路径moveTo(x, y)- 将画笔移动到起点(不画线)lineTo(x, y)- 从当前点画线到指定点设置线条样式(
lineWidth、lineCap、lineJoin等)stroke()- 渲染线条
线条样式属性
三、语法与用法
moveTo - 移动画笔
代码示例
ctx.moveTo(x, y);
将画笔移动到坐标 (x, y),不绘制任何线条。相当于提起画笔再放下。
lineTo - 画线到指定点
代码示例
ctx.lineTo(x, y);
从当前点绘制一条直线到 (x, y),并将当前点移动到 (x, y)。
lineWidth - 线条宽度
代码示例
ctx.lineWidth = 5;
设置线条宽度,单位为像素。线条以路径为中心向两侧各延伸 lineWidth/2。
lineCap - 线条端点样式
代码示例
ctx.lineCap = 'butt'; // 默认,平直端点,不超过端点
ctx.lineCap = 'round'; // 圆形端点,超出端点半个线宽
ctx.lineCap = 'square'; // 方形端点,超出端点半个线宽lineJoin - 线条连接样式
代码示例
ctx.lineJoin = 'miter'; // 默认,尖角
ctx.lineJoin = 'round'; // 圆角
ctx.lineJoin = 'bevel'; // 斜角setLineDash - 虚线设置
代码示例
ctx.setLineDash([5, 10]); // 5px实线,10px空白
ctx.setLineDash([5, 5, 15, 5]); // 复杂虚线模式
ctx.setLineDash([]); // 实线lineDashOffset - 虚线偏移
代码示例
ctx.lineDashOffset = 5; // 虚线偏移5px四、代码示例
示例1:线条样式全面演示
以下示例演示了 lineCap、lineJoin、lineWidth 和虚线样式的视觉效果:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>线条样式全面演示</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>线条样式全面演示</h1>
<canvas id="myCanvas" width="750" height="550"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// === lineCap 演示 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('lineCap 端点样式', 375, 30);
const caps = ['butt', 'round', 'square'];
const capColors = ['#e94560', '#3fb950', '#58a6ff'];
caps.forEach((cap, i) => {
const x = 130 + i * 250;
const y = 80;
// 参考线
ctx.strokeStyle = '#30363d';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.beginPath();
ctx.moveTo(x, y - 20);
ctx.lineTo(x, y + 60);
ctx.moveTo(x + 150, y - 20);
ctx.lineTo(x + 150, y + 60);
ctx.stroke();
ctx.setLineDash([]);
// 示例线条
ctx.lineCap = cap;
ctx.strokeStyle = capColors[i];
ctx.lineWidth = 20;
ctx.beginPath();
ctx.moveTo(x, y + 20);
ctx.lineTo(x + 150, y + 20);
ctx.stroke();
// 标签
ctx.fillStyle = capColors[i];
ctx.font = '14px sans-serif';
ctx.fillText(cap, x + 75, y + 60);
});
// === lineJoin 演示 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 16px sans-serif';
ctx.fillText('lineJoin 连接样式', 375, 180);
const joins = ['miter', 'round', 'bevel'];
const joinColors = ['#f8b500', '#d2a8ff', '#f78166'];
joins.forEach((join, i) => {
const x = 100 + i * 250;
const y = 220;
ctx.lineJoin = join;
ctx.lineCap = 'butt';
ctx.strokeStyle = joinColors[i];
ctx.lineWidth = 20;
ctx.beginPath();
ctx.moveTo(x, y + 60);
ctx.lineTo(x + 60, y);
ctx.lineTo(x + 120, y + 60);
ctx.stroke();
ctx.fillStyle = joinColors[i];
ctx.font = '14px sans-serif';
ctx.fillText(join, x + 60, y + 90);
});
// === lineWidth 演示 ===
ctx.fillStyle = '#c9d1d9';
ctx.font = 'bold 16px sans-serif';
ctx.fillText('lineWidth 线条宽度', 375, 350);
ctx.lineCap = 'round';
ctx.strokeStyle = '#58a6ff';
for (let i = 1; i <= 8; i++) {
ctx.lineWidth = i;
ctx.beginPath();
ctx.moveTo(80, 370 + i * 20);
ctx.lineTo(670, 370 + i * 20);
ctx.stroke();
ctx.fillStyle = '#8b949e';
ctx.font = '12px sans-serif';
ctx.textAlign = 'left';
ctx.fillText(i + 'px', 680, 374 + i * 20);
}
ctx.textAlign = 'center';
</script>
</body>
</html>示例2:虚线与蚂蚁线动画
以下示例展示了多种虚线模式以及蚂蚁线(选区)动画效果:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>虚线与蚂蚁线动画</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>虚线与蚂蚁线动画</h1>
<canvas id="myCanvas" width="700" height="450"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let dashOffset = 0;
// 静态虚线展示
const dashPatterns = [
{ pattern: [5, 5], label: '[5, 5]' },
{ pattern: [10, 5], label: '[10, 5]' },
{ pattern: [15, 5, 5, 5], label: '[15, 5, 5, 5]' },
{ pattern: [20, 5, 5, 5, 5, 5], label: '[20, 5, 5, 5, 5, 5]' },
{ pattern: [2, 5], label: '[2, 5] 点线' },
{ pattern: [1, 3, 6, 3], label: '[1, 3, 6, 3] 点划线' }
];
dashPatterns.forEach((item, i) => {
const y = 30 + i * 30;
ctx.setLineDash(item.pattern);
ctx.strokeStyle = '#58a6ff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(30, y);
ctx.lineTo(300, y);
ctx.stroke();
ctx.fillStyle = '#8b949e';
ctx.font = '12px sans-serif';
ctx.textAlign = 'left';
ctx.fillText(item.label, 310, y + 4);
});
ctx.setLineDash([]);
// 蚂蚁线动画(选区效果)
function drawMarchingAnts() {
ctx.clearRect(350, 0, 350, 450);
ctx.fillStyle = '#16213e';
ctx.fillRect(350, 0, 350, 450);
ctx.fillStyle = '#eee';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('蚂蚁线动画(选区效果)', 525, 25);
ctx.setLineDash([6, 4]);
ctx.lineDashOffset = -dashOffset;
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1.5;
ctx.strokeRect(400, 50, 250, 150);
ctx.beginPath();
ctx.arc(525, 310, 80, 0, Math.PI * 2);
ctx.strokeStyle = '#3fb950';
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(525, 390);
ctx.lineTo(580, 420);
ctx.lineTo(560, 440);
ctx.lineTo(490, 440);
ctx.lineTo(470, 420);
ctx.closePath();
ctx.strokeStyle = '#f78166';
ctx.lineWidth = 2;
ctx.stroke();
ctx.setLineDash([]);
dashOffset++;
if (dashOffset > 10) dashOffset = 0;
requestAnimationFrame(drawMarchingAnts);
}
drawMarchingAnts();
</script>
</body>
</html>五、浏览器兼容性
六、注意事项与最佳实践
1. 像素对齐问题
线条宽度为奇数时,以像素边界为中心绘制,会产生抗锯齿模糊:
代码示例
// 问题:1px 线条模糊
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.stroke();
// 解决方案1:偏移 0.5 像素
ctx.moveTo(10, 10.5);
ctx.lineTo(100, 10.5);
// 解决方案2:使用偶数线宽
ctx.lineWidth = 2;2. lineWidth 的居中特性
线条以路径为中心向两侧延伸,实际占用宽度为 lineWidth:
代码示例
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 50);
ctx.stroke();
// 线条实际覆盖 y=45 到 y=55 的区域3. miterLimit 的作用
当 lineJoin = 'miter' 时,如果两条线夹角很小,尖角会非常长。miterLimit 限制斜接长度与线宽的比值:
代码示例
ctx.lineJoin = 'miter';
ctx.miterLimit = 10; // 默认值
// 如果斜接长度超过 lineWidth * miterLimit / 2,自动退化为 bevel4. 虚线重置
代码示例
// 设置虚线
ctx.setLineDash([5, 5]);
// 重置为实线
ctx.setLineDash([]);
// 或者
ctx.lineDashOffset = 0;5. 线条绘制性能
大量线条绘制时,合并为一条路径可以提升性能:
代码示例
// 推荐:合并路径
ctx.beginPath();
for (let i = 0; i < 100; i++) {
ctx.moveTo(Math.random() * 800, Math.random() * 600);
ctx.lineTo(Math.random() * 800, Math.random() * 600);
}
ctx.stroke(); // 一次绘制
// 不推荐:每条线单独绘制
for (let i = 0; i < 100; i++) {
ctx.beginPath();
ctx.moveTo(Math.random() * 800, Math.random() * 600);
ctx.lineTo(Math.random() * 800, Math.random() * 600);
ctx.stroke(); // 100次绘制
}七、代码规范示例
代码示例
// 推荐:封装线条绘制函数
function drawLine(ctx, x1, y1, x2, y2, options) {
const {
color = '#000000',
width = 1,
cap = 'butt',
dash = []
} = options || {};
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.lineCap = cap;
ctx.setLineDash(dash);
ctx.stroke();
ctx.setLineDash([]); // 重置虚线
}
// 推荐:绘制折线
function drawPolyline(ctx, points, options) {
const {
color = '#000000',
width = 1,
cap = 'round',
join = 'round',
dash = [],
close = false
} = options || {};
if (points.length < 2) return;
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
if (close) ctx.closePath();
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.lineCap = cap;
ctx.lineJoin = join;
ctx.setLineDash(dash);
ctx.stroke();
ctx.setLineDash([]);
}
// 使用示例
drawLine(ctx, 10, 10, 200, 10, {
color: '#58a6ff',
width: 2,
cap: 'round'
});
drawPolyline(ctx, [
[50, 100], [100, 50], [150, 100], [200, 50]
], {
color: '#3fb950',
width: 3,
join: 'round',
dash: [8, 4]
});八、常见问题与解决方案
1像素线条为什么显示为2像素且模糊?
原因:线条以路径为中心向两侧延伸 0.5 像素,落在像素边界上产生抗锯齿。
解决方案:
代码示例
// 方案1:坐标偏移 0.5
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(10.5, 10.5);
ctx.lineTo(100.5, 10.5);
ctx.stroke();
// 方案2:使用 2 像素线宽
ctx.lineWidth = 2;如何绘制平滑曲线而不是折线?
解决方案:使用贝塞尔曲线(后续教程详细讲解):
代码示例
// 使用二次贝塞尔曲线连接点
function drawSmoothLine(ctx, points) {
if (points.length < 2) return;
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (let i = 1; i < points.length - 1; i++) {
const xc = (points[i][0] + points[i + 1][0]) / 2;
const yc = (points[i][1] + points[i + 1][1]) / 2;
ctx.quadraticCurveTo(points[i][0], points[i][1], xc, yc);
}
const last = points[points.length - 1];
ctx.lineTo(last[0], last[1]);
ctx.stroke();
}如何实现蚂蚁线(选区虚线动画)?
解决方案:
代码示例
let offset = 0;
function animateDash() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setLineDash([6, 4]);
ctx.lineDashOffset = -offset;
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.strokeRect(50, 50, 200, 150);
offset++;
requestAnimationFrame(animateDash);
}
animateDash();lineJoin = 'miter' 时尖角太长怎么办?
解决方案:
代码示例
// 设置 miterLimit
ctx.lineJoin = 'miter';
ctx.miterLimit = 5; // 超过5倍线宽时退化为 bevel
// 或者直接使用 round/bevel
ctx.lineJoin = 'round';如何绘制渐变色线条?
解决方案:
代码示例
const gradient = ctx.createLinearGradient(50, 50, 300, 50);
gradient.addColorStop(0, '#e94560');
gradient.addColorStop(0.5, '#58a6ff');
gradient.addColorStop(1, '#3fb950');
ctx.strokeStyle = gradient;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(300, 50);
ctx.stroke();九、总结
线条绘制是 Canvas 图形的基础,关键要点:
-
moveTo/lineTo:定义线段路径,
moveTo提笔移动,lineTo画线 -
lineWidth:线条宽度,以路径为中心向两侧延伸
-
lineCap:端点样式,
butt(平直)、round(圆形)、square(方形) -
lineJoin:连接样式,
miter(尖角)、round(圆角)、bevel(斜角) -
setLineDash:虚线样式,通过数组定义实线与空白的交替模式
-
像素对齐:奇数线宽需偏移 0.5 像素以避免模糊
-
性能优化:合并多条线为一条路径一次绘制
下一教程将介绍如何绘制弧线与圆形。
本文涉及AI创作
内容由AI创作,请仔细甄别