绘制椭圆

椭圆是圆形的变体,有两个不同长度的半径。Canvas提供了ellipse方法专门用于绑制椭圆。

ellipse方法语法

ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
参数类型说明
xnumber椭圆中心x坐标
ynumber椭圆中心y坐标
radiusXnumber水平半径
radiusYnumber垂直半径
rotationnumber旋转角度(弧度)
startAnglenumber起始角度
endAnglenumber结束角度
anticlockwiseboolean是否逆时针

绘制基础椭圆

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

ctx.beginPath()
ctx.ellipse(200, 150, 100, 60, 0, 0, Math.PI * 2)
ctx.fillStyle = '#3498db'
ctx.fill()

radiusX是水平方向的半径,radiusY是垂直方向的半径。当两者相等时,椭圆就变成了圆形。

水平椭圆与垂直椭圆

// 水平椭圆(宽大于高)
ctx.beginPath()
ctx.ellipse(120, 100, 80, 40, 0, 0, Math.PI * 2)
ctx.fillStyle = '#e74c3c'
ctx.fill()

// 垂直椭圆(高大于宽)
ctx.beginPath()
ctx.ellipse(280, 100, 40, 80, 0, 0, Math.PI * 2)
ctx.fillStyle = '#2ecc71'
ctx.fill()

// 正圆
ctx.beginPath()
ctx.ellipse(200, 250, 50, 50, 0, 0, Math.PI * 2)
ctx.fillStyle = '#f39c12'
ctx.fill()

旋转椭圆

rotation参数可以让椭圆旋转指定角度:

// 不旋转
ctx.beginPath()
ctx.ellipse(100, 150, 60, 30, 0, 0, Math.PI * 2)
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 2
ctx.stroke()

// 旋转45度
ctx.beginPath()
ctx.ellipse(250, 150, 60, 30, Math.PI / 4, 0, Math.PI * 2)
ctx.strokeStyle = '#e74c3c'
ctx.stroke()

// 旋转90度
ctx.beginPath()
ctx.ellipse(400, 150, 60, 30, Math.PI / 2, 0, Math.PI * 2)
ctx.strokeStyle = '#2ecc71'
ctx.stroke()

旋转是围绕椭圆中心进行的,角度为正时顺时针旋转。

绘制椭圆弧

和arc一样,ellipse也可以绑制部分椭圆:

// 半椭圆
ctx.beginPath()
ctx.ellipse(150, 300, 80, 50, 0, 0, Math.PI)
ctx.fillStyle = '#9b59b6'
ctx.fill()

// 四分之一椭圆
ctx.beginPath()
ctx.ellipse(320, 300, 80, 50, 0, 0, Math.PI / 2)
ctx.strokeStyle = '#1abc9c'
ctx.lineWidth = 3
ctx.stroke()

填充与描边

// 填充椭圆
ctx.beginPath()
ctx.ellipse(100, 100, 70, 45, 0, 0, Math.PI * 2)
ctx.fillStyle = '#e74c3c'
ctx.fill()

// 描边椭圆
ctx.beginPath()
ctx.ellipse(250, 100, 70, 45, 0, 0, Math.PI * 2)
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 4
ctx.stroke()

// 填充+描边
ctx.beginPath()
ctx.ellipse(400, 100, 70, 45, 0, 0, Math.PI * 2)
ctx.fillStyle = '#2ecc71'
ctx.fill()
ctx.strokeStyle = '#27ae60'
ctx.lineWidth = 3
ctx.stroke()

实用示例:绑制跑道

function drawTrack(ctx, x, y, width, height, laneWidth) {
  const lanes = 4
  
  for (let i = 0; i < lanes; i++) {
    const currentWidth = width - i * laneWidth * 2
    const currentHeight = height - i * laneWidth * 2
    const radiusX = currentWidth / 2
    const radiusY = currentHeight / 2
    
    ctx.beginPath()
    
    // 上半圆弧
    ctx.arc(x + radiusX, y + laneWidth * i + radiusY, radiusY, Math.PI, 0, false)
    
    // 右侧直线
    ctx.lineTo(x + currentWidth + laneWidth * i, y + height - laneWidth * i - radiusY)
    
    // 下半圆弧
    ctx.arc(x + radiusX, y + height - laneWidth * i - radiusY, radiusY, 0, Math.PI, false)
    
    // 左侧直线
    ctx.closePath()
    
    ctx.strokeStyle = i % 2 === 0 ? '#c0392b' : '#e74c3c'
    ctx.lineWidth = laneWidth
    ctx.stroke()
  }
}

drawTrack(ctx, 50, 50, 300, 150, 15)

实用示例:绑制叶子

function drawLeaf(ctx, x, y, size, rotation, color) {
  ctx.save()
  ctx.translate(x, y)
  ctx.rotate(rotation)
  
  ctx.beginPath()
  ctx.ellipse(0, 0, size, size / 3, 0, 0, Math.PI * 2)
  ctx.fillStyle = color
  ctx.fill()
  
  // 叶脉
  ctx.beginPath()
  ctx.moveTo(-size, 0)
  ctx.lineTo(size, 0)
  ctx.strokeStyle = 'rgba(0,0,0,0.2)'
  ctx.lineWidth = 1
  ctx.stroke()
  
  ctx.restore()
}

drawLeaf(ctx, 100, 150, 40, Math.PI / 6, '#27ae60')
drawLeaf(ctx, 180, 120, 35, -Math.PI / 4, '#2ecc71')
drawLeaf(ctx, 250, 160, 45, Math.PI / 3, '#27ae60')
drawLeaf(ctx, 320, 130, 30, -Math.PI / 5, '#2ecc71')

兼容性说明

ellipse方法是较新的API,在老版本浏览器中可能不支持。可以使用arc模拟:

function drawEllipseCompat(ctx, x, y, radiusX, radiusY, rotation) {
  ctx.save()
  ctx.translate(x, y)
  ctx.rotate(rotation)
  ctx.scale(1, radiusY / radiusX)
  ctx.beginPath()
  ctx.arc(0, 0, radiusX, 0, Math.PI * 2)
  ctx.restore()
}

// 使用
drawEllipseCompat(ctx, 200, 150, 80, 50, 0)
ctx.fillStyle = '#3498db'
ctx.fill()

注意事项

1. 半径必须为正数

ctx.ellipse(100, 100, -50, 30, 0, 0, Math.PI * 2)  // 报错

2. rotation不影响绑制方向

旋转只改变椭圆的方向,不影响startAngle和endAngle的含义。

3. 使用beginPath

// 正确
ctx.beginPath()
ctx.ellipse(100, 100, 50, 30, 0, 0, Math.PI * 2)
ctx.stroke()

ctx.beginPath()
ctx.ellipse(200, 100, 50, 30, 0, 0, Math.PI * 2)
ctx.stroke()