[2026-04-25]
A-Frame can be developed from a plain HTML file without having to install anything.
A-FRAME A web framework for building 3D/AR/VR experiences
HTML を使った VR アプリの開発が簡単にできる
多くのプラットフォームで VR が利用できる
Vive, Meta Quest, WindowsMR, Apple Vision Pro
Entity-Component Architecture
JavaScript, DOM APIs, three.js, WebVR, WebGL などにアクセス可能
WebVR 向けにパフォーマンスを最適化
※下記のコードの一部はGeminiからの提案を改良して作成しています.
カメラが (0,0,0) にあるので,少し向こうに下がった場所(z=-5)に box を表示する
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-box height="1" width="1" depth="2" position="0 1 -5" color="#00FFCC"></a-box>
</a-scene>
</body>
</html>
aframe の読み込み
<script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
シーンの作成
<a-scene>
...
</a-scene>
Box の設定
大きさ:height(縦)=1, width(横)=2, depth(奥行き)=1, 座標:(0,1,-5), 色:#00FFCC
<a-box height="1" width="2" depth="1" position="0 1 -5" color="#00FFCC"></a-box>
Box をz 軸を中心に水平に回す
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
<script>
// コンポーネントの定義(<a-scene>より前に書く)
AFRAME.registerComponent('orbit', {
schema: {
radius: {type: 'number', default: 3}, // 半径
speed: {type: 'number', default: 1} // 速度
},
tick: function () { // 座標の計算
const t = time / 1000 * this.data.speed;
const x = Math.cos(t) * this.data.radius;
const z = Math.sin(t) * this.data.radius;
// 計算した座標をセット
this.el.setAttribute('position', {x: x, y: 1.5, z: z});
}
});
</script>
</head>
<body>
<a-scene>
<a-box orbit="radius: 4; speed: 2" height="1" width="2" depth="1" position="0 1 -5" color="#00FFCC"></a-box>
</a-scene>
</body>
</html>
コンポーネント orbit の定義 (JavaScript):<a-scene>より前に書く必要あり
schema 変数:radius=半径,speed=速度
<script>
AFRAME.registerComponent('orbit', {
schema: {
radius: {type: 'number', default: 3}, // 半径
speed: {type: 'number', default: 1} // 速度
},
...
});
</script>
AFrame の tick を用いた時間発展 (アニメーション):x=radius*cos(t), z=radius*sin(t), y=1.5
tick: function () {
const t = time / 1000 * this.data.speed;
const x = Math.cos(t) * this.data.radius;
const z = Math.sin(t) * this.data.radius;
this.el.setAttribute('position', {x: x, y: 1.5, z: z}); // このエレメントの3D座標の値を更新
}
box の設定
軌道設定:radius(半径)=4, speed(速度)=2
<a-box orbit="radius: 4; speed: 2" ...></a-box>
Lorenz方程式の軌道を描く
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('lorenz', {
init: function () {
this.rho = 28;
this.pos = {x: 0.1, y: 0.1, z: 0.1};
this.dt = 0.015;
this.scale = 0.15;
},
tick: function () {
const p = this.pos;
const rho = this.rho;
const dx = 10 * (p.y - p.x) * this.dt;
const dy = (p.x * (rho - p.z) - p.y) * this.dt;
const dz = (p.x * p.y - (8/3) * p.z) * this.dt;
p.x += dx; p.y += dy; p.z += dz;
// scaling and translation
const posX = p.x * this.scale, posY = p.z * this.scale, posZ = -p.y * this.scale - 5;
this.el.setAttribute('position', { x: posX, y: posY, z: posZ });
}
});
</script>
</head>
<body>
<a-scene>
<a-sphere lorenz radius="0.1" color="red"></a-sphere>
</a-scene>
</body>
</html>
コンポーネント lorenz の定義 (JavaScript):<a-scene>より前に書く必要あり
<script>
AFRAME.registerComponent('lorenz', {
init: function () {
... // 初期設定
},
tick: function (time, timeDelta) {
... // 各時刻における座標の計算
});
</script>
初期設定:rho = 28, 初期値 pos=(0.1, 0.1, 0.1), dt=0.015
init: function () {
this.rho = 28;
this.pos = {x: 0.1, y: 0.1, z: 0.1};
this.dt = 0.015;
this.scale = 0.15;
},
tick を用いた時間発展 (アニメーション):(posX, posY, posZ)=(x,z,-y), スケーリング (this.scale=0.15), 平行移動 (0,0,-5)
tick: function () {
const p = this.pos;
const rho = this.rho;
const dx = 10 * (p.y - p.x) * this.dt; // Lorenz equations
const dy = (p.x * (rho - p.z) - p.y) * this.dt;
const dz = (p.x * p.y - (8/3) * p.z) * this.dt;
p.x += dx; p.y += dy; p.z += dz; // (x,y,z) = (x,y,z)+(dx,dy,dz)
const posX = p.x * this.scale, posY = p.z * this.scale, posZ = -p.y * this.scale - 5;// scaling and translation
this.el.setAttribute('position', { x: posX, y: posY, z: posZ }); // このエレメントの3D座標の値を更新
}
ボールの設定
軌道設定:radius(半径)=0.1, color(色)="red"
<a-sphere lorenz radius="0.1" color="red"></a-sphere>
Lorenz方程式の複数の軌道を描く
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('lorenz-multi', {
schema: {color: {type: 'color', default: '#00FFCC'}},
init: function () {
this.rho = 28;
const offset = 0.001;
this.pos = {
x: 0.1 + (Math.random() - 0.5) * offset,
y: 0.1 + (Math.random() - 0.5) * offset,
z: 0.1 + (Math.random() - 0.5) * offset
};
this.dt = 0.015;
this.scale = 0.15;
},
tick: function () {
const p = this.pos;
const rho = this.rho;
const dx = 10 * (p.y - p.x) * this.dt;
const dy = (p.x * (rho - p.z) - p.y) * this.dt;
const dz = (p.x * p.y - (8/3) * p.z) * this.dt;
p.x += dx; p.y += dy; p.z += dz;
// scaling and translation
const posX = p.x * this.scale, posY = p.z * this.scale, posZ = -p.y * this.scale - 5;
this.el.setAttribute('position', { x: posX, y: posY, z: posZ });
}
});
</script>
</head>
<body>
<a-scene>
<a-sphere lorenz-multi radius="0.1" color="red"></a-sphere>
<a-sphere lorenz-multi radius="0.1" color="blue"></a-sphere>
<a-sphere lorenz-multi radius="0.1" color="yellow"></a-sphere>
</a-scene>
</body>
</html>
コンポーネント lorenz-multi の定義 (JavaScript):<a-scene>より前に書く必要あり
schema 変数の定義:オブジェクトごとに色を変えるため
schema: {color: {type: 'color', default: '#00FFCC'}},
初期値を少しづつずらす:pos=(0.1, 0.1, 0.1) + 乱数[-0.5,0.5) * offset
init: function () {
this.rho = 28;
const offset = 0.001;
this.pos = {
x: 0.1 + (Math.random() - 0.5) * offset,
y: 0.1 + (Math.random() - 0.5) * offset,
z: 0.1 + (Math.random() - 0.5) * offset
};
this.dt = 0.015;
this.scale = 0.15;
},
ボールの設定
色を変えて3つの軌道を設定:radius(半径)=0.1
<a-sphere lorenz-multi radius="0.1" color="red"></a-sphere>
<a-sphere lorenz-multi radius="0.1" color="blue"></a-sphere>
<a-sphere lorenz-multi radius="0.1" color="yellow"></a-sphere>
軌道計算の精度を上げるために,4次 Runge-Kutta 法を使ったスクリプトに書き直す.
AFRAME.registerComponent('lorenz-multi', {
…
tick: function () {
const p = this.pos;
// RNGKT4
const u = [p.x, p.y, p.z];
const p1=udot(u,this.rho);
let u1 = [u[0]+p1[0]*this.dt/2, u[1]+p1[1]*this.dt/2, u[2]+p1[2]*this.dt/2];
const p2=udot(u1,this.rho);
u1 = [u[0]+p2[0]*this.dt/2, u[1]+p2[1]*this.dt/2, u[2]+p2[2]*this.dt/2];
const p3=udot(u1,this.rho);
u1 = [u[0]+p3[0]*this.dt, u[1]+p3[1]*this.dt, u[2]+p3[2]*this.dt];
const p4=udot(u1,this.rho);
p.x += (p1[0]+2*p2[0]+2*p3[0]+p4[0])*this.dt/6.0;
p.y += (p1[1]+2*p2[1]+2*p3[1]+p4[1])*this.dt/6.0;
p.z += (p1[2]+2*p2[2]+2*p3[2]+p4[2])*this.dt/6.0;
const posX = p.x * this.scale, posY = p.z * this.scale, posZ = -p.y * this.scale - 5;
this.el.setAttribute('position', { x: posX, y: posY, z: posZ });
}
});
function udot(u,rho) {
const result = new Array(u.length);
// Lorenz eq.
const sigma = 10;
const beta = 8/3;
result[0] = sigma * (u[1] - u[0]);
result[1] = u[0] * (rho - u[2]) - u[1];
result[2] = u[0] * u[1] - beta * u[2];
return result;
};
AFrame にラベル,ボタン,スライダーなどのUIを追加することができます.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Slider Component example</title>
<script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
<script src="https://binzume.github.io/aframe-xylayout/dist/xylayout-all.min.js"></script>
<script>
// スライダーの値を反映する関数
window.onSliderChange = function(event) {
const sliderValue = event.detail.value; // スライダーの値
// ラベルのテキストも更新
document.querySelector('#textdisplay').setAttribute('value', "rho="+sliderValue.toFixed(1));
};
</script>
</head>
<body style="background-color: black; color:white;">
<a-scene>
<a-entity cursor="rayOrigin: mouse; fuse:false" raycaster="objects:.collidable;far:5500"></a-entity>
<a-entity id="cameraRig" wasd-controls>
<a-camera position="0 1.5 0" look-controls="enabled:false"></a-camera>
</a-entity>
<a-xywindow position="0 0 -12" width="6" height="5" title="UI Components" xywindow="background:true">
<a-xycontainer direction="column" spacing="0.4" padding="0.2">
<a-xylabel id="textdisplay" value="rho=2.0" width="2" height="0.5"></a-xylabel>
<a-xyrange id="myslider" class="collidable" width="2.5" min="0" max="10" value="2" onchange="onSliderChange(event)">
</a-xyrange>
<a-xybutton label="Reset" width="2" height="0.5"
onclick="document.querySelector('#myslider').value = 2; onSliderChange({detail:{value:2}})">
</a-xybutton>
</a-xycontainer>
</a-xywindow>
</a-scene>
</body>
</html>
スライダーの値を反映する関数:event 変数からスライダーの値を取り出し,id=textdisplay における値 (ラベルのテキスト) を更新する
<script>
window.onSliderChange = function(event) {
const sliderValue = event.detail.value; // スライダーの値
document.querySelector('#textdisplay').setAttribute('value', "rho="+sliderValue.toFixed(1)); // ラベルのテキストも更新
};
</script>
マウスによるカーソル操作 ,レイキャスティングの交差テストの設定
<a-entity cursor="rayOrigin: mouse; fuse:false" raycaster="objects:.collidable;far:5500"></a-entity>
カメラリグの設定 (WASD: Keboard による操作):look-controls を無効化
<a-entity id="cameraRig" wasd-controls>
<a-camera position="0 1.5 0" look-controls="enabled:false"></a-camera>
</a-entity>
UIパネル
パネル
<a-xywindow position="0 0 -12" width="6" height="5" title="UI Components" xywindow="background:true">
<a-xycontainer direction="column" spacing="0.4" padding="0.2"> // 縦並びのスペース 0.4
...
</a-xycontainer>
</a-xywindow>
ラベル表示
<a-xylabel id="textdisplay" value="rho=2" width="2" height="0.5"></a-xylabel>
スライダー:変更があったら onSliderChange() を呼び出す
<a-xyrange id="myslider" class="collidable" width="2.5" min="0" max="10" value="2"
onchange="onSliderChange(event)">
</a-xyrange>
リセットボタン:クリックされたら,最初にスライダーの値を 2 に変更し,次に値を 2 として onSliderChange() を呼び出す
<a-xybutton label="Reset" width="2" height="0.5"
onclick="document.querySelector('#myslider').value = 2; onSliderChange({detail:{value:2}})">
</a-xybutton>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Slider Component example</title>
<script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
<script src="https://binzume.github.io/aframe-xylayout/dist/xylayout-all.min.js"></script>
<script>
// スライダーの値を反映する関数
window.onSliderChange = function(event) {
const sliderValue = event.detail.value; // スライダーの値
// ラベルのテキストも更新
document.querySelector('#textdisplay').setAttribute('value', "rho="+sliderValue.toFixed(1));
};
</script>
</head>
<body style="background-color: black; color:white;">
<a-scene>
<a-entity cursor="rayOrigin: mouse; fuse:false" raycaster="objects:.collidable;far:5500"></a-entity>
<a-entity id="cameraRig" wasd-controls>
<a-camera position="0 1.5 0" look-controls="enabled:false"></a-camera>
<a-entity laser-controls="hand: right" raycaster="far:Infinity;objects:.collidable"></a-entity>
<a-entity laser-controls="hand: left" raycaster="far:Infinity;objects:.collidable"></a-entity>
</a-entity>
<a-xywindow position="0 0 -12" width="6" height="5" title="UI Components" xywindow="background:true">
<a-xycontainer direction="column" spacing="0.4" padding="0.2">
<a-xylabel id="textdisplay" value="rho=2.0" width="2" height="0.5"></a-xylabel>
<a-xyrange id="myslider" class="collidable" width="2.5" min="0" max="10" value="2" onchange="onSliderChange(event)">
</a-xyrange>
</a-xycontainer>
</a-xywindow>
</a-scene>
</body>
</html>
VRコントローラ用の設定:レーザーでカーソルを表示
<a-entity id="cameraRig" wasd-controls>
<a-camera position="0 1.5 0" look-controls="enabled:false"></a-camera>
<a-entity laser-controls="hand: right" raycaster="far:Infinity;objects:.collidable"></a-entity>
<a-entity laser-controls="hand: left" raycaster="far:Infinity;objects:.collidable"></a-entity>
</a-entity>