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

만델브로 집합 9종 — 무한 확대 줌·줄리아·버닝쉽을 복소평면 한 픽셀씩 계산하는 캔버스

z = z² + c 한 줄을 픽셀마다 반복해 발산 횟수를 세면 명치 끝에서 바깥으로 뻗는 만델브로 집합이 나오는데, 확대를 거듭해도 새로운 미니 만델브로가 끝없이 나오는 무한 줌을 라이브러리 없이 복붙으로 가져갈 수 있습니다.

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

데모

아홉 판 각각은 html 파일 하나로 자립해 열자마자 움직이고, 캔버스에 그리는 화면의 모든 변화는 CSS 가 아니라 픽셀을 다시 계산한 결과입니다. 24프레임 녹화 구간에서 아홉 판 전부 23프레임이 움직였으므로(움직인프레임 23) 어느 판을 멈춰 봐도 직전과 같은 그림이 없습니다. 03번은 포인터로 c 를 고르고 07번은 휠과 드래그로 직접 파고들 수 있으니 지나가기 전에 만져 보시기를 권합니다.

24프레임 실측 한눈에

항목 초점 누적면적(%) 강도 평균
01 탈출 시간 기본 명치와 전구가 그려지는 기본 화면 32.008 24.9
02 무한 딥 줌 목표점을 향한 x25 자동 잠수 34.847 73.4
03 줄리아 집합 연동 한 화면에서 만나는 두 집합 12.885 73.3
04 궤도 트랩 채색 궤적이 스친 모양으로 입히는 색 32.008 28.5
05 부다브로트 발산 궤적을 쌓아 뜨는 형상 45.133 34.4
06 버닝쉽 변형 절댓값 한 줄로 태어난 배 45.881 35.9
07 포인터 줌·팬 손으로 조종하는 탐험기 41.972 87.3
08 부드러운 반복수 계단 띠를 지우는 후처리 31.206 25.7
09 프랙탈 히어로 씬 대문에 바로 쓰는 조립판 27.910 48.7

수치는 성격으로 읽는다

누적면적은 06 버닝쉽이 45.881%로 가장 넓게 채우고 05 부다브로트가 45.133%로 뒤따릅니다. 프레임 변화의 강도는 07 포인터 줌·팬이 87.3으로 가장 밝고 02 딥 줌 73.4 · 03 줄리아 73.3 이 그 뒤를 이릅니다. 03 의 12.885% 는 화면을 두 캔버스로 나눠 각 150px 해상도로 동시 계산하는 구조상 한 프레임에 색이 바뀌는 픽셀이 절반 이하로 줄어든 자연스러운 값이고, 09 의 프레임최대 5.805% 는 느린 배경 줌(x3/2s) 위에 비네팅이 숨쉬는 장면 성격 그대로입니다.

탈출 시간부터 서비스 화면까지, 만델브로 아홉 겹을 쌓은 순서

순서는 화려함의 서열이 아니라 한 장의 만델브로 집합 프랙탈을 몇 겹으로 다시 읽어 내려가는가의 서열입니다. 먼저 탈출 시간 한 겹으로 기본 화면을 얹고(01), 시간축으로 확대해 들어가며(02), 공간축으로 옆의 줄리아와 연결합니다(03). 다음은 색을 정하는 값 자체를 바꾸는 두 단계 — 발산 횟수 대신 궤적이 스친 거리(04)와 궤적이 쌓인 밀도(05)로 그림이 바뀌고, 여섯째는 채색이 아니라 식에 절댓값 한 줄을 넣어 세계 자체를 바꿉니다(06). 일곱째부터는 관점의 전환 — 계산을 손에 쥐어 주고(07), 같은 계산에 후처리 한 겹을 얹어 품질을 올리며(08), 마지막에 그 모든 부품을 서비스의 첫 화면으로 조립합니다(09). 픽셀을 직접 굽는 이 계열의 이웃으로 거리 하나로 장면을 굽는 레이마칭이 있고, 반복의 무게를 프레임마다 적분하는 은하 충돌 시뮬레이션과도 같은 계보입니다.

항목별 코드

01탈출 시간 기본 — 픽셀마다 z = z² + c 를 반복해 색을 정한다

탈출 시간 기본 — 픽셀마다 z = z² + c 를 반복해 색을 정한다
새 창에서 열기

01 · 탈출 시간 기본 — 픽셀마다 z = z² + c 를 반복해 색을 정한다 — 측정 누적면적 32.008% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

CanvasRenderingContext2DcreateImageDatarequestAnimationFrame

뷰포트를 명치 중심(CX −0.75 · CY 0 · 폭 2.9)에 두고 내부 해상도 240px 캔버스의 모든 픽셀에 탈출 시간을 계산하면 심장 모양 명치와 위로 뻗는 전구가 한 장에 나타나며, 움직임은 2초 주기 팔레트 순환 하나로 반복 횟수가 정수라 생긴 색 띠 계단이 그대로 보이는 것이 이 판의 매력입니다(측정 누적면적 32.008% · 강도평균 24.9).

한 픽셀의 탈출 시간 반복

    for (var j = 0; j < ih; j++) {
      var ci = oy0 + j * dy;
      for (var i = 0; i < iw; i++) {
        var cr = CX - SPAN / 2 + i * dx;
        // z = 0 부터 z = z² + c 반복 — 발산 시점의 n 이 색 인덱스
        var zr = 0, zi = 0, n = 0;
        while (n < MAXIT && zr * zr + zi * zi <= 4) {
          var t = zr * zr - zi * zi + cr;
          zi = 2 * zr * zi + ci;
          zr = t;
          n++;
        }
      }
    }

02무한 딥 줌 — 목표점을 향해 계속 파고드는 자동 잠수

무한 딥 줌 — 목표점을 향해 계속 파고드는 자동 잠수
새 창에서 열기

02 · 무한 딥 줌 — 목표점을 향해 계속 파고드는 자동 잠수 — 측정 누적면적 34.847% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

createImageDatarequestAnimationFrameMath.log

배율 k 를 2초에 걸쳐 x25까지 곱해 명치 끝 나선 목표점(−0.743643887037, 0.13182590421)을 향해 잠수하며, 최대 반복을 96에서 500으로 함께 올려야 깊이 들어가도 윤곽이 끊기지 않습니다(내부 해상도 260px · 측정 누적면적 34.847% · 강도평균 73.4).

지수 잠수의 상태 함수

  function stateAt(t) {
    var k = Math.pow(ZOOM2S, t);
    var progress = Math.log(k) / Math.log(ZOOM2S);   // 배율→진행도 역변환(=t, 검산용)
    var cx = -0.75 + (TX + 0.75) * t;
    var cy = (TY) * t;
    var maxit = Math.round(MAXIT0 + (MAXIT1 - MAXIT0) * progress);
    return { cx: cx, cy: cy, scale: 2.9 / k, maxit: maxit, k: k };
  }

03줄리아 집합 연동 — 포인터가 만델브로 위 c 를 고르면 다른 창이 변한다

줄리아 집합 연동 — 포인터가 만델브로 위 c 를 고르면 다른 창이 변한다
새 창에서 열기

03 · 줄리아 집합 연동 — 포인터가 만델브로 위 c 를 고르면 다른 창이 변한다 — 측정 누적면적 12.885% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

CanvasRenderingContext2DcreateImageDatapointermove

왼쪽 판은 만델브로(폭 2.9), 오른쪽 판은 줄리아(폭 3.0)로, 포인터나 자동 순항이 만델브로 위에서 고른 좌표가 곧 오른쪽 줄리아의 고정 c 가 되어 구름·토끼·가지 모양이 실시간으로 바뀝니다. 자동 순항은 명치 오목부와 전구 안테나 사이를 2초 주기로 왕복하며, 두 판을 동시에 계산하므로 내부 해상도를 각 150px로 낮췄습니다(측정 누적면적 12.885% — 두 캔버스 분할 구조상 자연히 줄어든 값 · 강도평균 73.3).

고정하는 쪽이 바뀔 뿐인 줄리아 렌더

    for (var j = 0; j < ih; j++) {
      var zi0 = oy0 + j * dy;
      for (var i = 0; i < iw; i++) {
        var zr0 = -span / 2 + i * dx;
        var zr = zr0, zi = zi0, n = 0;
        while (n < MAXIT_J && zr * zr + zi * zi <= 4) {
          var t = zr * zr - zi * zi + cr;
          zi = 2 * zr * zi + ci;
          zr = t; n++;
        }
        var c = colorOf(n, MAXIT_J);
        px[k++] = c[0]; px[k++] = c[1]; px[k++] = c[2]; px[k++] = 255;
      }
    }

04궤도 트랩 채색 — 반복 궤적이 스치는 모양으로 색을 입힌다

궤도 트랩 채색 — 반복 궤적이 스치는 모양으로 색을 입힌다
새 창에서 열기

04 · 궤도 트랩 채색 — 반복 궤적이 스치는 모양으로 색을 입힌다 — 측정 누적면적 32.008% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

createImageDataMath.hypotrequestAnimationFrame

발산 횟수 대신 각 픽셀의 z 궤적이 실수축 · 허수축 · 반지름 0.62 원에 얼마나 가까이 스쳤는지의 최솟값을 기록해 색으로 입히면 띠 · 원문 · 대칭 무늬가 집합 위에 또 하나의 프랙탈처럼 겹쳐 나옵니다. 세 트랩을 2초 주기로 순환하고 스친 자리는 트랩 강조색(파스텔 골드)으로 밝히며 최대 반복은 110입니다(측정 누적면적 32.008% · 강도평균 28.5).

궤적과 트랩의 최근접 거리

  function trapDist(zr, zi, kind, best) {
    var d;
    if (kind === 0) d = Math.abs(zi);                    // 실수축 근접
    else if (kind === 1) d = Math.abs(zr);               // 허수축 근접
    else d = Math.abs(Math.hypot(zr, zi) - 0.62);        // 원 근접 — 반지름 0.62 원문
    return d < best ? d : best;
  }
        while (n < MAXIT && zr * zr + zi * zi <= 4) {
          var t = zr * zr - zi * zi + cr;
          zi = 2 * zr * zi + ci;
          zr = t;
          best = trapDist(zr, zi, kind, best);
          n++;
        }

05부다브로트 — 발산하는 궤적을 축적해 떠오르는 유령 형상

부다브로트 — 발산하는 궤적을 축적해 떠오르는 유령 형상
새 창에서 열기

05 · 부다브로트 — 발산하는 궤적을 축적해 떠오르는 유령 형상 — 측정 누적면적 45.133% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

createImageDataglobalAlphaMath.random

발산하는 점만 골라 그 궤적이 지나간 픽셀에 1씩 더하면 명치 모양이 사라지고 좌우 대칭의 명상하는 형상이 떠오르는데, 발산하지 않는 점의 궤적을 버리는 한 줄이 이 그림의 정체입니다. 프레임당 260개 궤적을 시드를 고정한 난수로 뿌리고 방문 30에 포화되는 로그 스케일로 밝기를 잡으며 감마 0.94 감쇠로 오래된 궤적이 잦아들다가 2초 루프마다 다시 떠오릅니다(측정 누적면적 45.133% · 강도평균 34.4).

발산하는 궤적만 남기는 추적

  function traceOrbit() {
    var cr = RX0 + rnd() * (RX1 - RX0);
    var ci = RY0 + rnd() * (RY1 - RY0);
    var zr = 0, zi = 0, n = 0;
    while (n < MAXIT && zr * zr + zi * zi <= 4) {
      var t = zr * zr - zi * zi + cr;
      zi = 2 * zr * zi + ci;
      zr = t; n++;
    }
    if (n >= MAXIT) return;                    // 집합 내부 — 궤적을 남기지 않는다(부다브로트의 조건)
    zr = 0; zi = 0;                            // 다시 출발해 흔적을 남긴다
    var iw = ov.width, ih = ov.height;
    for (var m = 0; m < n; m++) {
      var px = (zi - RX0) / (RX1 - RX0) * iw;  // 허수축을 가로로(회전)
      var py = (zr - RY0) / (RY1 - RY0) * ih;  // 실수축을 세로로
      var ix = px | 0, iy = py | 0;
      if (ix >= 0 && ix < iw && iy >= 0 && iy < ih) {
        acc[iy * iw + ix] += 1;                // 방문 가중치(긴 궤적이 더 밝다)
      }
      var t2 = zr * zr - zi * zi + cr;
      zi = 2 * zr * zi + ci;
      zr = t2;
    }
  }

06버닝쉽 변형 — 절댓값 한 줄로 불타는 배가 나온다

버닝쉽 변형 — 절댓값 한 줄로 불타는 배가 나온다
새 창에서 열기

06 · 버닝쉽 변형 — 절댓값 한 줄로 불타는 배가 나온다 — 측정 누적면적 45.881% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

createImageDataMath.absrequestAnimationFrame

제곱 전에 실수 · 허수에 절댓값을 한 번씩 씌운 z ← |z|² + c 만으로 만델브로가 사라지고 불꽃을 뿜는 배가 나타나는데, 절댓값이 궤적을 아래 반평면으로 접어 넣어 좌우 대칭의 불꽃 무늬가 생깁니다. 화면은 배의 본체(CX −1.755 · CY −0.04 · 폭 1.35)를 비추고 ±4.5% 숨쉬는 미세 줌을 얹어 매 프레임 살아 있게 하며 최대 반복은 128입니다(측정 누적면적 45.881% — 아홉 중 가장 넓음 · 강도평균 35.9).

절댓값 한 줄이 바꾸는 풍경

        while (n < MAXIT && zr * zr + zi * zi <= 4) {
          // ── 버닝쉽의 한 줄 — 제곱 전 절댓값 ──
          var ar = Math.abs(zr), ai = Math.abs(zi);
          if (POWER === 2) {
            var t = ar * ar - ai * ai + cr;
            zi = 2 * ar * ai + ci;
          } else {
            // 3차 변형 — (|z|)³: 데 무아브르 3배각
            var t = ar * ar * ar - 3 * ar * ai * ai + cr;
            zi = 3 * ar * ar * ai - ai * ai * ai + ci;
          }
          zr = t;
          n++;
        }

07포인터 줌·팬 — 클릭한 점으로 빨려 들어가는 탐험기

포인터 줌·팬 — 클릭한 점으로 빨려 들어가는 탐험기
새 창에서 열기

07 · 포인터 줌·팬 — 클릭한 점으로 빨려 들어가는 탐험기 — 측정 누적면적 41.972% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

pointerdownpointermovewheel

휠은 포인터 아래 점을 화면 중심으로 빨아들이는 줌이고 드래그는 패닝이며, 최대 반복은 배율 로그에 비례해 96에서 400까지 올라가고 미리보기는 사람의 손을 대신해 2초를 4막(전구로 줌인 → 위로 팬 → 더 줌 → 줌 아웃 복귀)으로 결정론 재생합니다(측정 누적면적 41.972% · 강도평균 87.3 — 아홉 중 가장 밝음).

포인터 아래를 중심 삼는 줌

  function zoomAt(px_, py_, factor) {
    // (px_, py_) 화면 좌표의 복소평면 점이 줌 뒤에도 그 자리에 남게 center 보정
    var p = toPlane(px_, py_);
    view.scale /= factor;
    view.cx = p.x + (view.cx - p.x) / factor;
    view.cy = p.y + (view.cy - p.y) / factor;
  }
  function panBy(dxPx, dyPx) {
    var dx = view.scale / W;
    view.cx -= dxPx * dx;
    view.cy += dyPx * dx;
  }
  function maxIter() {
    var k = startScale / view.scale;
    return Math.round(MAXIT0 + (MAXIT1 - MAXIT0) * Math.min(1, Math.log(k) / Math.log(150)));
  }

08부드러운 반복수 — 띠를 지우고 그라디언트로 흐르는 채색

부드러운 반복수 — 띠를 지우고 그라디언트로 흐르는 채색
새 창에서 열기

08 · 부드러운 반복수 — 띠를 지우고 그라디언트로 흐르는 채색 — 측정 누적면적 31.206% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

createImageDataMath.logcreateLinearGradient

탈출 직후 크기의 이중 로그를 빼 ν = n + 1 − log₂(log|z|) 로 소수 반복수를 만들면 정수 n 의 계단 띠가 연속 그라디언트로 흐릅니다. 화면은 왼쪽 정수 원본과 오른쪽 스무스 처리판을 나란히 두고 팔레트를 64 반복수 주기로 순환시켜 차이를 직접 비교하며, 조리개 하나(smooth 불리언)로 같은 렌더러가 두 장면을 냅니다(측정 누적면적 31.206% · 강도평균 25.7).

소수 반복수를 만드는 후처리

        while (n < MAXIT && (zz = zr * zr + zi * zi) <= 4) {
          var t = zr * zr - zi * zi + cr;
          zi = 2 * zr * zi + ci;
          zr = t; n++;
        }
        var c;
        if (n >= MAXIT) {
          c = C_SET;
        } else {
          var nu;
          if (smooth) {
            // ── 스무스 공식 — 탈출 직후 |z| 의 이중 로그를 보간 ──
            var mod = Math.sqrt(zz);
            nu = n + 1 - Math.log(Math.log(mod)) / Math.LN2;
          } else {
            nu = n;                       // 원본 — 계단의 원인
          }
          c = palAt((nu / PERIOD) * 4 + phase);
        }

09프랙탈 히어로 씬 — 글자·별빛과 겹쳐 쓰는 대문 장면

프랙탈 히어로 씬 — 글자·별빛과 겹쳐 쓰는 대문 장면
새 창에서 열기

09 · 프랙탈 히어로 씬 — 글자·별빛과 겹쳐 쓰는 대문 장면 — 측정 누적면적 27.91% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

createRadialGradientcreateImageDatarequestAnimationFrame

02의 딥 줌을 느리게(2초에 x3) 배경에 깔고 그 위에 방사 비네팅 · 타이포(INFINITE DIVE) · 별점 맥동을 얹어 2초 한 호흡의 안내 장면을 완성합니다. 비네팅은 가장자리 알파가 0.42에서 0.62까지 숨쉬고 최대 반복은 90에서 260까지 오르므로 매 프레임 전면이 움직입니다(측정 누적면적 27.910% · 강도평균 48.7 — 프레임최대 5.805% 는 느린 배경 줌 위에 비네팅만 숨쉬는 장면 성격 그대로).

배경 줌 위에 숨쉬는 비네팅

    var k = Math.pow(ZOOM2S, t);
    var cx = -0.75 + (TX + 0.75) * t;
    var cy = TY * t;
    var maxit = Math.round(MAXIT0 + (MAXIT1 - MAXIT0) * t);
    var ctx = cv.getContext('2d');
    ctx.drawImage(renderFractal(cx, cy, 2.9 / k, maxit), 0, 0, W, H);
    // 방사 비네팅 — 중앙은 투명, 가장자리로 갈수록 잉크. 호흡(t 의 사인)에 맞춰 깊이가 운다
    var breath = 0.5 + 0.5 * Math.sin(t * Math.PI * 2);      // 0~1 — 2초 한 호흡
    var vg = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * 0.18, W / 2, H / 2, Math.max(W, H) * 0.72);
    vg.addColorStop(0, 'rgba(' + C_INK[0] + ',' + C_INK[1] + ',' + C_INK[2] + ',0)');
    vg.addColorStop(1, 'rgba(' + C_INK[0] + ',' + C_INK[1] + ',' + C_INK[2] + ',' + (0.42 + breath * 0.20).toFixed(3) + ')');
    ctx.fillStyle = vg;
    ctx.fillRect(0, 0, W, H);

딥 줌 배경과 포인터 탐험판 — 이 아홉 개를 실제로 어디에 붙이나

랜딩 페이지 대문을 프랙탈로 채우고 싶다면 09 프랙탈 히어로 씬을 그대로 올리는 것이 가장 빠릅니다. 글자·별빛과 겹쳐 쓰도록 이미 구도가 잡혀 있어 별도 CSS 없이도 대문 한 장이 완성됩니다. 포트폴리오나 실험실 페이지처럼 방문자가 손으로 조작해 보길 바라는 자리에는 02 무한 딥 줌 대신 07 포인터 줌·팬을 앉히면, 클릭한 좌표로 빨려 들어가는 상호작용이 스크롤 유도 장치를 겸합니다.

수학·알고리즘 교육 콘텐츠라면 03 줄리아 집합 연동이 맞습니다. 포인터가 고른 c 값이 바로 옆 창의 줄리아 집합을 바꾸는 두 캔버스 구성이라, 설명 문단 없이도 매개변수와 결과의 관계가 화면으로 보입니다. 세 판 모두 각자 완결된 파일이라 별도 프레임워크 없이 iframe 이나 정적 섹션에 그대로 끼워 넣을 수 있습니다.

최대 반복을 고정해 두면 딥 줌에서 깨지는 지점

전 해상도에서 돌리면 프레임이 죽는다

픽셀마다 최대 64~500번의 복소 반복이 도는 계산이라 480×300 전체에서 루프를 돌리면 프레임이 즉시 무너집니다. 아홉 판 전부 내부 캔버스를 150~260px로 낮게 잡아 계산한 뒤 putImageData 로 올리고 CSS 로 화면 크기에 확대하는데, 240px 는 면적이 1/4 이라 같은 장면을 네 배 값싸게 얻는 길입니다.

화면만 키우는 줌은 새 구조를 밝히지 못한다

transform: scale 로 캔버스를 키우면 보이는 픽셀만 커질 뿐 복소평면의 어느 좌표도 다시 계산되지 않습니다. 줌은 뷰포트 폭 SPAN 을 줄여 픽셀 → 복소평면 사상을 매 프레임 새로 세우는 것이어야 하고, 02 · 07 · 09 가 전부 그렇게 짜여 있습니다.

깊이 들어갈 때 반복을 못 올리면 윤곽이 끊긴다

배율이 커질수록 경계의 세세한 구조가 살아남으려면 반복이 더 필요한데, 최대 반복을 고정해 두면 딥 줌 중반부터 윤곽이 끊기고 집합 내부가 뻥 뚫려 보입니다. 02 는 96 → 500, 07 은 배율 로그에 비례해 96 → 400, 09 는 90 → 260 으로 각자 올립니다.

결정론과 제곱근 — 작은 습관 두 개가 무너뜨리는 것들

05 부다브로트에 Math.random 을 쓰면 렌더마다 다른 그림이 나와 재생이 결정론이 깨지므로 시드를 고정한 난수여야 하고, 반복문 안에서 Math.sqrt(hypot(zr, zi)) 를 판정에 쓰면 같은 결과를 훨씬 비싸게 얻습니다. 아홉 판 각각의 index.html 을 그대로 담은 암호 zip 의 비밀번호는 m5rczssw 이니, 풀면 본문의 코드 조각과 줄 단위로 나란히 대조해 보실 수 있습니다.

왜 무한히 파고들어도 새로운 모양이 나오나? — 탈출 시간과 자기유사

왜 크기 2 를 넘으면 발산으로 보나

z 가 한 번 크기 2 를 넘으면 그다음 제곱에서 크기가 지수적으로 불어나 되돌아오지 못하므로, 반복의 멈춤 조건을 |z| > 2 로 잡는 것이 수학적으로 정당합니다. 코드는 이 판정을 제곱근 없이 zr * zr + zi * zi <= 4 하나로 내리는데, 매 반복 도는 비교문에서 Math.sqrt 를 부르는 것과는 프레임 전체로 보면 수백만 번의 차이가 됩니다. 발산이 빠른 바깥 픽셀은 몇 번 만에 멈추고 명치와 전구 안쪽으로 갈수록 반복이 느려지며, 최대 반복까지 살아남는 점들이 집합 내부로 칠해집니다.

왜 계단 띠가 생기고 어떻게 지워지나

발산까지 걸린 횟수 n 은 정수이므로 n 을 팔레트 인덱스로 그대로 쓰면 색이 계단으로 끊깁니다. 01 은 이웃 색 사이를 소수로 선형 보간해 띠 안을 부드럽게 하고, 08 은 한 걸음 더 나아가 탈출 직후의 크기까지 반영해 반복수 자체를 소수로 만듭니다. 같은 프랙탈에 같은 팔레트를 쓰면서도 후처리 한 겹으로 인쇄물급 그라디언트가 나오는 이유가 여기 있습니다.

왜 파고들어도 새로운 모양이 나오나

집합의 경계는 자기유사 — 확대하면 비슷한 구조가 다시 나타나는 성질 — 를 따르는데, 명치 끝의 나선팔 안에서 미니 만델브로가 되풀이되는 것이 그 증거입니다. 다만 경계의 자기유사는 복사가 아니라 통계적인 것이어서 같은 모양의 무한 반복이 아니라 늘 변주된 비슷함이 이어지고, 이 늘 새로운 비슷함이 확대를 멈추기 어렵게 만듭니다. 만델브로 집합 그리기에서 계산량을 정하는 것도 바로 이 경계의 느림입니다 — 집합에서 먼 바깥은 몇 번에 끝나지만 경계 근처의 점은 최대 반복까지 버티므로, 줌이 깊어질수록 경계 픽셀이 늘고 반복 상한도 함께 올라야 합니다.

차수를 바꾸면 대칭이 통째로 달라지는 세 변형과 접근성

값 하나를 바꾼 세 변형

이름 바꾼 값 느낌
최대 반복 횟수 02 최대 반복 500 → 2,000 깊이 파고들수록 끊기던 윤곽이 이어지고 줌이 눈에 띄게 느려진다
팔레트 주기 08 색 순환 주기 64 → 16 색이 촘촘히 순환해 명치 안쪽 구조가 훨씬 또렷해진다
식 바꾸기 06 z = |z|² + c → z = z³ + c 대칭이 2회에서 3회로 바뀌며 완전히 다른 생물이 나온다

셋 다 구조를 손대지 않고 상수 하나만 고쳐 만듭니다. 반복 상한을 올리면 정밀과 속도의 관계를 손으로 확인할 수 있고, 팔레트 주기를 줄이면 같은 장면의 다른 얼굴이 나오며, 차수를 바꾸면 대칭의 수가 바뀌어 전혀 다른 집합이 열립니다.

동작을 줄인 사용자에게 주는 장면

각 판은 시작할 때 prefers-reduced-motion: reduce 일치 여부를 검사해, 켜져 있으면 애니메이션 루프를 돌리지 않고 대표적인 한 장면을 그려 고정한 뒤 캔버스에 고정 상태 표식을 붙입니다. 정지 사용자에게도 빈 캔버스가 아니라 프랙탈 한 장이 전달되는 것이 목적입니다.

손으로 만지는 두 판과 화면 낭독

03 은 pointermove 로 c 를 고르고 07 은 휠 · 드래그로 줌과 팬을 조작하는 두 판만 입력을 받으며, 07 의 휠 줌은 페이지 스크롤을 삼키도록 처리되어 있습니다. 모든 실험판에는 사람이 알 수 있는 aria-label 과 tabindex 가 붙고 내부 해상도 · 최대 반복 같은 측정 메타가 화면 아래에 함께 표시됩니다.

프랙탈 줌을 처음 붙이며 자주 나오는 질문

프랙탈 줌 장면을 제 페이지 대문에 그대로 올려도 되나요?

됩니다. 09는 대문 히어로용으로 구도를 잡은 판이고 02도 배경으로 깔면 본문 가독을 해치지 않는 어두운 팔레트입니다. zip 을 펼쳐 원하는 판의 index.html 하나만 올리면 그대로 움직이고, 색은 파일 머리의 팔레트 토큰을 바꿔 조정합니다.

만델브로 집합 그리기를 수학 배경 없이 따라할 수 있나요?

가능합니다. 코드의 복소수는 전부 실수 두 개(zr, zi)로 풀어 쓰여 있어 복소수 연산을 몰라도 줄마다 읽히고, zip 에는 각 판을 다시 만들어 내는 지시 프롬프트가 함께 들어 있어 코드 도구에 붙여 넣으면 같은 화면부터 시작합니다. 그 다음은 상수 블록의 값 하나씩 바꿔 보는 것이 가장 빠른 학습 순서입니다.

확대는 어디까지 가능하고 언제 느려지나요?

줌 자체는 무한히 이어지지만 배율이 대략 10의 13제곱을 넘으면 배정도 부동소수점의 유효숫자가 바닥나 인접 픽셀의 좌표가 구분을 잃습니다. 느려짐은 그 전에 옵니다 — 경계 픽셀이 늘고 최대 반복이 함께 올라가므로 07 처럼 로그 비례로 상한을 조절하는 것이 실용선이고, 그 이상은 정밀도와 반복 관리를 아예 설계에 넣어야 합니다.

더 읽을거리

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

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