绘制三角形

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()