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

アイソメトリック デザイン — 陰影計算で組む3Dジオラマシーン9種

光源角度からキューブ各面の明度を計算し、格子座標を2:1公式で画面座標に変換して等角投影を組みます。深度でスタック順を並べるペインターズアルゴリズムまで — 部屋と街のジオラマ9種、コードはパスワードzipで。

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

9つの順序は計算の依存関係そのものです。01が投影そのものを固定し、02がその投影の上にタイルを置く公式を担当し、03と04がその格子に陰影と深度を重ね、05と06がポインター入力を加え、07から09までが全部を組み上げて訪問者が部屋・街・ヒーローバナーとして認識できる完成シーンになります。

なぜ rotateX(-30deg) ではないのか — 真の等角投影行列と座標変換

チュートリアルでよく見るcssの等角キューブはrotateX(-30deg)で組まれています。近くまで見えますが、定規を当てると嘘がわかります。真の等角投影は3軸すべてをまったく同じ比率で縮め、その性質を満たす角度は1つしかありません。

正確な角度は-35.264度

その角度は-arctan(1/√2)、およそ-35.264度です。01はこの角度をrotateXのショートカットに任せず、JSで4×4回転行列を手で掛け合わせてから1本のmatrix3d()文字列にしてワールドコンテナに入れます。アイソメトリック web デザインで遠近感を出したくなっても、ここにCSSのperspective()を重ねてはいけません。等角は平行投影であり、視点を混ぜると消失点が生まれて等角そのものが崩れます。

格子座標は式で変える

ワールドが傾いた後、タイルの(col, row)はx = (col - row) * TILE_W / 2とy = (col + row) * TILE_H / 2で画面ピクセルに変わります。TILE_W/TILE_Hは34/17の2:1比率で、計算結果は--tx/--tyのCSS変数に入るため、格子の大きさを変えたら同じ式が全体を再配置します。目分量でdivを1つずつ動かす画面は、この記事の範囲外です。

9シーンの段階別コード

以下の9つはすべて、この節の等角行列と格子公式を再利用して、そこに陰影・積み順・ポインター入力・完成した構図のどれか1つだけを足します。コードフェンスはdemo/の実ファイルからそのまま抜き出したもので、完全なコード(vanilla 9種・React 9種)はパスワード付きzipに入っています。

01本物の等角 — rotateX(-30deg)ではなく正確な投影行列

近似のキューブと真の等角キューブを同じターンテーブル速度で並べて回し、歪みを理屈ではなく目で見えるようにした項目です。回転は毎秒24度(15秒で1周)で、各キューブの下に表示される縦横比は傾き角のcosからその場で計算されます。

本物の等角 — rotateX(-30deg)ではなく正確な投影行列
新しいタブで開く

01 · 本物の等角 — rotateX(-30deg)ではなく正確な投影行列 — 計測 累積面積 8.516% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

matrix3dtransform-stylebackface-visibility

行列を手で掛ける理由

mat4RotateX(-35.264)とmat4RotateZ(45 + spin)を掛けた積をmatrix3d()文字列に変換するまでが、真の等角の本体です。

  function mat4RotateX(deg) {
    var r = deg * Math.PI / 180, c = Math.cos(r), s = Math.sin(r);
    return [1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1];
  }
  function mat4RotateZ(deg) {
    var r = deg * Math.PI / 180, c = Math.cos(r), s = Math.sin(r);
    return [c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
  }
  function mat4Mul(a, b) {
    var out = new Array(16);
    for (var col = 0; col < 4; col++) {
      for (var row = 0; row < 4; row++) {
        var sum = 0;
        for (var k = 0; k < 4; k++) sum += a[k * 4 + row] * b[col * 4 + k];
        out[col * 4 + row] = sum;
      }
    }
    return out;
  }
  function mat4ToCss(m) {
    var s = '';
    for (var i = 0; i < m.length; i++) s += (i ? ',' : '') + m[i].toFixed(6);
    return 'matrix3d(' + s + ')';
  }

測定値が教えるもの

累積面積8.516%・面積最大1.985%・強度平均67.5で、動いたフレームは23/23です。中央の小さな2キューブが主役の構図として妥当な値です。

02格子座標変換 — (col, row) を画面 (x, y) に変える2:1公式

6×6の36マスが、ループ1つで菱形の格子に組み替わります。各マスの持ち上げ位相は(col + row) * 0.9 - t * 3.1で、波が格子を横切るように全部のタイルが呼吸します。

格子座標変換 — (col, row) を画面 (x, y) に変える2:1公式
新しいタブで開く

02 · 格子座標変換 — (col, row) を画面 (x, y) に変える2:1公式 — 計測 累積面積 8.835% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

translate3dCSS custom propertiescalc

ループで回す2:1公式

xは(col - row)から、yは(col + row)から出ます。マス目が増えても式は変わりません。

  var N = 6;                 // 一辺のマス数 — N×N格子
  var TILE_W = 34, TILE_H = 17;   // 等角2:1比率 — 幅は高さの2倍
  nEl.textContent = String(N * N);

  var tiles = [];
  var minX = Infinity, maxX = -Infinity, maxY = -Infinity;
  for (var row = 0; row < N; row++) {
    for (var col = 0; col < N; col++) {
      var x = (col - row) * TILE_W / 2;
      var y = (col + row) * TILE_H / 2;
      if (x < minX) minX = x;
      if (x > maxX) maxX = x;
      if (y > maxY) maxY = y;
      tiles.push({ col: col, row: row, x: x, y: y });
    }
  }
  var shiftX = -(minX + maxX) / 2;
  var shiftY = -maxY / 2;

中央揃えも計算で

累積面積8.835%・面積最大3.228%・強度平均45.9・動いたフレーム23/23です。中央への寄せはmin/maxから导かれたshiftX・shiftYで行われ、マージンは固定値を使いません。

03面シェーディング — 光源角度で3面の明度を計算する

キューブの上面・右面・前面の法線と光源ベクトルの内積から明度を作り、filter: brightness()に代入します。光源角度thetaは毎秒40度(9秒で1周)で回り、3面の明度が同じ式から同時に出直します。

面シェーディング — 光源角度で3面の明度を計算する
新しいタブで開く

03 · 面シェーディング — 光源角度で3面の明度を計算する — 計測 累積面積 5.965% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

filter: brightnessCSS custom propertieslinear-gradient

内積から明度への変換

明度式は0.32 + 0.92 * clamp(dot, 0, 1)です。光源角度の変数を1つ動かすと3面すべてが同じ計算で追従します。

  function shade(theta) {
    var r = theta * Math.PI / 180;
    var raw = [Math.cos(r), Math.sin(r), 0.55];
    var len = Math.sqrt(raw[0] * raw[0] + raw[1] * raw[1] + raw[2] * raw[2]);
    var L = [raw[0] / len, raw[1] / len, raw[2] / len];
    var bTop = 0.32 + 0.92 * clamp(dot3(N_TOP, L), 0, 1);
    var bRight = 0.32 + 0.92 * clamp(dot3(N_RIGHT, L), 0, 1);
    var bFront = 0.32 + 0.92 * clamp(dot3(N_FRONT, L), 0, 1);
    faces.top.style.filter = 'brightness(' + bTop.toFixed(2) + ')';
    faces.right.style.filter = 'brightness(' + bRight.toFixed(2) + ')';
    faces.front.style.filter = 'brightness(' + bFront.toFixed(2) + ')';
    pTop.textContent = String(Math.round(bTop * 100));
    pRight.textContent = String(Math.round(bRight * 100));
    pFront.textContent = String(Math.round(bFront * 100));
    var R = mm ? 58 : 84;
    sun.style.setProperty('--sx', (Math.cos(r) * R).toFixed(1) + 'px');
    sun.style.setProperty('--sy', (Math.sin(r) * R * 0.5).toFixed(1) + 'px');
  }

光源位置も同じ角度から

太陽の表示位置は光源角度のcos・sinそのもので、別のアニメーションはありません。累積面積5.965%・面積最大5.09%・強度平均67.9・動いたフレーム23/23です。

04積み順の計算 — 深度をz-index式に変えるペインターズアルゴリズム

固定ブロックのz-indexはMath.round((col + row) * 10)を1度だけ計算して固定し、移動ブロック(traveler)は自分の(col, row)が変わるたびに同じ式で再計算します。深度バッファのない2D描画で「奥から描く」を再現するのがペインターズアルゴリズムです。

積み順の計算 — 深度をz-index式に変えるペインターズアルゴリズム
新しいタブで開く

04 · 積み順の計算 — 深度をz-index式に変えるペインターズアルゴリズム — 計測 累積面積 4.119% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

z-indexCSS custom propertiescalc

同じ式を2回計算する

配置を担うplace()は02と同じ座標式です。固定と移動の違いは、式を1回だけ呼ぶか毎フレーム呼ぶかだけです。

  function place(el, col, row) {
    var x = (col - row) * TILE_W / 2;
    var y = (col + row) * TILE_H / 2;
    el.style.setProperty('--tx', x.toFixed(2) + 'px');
    el.style.setProperty('--ty', y.toFixed(2) + 'px');
  }

  // 固定格子ブロック — z-indexは一度だけ計算して固定する
  for (var row = 0; row < N; row++) {
    for (var col = 0; col < N; col++) {
      var block = document.createElement('div');
      block.className = 'ps__block';
      block.style.setProperty('--w', SIDE + 'px');
      place(block, col, row);
      block.style.zIndex = String(Math.round((col + row) * 10));
      world.appendChild(block);
    }
  }

  // 移動ブロック — 毎フレーム(col,row)を再計算し同じ式でz-indexを更新する
  var traveler = document.createElement('div');
  traveler.className = 'ps__block ps__block--traveler';

同じマスでは+1で上に重なる

travelerのz-indexは式に+1した値で、同じマスに来たとき手前に出ます。累積面積4.119%・面積最大0.933%・強度平均127.4(9種で最も明るい)・動いたフレーム23/23です。

05ポインター視差 — ジオラマ全体がわずかに追従する計算

pointermove座標をフィールド中心基準で-1〜1に正規化し、clamp(±9度)で抑えた目標角をrequestAnimationFrameのループの中で毎フレーム0.16ずつ補間します。等角格子はそのままで、カメラだけが回ったように見える計算です。

ポインター視差 — ジオラマ全体がわずかに追従する計算
新しいタブで開く

05 · ポインター視差 — ジオラマ全体がわずかに追従する計算 — 計測 累積面積 1.605% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermoverequestAnimationFrameclamp

clampで抑えてlerpで滑らかに

目標角を即座に適用しないのが滑らかさの源です。1.1秒入力がなければ自動走査に切り替わります。

  var IDLE_MS = 1100;

  field.addEventListener('pointermove', function (ev) {
    var rect = field.getBoundingClientRect();
    var nx = ((ev.clientX - rect.left) / rect.width) * 2 - 1;   // -1..1
    var ny = ((ev.clientY - rect.top) / rect.height) * 2 - 1;
    targetX = clamp(-ny * LIMIT, -LIMIT, LIMIT);
    targetY = clamp(nx * LIMIT, -LIMIT, LIMIT);
    lastInput = performance.now();
  });
  field.addEventListener('pointerleave', function () { lastInput = -Infinity; });

  function autoPath(t) {
    targetX = Math.sin(t * 0.6) * LIMIT * 0.85;
    targetY = Math.cos(t * 0.42) * LIMIT;
  }

  function paint(now, t) {
    if (now - lastInput > IDLE_MS) autoPath(t);
    curX += (targetX - curX) * 0.16;
    curY += (targetY - curY) * 0.16;
    tilt.style.transform = 'rotateX(' + curX.toFixed(2) + 'deg) rotateY(' + curY.toFixed(2) + 'deg)';
    rxEl.textContent = curX.toFixed(1);
    ryEl.textContent = curY.toFixed(1);
  }

人が離れても計算は止まらない

自動走査はsinとcosの経路で、キャプチャでも静止画にならずに済みます。累積面積1.605%・面積最大0.517%・強度平均67.0・動いたフレーム23/23です。

06分解図 — 深度順をそのまま展開距離に使うホバー

04の深度順の考え方を層インデックスiに置き換え、目標translateZをGAP × (LAYERS.length - i)で1度だけ計算して--tz-openに保存します。層はroof・attic・room・floorの4層で、ホバー時に上の層ほど遠くへ押し出される分解図になります。

分解図 — 深度順をそのまま展開距離に使うホバー
新しいタブで開く

06 · 分解図 — 深度順をそのまま展開距離に使うホバー — 計測 累積面積 11.103% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

translateZtransitionCSS custom properties

層番号に間隔を掛ける

GAPはデスクトップ18px・モバイル12pxです。戻る際も同じ層番号が順序を保証するため、レイヤー同士が飛び越えて重なりません。

  var GAP = mm ? 12 : 18;
  gapEl.textContent = String(GAP);

  var LAYERS = [
    { name: 'roof', w: 96 },
    { name: 'attic', w: 84 },
    { name: 'room', w: 84 },
    { name: 'floor', w: 96 }
  ];

  for (var i = 0; i < LAYERS.length; i++) {
    var layer = document.createElement('div');
    layer.className = 'ev__layer ev__layer--' + LAYERS[i].name;
    layer.style.setProperty('--w', (mm ? LAYERS[i].w * 0.78 : LAYERS[i].w) + 'px');
    // 逆順 — roof(屋根)が一番上の層なので、展開時に最も遠くへ動く必要がある
    layer.style.setProperty('--tz-open', (GAP * (LAYERS.length - i)) + 'px');
    world.appendChild(layer);
  }

展開と静止の区間

累積面積11.103%・面積最大7.998%・強度平均99.1で、動いたフレームは12/23です。ホバーしていない区間は閉じた積み上げを保つため、フレームに静止区間があります。

07部屋のジオラマ — 01〜04を組み立てた完成シーン

床は最初からその場に動かずに置かれ、左壁・右壁・屋根だけが同じtransition関数を--d(遅延)だけ変えて順番に立ち上がります。遅延は0ms・140ms・320msの3つで、組み立て順はこの3つの数字だけで決まり、3つの別々のアニメーションは存在しません。

部屋のジオラマ — 01〜04を組み立てた完成シーン
新しいタブで開く

07 · 部屋のジオラマ — 01〜04を組み立てた完成シーン — 計測 累積面積 9.435% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

matrix3dCSS custom propertiestransition

同じtransitionに3つの遅延

壁はscaleY(0.06)からscaleY(1)へ開きます。ワールド座標系はmat4ToCss(mat4Mul(mat4RotateX(-35.264), mat4RotateZ(45)))で一度だけ計算して固定する、01と同じ行列です。

.rb__wall {
  position: absolute;
  left: 0;
  top: 0;
  width: 56px;
  height: 46px;
  transform-origin: bottom center;
  transition: transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
  transition-delay: var(--d, 0ms);
  border: 1px solid rgba(23, 20, 26, 0.3);
  border-radius: 4px;
}

.rb__wall--left {
  background: #6ec8ff;
  transform: translate3d(-46px, -10px, 0) scaleY(0.06);
}

.rb__wall--right {
  background: #4cd4a6;
  transform: translate3d(2px, -10px, 0) scaleY(0.06);
}

完成した部屋の測定値

累積面積9.435%・面積最大6.259%・強度平均65.7で、動いたフレームは16/23です。1秒ごとの組み立て・分解サイクルで、2秒のキャプチャ窓に1回の組み立てが収まります。

08街ブロック — 建物の高さがそれぞれ違うシーン配置計算

3×3の9棟それぞれについて、建物の高さ(HMIN + rand() × HRANGE)・窓の数(2〜3個)・点滅の開始位相を、シード固定のmulberry32乱数で決めます。座標とz-indexは02・04と同じ式で、Math.random()は一度も呼ばれません。

街ブロック — 建物の高さがそれぞれ違うシーン配置計算
新しいタブで開く

08 · 街ブロック — 建物の高さがそれぞれ違うシーン配置計算 — 計測 累積面積 1.497% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CSS custom properties@keyframesrandom

Math.randomの代わりにシード

mulberry32(42)は実行のたびに同じ数列を返すため、スクリーンショット・gifキャプチャ・再読み込みがすべて同じ街並みになります。

  function mulberry32(seed) {
    return function () {
      seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
      var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
      t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    };
  }

  var SEED = 42;

窓の点滅も同じシードから

窓は--delayに-3〜0秒の位相を受け取り、@keyframesの無限ループで点滅します。累積面積1.497%・面積最大0.291%・強度平均17.3(9種で最も暗い夜のトーン)・動いたフレーム23/23です。

09ヒーローシーン — 昼夜照明と視差が一緒に回る完成版

ポインターの縦位置をclamp(0, 1)した値をtargetTとし、curTを毎フレーム0.14ずつ補間します。この1つの0〜1の数が空の色([91,140,248]から[23,20,26]へ)・窓の不透明度(0.15 + curT × 0.85)・太陽と月の軌道を同時に動かし、curTが0.5を超えるとis-nightクラスが切り替わります。

ヒーローシーン — 昼夜照明と視差が一緒に回る完成版
新しいタブで開く

09 · ヒーローシーン — 昼夜照明と視差が一緒に回る完成版 — 計測 累積面積 21.418% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CSS custom propertiestransitionpointermove

1つの数が空と窓と軌道を動かす

天体の位置はox = (curT - 0.5) × 150、oy = -cos(curT × π) × 76の軌道です。1.1秒無入力なら(1 - cos(t × 3)) / 2の自動スイープに切り替わります。

  curT += (targetT - curT) * 0.14;

  var r = Math.round(lerp(DAY[0], NIGHT[0], curT));
  var g = Math.round(lerp(DAY[1], NIGHT[1], curT));
  var b = Math.round(lerp(DAY[2], NIGHT[2], curT));
  sky.style.backgroundColor = 'rgb(' + r + ',' + g + ',' + b + ')';

  for (var i = 0; i < wins.length; i++) {
    wins[i].style.opacity = String((0.15 + curT * 0.85).toFixed(2));
  }

  var isNight = curT > 0.5;
  if (isNight !== wasNight) {
    field.classList.toggle('is-night', isNight);
    wasNight = isNight;
  }

  var orbY = mm ? 60 : 76;
  var ox = (curT - 0.5) * (mm ? 120 : 150);
  var oy = -Math.cos(curT * Math.PI) * orbY;
  sun.style.transform = 'translate3d(' + ox.toFixed(1) + 'px,' + oy.toFixed(1) + 'px,0)';
  moon.style.transform = 'translate3d(' + ox.toFixed(1) + 'px,' + oy.toFixed(1) + 'px,0)';

  pctEl.textContent = String(Math.round(curT * 100));

昼夜の境目は0.5

累積面積21.418%・面積最大21.219%・強度平均37.6で、9種で最も広い面積です。動いたフレームは23/23で、昼と夜の往復が最後まで続きます。

明度は光源角度だ — 面シェーディングと積み順の計算

9つのうち2つが残り全部を支えています。この記事で「3Dらしく」見えるものはすべて、明度の数か積み順の数のどちらかで、描かれた影や手で並べたレイヤー順は1つもありません。

面の明るさは毎フレーム再計算

03のshade(theta)と09の昼夜空はどちらも内積から明度を出す同じ考えを毎フレーム走らせているため、光源角度の変数を動かすと影響を受ける面が全部同時に変わります。1つの面だけ固定のhex色を書いていると、そこだけ取り残されます。

積み順も毎フレーム再計算

04のtravelerと06の展開層は、z-indexをプロパティではなく式の出力として扱います。移動ブロックは(col + row) × 10、展開層はGAP × (LAYERS.length - i)で、格子を横切ったりスタックを開いたりすると数字が変わり、DOMを手で並べ替える必要がありません。ポインター座標をclampとlerpで角度に変える立体シーンの9種や、停止角を数式で出し続ける回転盤の9種も、同じ「式を毎フレーム再計算する」作法で動く隣の実装です。

rotateX近似と固定z-indexは、どこで壊れるか

どの失敗も「等角に見えるが等角ではない画面」を作ります。rotateX(-30deg)を等角と呼ぶのは本当の角度を飛ばして3面の面積比率をわずかに歪ませることで、さらにその上にCSSのperspective()を重ねるのは悪化です。等角は平行投影なので、視点を混ぜると消失点が生まれて等角と衝突します。格子座標を式ではなく目分量のpxで固定すると、サイズ変更のたびに1つずつ測り直す作業に戻り、面ごとの明るさを固定のhex色で指定すると光源角度をいくら動かしても面間の明度関係が変わりません。移動ブロックのz-indexを作成時に1度だけ決めて毎フレーム再計算しないのは、等角で最も多いバグで、「手前」に読まれるべきブロックが誰も確認しないまま奥に留まります。05の視差にclampを設けないと画面端で回転角が過大になり場面がにじみ、08の建物の高さをシード固定でなくMath.random()で引き直すと、この記事のスクリーンショットと同じプロンプトからの再現が取れません — 9シーンの完全なコード(vanilla 9種・React 9種)はパスワード付きzipに入っていて、r5cadxdwを入力すると解凍できるので、シード付きの出力と自分の実行を突き合わせて確認できます。誰もいないときに05や09を止めたままにするとgifキャプチャが1枚の死んだフレームで終わり、prefers-reduced-motionのユーザーに空のキャンバスを見せるのは動きを減らせという要求を情報を消せという意味に読み違えたものです。

9枚をどの画面に実際に置くか

ヒーローセクションには09が向いています。昼夜照明とポインター視差が同じcurTの値で一緒に動くため、スクロールなしでも背景が生きて見え、見出しやCTAボタンを重ねるときはz-indexの順番だけ気にすればすみます。

会社概要ページのような静的な背景には07の部屋ジオラマが合います — 01〜04の計算を1度だけ組み立てて、演出が終われば止まるので、本文を読んでいる間に視線を奪いません。逆に料金表や実績セクションの背景には08の街ブロックが向いていて、シード乱数で決まる建物の高さが再読み込みのたびに変わるため、同じ背景が毎回同じに見えて飽きることがありません。

タッチ中心のトラフィックが多いページでは、05のポインター視差だけに頼らず、09のように1.1秒操作がないと自動で軌道を描く項目を選ぶと、画面が止まって見えません。

等角の角度と光源値を動かすとアクセシビリティも見えてくる

バリエーション 変える値 トレードオフ
等角の角度 01の傾き -35.264deg → 45deg(ミリタリー投影) 正確な等角は3面が同じ重みで読め、45度に倒すと上面が強調されて地図やゲーム盤のように読める
光源角度 03の左上45deg → 正面0deg 左上の光源は立体感が明確で、正面光は面間の明度差が狭まりアイソメトリック フラット デザインに近い落ち着いた印象になる
格子の密度 02の6×6 → 16×16 密な格子は街シーンを豊かにするが、タイルごとの計算量が増えて低スペック環境ではフレームが落ちることがある

prefers-reduced-motionでは完成した1枚を見せる

08を除く8つの項目は読み込み時にJSでwindow.matchMedia('(prefers-reduced-motion: reduce)')を1回だけ確認し、どの項目も空のフレームには戻りません。08だけはそもそも止めるループを持たず、CSSの@media (prefers-reduced-motion: reduce)で窓の点滅animationだけを止めて明度を固定値にします。03は光源角度を35度で止め、06は4層を閉じた積み上げのまま保ち、09はcurTを昼夜の境目を越えた0.6に置いて、減速を求めた訪問者には灯りのついた窓と昇った月を見せます。

9枚のシーンを組み込む前によくある質問

アイソメトリック デザイン 作り方として最初に組み上げるべきシーンはどれですか

07の部屋です。01の行列・02の座標・03の陰影・04の積み順がすべて1つの完成した部屋に入っていて、07が動けば残りのシーンは部品の付け足しで作れます。逆に01を飛ばして02から始めると、投影の歪みが全部の後段に持ち込まれます。

等角のmatrix3dにCSSのperspectiveを足してもよいですか

足しません。等角は平行投影で、perspective()は消失点を作る遠近の仕組みです。2つを重ねると遠いブロックが小さくなる歪みが等角の上に乗り、-35.264度の角度が前提とする縮尺関係が崩れます。

08の街が読み込むたびに違って見えるのはなぜですか

違って見えるなら、どこかでシードが変わっています。mulberry32(42)は決定論的で、同じシードは同じ建物の高さと窓の点滅遅延を毎回返す作りです。再読み込みとキャプチャで街並みが一致しない場合は、Math.random()が紛れ込んでいないかを先に疑ってください。

もっと読む

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

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