椭圆是圆形的变体,有两个不同长度的半径。Canvas提供了ellipse方法专门用于绑制椭圆。
ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
| 参数 | 类型 | 说明 |
|---|---|---|
| x | number | 椭圆中心x坐标 |
| y | number | 椭圆中心y坐标 |
| radiusX | number | 水平半径 |
| radiusY | number | 垂直半径 |
| rotation | number | 旋转角度(弧度) |
| startAngle | number | 起始角度 |
| endAngle | number | 结束角度 |
| anticlockwise | boolean | 是否逆时针 |
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()