Canvas绘制旋转对称图案

背景

遇到了一个显示色块的问题,这些圆形色块如果顺序排开,横向空间会显得过长,九宫格和饼状统计图的样式就不太美观。因此,提出了如上图所示的排列方案。

解决思路

首先分析这种排列方式:小圆形相互堆叠,围成一个类似多边形的形状。通过观察和思考,我们发现这些小圆的圆心是平均分布在一个大圆的圆周上的,如下图所示:

基于这个思路,我们可以先根据小圆的数量计算出它们的圆心位置。

然后,通过控制绘制顺序,使后绘制的小圆覆盖先绘制的小圆,从而达到堆叠效果。

位置计算方法

原理

既然是圆形,而且是确定环形的位置,那肯定得选择极坐标系

在极坐标系中,我们知道点的位置由以下两个参数确定:

  • r (半径或径向距离): 从原点到点的距离。
  • θ (角度): 从极轴到点的方向,与极轴的夹角,通常以弧度 radian 表示。

既然是平均分布的,那每个圆心所在的角度应该是2π/n ,其中n就是圆的数量

也就是把360°进行均分

首先应该计算第一个小圆心在极坐标系中的角度,circleCount是圆形的总数量,最后i就是第几个圆,

第一个圆的i就是0

const angle = (2 * Math.PI / circleCount) * i; 

然后把极坐标转换成直角坐标,因为canvas不能直接传入极坐标。

极坐标和直角坐标转换(可以不看)

我们知道极坐标和直角坐标的转换公式是:

$$ x_i = centerX + largeRadius \cdot \cos\left(\frac{2\pi \cdot i}{n}\right) $$
$$ y_i = centerY + largeRadius \cdot \sin\left(\frac{2\pi \cdot i}{n}\right) $$

其中:

  • centerXcenterY 是大圆的圆心坐标。
  • largeRadius 是大圆的半径,决定了小圆心距大圆心的距离。
  • i 是小圆的索引,n 是小圆的总数。

假设有 5 个小圆 (n=5),这些小圆均匀分布在一个半径为 60 像素的大圆圆周上。对于第一个小圆(i=0),它的角度$ θ_0 $为 0 度(即 0 弧度),因此圆心的坐标为:

$$ x_0 = centerX + 60 \cdot \cos(0) = centerX + 60 $$ $$ y_0 = centerY + 60 \cdot \sin(0) = centerY + 0 $$

对于第二个小圆(i=1),它的角度 $ 0_1 $ 为:

$$ \theta_1 = \frac{2\pi \cdot 1}{5} = \frac{2\pi}{5} $$

相应的圆心坐标为:

$$ x_1 = centerX + 60 \cdot \cos\left(\frac{2\pi}{5}\right) $$
$$ y_1 = centerY + 60 \cdot \sin\left(\frac{2\pi}{5}\right) $$

轨迹圆(大圆)半径的确定

通过观察发现,如果两个小圆和五个小圆使用的大圆半径一样的画,美观度很低。

两个圆比例合适的时候,五个圆就显得有点紧凑。

五个圆比例合适的时候,而两个圆就显得有点间距过大。

所以就需要动态的调整圆的紧凑程度,也就是轨迹圆的半径。

经过手动调整后目测认为,在有 2 个小圆时候,轨迹圆的半径为小圆的70%最美观

5个圆的时候大圆和小圆半径相等最美观,所以2,3,4,5按比例调整百分比,

轨迹圆的半径分别是小圆半径的70%,80%,90%,100%,得到代码:

const largeRadius = smallRadius * (0.7 + (circleCount - 2) / 10);

代码实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas Circle Icon</title>
</head>

<body>
<canvas id="circleCanvas1" width="150" height="150" count="1" style="margin: 5px;"></canvas>
<canvas id="circleCanvas2" width="150" height="150" count="2" style="margin: 5px;"></canvas>
<canvas id="circleCanvas3" width="150" height="150" count="3" style="margin: 5px;"></canvas>
<canvas id="circleCanvas4" width="150" height="150" count="4" style="margin: 5px;"></canvas>
<canvas id="circleCanvas5" width="150" height="150" count="5" style="margin: 5px;"></canvas>

<script>
    function drawCircles(canvasId) {
        const canvas = document.getElementById(canvasId);
        const ctx = canvas.getContext('2d');
        const count = parseInt(canvas.getAttribute('count'));

        const colors = ['#1E90FF', '#800080', '#FFD700', '#32CD32', '#FF4500'];

        const centerX = canvas.width / 2; // 画布中心X坐标
        const centerY = canvas.height / 2; // 画布中心Y坐标
        const smallRadius = 20; // 小圆的半径
        const largeRadius = smallRadius * (0.7 + (count - 2) / 10); // 中心圆的半径,减小以使小圆重叠



        for (let i = 0; i < count; i++) {
            const angle = (2 * Math.PI / count) * i; // 计算每个小圆的角度
            const x = centerX + largeRadius * Math.cos(angle); // 计算X坐标
            const y = centerY + largeRadius * Math.sin(angle); // 计算Y坐标

            ctx.beginPath();
            ctx.arc(x, y, smallRadius, 0, Math.PI * 2);
            ctx.fillStyle = colors[i];
            ctx.fill();
        }
    }

    drawCircles('circleCanvas1');
    drawCircles('circleCanvas2');
    drawCircles('circleCanvas3');
    drawCircles('circleCanvas4');
    drawCircles('circleCanvas5');
</script>

</body>

</html>

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇