円と三角関数で曲線を描く
四角形だけではなく、滑らかな曲線や複雑な形を描きたい——そのためには三角関数が必要です。
Math.sin() と Math.cos() は、ビジュアルコーディングで最もよく使う関数です。この2つを理解すれば、円・波・スパイラル・ローズ曲線・リサジュー図形など、無限の表現が手に入ります。
円を描く — arc()
Canvasで円を描くには ctx.arc() を使います。
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.fill(); // または ctx.stroke();
| 引数 | 意味 |
|---|---|
x, y | 円の中心座標 |
radius | 半径(ピクセル) |
startAngle | 描き始める角度( ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。 ) |
endAngle | 描き終わる角度( ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。 ) |
完全な円を描くには endAngle を Math.PI * 2 にします(360°分のラジアン)。
ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.fillStyle = '#00e5ff';
ctx.fill();
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.fillStyle = '#00e5ff';
ctx.fill(); ctx.beginPath() を忘れると前の描画経路が残り、意図しない形になります。新しい図形を始める前には必ず呼びましょう。
輪郭線を描く
fill() の代わりに stroke() を使うと、塗りつぶさずに輪郭だけ描けます。
ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 3;
ctx.stroke();
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 3;
ctx.stroke(); ラジアンとは
arc() の角度は** ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。 **という単位を使います。
- 0° = 0 ラジアン
- 180° =
Math.PI≈ 3.14159 ラジアン - 360° =
Math.PI * 2≈ 6.28318 ラジアン
変換式:ラジアン = 度数 × Math.PI / 180
// 度数からラジアンに変換するヘルパー
function deg(d) { return d * Math.PI / 180; }
ctx.arc(W/2, H/2, 100, deg(0), deg(270)); // 0°から270°まで
ビジュアルコーディングでは Math.PI をそのまま使うことが多く、* 2 で360°、/ 2 で90°などと組み合わせます。
sin と cos — 円周上の点を計算する
Math.sin() と Math.cos() の核心的な使い方は円周上の点座標を求めることです。
var angle = Math.PI * 2 * (i / n); // i番目の角度(0〜2π)
var x = cx + Math.cos(angle) * r; // x = 中心x + 半径 × cos
var y = cy + Math.sin(angle) * r; // y = 中心y + 半径 × sin
これで「中心 (cx, cy)、半径 r の円周を n 等分したときの i 番目の座標」が計算できます。
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var cx = W / 2, cy = H / 2, r = 110, n = 24;
for (var i = 0; i < n; i++) {
var a = Math.PI * 2 * (i / n);
var x = cx + Math.cos(a) * r;
var y = cy + Math.sin(a) * r;
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + (i / n * 360) + ', 90%, 60%)';
ctx.fill();
} sin波を描く
Math.sin() の値域は -1 〜 1。これをy座標にマッピングすれば波の曲線になります。
ctx.beginPath();
for (var x = 0; x <= W; x++) {
var y = H / 2 + Math.sin(x * 0.03) * 80;
// x * 0.03 → 周波数を調整(大きいほど細かい波)
// * 80 → 振幅(大きいほど大きい波)
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#00e5ff';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
for (var x = 0; x <= W; x++) {
var y = H / 2 + Math.sin(x * 0.03) * 90;
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#00e5ff';
ctx.lineWidth = 2;
ctx.stroke(); 複数の周波数を重ね合わせると複雑な波形になります。
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
for (var x = 0; x <= W; x++) {
var y = H / 2
+ Math.sin(x * 0.02) * 60
+ Math.sin(x * 0.05) * 30
+ Math.sin(x * 0.11) * 15;
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#ff6b9d';
ctx.lineWidth = 2;
ctx.stroke(); ローズ曲線(花)を描く
** パラメトリック曲線? パラメーターtを変化させてx=f(t), y=g(t)で点の軌跡を描く方法。ローズ曲線・リサジュー曲線など複雑な形状を少ない式で表現できる。 **の力を見てみましょう。ローズ曲線は r = cos(n × θ) という式で描けます。
r(極座標の半径) = cos(n × θ)
x = r × cos(θ)
y = r × sin(θ)
n を変えると花びらの枚数が変わります(奇数のnだとn枚、偶数のnだと2n枚)。
var n = 5; // 花びらの数
ctx.beginPath();
for (var i = 0; i <= 720; i++) {
var a = i * Math.PI / 180; // 0〜4π(720°)
var r = 120 * Math.cos(n * a); // 極座標の半径
var x = W / 2 + r * Math.cos(a);
var y = H / 2 + r * Math.sin(a);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#ff6b9d';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var n = 5;
ctx.beginPath();
for (var i = 0; i <= 720; i++) {
var a = i * Math.PI / 180;
var r = 110 * Math.cos(n * a);
var x = W / 2 + r * Math.cos(a);
var y = H / 2 + r * Math.sin(a);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#ff6b9d';
ctx.lineWidth = 1.5;
ctx.stroke(); n の値を変えると形が変わります。n = 3(3枚)、n = 7(7枚)などで試してみましょう。
スパイラルを描く
角度 θ を増やしながら半径 r も増やすと、螺旋(スパイラル)になります。
for (var i = 0; i < 800; i++) {
var a = i * 0.3; // 角度(どんどん増える)
var r = i * 0.25; // 半径(角度に比例して増える)
var x = W / 2 + Math.cos(a) * r;
var y = H / 2 + Math.sin(a) * r;
ctx.fillStyle = 'hsl(' + (i * 0.45) + ', 80%, 60%)';
ctx.fillRect(x - 1.5, y - 1.5, 3, 3);
}
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
for (var i = 0; i < 800; i++) {
var a = i * 0.3;
var r = i * 0.25;
var x = W / 2 + Math.cos(a) * r;
var y = H / 2 + Math.sin(a) * r;
ctx.fillStyle = 'hsl(' + (i * 0.45) + ', 80%, 60%)';
ctx.fillRect(x - 1.5, y - 1.5, 3, 3);
} 黄金角スパイラル(ひまわり)
自然界の植物の種の配置に現れる黄金角(約137.5°)を使うと、ひまわりのような美しいパターンが生まれます。
var golden = Math.PI * (3 - Math.sqrt(5)); // ≈ 2.399(黄金角ラジアン)
for (var i = 0; i < 600; i++) {
var a = i * golden;
var r = Math.sqrt(i) * 8;
...
}
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var golden = Math.PI * (3 - Math.sqrt(5));
for (var i = 0; i < 600; i++) {
var a = i * golden;
var r = Math.sqrt(i) * 7;
var x = W / 2 + Math.cos(a) * r;
var y = H / 2 + Math.sin(a) * r;
var size = Math.sqrt(i) * 0.4 + 1.5;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + (i * 0.6) + ', 80%, 60%)';
ctx.fill();
} まとめ
この回でやったこと:
ctx.arc()で円と円弧を描いた- ** ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。 **の概念を理解した
Math.cos(a) * r、Math.sin(a) * rで円周上の座標を計算した- パラメトリック曲線? パラメーターtを変化させてx=f(t), y=g(t)で点の軌跡を描く方法。ローズ曲線・リサジュー曲線など複雑な形状を少ない式で表現できる。 (ローズ曲線・スパイラル)で複雑な形を作った
- 黄金角で自然界のパターンを再現した
次回はここに時間の概念を加えます。requestAnimationFrame を使ってアニメーションを作れば、これらの図形が動き出します。