1. Canvas绘制圆角矩形核心原理
在HTML5 Canvas中绘制圆角矩形看似简单,实则包含多个关键参数和绘制逻辑。与普通矩形不同,圆角矩形需要处理四个角的圆弧过渡,这涉及到贝塞尔曲线的精确控制。
核心参数包括:
- x/y:矩形左上角坐标
- width/height:矩形宽高
- cornerRadius:圆角半径(可统一或分别设置四个角)
- fill/stroke:填充与描边样式
绘制原理是通过连接直线段和圆弧段来构造路径:
- 从左上角开始顺时针绘制
- 每个转角处用arcTo()方法创建1/4圆弧
- 圆弧与相邻边通过切线自然连接
重要提示:arcTo()的坐标参数是控制点而非终点,理解这一点才能准确控制曲线弧度
2. 基础绘制实现步骤
2.1 基本绘制函数实现
javascript复制function drawRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
// 左上角→右上角
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.arcTo(x + width, y, x + width, y + radius, radius);
// 右上角→右下角
ctx.lineTo(x + width, y + height - radius);
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
// 右下角→左下角
ctx.lineTo(x + radius, y + height);
ctx.arcTo(x, y + height, x, y + height - radius, radius);
// 左下角→左上角
ctx.lineTo(x, y + radius);
ctx.arcTo(x, y, x + radius, y, radius);
ctx.closePath();
}
2.2 参数校验与边界处理
实际开发中需要增加健壮性检查:
javascript复制// 半径不能超过矩形最小边的一半
const maxRadius = Math.min(width, height) / 2;
radius = Math.min(radius, maxRadius);
// 处理radius为0的情况
if(radius <= 0) {
ctx.rect(x, y, width, height);
return;
}
3. 高级绘制技巧
3.1 独立控制四个圆角
实际项目常需要单独控制每个角的半径:
javascript复制function drawRoundedRect(
ctx, x, y, width, height,
topLeft, topRight, bottomRight, bottomLeft
) {
ctx.beginPath();
// 左上角
ctx.moveTo(x + topLeft, y);
// 右上角
ctx.lineTo(x + width - topRight, y);
ctx.arcTo(x + width, y, x + width, y + topRight, topRight);
// 右下角
ctx.lineTo(x + width, y + height - bottomRight);
ctx.arcTo(x + width, y + height, x + width - bottomRight, y + height, bottomRight);
// 左下角
ctx.lineTo(x + bottomLeft, y + height);
ctx.arcTo(x, y + height, x, y + height - bottomLeft, bottomLeft);
// 返回左上角
ctx.lineTo(x, y + topLeft);
ctx.arcTo(x, y, x + topLeft, y, topLeft);
ctx.closePath();
}
3.2 性能优化方案
高频绘制时需要优化:
- 使用Path2D对象缓存路径:
javascript复制const roundedRect = new Path2D();
// 构建路径...
// 后续直接使用
ctx.fill(roundedRect);
- 批量绘制时使用状态管理:
javascript复制ctx.save();
ctx.fillStyle = '#f00';
drawRoundedRect(ctx, 10, 10, 100, 50, 5);
ctx.restore();
4. 常见问题与解决方案
4.1 边缘锯齿问题
现象:高DPI设备上出现边缘毛刺
解决方案:
javascript复制// 1. 获取设备像素比
const dpr = window.devicePixelRatio || 1;
// 2. 缩放Canvas
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.scale(dpr, dpr);
// 3. 绘制时使用整数坐标
Math.floor(x), Math.floor(y)
4.2 动画中的性能瓶颈
优化策略:
- 使用requestAnimationFrame
- 避免在动画中重复创建路径
- 对于静态背景使用离屏Canvas
javascript复制// 离屏Canvas示例
const offscreen = document.createElement('canvas');
const offCtx = offscreen.getContext('2d');
// 预先绘制...
// 主线程中直接绘制
ctx.drawImage(offscreen, 0, 0);
5. 实际应用案例
5.1 聊天气泡实现
javascript复制function drawSpeechBubble(ctx, x, y, width, height, radius, pointer) {
drawRoundedRect(ctx, x, y, width, height, radius);
// 绘制三角形指针
ctx.beginPath();
ctx.moveTo(x + width/2 - 10, y + height);
ctx.lineTo(x + width/2 + 10, y + height);
ctx.lineTo(x + width/2, y + height + 15);
ctx.closePath();
ctx.fill();
}
5.2 进度条圆角控制
javascript复制function drawProgressBar(ctx, x, y, width, height, radius, progress) {
// 背景
drawRoundedRect(ctx, x, y, width, height, radius);
ctx.fillStyle = '#eee';
ctx.fill();
// 进度
const progressWidth = width * Math.min(progress, 1);
if(progressWidth > radius) {
drawRoundedRect(ctx, x, y, progressWidth, height, radius);
ctx.fillStyle = '#09f';
ctx.fill();
}
}
6. 扩展知识
6.1 贝塞尔曲线原理
arcTo()底层使用二次贝塞尔曲线实现:
- 控制点:两条虚拟切线的交点
- 起点:当前路径点
- 终点:目标点
数学表达式:
code复制B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2
6.2 圆角矩形面积计算
精确计算需要考虑四个角的圆形区域:
code复制总面积 = 矩形面积 - 4个直角面积 + 4个1/4圆面积
= width*height - 4*radius² + π*radius²
这个计算在需要精确碰撞检测时非常有用。