easy-three provides a small wrapper for WebXR.
You can add a VR button, enable controller models, enable hand tracking models, and pick objects with a controller ray.
WebXR works only in supported browsers and devices. For example, use Meta Quest Browser or a WebXR-compatible desktop browser connected to a VR headset.
If you are new to easy-three, start from the template on the Getting Started page. The template already includes the importmap needed to load three.js and easy-three.
When using CDN directly, use the same importmap style as below.
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.185.1/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.1/examples/jsm/",
"@pixiv/three-vrm": "https://cdn.jsdelivr.net/npm/@pixiv/three-vrm@3/lib/three-vrm.module.min.js",
"@masabando/easy-three": "https://cdn.jsdelivr.net/gh/masabando/easy-three@1.15.0/dist/easy-three.js"
}
}
</script>Get xr from init(), then call xr.setup(). This adds the VR button to the page and enables WebXR rendering.
import { init } from "@masabando/easy-three";
const { camera, create, animate, xr } = init();
camera.position.set(0, 1.6, 3);
create.ambientLight();
create.directionalLight();
create.cube({
position: [0, 1.6, -2],
});
xr.setup();
animate();controls or fpv after entering XR. In XR mode, the headset controls the camera position and rotation.To play in VR, click the VR button added to the page. Put on your VR headset and use the controllers to interact with the scene.
WebXR requires HTTPS.
Recommended static hosting services include GitHub Pages, Vercel, and Netlify.
When checking locally, it is recommended to use VSCode's Live Server extension in combination with port forwarding.
The Live Server extension allows you to start a server with HTTP communication locally.
Then, open the terminal in VSCode from the menu "View" → "Terminal", and set the Live Server port (usually 5500) from "Port Forwarding" in the "Ports" tab to issue a URL.
By setting the display range to "Public", you can also access it from the browser of the VR headset.
After use, it is recommended to disable port forwarding.
If the original (non-XR) rendering is not fullscreen, the container passed to init() must have position: relative for the VR button to display correctly.
The camera position set in init() is used only in non-XR mode. In XR mode, the camera position is controlled by the headset.
To change the initial camera position, put the camera returned from init() into a group, and change the position of that group. In XR mode, the position of the group is used as the initial camera position.
// カメラを格納するグループを作成
const cameraGroup = create.group({
position: [0, 0, 3],
})
// カメラをグループに追加
cameraGroup.add(camera);
Pass objects to selectableObjects. When an object is selected by the controller, the selection information is stored in controller.userData.selected. You can get the selected object with controller.userData.selected[0].object.
const { create, animate, xr } = init();
create.ambientLight();
create.directionalLight();
const cube1 = create.cube({
position: [-0.7, 1.5, -2],
});
const cube2 = create.cube({
position: [0.7, 1.5, -2],
});
const { rightController } = xr.setup({
selectableObjects: [cube1, cube2],
});
animate(({ delta }) => {
cube1.material.color.set(0x3366ff);
cube2.material.color.set(0x3366ff);
const selected = rightController.userData.selected?.[0];
if (selected) {
selected.object.rotation.y += delta;
selected.object.material.color.set(0xff3333);
}
});The controller has no mousemove event. Instead, use the getIntersections() method of the controller to get information about objects that intersect with the controller ray.
const cube = create.cube()
const { rightController } = xr.setup({
selectableObjects: [cube],
});
animate(() => {
if (rightController.userData.selected?.[0]) {
const intersections = rightController.getIntersections()
if (intersections.length > 0) {
const p = intersections[0].point;
const object = rightController.userData.selected[0].object;
object.position.x = p.x;
object.position.y = p.y;
}
}
})| Option | Default | Description |
|---|---|---|
leftController | true | Shows the left controller model. |
rightController | true | Shows the right controller model. |
leftHand | true | Shows the left hand model when hand tracking is available. |
rightHand | true | Shows the right hand model when hand tracking is available. |
buttonTarget | domElement | Sets where the VR button is appended. |
selectableObjects | [] | Sets the objects that can be selected by the controller ray. |
In React, call init() and xr.setup() inside useEffect. Return destroy() from the effect so the renderer is cleaned up when the component is removed.
import { init } from "@masabando/easy-three";
import { useEffect, useRef } from "react";
export function MyXRScene() {
const ref = useRef();
useEffect(() => {
const { camera, create, animate, xr, destroy } = init(ref.current);
camera.position.set(0, 1.6, 3);
create.ambientLight();
create.directionalLight();
create.cube({
position: [0, 1.6, -2],
});
xr.setup();
animate();
return () => {
destroy();
};
}, []);
return <div
ref={ref}
style={{
width: "100%",
height: "400px",
position: "relative",
}}
/>;
}For a longer sample using sky, ocean, and controller selection, see the xr reference page.