Estimated time: 30 mins
A-Frame: A web framework for building augmented/virtual reality experiences using HTML and Javascript. It uses the WebXR API underneath but simplifies VR scene creation by using custom HTML elements and is easy to use for beginners. It is cross-platform (Desktop, Mobile, VR).
CodeSandbox: An online, cloud-based development environment for building, sharing, and collaborating on web applications. Supports AFrame comoponents and AR/VR environments.
WebXR: A web API that enables virtual and augmented reality experiences directly in web browsers, supporting devices like Meta Quest without requiring app installation.
Hardware Requirements
Any computer with a modern web browser
Meta Quest headset for testing
Software Requirements:
CodeSandbox account
Create CodeSandbox Project:
Go to https://codesandbox.io/signin and create an account with Github, Google, or Apple. You will not be able to share your workspace without signing in.
Once signed in, navigate to https://codesandbox.io/templates and fork the HTML + CSS Template
Replace the contents of index.html with the basic A-Frame template, which will create a simple static 3D AR environment with a box, sphere, plane and sky and render your controllers with a pointable raycast. When developing, it is helpful to have the preview righthand sidepage open to see the effect of your changes. See the inline comments for description of each and the Aframe website docs (above) for more information:
```
<!DOCTYPE html>
<html>
<head>
<title>A-Frame AR Demo for Quest 3</title>
<!-- Load the minified A-Frame library -->
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<!-- Load the event-set component for interaction effects -->
<script src="https://cdn.jsdelivr.net/npm/aframe-event-set-component@5.0.0/dist/aframe-event-set-component.min.js"></script>
<script src="script.js" defer></script>
</head>
<body>
<!--
The <a-scene> is configured for AR:
• xr="mode: ar; referenceSpaceType: local-floor" starts an immersive AR session with floor-level tracking.
• renderer="alpha: true" makes the background transparent so the passthrough shows.
-->
<a-scene xr="mode: ar; referenceSpaceType: local-floor" renderer="alpha: true">
<!-- Controller tracking -->
<a-entity oculus-touch-controls="hand: left"></a-entity>
<a-entity oculus-touch-controls="hand: right"></a-entity>
<!-- Laser controls for pointer interaction, targets objects with class "interactive" -->
<a-entity laser-controls="hand: right" raycaster="objects: .interactive"></a-entity>
<!-- Interactive box that scales on hover -->
<a-box
class="interactive"
position="-1 0.5 -3"
rotation="0 45 0"
color="#4CC3D9">
</a-box>
<!-- Sphere -->
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E">
</a-sphere>
<!-- Static cylinder -->
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
</a-scene>
</body>
</html>
```
Add Interactive Elements
Create a new file in the Nodebox on the left, and name it script.js
Select the following JavaScript and paste it in the script.js file. This script will programmatically scale the blue box when pointed at using event listeners:
```
const scene = document.querySelector('a-scene');
const box = document.querySelector('a-box');
box.addEventListener('mouseenter', function () {
box.setAttribute('scale', '2.5 2.5 2.5');
});
box.addEventListener('mouseleave', function () {
box.setAttribute('scale', '1 1 1');
});
```
Add Animation. You can also add animations to objects. To add a bouncing effect to the sphere, replace the previous sphere with this:
```
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"
animation="property: position; to: 0 2.25 -5; dir: alternate; dur: 2000; loop: true">
</a-sphere>
```
Save your project at any time by pressing Cmd S at any time (or Ctrl S for Windows). You won't be able to view updates without saving.
When done, send the project URL found in the "Share" button on the class activity board. Make sure the permissions for the project are either Unlisted (anyone with the link can access) or Public (anyone can access)
Accessing Your Project in Quest:
Put on your Quest headset
Open the Oculus Browser
Navigate to your CodeSanbox project URL.
If the Preview is not open, click "Open Preview" at the very bottom
At the top right of the Preview window, click on "Open in a new tab" (rightmost button)
In the new tab, this part is a little annoying -- the AR and VR buttons are hidden behind the "Open Sandbox" button in the bottom right. The AR button is towards the left behind "Open Sandbox", and the VR button is towards the right.
We will complete this activity in AR mode, so press the button on the left. If you face difficulties in pressing the button without clicking "Open Sandbox", try zooming into the browser -- you can do this by clicking "Menu" (three dots) in the top right of the browser, and then adjusting the zoom settings from there (go to the max at 500%)
If Quest asks for any space permissions, click “Allow”
Now you should be in AR! Use the Quest controls to interact with the blue box.
Take a screenshot of the environment with your controller pointed at the blue box and put it on the activity board!
Another very useful feature is the ability to load any 3D model. Now we will load in and visualize a model of a mosquito in amber. We will first view this model from different angles with physical movement, and then with joystick movement.
Download this model from this link on Sketchfab by clicking the Download 3D Model button (download the GLB 20MB version). Sketchfab is a great place to find free 3D models (the author has many other neat data viz models).
Then, open CodeSandbox and upload your downloaded model (you can drag and drop on the Nodebox).
Then, replace your entire <a-scene> component with this, which will add movement controls to your camera and load the .glb mosquito model. Make sure the src field of <a-gltf-model> matches the file name and path of your glb file.
```
<a-scene>
<!-- Controller tracking -->
<a-entity oculus-touch-controls="hand: left"></a-entity>
<a-entity oculus-touch-controls="hand: right"></a-entity>
<!-- Mosquito Model -->
<a-gltf-model src="./mosquito_in_amber.glb" position="0 0 -3" scale="1 1 1" rotation="0 180 0"></a-gltf-model>
</a-scene>
```
Click the Meta button, refresh the page, and enter AR again to see your changes.
You should now be inside a mosquito inside amber. Move around the model in physical space and observe it from different viewpoints. You will have to set the boundary to roomscale to do this. A prompt should display for this option when you step outside your stationary boundary (there will be obstacles in the way, but that is okay since you are in AR!).
Please make mental note of the experience of physical movement and take a screenshot of the model from your favorite angle and post it on the activity board as well. We will discuss comparisons of both options afterwards.
We will now enable joystick movement using the aframe-extras external component (learn more about controller interaction libraries here). First, add this line to your <head> section:
```
<script src="
https://cdn.jsdelivr.net/npm/aframe-extras@7.5.4/dist/aframe-extras.min.js
"></script>
```
Then replace the "Controller Tracking" a-entities at the top of your a-scence component with this:
```
<!-- Camera rig with movement-controls enabled for joystick navigation -->
<a-entity id="rig" movement-controls position="0 0 0">
<!-- The camera tracks your head movement and is positioned at typical eye height -->
<a-camera position="0 1.6 0" look-controls></a-camera>
</a-entity>
```
Use left joystick to move around and right joystick to rotate.
Please make mental note of the experience of joystick movement and take a screenshot of the model from your favorite angle and post it on the activity board as well.
The earlier scripts we used (for camera movement) were pre-written A-frame scripts. You can write your own custom scripts using JavaScript to enable custom movements and actions. We will try this now with another model.
Custom Script in CodeSandbox:
Create a new project in CodeSandbox by forking the HTML/CSS template, as before
Download this GLB model and upload it to your Nodebox (drag and drop)
Add a new file in the Nodebox -- name it quest-vertical.js
Paste this into quest-vertical.js -- We will use this as a script to enable vertical movement on our project
```
AFRAME.registerComponent("quest-vertical-buttons", {
schema: {
speed: { type: "number", default: 1.5 }, // meters/sec
hand: { type: "selector" }, // controller element to listen on
minY: { type: "number", default: -Infinity }, // optional clamp
maxY: { type: "number", default: Infinity }, // optional clamp
},
init: function () {
// Default to #rightHand if user didn't pass a hand selector.
if (!this.data.hand) {
this.data.hand = document.querySelector("#rightHand");
}
this.movingUp = false;
this.movingDown = false;
if (!this.data.hand) {
console.warn(
"[quest-vertical-buttons] No controller element found. " +
"Add id='rightHand' to your right controller or pass hand: #rightHand"
);
return;
}
// Quest right controller: A/B
this.onAUp = () => (this.movingUp = true);
this.onADone = () => (this.movingUp = false);
this.onBDown = () => (this.movingDown = true);
this.onBDone = () => (this.movingDown = false);
this.data.hand.addEventListener("abuttondown", this.onAUp);
this.data.hand.addEventListener("abuttonup", this.onADone);
this.data.hand.addEventListener("bbuttondown", this.onBDown);
this.data.hand.addEventListener("bbuttonup", this.onBDone);
},
remove: function () {
if (!this.data.hand) return;
this.data.hand.removeEventListener("abuttondown", this.onAUp);
this.data.hand.removeEventListener("abuttonup", this.onADone);
this.data.hand.removeEventListener("bbuttondown", this.onBDown);
this.data.hand.removeEventListener("bbuttonup", this.onBDone);
},
tick: function (t, dt) {
if (!dt) return;
dt = dt / 1000;
if (!this.movingUp && !this.movingDown) return;
const pos = this.el.object3D.position;
let y = pos.y;
if (this.movingUp) y += this.data.speed * dt;
if (this.movingDown) y -= this.data.speed * dt;
// Clamp if desired
if (y < this.data.minY) y = this.data.minY;
if (y > this.data.maxY) y = this.data.maxY;
pos.y = y;
},
});
```
Replace the content in your index.html with the following -- this is the same format as before, except we are adding our own script to enable vertical movement (quest-vertical.js), and have changed the model we are displaying (turbines.glb)
```
<!DOCTYPE html>
<html>
<head>
<title>A-Frame GLB Test</title>
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-extras@7.5.4/dist/aframe-extras.min.js"></script>
<script src="./quest-vertical.js"></script>
</head>
<body>
<!-- Camera rig with movement-controls enabled for joystick navigation -->
<a-entity id="rig" movement-controls position="0 0 0">
<!-- The camera tracks your head movement and is positioned at typical eye height -->
<a-camera position="0 1.6 0" look-controls></a-camera>
</a-entity>
<a-scene>
<a-entity id="leftHand" oculus-touch-controls="hand: left"></a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right"></a-entity>
<a-entity
id="rig"
movement-controls="speed: 0.2"
quest-vertical-buttons="speed: 1.5;"
position="0 0 0"
>
<a-camera position="0 1.6 0" look-controls></a-camera>
</a-entity>
<!-- GLB MODEL -->
<a-gltf-model
src="./turbines.glb"
position="0 0 -3"
scale="2 2 2"
rotation="0 180 0"
></a-gltf-model>
</a-scene>
</body>
</html>
```
Save your file and send the project URL found in the "Share" button on the class activity board. Make sure the permissions for the project are either Unlisted (anyone with the link can access) or Public (anyone can access)
Using the share URL, open the project in your Meta Quest 3 and test out seeing this model! The controls are the same as before, with the addition of pressing A/B buttons for vertical movement (i.e. what we enabled with our custom script!)
Take a screenshot of the model from your favorite angle and post it on the activity board.
There is a lot more you can do with Aframe, but hopefully this gives you a good introduction to its basic features and development!