参考: https://www.cnblogs.com/lufy/archive/2012/06/21/2558049.html
一、理论推理(想得通的方式)
- 想要元素平均分布在圆环上 =》那么元素之间的弧长相等 =》相邻元素所在半径形成的圆心角大小一致
- 角度数360° = 弧度数 2π =》1° = π/180 =》360°/2π = 53.7° = 1
- 在直角坐标系下,以x轴的非负轴作为起始边,逆时针旋转,圆心角为正
此时,单个元素中心点在圆弧上的坐标位置 (Ox+Math.cos(X)*R , Oy-Math.sin(X)*R),弧度数X = 角度数*π/180 。
二、canvas实践:
大前提:注意事项:
在canvas的2D绘图环境中,坐标系同窗口坐标系,左上角为原点,向右x正轴,向下y正轴。
所以坐标y改为Oy-Math.sin(X)*R,从视觉上达到与直角坐标系同样的效果。
1. 渲染一个元素
// 外圆 (X,Y) R
let X = 400,Y = 400,R = 150;
cxt.beginPath();
cxt.arc(X, Y, R, 0, Math.PI * 2);
cxt.stroke();
// 小圆
cxt.beginPath();
cxt.arc( X + Math.cos((30 * Math.PI) / 180 ) * R, Y - Math.sin((30 * Math.PI) / 180 ) * R, 50, 0, Math.PI * 2 );
cxt.closePath();
cxt.stroke();
2. 可借助数组下标,改变各个元素的圆心角,渲染一周8个元素
let n = 8;
for (let i = 0; i < n; i++) {
cxt.beginPath();
cxt.arc( X + Math.cos(((2 * Math.PI) / n) * i) * R, Y - Math.sin(((2 * Math.PI) / n) * i) * R, 50, 0,Math.PI * 2);
cxt.stroke();
}
其实,渲染方向与起始位都是很灵活的
3. 结合点阵规律,渲染多层环形
结合点阵规律 正a边形 第一层1,第二层a, 第三层2a, ... , 第n层 a(n-1)
但当下讨论的是除去中心点的点阵规律:因此不要第一层,则对于正a边形来说,第n层有a * n个点,n层共有a * n * (n + 1)) / 2 个点。
① 渲染一层
let a = 6; //正六边形
let level = 2; //第2层
let n = a * level; //该层需要分布元素个数
for (let i = 0; i < n; i++) {
cxt.beginPath();
cxt.arc(X + Math.sin(((2 * Math.PI) / n) * i) * R, Y - Math.cos(((2 * Math.PI) / n) * i) * R, 50, 0, Math.PI * 2);
cxt.stroke();
}
② 渲染多层
let a = 6; //正六边形
let levelNum = 2; //层数
let r = 30;
for (let level = 1; level <= levelNum; level++) {
let seatNum = a * level;
for (let seat = 0; seat < seatNum; seat++) {
cxt.beginPath();
cxt.arc(
X + Math.sin(((2 * Math.PI) / seatNum) * seat) * (R + 60 * (level - 1)),
Y - Math.cos(((2 * Math.PI) / seatNum) * seat) * (R + 60 * (level - 1)),
r,
0,
Math.PI * 2
);
cxt.stroke();
}
}
可以看到,如果不好好调整 影响第一层轨迹的R , 层之间的距离(这里是60),就会很丑
4. 在实际情况下,往往给定总数,去渲染多层,所以只要先排座、再渲染就好
let X = 300;
let Y = 300;
let R = 45; //影响第一排轨迹,即红圈
let distance = 50; //两排元素中心的间距
let r = 25;
let rankList = [];
// 画红圈
cxt.beginPath();
cxt.strokeStyle = '#ff0000'
cxt.arc(X, Y, R, 0, Math.PI * 2);
cxt.stroke();
// 排座,排在几排几座,好确定各小球圆心位置
function sortCoords(sum, a) { // 将总数sum的小球排成正a边形点阵
let level = 1;
let seat = -1;
let seatNum = a * level;
for (let i = 1; i <= sum; i++) {
if (i <= (a * level * (level + 1)) / 2) {
seat++;
} else {
level++;
seat = 0;
seatNum = a * level;
}
const tempX = X + Math.sin(((2 * Math.PI) / seatNum) * seat) * (R + distance * (level - 1));
const tempY = Y - Math.cos(((2 * Math.PI) / seatNum) * seat) * (R + distance * (level - 1));
rankList[i - 1] = { level, seat, x: tempX, y: tempY, r };
}
return rankList;
}
rankList = sortCoords(30, 5);
// 渲染小球
rankList.forEach((item) => {
cxt.beginPath();
cxt.strokeStyle = '#333'
cxt.arc(item.x, item.y, item.r, 0, Math.PI * 2);
cxt.stroke();
});
总结:其实通篇都是废话,最重要的是确定各元素的中心位置,总结出通用的位置公式 ,这样就解决了90% 。