简单记录 Canvas 中绘制椭圆形
虽然可以直接用 CanvasRenderingContext2D.ellipse() (Canvas 2D API 添加椭圆路径的方法) 方法去绘制。但因为考虑到浏览器兼容性的问题,所以还需要有个备选方案。
Canvas中绘制椭圆的方法有压缩法,计算法,贝塞尔曲线法等多种方式,下面代码中用的是最简单的压缩法。
import { useEffect, useRef } from 'react';
export default function GroupCanvas() {
const ref = useRef(null);
useEffect(() => {
if (!ref.current.getContext) return;
let ctx = ref.current.getContext('2d');
ctx.strokeStyle = '#289FD9';
ctx.lineWidth = 1;
ellipse(75, 120, 37, 25, 0, 0, Math.PI * 2);
function ellipse(
x,
y,
radiusX,
radiusY,
rotation,
startAngle,
endAngle,
anticlockwise,
) {
ctx.beginPath();
if (ctx.ellipse) {
ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
} else {
let r = radiusX > radiusY ? radiusX : radiusY; //用大的数为半径
let scaleX = radiusX / r; //计算缩放的x轴比例
let scaleY = radiusY / r; //计算缩放的y轴比例
ctx.save(); //保存当前环境的状态。
ctx.translate(x, y); //移动到圆心位置
ctx.rotate(rotation); //进行旋转
ctx.scale(scaleX, scaleY); //进行缩放
ctx.arc(0, 0, r, startAngle, endAngle, anticlockwise); //绘制圆形
ctx.restore(); //返回之前保存过的路径状态和属性。
}
ctx.stroke();
}
}, []);
return <canvas ref={ref}></canvas>;
}
最后下面是我需要用到椭圆形的demo。可通过下面配置项,动态生成。
import { useEffect, useRef } from 'react';
export default function GroupCanvas(props) {
const ref = useRef(null);
useEffect(() => {
if (!ref.current.getContext) return;
let {
groupSum,
circleCenterX,
circleCenterY,
radiusX,
radiusY,
textInCircle,
firstFontYInCircleY,
fontSpacing,
curveTopColor,
curveBottomColor,
} = { ...props };
let ctx = ref.current.getContext('2d');
//清空canvas
ctx.clearRect(0, 0, ref.current.width, ref.current.height);
ctx.fillStyle = '#FF8D02';
ctx.textAlign = 'center';
ctx.font = '400 16px SourceHanSansCN';
ctx.lineWidth = 1;
for (let i = 0; i < groupSum; i++) {
//绘制椭圆
ellipse(
ctx,
circleCenterX[i],
circleCenterY,
radiusX,
radiusY,
0,
0,
Math.PI * 2,
);
// 绘制椭圆内的字
for (let t = 0; t < textInCircle[i].length; t++) {
ctx.fillText(
textInCircle[i][t],
circleCenterX[i],
circleCenterY - radiusY + firstFontYInCircleY + fontSpacing * t,
);
}
// 绘制上曲线
if (i === groupSum - 1) {
let curveRadiusX = (circleCenterX[groupSum - 1] - circleCenterX[0]) / 2;
ellipse(
ctx,
circleCenterX[0] + curveRadiusX,
circleCenterY - radiusY,
curveRadiusX,
80,
0,
0,
Math.PI,
true,
);
trilateral(
ctx,
{ x: circleCenterX[0], y: circleCenterY - radiusY },
'bottom',
'#289FD9',
15,
);
trilateral(
ctx,
{
x: circleCenterX[groupSum - 1],
y: circleCenterY - radiusY,
},
'bottom',
'#289FD9',
345,
);
//曲线上字
curveOnWord(
ctx,
'10%(92112)',
circleCenterX[0] + curveRadiusX,
circleCenterY - radiusY - 80 - 10,
);
} else if (groupSum > 2) {
let curveRadiusX = (circleCenterX[i + 1] - circleCenterX[i]) / 2;
ellipse(
ctx,
circleCenterX[i] + curveRadiusX,
circleCenterY - radiusY,
curveRadiusX,
18,
0,
0,
Math.PI,
true,
curveTopColor[i],
);
trilateral(
ctx,
{ x: circleCenterX[i], y: circleCenterY - radiusY },
'bottom',
curveTopColor[i],
45,
);
trilateral(
ctx,
{
x: circleCenterX[i + 1],
y: circleCenterY - radiusY,
},
'bottom',
curveTopColor[i],
315,
);
//曲线上字
curveOnWord(
ctx,
'10%(92112)',
circleCenterX[i] + curveRadiusX,
circleCenterY - radiusY - 18 - 10,
);
}
//下曲线
if (groupSum > 3 && i >= 2) {
ellipse(
ctx,
circleCenterX[i - 1],
circleCenterY + radiusY,
(circleCenterX[i] - circleCenterX[i - 2]) / 2,
35,
0,
0,
Math.PI,
false,
curveBottomColor[i - 2],
);
trilateral(
ctx,
{ x: circleCenterX[i - 2], y: circleCenterY + radiusY },
'top',
curveBottomColor[i - 2],
-35,
);
trilateral(
ctx,
{ x: circleCenterX[i], y: circleCenterY + radiusY },
'top',
curveBottomColor[i - 2],
35,
);
//曲线上字
curveOnWord(
ctx,
'10%(92112)',
circleCenterX[i - 1],
circleCenterY + radiusY + 35+20,
);
}
}
}, [props.groupSum]);
//曲线上的字体
function curveOnWord(ctx, content, x, y) {
ctx.save();
ctx.font = '400 12px SourceHanSansCN';
ctx.fillStyle = '#fff';
ctx.fillText(content, x, y);
ctx.restore();
}
//绘制三角形
function trilateral(ctx, where, direction = 'top', color, rotate = 0) {
ctx.save();
ctx.beginPath();
ctx.moveTo(where.x, where.y);
ctx.translate(where.x, where.y);
ctx.rotate((rotate * Math.PI) / 180);
if (direction === 'top') {
ctx.lineTo(-5, +9);
ctx.lineTo(+5, +9);
} else {
ctx.lineTo(-5, -9);
ctx.lineTo(+5, -9);
}
if (color) {
ctx.fillStyle = color;
} else {
ctx.fillStyle = '#289FD9';
}
ctx.fill();
ctx.restore();
}
//绘制椭圆曲线
function ellipse(
ctx,
x,
y,
radiusX,
radiusY,
rotation,
startAngle,
endAngle,
anticlockwise,
color = '#289FD9',
) {
ctx.strokeStyle = color;
ctx.save();
ctx.beginPath();
if (ctx.ellipse) {
ctx.ellipse(
x,
y,
radiusX,
radiusY,
rotation,
startAngle,
endAngle,
anticlockwise,
);
ctx.stroke();
} else {
let r = radiusX > radiusY ? radiusX : radiusY;
let scaleX = radiusX / r;
let scaleY = radiusY / r;
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.scale(scaleX, scaleY);
ctx.arc(0, 0, r, startAngle, endAngle, anticlockwise);
}
ctx.stroke();
ctx.restore();
}
return (
<canvas
ref={ref}
width={160 + props.groupSum * 150 + (props.groupSum - 1) * 60}
height={386}
style={{
...props.outStyle,
}}
></canvas>
);
}
GroupCanvas.defaultProps = {
//椭圆的xy坐标
circleCenterX: [155, 367, 579, 791, 1003],
circleCenterY: 216,
//椭圆xy半径
radiusX: 75,
radiusY: 50,
//椭圆内的字
textInCircle: [
['10%', '(10025)', 'A区团'],
['10%', '(10025)', 'A区团'],
['10%', '(10025)', 'A区团'],
['10%', '(10025)', 'A区团'],
['10%', '(10025)', 'A区团'],
],
//椭圆内第一个字距离圆顶部距离
firstFontYInCircleY: 30,
//椭圆内字体间距
fontSpacing: 25,
curveTopColor: ['#D99E28', '#BC28D9', '#D92828', '#2928D9'],
curveBottomColor: ['#D9C628', '#289FD9', '#D92828'],
};
参考链接:
https://blog.csdn.net/gao_xu_520/article/details/58588020
https://www.cnblogs.com/flybeijing/p/canvas_ellipse.html
https://www.cnblogs.com/fangsmile/p/9530226.html
发表评论 (审核通过后显示评论):