GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

3D 地球儀サイト演出9選 — Three.jsなしのcanvas計算

3d 地球儀のサイト演出は普通、Three.js に任せます。ここでは衛星軌道・航路曲線・昼夜の境界まで9種を、Three.jsなしで canvas 2D の計算だけで描き、いちばん広く動いた08粒子地球儀が26.98パーセント、06星空背景が22.07パーセントを記録しました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

並べ方は派手さではなく球面計算が積み上がる順です。まず球座標を画面座標に移す正射影が生まれ(01)、その上に点・曲線・光の計算が乗り(02〜05)、場面が球を離れて背景と軌道へ広がり(06・07)、最後に粒子とリングが演出をひとつにまとめます(08・09)。ピクセルを直接屈折させるcanvas特殊効果と、シードで模様を引くCSS背景パターンがこの記事の隣人です。

01緯度経度線で描く回転地球儀

グローバルサービス紹介のヒーローに使う、緯線・経線で描いた回転地球儀です。

緯度経度線で描く回転地球儀
新しいタブで開く

01 · 緯度経度線で描く回転地球儀 — 計測 累積面積 14.28% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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ドットで埋めたドットマトリクス地球儀

世界の訪問者数を見せるセクションに合う、850個の点で埋めた地球儀です。

ドットで埋めたドットマトリクス地球儀
新しいタブで開く

02 · ドットで埋めたドットマトリクス地球儀 — 計測 累積面積 10.422% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 オリジナル · SCSS と React は zip に同梱 · パスワードを見る

great-circle arcBezier interpolationquadratic curve

球面線形補間が最短経路を描く

// slerp — 球面線形補間。omega は2点間の中心角
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 };
}

2つの都市を単位球上の点にとって slerp で結ぶと、地球を貫かない大圏コースが得られます。弧は球半径の1.10倍の高さに浮かせて曲面にへばりつかず、光る点が弧に沿って流れて運航中を示します。

05昼と夜が分かれる地球儀

時差案内・世界の勤務時間ウィジェットに使う、太陽の方向で明るくなる地球儀です。

昼と夜が分かれる地球儀
新しいタブで開く

05 · 昼と夜が分かれる地球儀 — 計測 累積面積 14.608% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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個を3層に分けて層ごとに視差倍率を変えると、マウスを動かしたとき遠い星はゆっくり、近い星は速く流れて奥行きが感じられます。下のシード入力と新しい星空ボタンで配置を引き直せ、瞬きの位相までシードが決めます(実測: 累積面積 22.07パーセントで9つのうち2番目)。

07複数の惑星が回る太陽系モデル

教育コンテンツ・展示紹介に使う、ケプラーの第2法則に従う太陽系模型です。

複数の惑星が回る太陽系モデル
新しいタブで開く

07 · 複数の惑星が回る太陽系モデル — 計測 累積面積 10.285% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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;
  }
}

惑星が近日点で速くなり遠日点で遅くなる様子を、角速度ではなく面積速度で計算します。太陽を中心とした扇形を軌道の上にうっすら残して、同じ時間に払う面積が常に同じという法則を絵で示します。惑星ごとに公転周期と離心率が違い、5つの軌道がずれながら回ります。

08散らばって集まる粒子地球儀

ブランドイントロ・ローディング演出に使う、散らばった粒子が集まって地球儀になる場面です。

散らばって集まる粒子地球儀
新しいタブで開く

08 · 散らばって集まる粒子地球儀 — 計測 累積面積 26.978% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 で大きさと透明度を調えて正射影の立体感をそのまま使います。9つの場面の中でいちばん広く動いて累積面積26.98パーセントを記録しました(実測: 強度 58.6、23/23フレーム)。

09リングをまとった地球儀

プレミアムサービス紹介の締めの場面、リングをまとった地球儀です。

リングをまとった地球儀
新しいタブで開く

09 · リングをまとった地球儀 — 計測 累積面積 19.972% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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パーセント)。

なぜ地球儀を canvas の座標計算で描くのか

地球儀 3d モデルの演出はたいてい Three.js のようなライブラリに任せますが、この9つの場面は球座標を画面座標に変える正射影の一行で足ります。回転は cos と sin の2値で軸を回したもの、隠蔽は深さ z で描く順を決めたもの、昼夜は法線と太陽ベクトルの内積です。ライブラリがないので読み込むファイルは0個、canvas 1枚に描くのでどのページに貼ってもそのページのカラートークンをそのまま受け取ります。

実測では9つの場面すべてが23枠のフレームで動き(生きている)、累積面積は10.15パーセント(04)から26.98パーセント(08)まで — 星と粒子が画面全体に散らばる06(22.07%)と08が広く動き、球が中心の場面は控えめに回ります。時間はすべて requestAnimationFrame が渡す値から直接測り、60fps を保っていました。

どこで壊れるか — 衛星が地球儀を貫通していた初版

初版の衛星は軌道に沿ってよく回るものの、地球儀を貫通して行き来していました。軌道楕円だけを画面に描き、衛星の位置は2次元の楕円式で計算していたため、地球儀という円との深さが比べられず、前か後かの判定がなかったのです。衛星の位置を3次元の軌道で計算して地球儀と同じ配列に入れ z で並べ替えて初めて(03)、隠蔽が自然になりました。

05の昼夜境界も初版では CSS グラデーションを重ねて作りましたが、境界線が常に画面の同じ角度に立つだけで、太陽が回っても半分だけ明るい貼り付けの地球儀になってしまいました。ピクセルごとに内積を計算する今の形に変えて初めて、境界が球面に沿って曲がりました。9つの場面の完成したソースと React 版は記事の下のパスワード zip に入っていて、解凍のパスワードは btsc7vfs です。

変化3つとアクセシビリティ

名前 変える値 一言の感じ
点の密度 02 N 850 → 1400 地球儀が密集して、より固い塊に見える
視差の強さ 06 par 0.35/0.75/1.25 → 0.2/0.5/1.6 層の分離が大きくなり奥行きが劇的になる
回転速度 09 rotY 0.26 → 0.6 リングと地球儀が速く周り、土星の一日が短くなる

アクセシビリティは9つの盤が同じ原則を共有します。prefers-reduced-motion: reduce のユーザーには rAF ループを始めず、各盤がいちばん読み取れる静的な1枚だけを描きます — 01は緯線経線の完成した地球儀、05は昼夜の分かれた境界線、08は粒子が球になった瞬間です。02・06にはシード入力と tabindex・role があり、キーボードでシードを変えて視差を確かめられ、画面の文言は3言語に分かれています。

FAQ

地球儀 3d サイトの演出をライブラリなしで作っても性能は持ちますか?

持ちます。この9つは毎フレーム描くのが線と点だけで、いちばん重い05のピクセル走査も円の内側だけを計算します。canvas の大きさは盤に合わせて devicePixelRatio だけで伸ばすので、レティナでも計算量は同じです。

地球儀 アニメーションの03は実際の物理計算ですか?

軌道の形は楕円のパラメータに沿って回し、速度は項目ごとに固定した係数でずらした近似です。物理的な正確さが必要なら、07のケプラーの面積速度の式を03に乗せると近日点で速くなる衛星になります。

ソースコードをそのままコピーしてもいいですか?

いいです。下のパスワード zip に9つの場面の vanilla 版と React 版がそのまま入っていて、貼り付ければすぐ動きます。色は各盤の scss 冒頭のトークン変数で付け替えられ、シードを変える入力も02・06にそのまま入っています。

さらに読む

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。