はじめに
この記事はElixirアドベントカレンダー2025のシリーズ2、10日目の記事です
今回はBabylon.jsのチュートリアルの1章をLiveView+Babylon.jsでやっていく記事になります
Hooksの作成
前回はちょろっとだけだったのでColocatedHookで操作しましたが、しっかりと書く場合はちゃんとJS Hookとして書いた方が良いのでhooksを設定してきます
チャプターごとにHabylonxxx.jsを作成してHooksで読み込んでまとめます
それをapp.jsで展開するという感じです
const BabylonHookxxx = {
mounted() {
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
},
};
export default BabylonHook;
const Hooks = {
};
export default Hooks;
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import { hooks as colocatedHooks } from "phoenix-colocated/babylon";
import topbar from "../vendor/topbar";
+ import Hooks from "./hooks";
const csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
const liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken },
- hooks: { ...colocatedHooks},
+ hooks: { ...colocatedHooks, ...Hooks },
});
1-01 HelloWorld
チュートリアル1-01のHelloWorldをやります、豆腐の出現ですね
チュートリアルのシーン一覧みたいにしたいので色々手を加えていきます
home
-> チュートリアル1-01
-> チュートリアル1-03
のような感じですね
defmodule BabylonWeb.HomeLive do
use BabylonWeb, :live_view
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<div class="flex flex-col w-1/2 gap-4">
<.button navigate={~p"/ch101"}>Chapter1-01</.button>
</div>
</Layouts.app>
"""
end
end
一覧に追加したら、ルーティングにも追加します
scope "/", BabylonWeb do
pipe_through :browser
live "/", HomeLive
+ live "/ch101", Ch101Live
end
追加したら画面を作っていきます、戻るボタンをつけたヘッダーとCanvasを表示します
hookの指定は先程作成した BablyonHook101を指定します
defmodule BabylonWeb.Ch101Live do
use BabylonWeb, :live_view
def render(assigns) do
~H"""
<div class="p-4">
<.header>
Chapter 1-01
<:actions><.button navigate={~p"/"}>Back</.button></:actions>
</.header>
</div>
<canvas class="w-screen h-[88vh]" id="renderCanvas" phx-hook="BabylonHook101" phx-update="ignore">
</canvas>
"""
end
end
hooksはこのように貼り付けます
チュートリアルにはありませんがengine.runRenderLoopしないとレンダリングされないので注意してください
const BabylonHook101 = {
mounted() {
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
// シーンを作成
const scene = new BABYLON.Scene(engine);
// カメラを作成
const camera = new BABYLON.ArcRotateCamera(
"camera",
-Math.PI / 2,
Math.PI / 2.5,
3,
new BABYLON.Vector3(0, 0, 0),
scene
);
// ユーザからの入力でカメラをコントロールするため、カメラをキャンバスにアタッチ
camera.attachControl(canvas, true);
// ライトを作成
const light = new BABYLON.HemisphericLight(
"light",
new BABYLON.Vector3(0, 1, 0),
scene
);
// 箱 (豆腐) を作成
const box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
engine.runRenderLoop(() => {
scene.render();
});
},
};
export default BabylonHook101;
import BabylonHook101 from "./BabylonHook101";
const Hooks = {
BabylonHook101: BabylonHook101,
};
export default Hooks;
動かすとこんな感じ
1-03 モデル(シーン)の読み込み
02はviewの話なので飛ばして、モデルの読み込みですね
defmodule BabylonWeb.HomeLive do
use BabylonWeb, :live_view
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<div class="flex flex-col w-1/2 gap-4">
<.button navigate={~p"/ch101"}>Chapter1-01</.button>
+ <.button navigate={~p"/ch103"}>Chapter1-03</.button>
</div>
</Layouts.app>
"""
end
end
一覧に追加したら、ルーティングにも追加します
scope "/", BabylonWeb do
pipe_through :browser
live "/", HomeLive
live "/ch101", Ch101Live
+ live "/ch103", Ch103Live
end
公式のシーンを読み込むコードがあるのでそちらを以下の変更します
Elixirやってるとめっちゃ副作用発生して気持ち悪いっすね・・・
流れ的には
- scene読み込み完了
- sceneからメッシュ名で取得してy方向に2移動
- インデックスで取得してy方向に1移動
- sceneを返却
- レンダリング開始
となってますね
const BabylonHook103 = {
mounted() {
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
createAttachment(scene, canvas);
loadScene(scene);
engine.runRenderLoop(() => {
scene.render();
});
},
};
const createAttachment = ({ scene, canvas }) => {
const camera = new BABYLON.ArcRotateCamera(
"camera",
-Math.PI / 2,
Math.PI / 2.5,
15,
new BABYLON.Vector3(0, 0, 0),
scene
);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0), scene);
};
const loadScene = (scene) => {
// モデル読み込み
BABYLON.SceneLoader.ImportMeshAsync(
"",
"https://assets.babylonjs.com/meshes/",
"both_houses_scene.babylon",
scene
).then((result) => {
// 読み込みが完了したら
const house1 = scene.getMeshByName("detached_house");
house1.position.y = 2; // y 軸方向(↑)に +2
const house2 = result.meshes[2];
house2.position.y = 1;
});
};
export default BabylonHook103;
import BabylonHook101 from "./BabylonHook101";
+ import BabylonHook103 from "./BabylonHook103";
const Hooks = {
BabylonHook101: BabylonHook101,
+ BabylonHook103: BabylonHook103,
};
export default Hooks;
追加したら画面を作っていきます、戻るボタンをつけたヘッダーとCanvasを表示します
hookの指定は先程作成した BablyonHook103を指定します
defmodule BabylonWeb.Ch103Live do
use BabylonWeb, :live_view
def render(assigns) do
~H"""
<div class="p-4">
<.header>
Chapter 1-03
<:actions><.button navigate={~p"/"}>Back</.button></:actions>
</.header>
</div>
<canvas class="w-screen h-[88vh]" id="renderCanvas" phx-hook="BabylonHook103" phx-update="ignore">
</canvas>
"""
end
end
こんな感じで表示されます
最後に
1-02、1-04はWebページで表示する方法なので今回はスキップしました
まだほぼ貼り付けるだけなので、LiveViewに乗せる利点のほうがでてくるとよいかな・・・
次回は2章を進めていきます本記事は以上になりますありがとうございました

