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

3D 지구본 캔버스 장면 9종 — Three.js 없이

3D 지구본은 웹사이트에 우주적인 규모감을 주는 장면입니다. 위성 궤도와 항로 곡선, 낮밤 경계까지 아홉 종을 Three.js 나 WebGL 없이 캔버스 2D 계산만으로 만들었고, 실측으로 가장 넓게 움직인 08 입자 지구본이 26.98퍼센트, 06 별밭 배경이 22.07퍼센트를

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

순서는 화려한 순서가 아니라 구면 계산이 쌓이는 순서입니다. 먼저 구 좌표를 화면으로 옮기는 정사도 투영이 바탕이 되고(01), 그 위에 점·곡선·빛 계산이 얹히고(02~05), 장면이 구를 벗어나 배경과 궤도로 확장되고(06·07), 마지막에 입자와 고리가 장면을 하나의 연출로 마감합니다(08·09). 픽셀을 직접 굴절시키는 캔버스 특수효과와 시드로 무늬를 뽑는 CSS 배경 패턴이 이 편의 이웃입니다.

01위경도 선 회전 지구본

랜딩 히어로의 글로벌 서비스 소개에 쓰는, 위도·경도 선으로 그린 회전 지구본입니다.

위경도 선으로 그린 회전 지구본
새 창에서 열기

01 · 위경도 선으로 그린 회전 지구본 — 측정 누적면적 14.28% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

orthographic projectionbackface cullingrequestAnimationFrame

뒷면은 그리지 않는 것이 입체다

  function polyline(pts, style, width) {
    ctx.strokeStyle = style;
    ctx.lineWidth = width;
    ctx.beginPath();
    var pen = false;
    for (var i = 0; i < pts.length; i++) {
      var p = pts[i];
      if (p.z >= 0) {
        var x = cx + p.x * R, y = cy - p.y * R;
        if (pen) ctx.lineTo(x, y); else { ctx.moveTo(x, y); pen = true; }
      } else {
        pen = false;   // 뒤로 넘어가는 지점에서 펜을 든다
      }
    }
    ctx.stroke();
  }

구면 좌표를 축 기울기와 자전 각도로 회전해 화면 좌표로 바꾼 뒤 깊이 z 가 음수인 뒷면 구간은 펜을 들어 건너뛰는데, 선을 겹쳐 흐리게 하는 대신 아예 지우니 구가 투명 유리구가 아니라 뭉근한 덩어리로 읽힙니다. 마우스를 판 위에 올리면 축이 목표 기울기로 매 프레임 12퍼센트씩 다가갑니다(실측: 누적면적 14.28퍼센트, 23/23 프레임 움직임).

02점으로 채운 도트매트릭스 지구본

글로벌 접속자 수를 보여주는 섹션에 어울리는, 점 천 개로 채운 지구본입니다.

점으로 채운 도트매트릭스 지구본
새 창에서 열기

02 · 점으로 채운 도트매트릭스 지구본 — 측정 누적면적 10.422% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

Fibonacci sphereseeded randomdepth sort

같은 시드는 같은 그림을 뽑는다

// 결정적 난수 — mulberry32. 같은 시드면 같은 수열을 돌려준다
function mulberry32(a) {
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    var t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 429497296;
  };
}

점 850개는 피보나치 구 분포로 놓아 극지방이 촘촘해지는 일 없이 고르게 퍼지고, 각 점의 반경·밝기·반짝임 위상은 시드가 정한 난수 계열이 결정합니다. 판 아래 시드 입력에 숫자를 넣으면 점 배치가 결정적으로 다시 그려지고, 같은 시드를 다시 넣으면 정확히 같은 그림이 돌아옵니다(실측: 시드 42에서 7로 바꾸면 점 배치 전체 재생성).

03지구본을 도는 위성 궤도

우주·통신 서비스 소개에 쓰는, 타원 궤도를 도는 위성 무리입니다.

지구본을 도는 위성 궤도
새 창에서 열기

03 · 지구본을 도는 위성 궤도 — 측정 누적면적 14.463% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

elliptical orbitpainter's algorithmz-sort

화가 알고리즘 — 뒤에서부터 앞으로 칠한다

  // ── 2단계: 지구본 + 위성을 z 로 한번에 정렬(화가 알고리즘)
  var items = [{ kind: 'globe', z: 0 }];
  for (var m = 0; m < sats.length; m++) {
    items.push({ kind: 'sat', z: sats[m].p.z, ref: sats[m] });
  }
  items.sort(function (a, b) { return a.z - b.z; });   // 뒤(z 작음)부터 — z-sort 키로 오름차순

지구본과 위성을 한 배열에 담아 깊이 z 로 정렬한 뒤 뒤에서부터 앞으로 그리면, 위성이 지구본 뒤로 지날 때는 원에 가려지고 앞으로 나올 때는 위에 얹힙니다. 궤도 타원의 뒤쪽 절반은 알파를 낮춰 입체를 읽게 하고, 위성마다 속도가 달라 궤도가 서로 어긋납니다.

04도시를 잇는 항로 곡선

글로벌 배송·항공 서비스 소개의 대표 장면인, 도시를 잇는 발광 항로입니다.

도시를 잇는 항로 곡선
새 창에서 열기

04 · 도시를 잇는 항로 곡선 — 측정 누적면적 10.153% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

great-circle arcBezier interpolationquadratic curve

구면 선형 보간이 최단 경로를 그린다

// slerp — 구면 선형 보간. omega 는 두 점 사이 중심각
function slerp(p0, p1, t) {
  var dot = p0.x * p1.x + p0.y * p1.y + p0.z * p1.z;
  dot = Math.max(-1, Math.min(1, dot));
  var omega = Math.acos(dot);
  if (omega < 1e-5) return { x: p0.x, y: p0.y, z: p0.z };
  var so = Math.sin(omega);
  var k0 = Math.sin((1 - t) * omega) / so;
  var k1 = Math.sin(t * omega) / so;
  return { x: p0.x * k0 + p1.x * k1, y: p0.y * k0 + p1.y * k1, z: p0.z * k0 + p1.z * k1 };
}

두 도시를 단위구 위 점으로 잡고 slerp 로 잇면 지구 본체를 뚫지 않는 대원 항로가 나옵니다. 호는 구 반지름의 1.10배 높이로 떠 있어 곡선이 지구본 표면에 눌리지 않고, 빛나는 점이 호를 따라 흐르며 운항 중임을 보여줍니다.

05낮과 밤이 갈라지는 지구본

시차 안내·글로벌 근무시간 위젯에 쓰는, 태양 방향에 따라 밝아지는 지구본입니다.

낮과 밤이 갈라지는 지구본
새 창에서 열기

05 · 낮과 밤이 갈라지는 지구본 — 측정 누적면적 14.608% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

dot product shadingsun vectorradial gradient

픽셀마다 법선과 태양을 내적한다

      var nx = dx / R, ny = -dy / R;
      var nzsq = 1 - nx * nx - ny * ny;
      if (nzsq < 0) continue;
      var nz = Math.sqrt(nzsq);
      var ndotl = nx * sun.x + ny * sun.y + nz * sun.z;   // 법선·태양 내적
      var lit = Math.max(0, ndotl);
      var t = Math.min(1, lit * 2.2);                     // 경계 부근을 넓혀 그라디언트로

원 안의 모든 픽셀에 대해 법선 벡터를 계산하고 태양 벡터와 내적하면 밝기가 나옵니다. CSS 그라디언트를 겹치는 것이 아니라 매 프레임 ImageData 를 채우는 픽셀 단위 계산이라, 태양이 돌 때 낮밤 경계선이 둥글게 휘어 움직입니다(실측: 강도 19.4, 23/23 프레임).

06층이 갈라지는 별밭 배경

우주 컨셉 랜딩의 다크 히어로 배경인, 시차로 갈라지는 별바다입니다.

층이 갈라지는 별밭 배경
새 창에서 열기

06 · 층이 갈라지는 별밭 배경 — 측정 누적면적 22.067% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

seeded randomparallax layerstwinkle noise

시드가 별하늘을 결정한다

  // 3층 — 뒤(느림·작음·많음)부터 앞(빠름·큼·적음)
  var defs = [
    { n: 110, par: 0.35, size: [0.6, 1.4], drift: 4 },
    { n: 70,  par: 0.75, size: [1.0, 2.0], drift: 9 },
    { n: 34,  par: 1.25, size: [1.5, 2.8], drift: 16 }
  ];

같은 씨앗은 같은 별하늘을 보장하며, 별 214개를 세 층으로 나눠 층마다 시차 배율을 다르게 두면 마우스를 움직일 때 멀리 있는 별은 느리게, 가까운 별은 빠르게 밀려 깊이가 느껴집니다. 판 아래 시드 입력과 새 별하늘 단추로 배치를 다시 뽑을 수 있고, 반짝임 위상까지 시드가 정하며 누적면적 22.07퍼센트로 아홉 장면 중 두 번째로 넓게 움직입니다.

07여러 행성이 도는 태양계 모형

교육 콘텐츠·전시 소개에 쓰는, 케플러 제2법칙을 따르는 오러리입니다.

여러 행성이 도는 태양계 모형
새 창에서 열기

07 · 여러 행성이 도는 태양계 모형 — 측정 누적면적 10.285% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

Kepler's second laworbital periodellipse parametric

같은 시간에 같은 넓이를 쓸어간다

// r(theta) — 초점이 원점(태양)인 타원의 극좌표식
function radiusAt(p, theta) {
  return p.a * (1 - p.e * p.e) / (1 + p.e * Math.cos(theta));
}

function step(dt) {
  for (var i = 0; i < PLANETS.length; i++) {
    var p = PLANETS[i];
    var r = radiusAt(p, p.theta);
    var dtheta = p.L / (r * r);          // 케플러 제2법칙: dA/dt = (1/2) r^2 dtheta/dt = const = L/2
    p.theta = (p.theta + dtheta * dt) % TAU;
  }
}

행성이 근일점에 가까울수록 빨라지고 원일점에서 느려지는 것을 각속도가 아니라 면적 속도로 계산하고, 태양을 중심으로 한 부채꼴을 궤적 위에 옅게 남겨 같은 시간에 쓸어간 넓이가 항상 같다는 법칙을 그림으로 보여줍니다.

08흩어졌다 뭉치는 입자 지구본

브랜드 인트로·로딩 연출에 쓰는, 흩어진 입자가 모여 지구본을 이루는 장면입니다.

흩어졌다 뭉치는 입자 지구본
새 창에서 열기

08 · 흩어졌다 뭉치는 입자 지구본 — 측정 누적면적 26.978% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

lerp interpolationeasing functiontarget assignment

목표점으로 이징 보간한다

// 사이클 타임라인(초) — hold(흩어짐) -> assemble -> hold(구) -> disperse -> 반복
var T_HOLD_OUT = 1.2, T_ASSEMBLE = 2.6, T_HOLD_IN = 2.6, T_DISPERSE = 2.6;
var CYCLE = T_HOLD_OUT + T_ASSEMBLE + T_HOLD_IN + T_DISPERSE;

function phaseOf(clock) {
  var c = clock % CYCLE;
  if (c < T_HOLD_OUT) return { k: 'out', t: 0 };
  c -= T_HOLD_OUT;
  if (c < T_ASSEMBLE) return { k: 'assemble', t: easeInOut(c / T_ASSEMBLE) };
  c -= T_ASSEMBLE;
  if (c < T_HOLD_IN) return { k: 'in', t: 1 };
  c -= T_HOLD_IN;
  return { k: 'disperse', t: easeInOut(c / T_DISPERSE) };
}

입자 260개는 흩어진 랜덤 시작점에서 피보나치 구의 목표점으로 이징 보간되어 이동하고, 깊이 z 로 크기와 알파를 조절해 정사도 투영의 입체감을 그대로 씁니다. 아홉 장면 중 가장 넓게 움직여 누적면적 26.98퍼센트를 기록했고(실측: 강도 58.6, 23/23 프레임) 프레임당 그리는 비용은 점 260개라 가볍습니다.

09고리를 두른 지구본

프리미엄 서비스 소개의 마무리 장면인, 고리를 두른 지구본입니다.

고리를 두른 지구본
새 창에서 열기

09 · 고리를 두른 지구본 — 측정 누적면적 19.972% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

ellipse projectionocclusion splitperspectiveScale

앞뒤를 갈라 그려 가림을 만든다

// 1) 고리 뒷부분(구 뒤로 지나가는 절반)
drawRingHalf(ctx, cx, cy, R, C_ring, C_ringHi, false);

// 2) 지구본 — 편구 셰이딩
var g = ctx.createRadialGradient(cx - R * 0.35, cy - R * 0.4, R * 0.1, cx, cy, R);
g.addColorStop(0, C_deep);
g.addColorStop(1, C_sphere);
ctx.beginPath();
ctx.arc(cx, cy, R, 0, TAU);
ctx.fillStyle = g;
ctx.fill();

// 3) 고리 앞부분(구 앞을 지나가는 절반) — 지구본을 덮어 가림을 완성
drawRingHalf(ctx, cx, cy, R, C_ring, C_ringHi, true);

기울어진 타원 고리의 각 점을 지구본과 같은 자전·기울기로 회전해 깊이 z 부호로 앞뒤를 가리고, 뒷절반 → 지구본 → 앞절반 순서로 그리는 화가 알고리즘 변형이라 고리가 구를 실제로 감싸는 것처럼 보입니다. 고리는 세 겹의 띠로 그리되 가운데 띠만 밝은 색으로 칠해 두른 결이 살아 있습니다(실측: 누적면적 19.97퍼센트).

지구본을 왜 캔버스 좌표 계산으로 그리나?

3D 지구본 사이트 연출은 대부분 Three.js 같은 라이브러리에 맡기지만, 이 편의 아홉 장면은 구면 좌표를 화면 좌표로 바꾸는 정사도 투영 한 줄이면 충분합니다. 회전은 cos 과 sin 두 값으로 축을 돌린 것이고, 가림은 깊이 z 로 그릴 순서를 정한 것이고, 낮밤은 법선과 태양 벡터의 내적입니다. 라이브러리가 없으니 불러오는 파일이 0개이고, 캔버스 한 장에 그리므로 어느 페이지에 붙여도 그 페이지의 색 토큰을 그대로 받습니다.

실측에서 아홉 장면 전부 23칸 프레임이 움직였고(살아 있음), 누적면적은 10.15퍼센트(04)부터 26.98퍼센트(08)까지 — 장면 전체에 별과 입자가 흩어진 06(22.07)과 08 이 넓게 움직이고, 구 하나가 중심인 장면들은 절제되게 돕니다. 시간은 전부 requestAnimationFrame 이 넘겨주는 값으로 직접 재었고 60fps 에 붙어 있었습니다.

어디서 깨지는가 — 위성이 지구본을 뚫고 지나다니던 첫 판

첫 판의 위성은 궤도를 따라 잘 돌다가 지구본을 뚫고 지나다녔습니다. 궤도 타원만 화면에 그리고 위성 위치는 2차원 타원 식으로 계산했더니, 지구본이라는 원과 위성의 깊이가 서로 비교되지 않아 앞인지 뒤인지 판정이 없었던 것입니다. 위성 위치를 3차원 궤도에서 계산해 지구본과 한 배열에 담아 z 로 정렬하고 나서야(03) 가림이 자연스러워졌습니다.

05 의 낮밤 경계도 첫 판에서는 CSS 그라디언트를 겹쳐 만들었는데, 경계선이 항상 화면의 같은 각도로만 서 있어 태양이 돌아도 생글생글 움직이는 반쪽짜리 지구본이 되었습니다. 픽셀마다 내적을 계산하는 지금의 방식으로 바꾼 뒤에야 경계가 구면을 따라 휘었습니다. 아홉 장면의 완성된 소스와 React 판은 글 아래 암호 zip 안에 들어 있고, 푸는 암호는 btsc7vfs 입니다.

변형 세 가지와 접근성

이름 바꾼 값 느낌 한 줄
점 밀도 02 N 850 → 1400 지구본이 촘촘해져 더 단단한 덩어리로 읽힌다
시차 세기 06 par 0.35/0.75/1.25 → 0.2/0.5/1.6 층 분리가 커져 깊이가 극적으로 과장된다
회전 속도 09 rotY 0.26 → 0.6 고리와 지구본이 빠르게 돌아 토성의 하루가 짧아진다

접근성은 아홉 판이 한 원칙을 공유합니다. prefers-reduced-motion: reduce 를 켠 사용자에게는 rAF 루프를 시작하지 않고 각 판이 가장 잘 읽히는 정적 한 장만 그립니다 — 01 은 위경도 선이 완성된 지구본, 05 는 낮밤이 갈린 경계선, 08 은 입자가 구를 이룬 순간입니다. 02·06 에는 시드 입력과 tabindex · role 이 있어 키보드로 시드를 바꾸고 시차를 몰아볼 수 있고, 03 도 tabindex 로 초점을 받아 화면 낭독기에 안내문이 전달됩니다.

FAQ

3d 지구본 사이트 연출을 라이브러리 없이 만들어도 성능이 될까요?

됩니다. 이 아홉 장면은 매 프레임 그리는 선과 점이 전부라 05 의 픽셀 순회가 가장 무거운데도 원 안쪽만 계산합니다. 캔버스 크기는 판 안에 맞추고 devicePixelRatio 로 백킹스토어만 물리 픽셀로 키우므로 레티나에서도 같은 계산량을 유지합니다.

위성 궤도 시뮬레이션처럼 보이는 03 은 실제 물리 계산인가요?

궤도 모양은 타원 매개변수를 따라 돌고 속도는 위성마다 고정된 계수로 어긋나게 한 근사입니다. 물리 정확도가 필요하면 07 의 케플러 면적 속도 방식을 03 에 얹으면 근일점에서 빨라지는 위성이 됩니다.

소스 코드를 그대로 복사해도 되나요?

됩니다. 글 아래 암호 zip 에 아홉 장면의 바닐라 판과 React 판이 통째로 들어 있어 붙여 넣으면 바로 돌아갑니다. 색은 각 판 scss 맨 위의 토큰 변수로 갈아끼우면 되고, 시드를 바꾸는 입력도 02·06 에 그대로 들어 있습니다.

더 읽을거리

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

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