绘制圆形

Canvas绘制圆形教程,使用arc方法绑制各种圆形和圆弧。Canvas中使用arc方法绑制圆形和圆弧。圆形是最常用的基础图形之一。

arc方法

语法

ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)

参数说明

参数类型说明
xnumber圆心x坐标
ynumber圆心y坐标
radiusnumber半径
startAnglenumber起始角度(弧度)
endAnglenumber结束角度(弧度)
counterclockwiseboolean是否逆时针(可选,默认false)

绘制完整圆形

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

// 填充圆
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI * 2)
ctx.fillStyle = '#3498db'
ctx.fill()

// 描边圆
ctx.beginPath()
ctx.arc(250, 100, 50, 0, Math.PI * 2)
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 3
ctx.stroke()

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

角度与弧度

Canvas使用弧度而非角度:

// 角度转弧度
function toRadians(degrees) {
  return degrees * Math.PI / 180
}

// 常用角度
const PI = Math.PI
const HALF_PI = Math.PI / 2      // 90度
const QUARTER_PI = Math.PI / 4   // 45度
const TWO_PI = Math.PI * 2       // 360度

绘制圆弧

半圆

// 上半圆
ctx.beginPath()
ctx.arc(100, 200, 40, 0, Math.PI)
ctx.fillStyle = '#9b59b6'
ctx.fill()

// 下半圆
ctx.beginPath()
ctx.arc(200, 200, 40, Math.PI, 0)
ctx.fillStyle = '#8e44ad'
ctx.fill()

// 左半圆
ctx.beginPath()
ctx.arc(300, 200, 40, HALF_PI, -HALF_PI)
ctx.fillStyle = '#1abc9c'
ctx.fill()

// 右半圆
ctx.beginPath()
ctx.arc(400, 200, 40, -HALF_PI, HALF_PI)
ctx.fillStyle = '#16a085'
ctx.fill()

四分之一圆

ctx.beginPath()
ctx.arc(100, 350, 50, 0, Math.PI / 2)
ctx.lineTo(100, 350)  // 连接到圆心
ctx.closePath()
ctx.fillStyle = '#e67e22'
ctx.fill()

任意角度圆弧

// 60度圆弧
ctx.beginPath()
ctx.arc(250, 350, 50, 0, toRadians(60))
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 3
ctx.stroke()

// 120度圆弧
ctx.beginPath()
ctx.arc(400, 350, 50, toRadians(-60), toRadians(60))
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 3
ctx.stroke()

顺时针与逆时针

// 顺时针(默认)
ctx.beginPath()
ctx.arc(100, 500, 40, 0, Math.PI, false)
ctx.fillStyle = '#3498db'
ctx.fill()

// 逆时针
ctx.beginPath()
ctx.arc(200, 500, 40, 0, Math.PI, true)
ctx.fillStyle = '#e74c3c'
ctx.fill()

绘制扇形

扇形需要连接圆心:

function drawSector(ctx, x, y, radius, startAngle, endAngle, fillColor) {
  ctx.beginPath()
  ctx.moveTo(x, y)  // 移动到圆心
  ctx.arc(x, y, radius, startAngle, endAngle)
  ctx.closePath()
  ctx.fillStyle = fillColor
  ctx.fill()
}

// 绘制饼图
const data = [30, 25, 20, 15, 10]
const colors = ['#3498db', '#e74c3c', '#2ecc71', '#f1c40f', '#9b59b6']
let startAngle = 0

data.forEach((value, index) => {
  const angle = (value / 100) * Math.PI * 2
  drawSector(ctx, 300, 500, 80, startAngle, startAngle + angle, colors[index])
  startAngle += angle
})

绘制圆环

function drawRing(ctx, x, y, outerRadius, innerRadius, color) {
  ctx.beginPath()
  ctx.arc(x, y, outerRadius, 0, Math.PI * 2)
  ctx.arc(x, y, innerRadius, 0, Math.PI * 2, true)  // 逆时针,形成环形
  ctx.fillStyle = color
  ctx.fill()
}

drawRing(ctx, 500, 500, 60, 40, '#9b59b6')

绘制进度环

function drawProgressRing(ctx, x, y, radius, progress, lineWidth, bgColor, progressColor) {
  // 背景圆环
  ctx.beginPath()
  ctx.arc(x, y, radius, 0, Math.PI * 2)
  ctx.strokeStyle = bgColor
  ctx.lineWidth = lineWidth
  ctx.stroke()
  
  // 进度圆环
  const endAngle = -Math.PI / 2 + progress * Math.PI * 2
  ctx.beginPath()
  ctx.arc(x, y, radius, -Math.PI / 2, endAngle)
  ctx.strokeStyle = progressColor
  ctx.lineCap = 'round'
  ctx.stroke()
  
  // 进度文字
  ctx.fillStyle = '#333'
  ctx.font = 'bold 20px Arial'
  ctx.textAlign = 'center'
  ctx.textBaseline = 'middle'
  ctx.fillText(Math.round(progress * 100) + '%', x, y)
}

drawProgressRing(ctx, 150, 650, 50, 0.75, 10, '#ecf0f1', '#3498db')

实际应用:绘制同心圆

function drawConcentricCircles(ctx, x, y, count, maxRadius) {
  const step = maxRadius / count
  
  for (let i = count; i > 0; i--) {
    const radius = i * step
    const alpha = i / count
    
    ctx.beginPath()
    ctx.arc(x, y, radius, 0, Math.PI * 2)
    ctx.fillStyle = `rgba(52, 152, 219, ${alpha})`
    ctx.fill()
  }
}

drawConcentricCircles(ctx, 400, 650, 5, 80)

常见问题

问题1:忘记beginPath

// 错误:没有beginPath,会连接到上一个路径
ctx.arc(100, 100, 30, 0, Math.PI * 2)
ctx.fill()
ctx.arc(200, 100, 30, 0, Math.PI * 2)  // 会和上一个圆连接
ctx.fill()

// 正确:每次都beginPath
ctx.beginPath()
ctx.arc(100, 100, 30, 0, Math.PI * 2)
ctx.fill()

ctx.beginPath()
ctx.arc(200, 100, 30, 0, Math.PI * 2)
ctx.fill()

问题2:角度方向

// Canvas中0度在3点钟方向
// 角度顺时针增加

// 从12点钟开始
ctx.arc(x, y, r, -Math.PI/2, Math.PI/2)  // 右半圆

// 从9点钟开始
ctx.arc(x, y, r, Math.PI, 0)  // 上半圆

问题3:半径为0

ctx.arc(100, 100, 0, 0, Math.PI * 2)  // 不绘制任何内容