Canvas绑制三角形详解,包括等边三角形、等腰三角形、直角三角形的绑制方法。三角形是最简单的多边形,由三条边组成。Canvas中没有专门的三角形方法,需要通过路径绑制。
通过三个点连接成三角形:
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(150, 50)
ctx.lineTo(100, 150)
ctx.lineTo(200, 150)
ctx.closePath()
ctx.fillStyle = '#3498db'
ctx.fill()
三个顶点坐标:(150, 50)、(100, 150)、(200, 150)
// 填充三角形
ctx.beginPath()
ctx.moveTo(80, 50)
ctx.lineTo(30, 130)
ctx.lineTo(130, 130)
ctx.closePath()
ctx.fillStyle = '#e74c3c'
ctx.fill()
// 描边三角形
ctx.beginPath()
ctx.moveTo(220, 50)
ctx.lineTo(170, 130)
ctx.lineTo(270, 130)
ctx.closePath()
ctx.strokeStyle = '#2ecc71'
ctx.lineWidth = 3
ctx.stroke()
// 填充+描边
ctx.beginPath()
ctx.moveTo(360, 50)
ctx.lineTo(310, 130)
ctx.lineTo(410, 130)
ctx.closePath()
ctx.fillStyle = '#f39c12'
ctx.fill()
ctx.strokeStyle = '#d35400'
ctx.lineWidth = 3
ctx.stroke()
等边三角形的三条边相等,三个角都是60度:
function drawEquilateralTriangle(ctx, x, y, size, rotation = 0) {
const height = size * Math.sqrt(3) / 2
ctx.save()
ctx.translate(x, y)
ctx.rotate(rotation)
ctx.beginPath()
ctx.moveTo(0, -height * 2 / 3)
ctx.lineTo(-size / 2, height / 3)
ctx.lineTo(size / 2, height / 3)
ctx.closePath()
ctx.restore()
}
drawEquilateralTriangle(ctx, 100, 100, 80)
ctx.fillStyle = '#9b59b6'
ctx.fill()
drawEquilateralTriangle(ctx, 250, 100, 60, Math.PI / 6)
ctx.strokeStyle = '#1abc9c'
ctx.lineWidth = 3
ctx.stroke()
等腰三角形有两条相等的边:
function drawIsoscelesTriangle(ctx, x, y, base, height) {
ctx.beginPath()
ctx.moveTo(x, y - height / 2)
ctx.lineTo(x - base / 2, y + height / 2)
ctx.lineTo(x + base / 2, y + height / 2)
ctx.closePath()
}
drawIsoscelesTriangle(ctx, 100, 120, 60, 100)
ctx.fillStyle = '#e67e22'
ctx.fill()
直角三角形有一个90度的角:
function drawRightTriangle(ctx, x, y, width, height) {
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x, y + height)
ctx.lineTo(x + width, y + height)
ctx.closePath()
}
drawRightTriangle(ctx, 50, 50, 120, 80)
ctx.fillStyle = '#16a085'
ctx.fill()
通过变换实现旋转:
function drawRotatedTriangle(ctx, x, y, size, angle) {
ctx.save()
ctx.translate(x, y)
ctx.rotate(angle)
ctx.beginPath()
ctx.moveTo(0, -size)
ctx.lineTo(-size * 0.866, size * 0.5)
ctx.lineTo(size * 0.866, size * 0.5)
ctx.closePath()
ctx.restore()
}
const colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6']
for (let i = 0; i < 5; i++) {
drawRotatedTriangle(ctx, 80 + i * 70, 150, 30, i * Math.PI / 10)
ctx.fillStyle = colors[i]
ctx.fill()
}
function drawTrianglePattern(ctx, startX, startY, size, rows, cols) {
const height = size * Math.sqrt(3) / 2
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = startX + col * size + (row % 2) * (size / 2)
const y = startY + row * height
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x - size / 2, y + height)
ctx.lineTo(x + size / 2, y + height)
ctx.closePath()
const hue = (row * cols + col) * (360 / (rows * cols))
ctx.fillStyle = `hsl(${hue}, 70%, 60%)`
ctx.fill()
}
}
}
drawTrianglePattern(ctx, 50, 30, 40, 5, 8)
function drawPyramid(ctx, x, y, baseWidth, levels) {
const levelHeight = 40
for (let i = 0; i < levels; i++) {
const currentWidth = baseWidth - i * (baseWidth / levels)
const currentX = x + (baseWidth - currentWidth) / 2
const currentY = y + i * levelHeight
ctx.beginPath()
ctx.moveTo(currentX + currentWidth / 2, currentY)
ctx.lineTo(currentX, currentY + levelHeight)
ctx.lineTo(currentX + currentWidth, currentY + levelHeight)
ctx.closePath()
const brightness = 50 + i * 10
ctx.fillStyle = `hsl(30, 70%, ${brightness}%)`
ctx.fill()
ctx.strokeStyle = '#8b4513'
ctx.lineWidth = 1
ctx.stroke()
}
}
drawPyramid(ctx, 100, 50, 200, 5)
function drawArrow(ctx, x, y, width, height, headSize) {
const bodyWidth = width - headSize
// 箭头主体(矩形)
ctx.fillRect(x, y + height / 4, bodyWidth, height / 2)
// 箭头头部(三角形)
ctx.beginPath()
ctx.moveTo(x + bodyWidth, y)
ctx.lineTo(x + width, y + height / 2)
ctx.lineTo(x + bodyWidth, y + height)
ctx.closePath()
ctx.fill()
}
ctx.fillStyle = '#3498db'
drawArrow(ctx, 50, 100, 150, 40, 40)
绑制三角形时可能需要计算面积:
function getTriangleArea(x1, y1, x2, y2, x3, y3) {
return Math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2)
}
const area = getTriangleArea(150, 50, 100, 150, 200, 150)
console.log('三角形面积:', area)
function isPointInTriangle(px, py, x1, y1, x2, y2, x3, y3) {
const area = getTriangleArea(x1, y1, x2, y2, x3, y3)
const area1 = getTriangleArea(px, py, x2, y2, x3, y3)
const area2 = getTriangleArea(x1, y1, px, py, x3, y3)
const area3 = getTriangleArea(x1, y1, x2, y2, px, py)
return Math.abs(area - (area1 + area2 + area3)) < 0.001
}
console.log(isPointInTriangle(150, 100, 150, 50, 100, 150, 200, 150))
1. 使用closePath闭合
// 不推荐:手动闭合
ctx.beginPath()
ctx.moveTo(150, 50)
ctx.lineTo(100, 150)
ctx.lineTo(200, 150)
ctx.lineTo(150, 50) // 手动回到起点
// 推荐:使用closePath
ctx.beginPath()
ctx.moveTo(150, 50)
ctx.lineTo(100, 150)
ctx.lineTo(200, 150)
ctx.closePath()
2. 顶点顺序影响填充
顶点顺序(顺时针或逆时针)不影响填充结果,但影响某些高级操作如裁剪。
3. 绘制顺序
先填充后描边,描边会覆盖在填充之上:
ctx.beginPath()
ctx.moveTo(150, 50)
ctx.lineTo(100, 150)
ctx.lineTo(200, 150)
ctx.closePath()
ctx.fillStyle = '#3498db'
ctx.fill()
ctx.strokeStyle = '#2980b9'
ctx.lineWidth = 5
ctx.stroke()