GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

스피로그래프 그리기 9가지 — 톱니비율로 그리는 기하 무늬

톱니바퀴 두 개의 이빨 수가 서로 소수일 때 곡선은 닫히고, 비율이 어긋나는 순간 종잇장은 끝없이 헤맨다 — 03번은 그 비율로 무늬 대칭 축 수를 계산한다. 스피로그래프 그리기를 캔버스 2d 좌표 계산으로 옮긴 아홉 장면이고, 파일을 내려받으면 그대로 켜진다.

톱니 곡선 아홉 무늬를 화면에서 바로 확인한다

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

스피로그래프 그리기 9가지 — 이빨 수 비율이 무늬 전체를 결정한다

아홉 장면의 순서는 계산이 한 겹씩 쌓이는 순서다. 01 과 02 가 안쪽·바깥쪽 좌표식을 보여 주고, 03 이 이빨 수의 최대공약수로 대칭 축 수를 뽑고, 04 가 펜 구멍을 네 개로 늘리고, 05 가 지난 궤적을 옅게 지우고, 06 이 톱니 원을 곡선 위에 함께 그리고, 07 이 포인터 거리를 이빨 수로 바꾸고, 08 이 완성된 도형을 통째로 돌리고, 09 가 이빨 수 쌍이 다른 네 곡선을 한 화면에 모은다. 어느 장면이든 곡선을 정하는 것은 굴러가는 톱니와 링의 이빨 수 비율이다.

기법별 코드로 보는 톱니 곡선 계산

스피로그래프 사이클로이드 계열 곡선은 큰 링의 안쪽이나 바깥을 굴러가는 작은 톱니 위의 한 점이 남기는 궤적이고, 안쪽이면 하이포트로코이드, 바깥이면 에피트로코이드라고 부른다. 아래 코드는 각 데모 index.html 의 연속된 줄을 그대로 옮겼으며, 항목마다 이빨 수 상수와 gcd 로 얻은 바퀴 수가 곡선 한 점을 정하는 식에 어떻게 들어가는지 따라가면 된다.

01안쪽 톱니를 따라 도는 기본 스피로그래프 곡선

point(t) 가 링 반지름 R 과 굴러가는 톱니 반지름 r 의 차를 팔 길이로 삼고, 펜 구멍 거리 d 를 r 의 0.6 배로 둔 하이포트로코이드 좌표를 한 점씩 돌려준다. gcd(72, 27) 이 9 라서 TURNS 는 3 이 되어 곡선이 3 바퀴째에 출발점으로 돌아와 닫히며, 곡선 안쪽 반투명 채움과 펜 끝을 따라가는 배경 그라디언트는 계산이 아니라 그 위에 얹은 표현 층이다. 이 장면은 누적면적 65.7%·평균 강도 22.7·23/23 프레임이 움직인다.

안쪽 톱니를 따라 도는 기본 스피로그래프 곡선
새 창에서 열기

01 · 안쪽 톱니를 따라 도는 기본 스피로그래프 곡선 — 측정 누적면적 65.706% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

GEAR_RING_TEETHSEGMENTS_PER_TURNshadowBlur
  function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { var t = b; b = a % b; a = t; } return a; }

  var R = GEAR_RING_TEETH * TOOTH_UNIT;
  var r = GEAR_ROLL_TEETH * TOOTH_UNIT;
  var d = r * PEN_TIP_RATIO;
  var g = gcd(GEAR_RING_TEETH, GEAR_ROLL_TEETH);
  var TURNS = GEAR_ROLL_TEETH / g;
  var TOTAL_T = TURNS * Math.PI * 2;
  var CYCLE_SECONDS = 6;
  var SPEED = TOTAL_T / CYCLE_SECONDS;

  function point(t) {
    var k = (R - r) / r;
    return {
      x: (R - r) * Math.cos(t) + d * Math.cos(k * t),
      y: (R - r) * Math.sin(t) - d * Math.sin(k * t)
    };
  }

02바깥으로 굴러가는 톱니가 그리는 바깥 곡선

같은 구조에서 R - r 이 R + r 로, k 도 (R + r) / r 로 바뀌고 펜 항의 부호까지 달라져, 안쪽으로 말리던 곡선이 바깥으로 꽃잎을 뻗는다. gcd(96, 36) 이 12 라서 TURNS 는 3 이고, TOOTH_UNIT 을 0.6 으로 줄여 최대 반경이 92.2 로 CALC_DIM 200 의 반폭 100 안에 들어온다. 이 장면은 누적면적 66.2%·평균 강도 26.3·23/23 프레임이 움직인다.

바깥으로 굴러가는 톱니가 그리는 바깥 곡선
새 창에서 열기

02 · 바깥으로 굴러가는 톱니가 그리는 바깥 곡선 — 측정 누적면적 66.216% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

GEAR_TEETH_OUTERTOOTH_UNITdrawFull
  function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { var t = b; b = a % b; a = t; } return a; }

  var R = GEAR_TEETH_OUTER * TOOTH_UNIT;
  var r = GEAR_ROLL_TEETH * TOOTH_UNIT;
  var d = r * PEN_TIP_RATIO;
  var g = gcd(GEAR_TEETH_OUTER, GEAR_ROLL_TEETH);
  var TURNS = GEAR_ROLL_TEETH / g;
  var TOTAL_T = TURNS * Math.PI * 2;
  var CYCLE_SECONDS = 6;
  var SPEED = TOTAL_T / CYCLE_SECONDS;

  function point(t) {
    var k = (R + r) / r;
    return {
      x: (R + r) * Math.cos(t) - d * Math.cos(k * t),
      y: (R + r) * Math.sin(t) - d * Math.sin(k * t)
    };
  }

03이빨 수 비율로 대칭 축 수가 정해지는 무늬

gcd(90, 24) 가 6 이라서 AXIS_COUNT 는 90 / 6 으로 15, TURN_COUNT 는 24 / 6 으로 4 가 되고, 화면 위 배지가 대칭 축 15개와 4바퀴 닫힘으로 그 값을 그대로 보여 준다. 스피로그래프 최소공배수로 풀면 90 과 24 의 최소공배수 360 을 90 으로 나눈 4 가 닫힘 바퀴 수이고 24 로 나눈 15 가 대칭 축 수다. 이 장면은 누적면적 59.4%·평균 강도 19.5·23/23 프레임이 움직인다.

이빨 수 비율로 대칭 축 수가 정해지는 무늬
새 창에서 열기

03 · 이빨 수 비율로 대칭 축 수가 정해지는 무늬 — 측정 누적면적 59.435% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

PEN_RADIUS_RATIOAXIS_COUNTTURN_COUNT
  function gcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { var t = b; b = a % b; a = t; } return a; }

  var R = GEAR_RING_TEETH * TOOTH_UNIT;
  var r = GEAR_ROLL_TEETH * TOOTH_UNIT;
  var d = r * PEN_RADIUS_RATIO;
  var g = gcd(GEAR_RING_TEETH, GEAR_ROLL_TEETH);
  var AXIS_COUNT = GEAR_RING_TEETH / g;
  var TURN_COUNT = GEAR_ROLL_TEETH / g;
  var TOTAL_T = TURN_COUNT * Math.PI * 2;
  var CYCLE_SECONDS = 6;
  var SPEED = TOTAL_T / CYCLE_SECONDS;

  badge.textContent = '대칭 축 ' + AXIS_COUNT + '개 · ' + TURN_COUNT + '바퀴 닫힘';

  function point(t) {
    var k = (R - r) / r;
    return {
      x: (R - r) * Math.cos(t) + d * Math.cos(k * t),
      y: (R - r) * Math.sin(t) - d * Math.sin(k * t)
    };
  }

04구멍 여러 개가 동시에 그리는 파스텔 톱니 무늬

PEN_RATIOS 네 값(0.35·0.5·0.65·0.8)이 같은 톱니의 펜 구멍 네 곳이 되어 drawPen(idx, tMax) 에서 각자 d = r * PEN_RATIOS[idx] 로 좌표를 따로 계산하고, 색은 PEN_COLORS 에서 하나씩 받는다. 시작 각도는 seedRand(43304) 로 고정한 PEN_PHASE 가 펜마다 다르게 밀어서 네 곡선이 겹치되 포개지지는 않는다. 이 장면은 누적면적 10.1%·평균 강도 38.2·23/23 프레임이 움직인다.

구멍 여러 개가 동시에 그리는 무지개 톱니 무늬
새 창에서 열기

04 · 구멍 여러 개가 동시에 그리는 무지개 톱니 무늬 — 측정 누적면적 10.149% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

PEN_RATIOSPEN_COLORSPEN_PHASE
  function drawPen(idx, tMax) {
    var d = r * PEN_RATIOS[idx];
    var phase = PEN_PHASE[idx];
    ctx.strokeStyle = PEN_COLORS[idx];
    ctx.lineWidth = Math.max(2.4, scale * 0.032);
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';
    var steps = Math.max(2, Math.round((tMax / (Math.PI * 2)) * SEGMENTS_PER_TURN));
    ctx.beginPath();
    for (var i = 0; i <= steps; i += 1) {
      var t = (i / steps) * tMax;
      var s = toScreen(point(t + phase, d));
      if (i === 0) ctx.moveTo(s.x, s.y); else ctx.lineTo(s.x, s.y);
    }
    ctx.stroke();
    ctx.save();
    ctx.fillStyle = PEN_COLORS[idx].replace('.85', '.13');
    ctx.fill('evenodd');
    ctx.restore();

05궤적이 서서히 사라지며 계속 그리는 장면

simStep() 이 프레임마다 배경색 사각형을 FADE_ALPHA 만큼 덮어 지난 선을 옅게 만든 다음, 이전 점 prev 에서 새 점까지 선 한 토막만 새로 긋는다. FADE_ALPHA 는 3.0 / TRAIL_FADE_FRAMES 이므로 240 일 때 0.0125 이고, 곡선 전체를 다시 계산하지 않고도 궤적이 서서히 사라진다. 이 장면은 누적면적 49.9%·평균 강도 18.8·23/23 프레임이 움직인다.

궤적이 서서히 사라지며 계속 그리는 장면
새 창에서 열기

05 · 궤적이 서서히 사라지며 계속 그리는 장면 — 측정 누적면적 49.931% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

TRAIL_FADE_FRAMESFADE_ALPHAsimStep
  function simStep() {
    ctx.fillStyle = 'rgba(23,20,26,' + FADE_ALPHA + ')';
    ctx.fillRect(0, 0, cw, ch);
    t += ANGLE_SPEED * (STEP_MS / 1000);
    var p = toScreen(point(t));
    ctx.strokeStyle = 'rgba(255,247,230,.95)';
    ctx.shadowColor = 'rgba(255,214,200,.7)';
    ctx.shadowBlur = Math.max(8, scale * 0.14);
    ctx.lineWidth = Math.max(3, scale * 0.045);
    ctx.lineJoin = 'round';
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(prev.x, prev.y);
    ctx.lineTo(p.x, p.y);
    ctx.stroke();
    ctx.shadowBlur = 0;
    var halo = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, Math.max(cw, ch) * 0.34);
    halo.addColorStop(0, 'rgba(255,200,215,.075)');
    halo.addColorStop(0.5, 'rgba(190,225,255,.04)');
    halo.addColorStop(1, 'rgba(190,225,255,0)');
    ctx.fillStyle = halo;
    ctx.fillRect(0, 0, cw, ch);
    prev = p;
  }

06톱니와 펜이 곡선 위에 같이 보이는 가이드

penAt(t) 가 펜 끝을, rollCenterAt(t) 가 굴러가는 톱니의 중심을 같은 각도 t 로 돌려주어, 톱니 원과 중심에서 펜까지 잇는 선이 곡선 위에 함께 그려진다. gcd(60, 24) 가 12 라서 TURNS 는 2, AXIS_COUNT 는 5 이고, HOLD_FRACTION 0.84 지점에서 곡선이 완성되어 잠시 멈춘다. 이 장면은 누적면적 59.2%·평균 강도 62.8·23/23 프레임이 움직인다.

톱니와 펜이 곡선 위에 같이 보이는 가이드
새 창에서 열기

06 · 톱니와 펜이 곡선 위에 같이 보이는 가이드 — 측정 누적면적 59.176% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

HOLD_FRACTIONAXIS_COUNTrollCenterAt
  function gcd(a, b) { while (b) { var t = b; b = a % b; a = t; } return a; }
  var g = gcd(GEAR_RING_TEETH, GEAR_ROLL_TEETH);
  var TURNS = GEAR_ROLL_TEETH / g;
  var AXIS_COUNT = GEAR_RING_TEETH / g;

  var R = GEAR_RING_TEETH * TOOTH_UNIT;
  var r = GEAR_ROLL_TEETH * TOOTH_UNIT;
  var d = r * PEN_TIP_RATIO;
  var K = (R - r) / r;

  function penAt(t) {
    return {
      x: (R - r) * Math.cos(t) + d * Math.cos(K * t),
      y: (R - r) * Math.sin(t) - d * Math.sin(K * t)
    };
  }
  function rollCenterAt(t) {
    return { x: (R - r) * Math.cos(t), y: (R - r) * Math.sin(t) };
  }

07포인터 거리로 이빨 비율이 바뀌는 무늬

포인터와 화면 중심 사이 거리를 POINTER_MAX_DIST 90 으로 나눈 값이 rebuildForRatio() 로 들어가 굴러가는 톱니 이빨 수를 14 에서 34 사이 정수로 정하고, 값이 바뀔 때만 computePath() 가 곡선 점 배열을 새로 만든다. 포인터가 없어도 방향키 왼쪽·오른쪽이 KEY_STEP 0.08 씩 같은 비율을 움직이므로 키보드만으로도 무늬가 바뀐다. 이 장면은 누적면적 11.3%·평균 강도 103.6·23/23 프레임이 움직인다.

포인터 거리로 이빨 비율이 바뀌는 무늬
새 창에서 열기

07 · 포인터 거리로 이빨 비율이 바뀌는 무늬 — 측정 누적면적 11.26% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

POINTER_MAX_DISTROLL_TEETH_MINROLL_TEETH_MAX
  function rebuildForRatio(ratio) {
    ratioParam = Math.max(0, Math.min(1, ratio));
    var newRoll = Math.round(ROLL_TEETH_MIN + ratioParam * (ROLL_TEETH_MAX - ROLL_TEETH_MIN));
    if (newRoll !== currentRoll) {
      currentRoll = newRoll;
      pathData = computePath(currentRoll);
    }
  }

  function handlePointer(px, py) {
    var dist = Math.hypot(px, py);
    markerAngle = Math.atan2(py, px);
    markerDist = Math.min(POINTER_MAX_DIST, dist);
    rebuildForRatio(dist / POINTER_MAX_DIST);
  }

08완성 무늬가 도는 회전 판 장면

곡선 좌표는 pathData 에 한 번만 담고, 프레임마다 ctx.rotate(rot) 로 캔버스 좌표계를 돌려 같은 점 배열을 그대로 다시 긋는다. gcd(48, 18) 이 6 이라 AXIS_COUNT 는 8 이고, ROTATE_SPEED 로 9초에 한 바퀴 도는 동안 접시 위로 GLOSS_SWEEP_SECONDS 3.4초 주기의 광택 띠가 쓸고 지나간다. 이 장면은 누적면적 14.8%·평균 강도 96.2·23/23 프레임이 움직인다.

완성 무늬가 도는 회전 판 장면
새 창에서 열기

08 · 완성 무늬가 도는 회전 판 장면 — 측정 누적면적 14.838% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

DISC_RATIOGLOSS_SWEEP_SECONDSAXIS_COUNT
    ctx.save();
    ctx.translate(cx, cy);
    ctx.rotate(rot);
    ctx.beginPath();
    for (var i = 0; i < pathData.length; i += 1) {
      var p = pathData[i];
      var sx = p.x * scale, sy = p.y * scale;
      if (i === 0) { ctx.moveTo(sx, sy); } else { ctx.lineTo(sx, sy); }
    }
    ctx.closePath();
    ctx.strokeStyle = 'rgba(10,79,232,.82)';
    ctx.lineWidth = Math.max(1.6, scale * 0.022);
    ctx.lineJoin = 'round';
    ctx.stroke();
    ctx.restore();

09비율·펜·잔상을 한 화면에 모은 히어로 갤러리

makeCurve(ringTeeth, rollTeeth, penRatio) 가 R·r·d·K 와 닫힘 바퀴 수를 객체 하나로 묶고 pointOn(curve, t) 가 그 객체로 좌표를 계산해서, 이빨 수 쌍 (60, 23)·(54, 19)·(48, 17)·(66, 25) 네 곡선을 같은 함수로 그린다. 네 쌍 모두 최대공약수가 1 이라 바퀴 수가 굴러가는 톱니 이빨 수와 같은 23·19·17·25 이고, HERO_SPEED 도 곡선마다 달라 네 칸이 서로 다른 박자로 채워진다. 이 장면은 누적면적 48.7%·평균 강도 21.7·23/23 프레임이 움직인다.

비율·펜·잔상을 한 화면에 모은 히어로 갤러리
새 창에서 열기

09 · 비율·펜·잔상을 한 화면에 모은 히어로 갤러리 — 측정 누적면적 48.697% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

HERO_RING_TEETHHERO_PEN_RATIOSHERO_TRAIL_FADE_FRAMES
  function makeCurve(ringTeeth, rollTeeth, penRatio) {
    var R = ringTeeth * HERO_TOOTH_UNIT;
    var r = rollTeeth * HERO_TOOTH_UNIT;
    var d = r * penRatio;
    var K = (R - r) / r;
    var g = gcd(ringTeeth, rollTeeth);
    var turns = rollTeeth / g;
    return { R: R, r: r, d: d, K: K, turns: turns, maxT: 2 * Math.PI * turns, patternRadius: R + d };
  }
  function pointOn(curve, t) {
    return {
      x: (curve.R - curve.r) * Math.cos(t) + curve.d * Math.cos(curve.K * t),
      y: (curve.R - curve.r) * Math.sin(t) - curve.d * Math.sin(curve.K * t)
    };
  }

아홉 장면을 실측하면 이런 값이 나온다

누적면적은 곡선 선 한 가닥의 넓이가 아니라 움직임이 닿은 화면 영역의 비율이라, 펜 끝을 따라 배경이 함께 움직이는 장면은 값이 크게 나온다. 가장 낮은 값은 04 의 10.1% 와 07 의 11.3% 이고, 아홉 장면 모두 23/23 프레임이 움직였다.

항목 누적면적 평균 강도 움직인 프레임
01 하이포트로코이드 65.7% 22.7 23/23
02 에피트로코이드 66.2% 26.3 23/23
03 대칭 축 계산 59.4% 19.5 23/23
04 펜 네 개 10.1% 38.2 23/23
05 잔상 페이드 49.9% 18.8 23/23
06 톱니 가이드 59.2% 62.8 23/23
07 포인터 비율 11.3% 103.6 23/23
08 회전 판 14.8% 96.2 23/23
09 히어로 갤러리 48.7% 21.7 23/23

값이 무늬를 바꾸는 곳 — 이빨 수·펜 위치·회전 속도

무늬에 눈에 띄게 영향을 주는 값은 다섯 개로 좁혀진다. 02 의 GEAR_TEETH_OUTER 는 링 이빨 수라 곡선 크기와 꽃잎 수를 바꾸고(굴러가는 톱니와의 gcd 가 그대로면 바퀴 수는 그대로다), 03 의 PEN_RADIUS_RATIO 는 펜이 톱니 중심에서 얼마나 떨어져 있는지를 정해 꽃잎이 둥근지 뾰족한지를 가른다. 05 의 TRAIL_FADE_FRAMES 는 선이 지워지는 속도를, 07 의 POINTER_MAX_DIST 는 포인터가 얼마나 멀리 가야 이빨 수 상한에 닿는지를, 08 의 ROTATE_SPEED 는 완성된 도형이 도는 속도를 정한다. 값은 하나만 바꾸고, 이빨 수를 건드릴 때는 03 의 배지가 보여 주는 대칭 축 수와 바퀴 수가 어떻게 움직이는지 같이 읽으면 어느 값이 무엇을 움직였는지 가장 빨리 보인다.

어디서 깨지는가 — 곡선이 닫히지 않을 때·좁은 화면·선이 겹칠 때

이빨 수를 최대공약수가 큰 쌍으로 고르면 TURNS 가 줄어 곡선이 일찍 닫히고, 반대로 서로소에 가까울수록 바퀴 수가 늘어 같은 시간에 훨씬 촘촘한 선을 그어야 한다. SEGMENTS_PER_TURN 을 지나치게 낮추면 한 바퀴를 몇 개 선분으로만 이어 곡선이 각져 보이고, 07 은 이빨 수가 정수로만 바뀌어서 포인터를 천천히 움직여도 무늬가 부드럽게 변하지 않고 한 칸씩 바뀐다. 선이 여러 가닥 겹치는 04·09 는 좁은 화면에서 가닥끼리 구분하기 어려워지고, 아홉 장면 모두 @media (max-width: 340px) 에서 캡션(03 은 배지 포함) 글자만 줄일 뿐 캔버스 안 계산은 그대로다. 곡선 안 반투명 채움과 펜 끝을 따라가는 파스텔 그라디언트는 보기 좋게 얹은 표현 층이라 지워도 곡선은 같고, 계산이 들어간 것은 좌표식과 이빨 수의 최대공약수뿐이다. 원본 소스는 압축 파일로 묶여 있고, 여는 비밀번호는 899p5t2j 이다.

변형 세 가지와 접근성

이름 값 느낌
톱니 이빨 수 비율 조정 02 GEAR_TEETH_OUTER 96 → 60 값을 96에서 60으로 바꾸면 굴러가는 톱니(36개)와의 최대공약수는 12로 그대로라 닫힐 때까지 도는 바퀴 수는 3으로 같지만, 꽃잎 수가 8개에서 5개로 줄어 무늬가 성글어진다. 최대공약수가 작아지는 값을 고르면 바퀴 수가 늘며 더 촘촘한 무늬가 된다.
펜 구멍 위치 비율 조정 03 PEN_RADIUS_RATIO 0.6 → 0.95 값을 줄이면 펜이 작은 톱니 중심 가까이를 돌아 무늬가 둥글고 온화해지고, 값을 늘리면 펜이 톱니 가장자리에 붙어 곡선이 날카롭게 꺾이며 바깥으로 크게 뻗는 무늬가 된다.
궤적 잔상 길이 조정 05 TRAIL_FADE_FRAMES 240 → 60 값을 줄이면 그려진 선이 빠르게 사라져 펜이 지나가는 순간의 궤적만 보이고, 값을 늘리면 완성된 무늬가 오래 남아 한 화면에 여러 바퀴의 흔적이 겹쳐 쌓인다.

아홉 장면은 전부 matchMedia('(prefers-reduced-motion: reduce)') 를 읽는다. 01~04 는 완성된 곡선 한 장(drawFull(TOTAL_T))을 그리고 멈추고, 05 와 09 는 잔상 계산을 미리 돌려 둔 정지 화면을, 06 과 08 은 render() 한 번으로 정해 둔 장면을 남긴다. 키보드와 포인터 입력이 있는 항목은 07 하나뿐이고(방향키 왼쪽·오른쪽과 pointermove), 나머지 여덟 장면은 입력 없이 시간만으로 흐른다. prefers-reduced-motion 이 켜져 있으면 07 도 입력이 들어올 때만 다시 그린다.

스피로그래프 수학 원리는 코드에서 무엇을 계산하는가?

스피로그래프 수학 원리를 코드로 줄이면 링 이빨 수와 굴러가는 톱니 이빨 수를 gcd 로 나눈 몫 두 개가 전부다. 하나는 대칭 축 수, 다른 하나는 곡선이 닫히기까지 각도 t 를 돌려야 하는 바퀴 수이고, 그 바퀴 수만큼만 돌리면 선이 정확히 출발점에서 끝난다. 같은 원 위의 점을 다루더라도 푸리에 변환으로 그림 그리기 9종 — 원이 도는 방식으로 별·하트를 되살리는 캔버스 는 원을 이어 붙여 임의의 윤곽을 따라가고, 리사주 곡선 만들기 9종 — 파동 간섭으로 그리는 캔버스 는 가로·세로 진동수의 비로 닫힘을 정하며, 만화경 그리기 — 대칭 반사 각도로 그리는 캔버스 9종 은 이미 그린 선을 대칭 반사해 무늬를 늘린다. 픽셀을 직접 읽고 쓰는 쪽이 궁금하다면 html5 canvas 애니메이션 9종 — 물방울·블랙홀 특수효과 가 같은 캔버스 2d 로 굴절과 중력렌즈를 계산한 사례다.

스피로그래프 무늬를 쓰기 전에 자주 묻는 질문

스피로그래프 작품으로 저장하려면 어떻게 하나?

이 아홉 데모에는 캔버스를 이미지로 내보내는 코드가 없다. 01 의 drawFull(TOTAL_T) 처럼 곡선이 완성된 상태를 한 번 그리는 함수가 있으니, 그 호출 뒤에 저장 코드를 직접 붙이는 것이 가장 짧은 길이다.

이빨 수는 아무 숫자나 넣어도 되나?

된다. 다만 최대공약수가 1 이면 TURNS 가 굴러가는 톱니 이빨 수와 같아져 09 처럼 17~25 바퀴를 돌아야 곡선이 닫히고, 그만큼 한 곡선을 끝까지 긋는 데 각도를 더 돌려야 한다.

화면이 느려지면 어디를 줄이나?

SEGMENTS_PER_TURN 이 있는 01~04·06~08 에서는 이 값을 줄이면 프레임마다 긋는 선분 수가 줄어든다. 동작 줄임 설정을 켜면 아홉 장면 모두 requestAnimationFrame 루프를 시작하지 않으므로 그것이 가장 확실한 감속이다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.