# クイックスタート — Five SDK

公式の React と TypeScript スターターで公開シーンを表示し、Team データに接続する前に Five SDK の操作を確認します。

Canonical: https://developer.realsee.ai/ja/docs/five-sdk/quickstart
Source: https://developer.realsee.ai/ja/docs/five-sdk/quickstart.md
Language: ja

公式スターター **1.3.0**：[ZIP](https://developer.realsee.ai/starters/react-five/1.3.0/starter.zip)・[マニフェストと SHA-256](https://developer.realsee.ai/starters/react-five/1.3.0/manifest.json)・[AI 用の実行手順（英語）](https://developer.realsee.ai/starters/react-five/1.3.0/agent.md)

新しいディレクトリに展開する前にマニフェストの SHA-256 を確認し、ロックファイルと固定された依存関係を保持してください。

実際の公開シーンを使い、React + TypeScript のプロジェクトを起動します。スターターは **Five SDK 6.9.2**、**Three.js 0.117.1**、**React 18.3.1** と依存関係のロックファイルを固定しています。

<span id="choose-how-to-start" class="docs-anchor-alias" aria-hidden="true"></span>

## 開始方法を選ぶ

上の「スターター用プロンプトをコピー」を使い、ファイル作成とターミナル操作ができる AI コーディングツールに貼り付けてください。コピー前に全文を確認できます。プロンプトには公式のバージョン付きアーカイブ、チェックサム、確認手順が含まれています。プロンプトとスターター内の操作ラベルは英語です。

手動で設定する場合は同じ ZIP を新しいディレクトリに展開し、展開した `realsee-react-five` ディレクトリでターミナルを開きます。同梱のロックファイルを保持してください。

<span id="what-you-need" class="docs-anchor-alias" aria-hidden="true"></span>

## 必要なもの

- Node **24** と npm。
- WebGL に対応したブラウザー。
- シーンの画像とモデルにアクセスできるネットワーク。
- 公開シーンにはアカウントやキーは不要です。

[Playground](https://developer.realsee.ai/ja/playground) ではホスト済みの公開シーンを試せます。公開サンプルと Playground のデモにはログイン、Developer app、OpenAPI のアクセス権限は不要です。

<span id="install-check-and-run" class="docs-anchor-alias" aria-hidden="true"></span>

## インストール・確認・起動

```sh
npm ci
npm run check
npm run build
npm run dev
```

ターミナルに表示されたアドレスを開きます。開発用フロントエンドの既定値は `http://127.0.0.1:5173`、ローカルの Node API はポート `4101` です。

ローカルで本番ビルドを動かす場合は、`npm run build` の後に `npm start` を実行し、表示されたアドレスを使います。停止するには Ctrl+C を押します。

<span id="check-the-actual-result" class="docs-anchor-alias" aria-hidden="true"></span>

## 実際の表示を確認する

**Ready** が表示されたら、ドラッグして見回し、床を選んで移動し、**Panorama** と **Floorplan** を切り替えます。**Reset view** で開始時のカメラに戻ります。

ビューアーには、その Work が参照する画像とモデルへのネットワークアクセスが必要です。

公開 Work は `@realsee/open-works@0.1.1` のデータです。このパッケージは Five SDK の peer dependency ではありません。Five SDK は `three@^0.117.1` を指定し、このスターターは互換性のある `0.117.1` に固定しています。

<span id="explore-the-sdk-examples" class="docs-anchor-alias" aria-hidden="true"></span>

## SDK のサンプルを試す

スターターで **Open SDK examples** を選ぶか、ローカル URL に `?sdk-examples=1` を付けます。この独立したページでは公開シーンと、次のガイドと共通のソースを使います。

- [シーンの読み込み](https://developer.realsee.ai/ja/docs/five-sdk/load-a-scene)。
- [カメラと表示モード](https://developer.realsee.ai/ja/docs/five-sdk/camera-and-modes)。
- [イベントと座標](https://developer.realsee.ai/ja/docs/five-sdk/events-and-coordinates)。
- [画像のキャプチャー](https://developer.realsee.ai/ja/docs/five-sdk/capture-images)。

SDK サンプルはアプリの認証情報を交換せず、Team へのアクセスも検証しません。

<span id="continue-with-your-data" class="docs-anchor-alias" aria-hidden="true"></span>

## 自分のデータに接続する

[Team API クイックスタート](https://developer.realsee.ai/ja/docs/open-api/quickstart) に進みます。`npm run configure -- --device` を実行し、表示された確認リンクを開きます。ログインして所有元の Team と有効なアプリを選び、**Authorize agent** を自分で選択してください。補助ツールがローカルサーバーの `.env` を設定します。デバイス認証を利用できない場合は `.env` をローカルで設定します。秘密情報をプロンプトやブラウザー環境変数に入れないでください。

ローカルサーバーはトークンと一覧の応答を確認し、有効期間の短い選択情報を発行して、選択した Work を取得します。ブラウザーはその Work を解析して表示します。認可済みの一覧が空でも API の正常な結果です。シーンの表示成功とは区別してください。

<span id="if-something-fails" class="docs-anchor-alias" aria-hidden="true"></span>

## 問題が起きた場合

| 段階 | 対応 |
| --- | --- |
| インストール | Node 24 とレジストリーへのアクセスを確認します。ロックファイルを保持し、依存関係の競合を隠すために `--force` を使わないでください。 |
| ローカルサーバー | 表示されたポートを使うか、ローカル環境で未使用のポートを設定します。 |
| WebGL・シーンのアセット | ブラウザーの対応状況とアセットへの接続を確認します。読み込み中やエラーの状態を表示したままにしてください。 |
| Team API | ローカルサーバーが報告した段階に沿って確認します。公開デモの成功だけでは認証情報を検証できません。 |

既存のアプリへの組み込み前に [React ライフサイクル](https://developer.realsee.ai/ja/docs/five-sdk/react-lifecycle)、Team データを使う環境の公開前に [本番環境のチェックリスト](https://developer.realsee.ai/ja/docs/five-sdk/production) を確認してください。

<span id="read-the-working-viewer" class="docs-anchor-alias" aria-hidden="true"></span>

## 動作するビューアーのコードを読む

既定のビューアーは React のライフサイクルフックと実際の SDK を使います。変更する準備ができたら全文を開いてください。以下のコードはドキュメントのビルド時にダウンロード用プロジェクトから読み込まれます。


**src/Viewer.tsx の全体を表示**


```tsx title="starters/react-five/src/Viewer.tsx"
import { useEffect, useRef, useState } from "react";
import type { Five as FiveInstance, Mode, State } from "@realsee/five";
import { requestApi } from "./api";

export type Scene =
  | { kind: "demo"; label: string }
  | { kind: "team"; label: string; handle: string };
export type SceneStatus = {
  state: "idle" | "loading" | "ready" | "error";
  message: string;
  stage: "demo" | "work" | "scene";
};

export function Viewer({
  scene,
  status,
  onStatus,
}: {
  scene: Scene | null;
  status: SceneStatus;
  onStatus: (value: SceneStatus) => void;
}) {
  const host = useRef<HTMLDivElement>(null);
  const viewer = useRef<FiveInstance | null>(null);
  const initial = useRef<State | null>(null);
  const [currentMode, setCurrentMode] = useState<Mode | null>(null);
  const [modeChanging, setModeChanging] = useState(false);
  const [modeError, setModeError] = useState("");

  useEffect(() => {
    if (!scene || !host.current) return;
    const controller = new AbortController();
    let instance: FiveInstance | null = null;
    let resize: ResizeObserver | null = null;
    let stopped = false;
    let stopModeListener: (() => void) | undefined;
    const layer = document.createElement("div");
    layer.className = "canvas-layer";
    host.current.append(layer);

    async function load() {
      onStatus({
        state: "loading",
        stage: scene!.kind === "demo" ? "demo" : "work",
        message:
          scene!.kind === "demo"
            ? "Loading the public sample…"
            : "Checking your selection and fetching Work…",
      });
      try {
        const [{ Five, parseWork }, work] = await Promise.all([
          import("@realsee/five"),
          scene!.kind === "demo"
            ? import(
                "@realsee/open-works/virtual/816lPVZQkQDF5XOpPo/work.json"
              ).then((module) => module.default)
            : requestApi<{ work: unknown }>("/api/team/work", {
                method: "POST",
                body: JSON.stringify({
                  sceneHandle: (scene as Extract<Scene, { kind: "team" }>)
                    .handle,
                }),
                signal: controller.signal,
              }).then((data) => data.work),
        ]);
        if (stopped) return;
        onStatus({
          state: "loading",
          stage: "scene",
          message: "Rendering the spatial scene…",
        });
        instance = new Five({ imageOptions: { size: 512 } });
        viewer.current = instance;
        stopModeListener = instance.on("modeChange", (mode) => {
          if (!stopped) setCurrentMode(mode);
        });
        instance.appendTo(layer);
        await instance.load(
          parseWork(work as Parameters<typeof parseWork>[0]),
          "initial"
        );
        if (stopped) return;
        initial.current = {
          ...instance.state,
          offset: instance.state.offset.clone(),
        };
        setCurrentMode(instance.state.mode);
        setModeError("");
        setModeChanging(false);
        instance.refresh();
        resize = new ResizeObserver(() => instance?.refresh());
        resize.observe(layer);
        onStatus({
          state: "ready",
          stage: "scene",
          message:
            "Scene ready. Drag to look around and select a floor point to move.",
        });
      } catch (error) {
        if (stopped) return;
        instance?.dispose();
        instance = null;
        viewer.current = null;
        layer.replaceChildren();
        const detail =
          error && typeof error === "object" && "detail" in error
            ? error.detail
            : undefined;
        const stage =
          detail && typeof detail === "object" && "stage" in detail
            ? detail.stage
            : "scene";
        onStatus({
          state: "error",
          stage: stage === "work" || stage === "selection" ? "work" : "scene",
          message:
            error instanceof Error && detail
              ? error.message
              : "The scene could not render. Check WebGL and access to the scene assets, then reload the scene. API success and scene rendering are separate checks.",
        });
      }
    }
    void load();
    return () => {
      stopped = true;
      controller.abort();
      resize?.disconnect();
      stopModeListener?.();
      if (viewer.current === instance) viewer.current = null;
      initial.current = null;
      instance?.dispose();
      layer.remove();
    };
  }, [scene, onStatus]);

  async function toggleMode() {
    const instance = viewer.current;
    if (!instance || modeChanging) return;
    setModeChanging(true);
    setModeError("");
    try {
      const nextMode =
        instance.state.mode === "Panorama" ? "Floorplan" : "Panorama";
      await instance.changeMode(nextMode);
    } catch {
      if (viewer.current === instance)
        setModeError(
          "This scene could not switch modes. Some Work data has no floorplan model. The current view is still available."
        );
    } finally {
      if (viewer.current === instance) setModeChanging(false);
    }
  }

  return (
    <section className="viewer-card" aria-label="Spatial scene viewer">
      <div className="viewer-heading">
        <span className="eyebrow">FIVE SDK · LIVE CANVAS</span>
        <span
          className={`scene-badge ${
            status.state === "ready" ? "is-ready" : ""
          }`}
        >
          <span className="status-dot" />
          {status.state === "ready"
            ? "Ready"
            : status.state === "loading"
            ? "Loading"
            : scene
            ? "Needs attention"
            : "No scene selected"}
        </span>
      </div>
      <div
        ref={host}
        className="viewer"
        role="region"
        aria-label={scene?.label || "Select a Team VR to render"}
        aria-busy={status.state === "loading"}
        tabIndex={0}
        onKeyDown={(event) => {
          if (
            event.target === event.currentTarget &&
            event.key.toLowerCase() === "r" &&
            initial.current
          )
            viewer.current?.setState(initial.current, true);
        }}
      >
        {status.state !== "ready" && (
          <div
            className="viewer-overlay"
            role={status.state === "error" ? "alert" : "status"}
          >
            <div>
              <div
                className={`spatial-mark ${
                  status.state === "loading" ? "is-loading" : ""
                }`}
                aria-hidden="true"
              >
                ⌘
              </div>
              <p>
                {scene
                  ? status.message
                  : "Select a VR from your Team directory."}
              </p>
              {!scene && (
                <span className="viewer-hint">
                  Your public demo is one tab away.
                </span>
              )}
            </div>
          </div>
        )}
      </div>
      <div className="viewer-footer">
        <span>
          {scene?.label || "Team scene"}{" "}
          <span className="subtle">
            ·{" "}
            {scene?.kind === "demo"
              ? "Public sample Work"
              : "Your configured Team"}
          </span>
        </span>
        <div className="viewer-actions">
          <button
            className="text-button"
            disabled={status.state !== "ready" || modeChanging}
            onClick={() => void toggleMode()}
          >
            {modeChanging
              ? "Switching…"
              : currentMode === "Panorama"
              ? "Switch to floorplan"
              : "Switch to panorama"}
          </button>
          <button
            className="text-button"
            disabled={status.state !== "ready" || modeChanging}
            onClick={() => {
              if (initial.current)
                viewer.current?.setState(initial.current, true);
            }}
          >
            Reset view <span aria-hidden="true">↗</span>
          </button>
        </div>
      </div>
      {modeError && (
        <p className="viewer-mode-error" role="alert">
          {modeError}
        </p>
      )}
    </section>
  );
}
```

