view source

JavaScript

document.title = '円形定規で幾何学模様を描くおもちゃ スピログラフ';

const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 800;
document.getElementById('demo').appendChild(canvas);
const ctx = canvas.getContext('2d');
document.getElementById('demo').style.textAlign = 'center';
canvas.classList.add('square');


const R = 400;
let r = 90 + Math.floor(Math.random() * 11) * 10;
let vertex = Math.floor(Math.random() * 6) + 3; // 3〜8角形
const offset = 60;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;

let t = 0;
let drawing = false;
let points = [];
let speed = 0.04; // 精密な閉じパターンには小さめがおすすめ
let teeth = 360;
let polygonSides = vertex;

// スピログラフを閉じるための正確な周期
let stepsToClose = Math.PI * 2 * r / gcd(R, r);

function drawGear(x, y, radius, teeth, angle, color = '#aaa') {
  const step = (Math.PI * 2) / (teeth * 2);
  const outer = radius;
  const inner = radius * 0.98;

  ctx.beginPath();
  for (let i = 0; i <= teeth * 2; i++) {
    const a = angle + i * step;
    const r = i % 2 === 0 ? outer : inner;
    const px = x + r * Math.cos(a);
    const py = y + r * Math.sin(a);
    if (i === 0) ctx.moveTo(px, py);
    else ctx.lineTo(px, py);
  }
  ctx.closePath();
  ctx.strokeStyle = color;
  ctx.stroke();
}

function drawPolygon(cx, cy, radius, sides, rotation = 0, color = '#888') {
  ctx.beginPath();
  for (let i = 0; i <= sides; i++) {
    const angle = rotation + (i * 2 * Math.PI) / sides;
    const x = cx + radius * Math.cos(angle);
    const y = cy + radius * Math.sin(angle);
    if (i === 0) ctx.moveTo(x, y);
    else ctx.lineTo(x, y);
  }
  ctx.strokeStyle = color;
  ctx.stroke();
}

function drawSmallShape(t) {
  const cx = centerX + (R - r) * Math.cos(t);
  const cy = centerY + (R - r) * Math.sin(t);
  const rot = -((R - r) / r) * t;

  if (vertex > 2) {
    drawPolygon(cx, cy, r, polygonSides, rot, '#888');
  } else {
    drawGear(cx, cy, r, teeth, rot, '#888');
  }
}

function computeDrawingPoint(t) {
  const cx = centerX + (R - r) * Math.cos(t);
  const cy = centerY + (R - r) * Math.sin(t);
  const rot = -((R - r) / r) * t;

  if (vertex > 2) {
    const angle = rot;
    const x = cx + offset * Math.cos(angle);
    const y = cy + offset * Math.sin(angle);
    return { x, y };
  } else {
    const x = cx + offset * Math.cos(((R - r) / r) * t);
    const y = cy - offset * Math.sin(((R - r) / r) * t);
    return { x, y };
  }
}

function draw() {
  if (!drawing) return;

  const point = computeDrawingPoint(t);
  points.push(point);

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  drawGear(centerX, centerY, R, teeth, 0, '#aaa');
  drawSmallShape(t);

  // グラデーション描画
  for (let i = 1; i < points.length; i++) {
    const hue = (i / points.length) * 360;
    ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
    ctx.beginPath();
    ctx.moveTo(points[i - 1].x, points[i - 1].y);
    ctx.lineTo(points[i].x, points[i].y);
    ctx.stroke();
  }

  t += speed;
  if (t < stepsToClose) {
    requestAnimationFrame(draw);
  } else {
    setTimeout(clearSmallShapeOnly, 800);
  }
}

function clearSmallShapeOnly() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  //drawGear(centerX, centerY, R, teeth, 0, '#aaa');
  points.push(points[0]);
  for (let i = 1; i < points.length; i++) {
    const hue = (i / points.length) * 360;
    ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
    ctx.beginPath();
    ctx.moveTo(points[i - 1].x, points[i - 1].y);
    ctx.lineTo(points[i].x, points[i].y);
    ctx.stroke();
  }

  drawing = false;
}

function gcd(a, b) {
  return b === 0 ? a : gcd(b, a % b);
}

canvas.addEventListener('click', () => {
  if (drawing) return;
  points = [];
  t = 0;
  r = 90 + Math.floor(Math.random() * 11) * 10;
  vertex = Math.floor(Math.random() * 6) + 3;
  stepsToClose = Math.PI * 2 * r / gcd(R, r);
  polygonSides = vertex;
  drawing = true;
  draw();
});

ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGear(centerX, centerY, R, teeth, 0, '#aaa');
canvas.click();

CSS

HTML

ページのソースを表示 : Ctrl+U , DevTools : F12

view-source:https://hi0a.com/demo/-js/js-spirograph/

ABOUT

hi0a.com 「ひまあそび」は無料で遊べるミニゲームや便利ツールを公開しています。

プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。

必要な機能の関数をコピペ利用したり勉強に活用できます。

プログラムの動作サンプル結果は画面上部に出力表示されています。

環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。

画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。

仕様変更、API廃止等、実験途中で動かないページもあります。