無限(または広大)な2D空間上でノード・カード・図形を自由に配置・移動・接続・拡大縮小するインタラクティブキャンバスUI。Figma(デザインキャンバス)・Miro(ホワイトボード)・Excalidraw(作図)・React Flow(フロービルダー)・Notion Canvas など、空間的な情報整理やフロー設計に使われる。パン(画面移動)・ズーム・ノードドラッグ・コネクター(矢印線)・複数選択が基本的なインタラクションセット。
この記事を読むと、transform: translate + scale によるパン・ズームの実装・座標変換(スクリーン座標⇔キャンバス座標)・ノードのドラッグ移動・SVGコネクター線の描画・ミニマップの設計・パフォーマンス最適化が自分でできるようになります。
1. UI例(Preview / Live)
実装で見る(GunjoUI)
この部品を、デザインシステム GUNJO の実装で確かめられます。
2. 定義(Definition)
CSS transform の translate と scale を組み合わせて、仮想2D空間上でパン(移動)とズームを実現するUIパターン。キャンバス上のオブジェクトはスクリーン座標ではなくキャンバス座標系で位置を管理し、ユーザーインタラクションのたびに座標変換を行う。
主要な座標変換:
スクリーン座標 → キャンバス座標:
canvasX = (screenX - offsetX) / scale
canvasY = (screenY - offsetY) / scale
キャンバス座標 → スクリーン座標:
screenX = canvasX * scale + offsetX
screenY = canvasY * scale + offsetY
3. 使い分け(When to use / When NOT to use)
3.1 When to use
- フロービルダー・ワークフロー設計:ノードとエッジで処理フローを視覚化する
- ホワイトボード・ブレインストーミング:アイデアを空間的に整理する
- ネットワーク・依存関係の可視化:マイクロサービス構成図・組織図
- デザインツール・作図:グラフィックの配置・整列
3.2 When NOT to use
- データが少ない(10件未満):通常のリストやグリッドの方が認知コストが低い
- スクロール一覧で十分な場合:Spatial Canvas は学習コストが高いため、単純な一覧には不向き
- モバイルのみ対応:パン・ズームのマルチタッチ操作は複雑で、デスクトップ向けUIとして設計する方が安全
4. 設計判断の核(Decision Principles)
Spatial Canvasの核は「座標変換の正確な実装」——パン・ズームはすべて
offset(translate)とscaleの2変数で管理し、ユーザーのすべてのインタラクション(クリック・ドラッグ・ホイール)はスクリーン座標からキャンバス座標に変換してから処理する。この変換が1px でもズレると、ノードのドラッグ・配置がすべてズレる。
判断の優先順位:① 座標変換の正確な実装 → ② ズームの中心点(マウス位置)→ ③ パフォーマンス(大量ノード)→ ④ ミニマップ
- ズームはマウス位置を中心に行う:単純に
scaleを変更すると原点(左上)を中心に拡大縮小されてしまい、操作感が不自然。offsetを同時に更新することでマウス位置を中心に拡大縮小する:newOffset.x = mouseX - (mouseX - offset.x) * (newScale / scale) - ノードのドラッグはポインターキャプチャを使う:
onMouseMoveをドキュメント全体で受け取るか、setPointerCaptureを使うことで、ノードの外にカーソルが出てもドラッグが続く実装が必要 - 大量ノードは仮想化(表示範囲外を非レンダリング)する:100件以上のノードを持つキャンバスでは、現在のビューポートに表示されているノードのみレンダリングするビューポートカリングが必要。パフォーマンスが劣化すると使い物にならない
- キーボード操作を提供する:矢印キーでパン、
+/-でズーム、0でリセット。ノード選択中は矢印キーで微調整移動を提供することで、キーボードユーザーも操作可能にする
5. 状態設計(States)
| 状態 | カーソル | 説明 |
|---|---|---|
| アイドル | grab | 何もしていない状態 |
| キャンバスパン中 | grabbing | マウスダウンしてキャンバスを移動中 |
| ノードドラッグ中 | grabbing | ノードを移動中 |
| ノード選択中 | default | ノードがアクティブ |
| ズーム中 | — | スクロールでズームイン/アウト |
| 接続線描画中 | crosshair | エッジを引く操作中 |
6. バリエーション設計(Variants)
| バリアント | 特徴 | 使用例 |
|---|---|---|
| フロービルダー | ノード+有向エッジ | ワークフロー・BPMNダイアグラム |
| ホワイトボード | 自由配置・図形・付箋 | Miro・FigJam |
| マインドマップ | 階層的なノードツリー | ブレインストーミング |
| 依存グラフ | 有向グラフ(DAG) | CI/CDパイプライン・データリネージ |
| デザインキャンバス | 図形・テキスト・画像 | Figma・Sketch |
7. パターン集(Good / Bad / How to fix)
7.0 よく崩れる設計パターン(3つ)
- ズームが原点(左上)中心になる:
scaleのみを変更しoffsetを更新していないため、ズームするたびにキャンバスが左上に引っ張られる - ノードのドラッグがズーム後にズレる:ドラッグ計算でスケールを考慮していないため、ズームしている状態でノードを動かすとカーソル位置とノードがズレる
- 100件以上のノードで激重になる:全ノードをDOMに描画しているため、ノード数が増えるとフレームレートが下がりスクロールが引っかかる
7.1 Bad(典型3つ)
setScale(newScale)のみ更新しoffsetを変更しないため、ズームするたびにビューが左上にジャンプする- ドラッグ中のノード位置を
deltaXで更新する際にスケール除算を忘れ、ズーム時にドラッグ量がズレる - 1000件のノードを
Array.map()でフルレンダリングし、パンのたびに全ノードが再レンダリングされる
7.2 Good(対になる3つ)
- ズーム時に
newOffset.x = mouseX - (mouseX - offset.x) * ratioを計算してオフセットも同時更新する - ノードドラッグのデルタを
deltaX / scale・deltaY / scaleでスケール補正してからキャンバス座標に適用する React.memo+ ビューポートカリング(ノードのキャンバス座標がビューポート範囲外ならnullを返す)でレンダリングを最適化する
7.3 How to fix(手順)
handleWheelでズーム時のoffset補正計算を追加する- ノードドラッグのデルタを
/scaleで補正する - ノードコンポーネントを
React.memoでメモ化する - キャンバス座標がビューポート外のノードをスキップするフィルタリングを追加する
8. ルール(Must / Better)
Must(守らないと壊れる)
Better(品質が跳ねる)
9. アクセシビリティ要件(必須)
Screen Reader / Keyboard
Spatial Canvas は本質的にビジュアルインタラクションに依存するUIのため、スクリーンリーダーとキーボードユーザー向けに代替アクセス手段を検討する:
<div
role="application"
aria-label="フローチャートキャンバス"
tabindex="0"
>
<!-- ノード -->
<div
role="button"
aria-label="ノード:デザイン(x:220, y:40)"
aria-selected="true"
tabindex="0"
>デザイン</div>
</div>
<!-- 代替:スクリーンリーダー向けのリスト表現 -->
<nav aria-label="ノード一覧(スクリーンリーダー向け)" class="sr-only">
<ul>
<li><a href="#node-start">開始ノード</a></li>
<li><a href="#node-design">デザイン</a></li>
</ul>
</nav>
キーボードナビゲーション:
Tab:ノード間のフォーカス移動- 矢印キー:選択ノードの微移動(±1px または ±10px)
+/-:ズームイン/アウト0:ビューリセットEscape:選択解除
10. 実装メモ(Implementation Notes)
- React Flow:ノード・エッジ管理・ズーム・パン・ミニマップがすべて組み込みのライブラリ。プロダクションレベルのフロービルダーには React Flow をベースにすることを強く推奨
- Excalidraw(ライブラリ):ホワイトボード型のキャンバスを自前のUIに組み込める React コンポーネントとして提供されている
- パフォーマンス:
transform: translate(Xpx, Ypx) scale(N)の変更は GPU合成レイヤーに委ねられるため、left/topを直接変更するよりパフォーマンスが高い。will-change: transformを追加すると最初のフレームが速くなる - タッチ対応:
onTouchStart/onTouchMoveでピンチズームとパンを実装する場合、2点タッチのdistanceでスケールを、midpointでオフセットを更新するロジックが必要
11. 関連リンク
- 関連するUIデザイン原則: 情報密度 (Information Density), フィードバック (Feedback)
- 用語集(定義): 情報アーキテクチャ
- 関連するUIコンポーネント(横): Resizable(リサイザブル), Inspector Panel(インスペクターパネル), Scroll Area(スクロールエリア), Floating Panel(フローティングパネル)
12. まとめ
Spatial Canvasの設計で最重要なのは「座標変換の正確な実装」です。パン(translate)とズーム(scale)の2変数を一元管理し、すべてのインタラクションでスクリーン座標⇔キャンバス座標の変換を正しく適用してください。ズーム時のマウス位置中心補正とノードドラッグのスケール補正は特に見落としやすいポイントです。プロダクションレベルで実装する場合は React Flow などの専用ライブラリをベースにすることで、座標変換・パフォーマンス・アクセシビリティの複雑な実装を回避できます。