直线是最基础的图形元素。在Canvas中,直线通过路径系统绑制,需要配合moveTo和lineTo两个方法。
| 方法 | 作用 |
|---|---|
| moveTo(x, y) | 移动画笔到指定位置(不画线) |
| lineTo(x, y) | 从当前位置画直线到指定位置 |
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(250, 150)
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 2
ctx.stroke()
moveTo设置起点,lineTo设置终点,stroke进行描边。
连续使用lineTo可以绑制折线:
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(150, 50)
ctx.lineTo(150, 150)
ctx.lineTo(250, 150)
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 3
ctx.stroke()
使用closePath可以自动连接终点和起点:
// 不闭合
ctx.beginPath()
ctx.moveTo(50, 200)
ctx.lineTo(100, 250)
ctx.lineTo(50, 300)
ctx.strokeStyle = '#3498db'
ctx.stroke()
// 闭合
ctx.beginPath()
ctx.moveTo(150, 200)
ctx.lineTo(200, 250)
ctx.lineTo(150, 300)
ctx.closePath()
ctx.strokeStyle = '#e74c3c'
ctx.stroke()
function drawGrid(ctx, width, height, step) {
ctx.strokeStyle = '#ecf0f1'
ctx.lineWidth = 1
// 垂直线
for (let x = 0; x <= width; x += step) {
ctx.beginPath()
ctx.moveTo(x, 0)
ctx.lineTo(x, height)
ctx.stroke()
}
// 水平线
for (let y = 0; y <= height; y += step) {
ctx.beginPath()
ctx.moveTo(0, y)
ctx.lineTo(width, y)
ctx.stroke()
}
}
drawGrid(ctx, 400, 300, 20)
function drawAxis(ctx, x, y, width, height) {
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
// X轴
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + width, y)
ctx.stroke()
// X轴箭头
ctx.beginPath()
ctx.moveTo(x + width, y)
ctx.lineTo(x + width - 10, y - 5)
ctx.lineTo(x + width - 10, y + 5)
ctx.closePath()
ctx.fillStyle = '#333'
ctx.fill()
// Y轴
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x, y - height)
ctx.stroke()
// Y轴箭头
ctx.beginPath()
ctx.moveTo(x, y - height)
ctx.lineTo(x - 5, y - height + 10)
ctx.lineTo(x + 5, y - height + 10)
ctx.closePath()
ctx.fill()
}
drawAxis(ctx, 50, 250, 300, 200)
const data = [30, 80, 45, 120, 90, 150, 110]
const startX = 50
const startY = 250
const chartWidth = 300
const chartHeight = 200
const stepX = chartWidth / (data.length - 1)
// 绘制坐标轴
drawAxis(ctx, startX, startY, chartWidth + 30, chartHeight + 30)
// 绘制折线
ctx.beginPath()
ctx.moveTo(startX, startY - data[0])
data.forEach((value, index) => {
const x = startX + index * stepX
const y = startY - value
ctx.lineTo(x, y)
})
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 2
ctx.stroke()
// 绘制数据点
data.forEach((value, index) => {
const x = startX + index * stepX
const y = startY - value
ctx.beginPath()
ctx.arc(x, y, 4, 0, Math.PI * 2)
ctx.fillStyle = '#fff'
ctx.fill()
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 2
ctx.stroke()
})
通过setLineDash方法设置虚线:
ctx.setLineDash([10, 5])
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(350, 50)
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 2
ctx.stroke()
// 重置为实线
ctx.setLineDash([])
function drawArrow(ctx, fromX, fromY, toX, toY, headLength, headAngle) {
const angle = Math.atan2(toY - fromY, toX - fromX)
ctx.beginPath()
ctx.moveTo(fromX, fromY)
ctx.lineTo(toX, toY)
ctx.stroke()
// 箭头
ctx.beginPath()
ctx.moveTo(toX, toY)
ctx.lineTo(
toX - headLength * Math.cos(angle - headAngle),
toY - headLength * Math.sin(angle - headAngle)
)
ctx.lineTo(
toX - headLength * Math.cos(angle + headAngle),
toY - headLength * Math.sin(angle + headAngle)
)
ctx.closePath()
ctx.fill()
}
ctx.strokeStyle = '#333'
ctx.fillStyle = '#333'
ctx.lineWidth = 2
drawArrow(ctx, 50, 150, 300, 100, 15, Math.PI / 6)
for (let i = 0; i < 20; i++) {
const x1 = Math.random() * 400
const y1 = Math.random() * 300
const x2 = Math.random() * 400
const y2 = Math.random() * 300
ctx.beginPath()
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.strokeStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.5)`
ctx.lineWidth = Math.random() * 3 + 1
ctx.stroke()
}
1. 必须先调用beginPath
// 错误:没有beginPath会累积之前的路径
ctx.moveTo(50, 50)
ctx.lineTo(100, 100)
ctx.stroke()
ctx.lineTo(200, 100) // 会从(100,100)继续画
ctx.stroke()
// 正确
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(100, 100)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(100, 100)
ctx.lineTo(200, 100)
ctx.stroke()
2. moveTo不只是设置起点
moveTo可以在路径中间"抬起画笔":
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(150, 50)
ctx.moveTo(50, 100) // 移动到新位置
ctx.lineTo(150, 100)
ctx.stroke()
3. 线条端点样式
lineCap影响线条端点外观:
ctx.lineWidth = 15
ctx.lineCap = 'butt'
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(200, 50)
ctx.stroke()
ctx.lineCap = 'round'
ctx.beginPath()
ctx.moveTo(50, 100)
ctx.lineTo(200, 100)
ctx.stroke()
ctx.lineCap = 'square'
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.lineTo(200, 150)
ctx.stroke()