Chapter 1.3: Adding interactivity with A-Frame and Vue JS
A new control panel, Item Favorites, and Controller Interaction. I’ve made a lot of progress since the last update.
Before I dive into the technical stuff, I want to review the current state of the prototype. As it stands today, this project has the following features
- Load data from the Extended Collection (in sets of 12)
- VR controller using the basic A-Frame Laser Pointer
- Item Cards are now interactable. Click on a photo to open and close a card. Click on a button to toggle the favorite value.
- Control Panel (new)
- Buttons to change the current card layout / formation
- Buttons to page through the API data
- Buttons to change the data source: Load data from the API or Favorites.
- Favorites are stored in Local Storage and can be loaded with a button on the control panel.
Note: the demo is no longer available, but you can see it in action in this video.
If the focus for the last week was about using Vue Binding with A-Frame entities, this week was all about listening to A-Frame events in Vue. First, I needed something that could trigger events. While there are a lot of great controller input components available, I decided to limit myself to only features built into the A-Frame core project. I settled on laser controls attached to VR controllers. Laser controls can be thought of as a VR mouse. You can “click” and hover over items.
I created a simple player rig with a camera and entities to represent the controllers. These entities should show the default controller for the headset you are using. I use an Oculus Quest 2 so I see the white controllers that ship with the headset. I can use the primary trigger to click on an object with the laser.
<!-- Player Rig -->
<a-entity id="rig">
<a-camera id="camera">
</a-camera>
<a-entity
laser-controls="hand: left; model: true;"
raycaster="near: 0.1; far: 3; objects: .clickable"
></a-entity>
<a-entity
laser-controls="hand: right; model: true;"
raycaster="near: 0.1; far: 3; objects: .clickable"
></a-entity>
</a-entity>
The first event/interaction that I made was a click event on the image of the item card. Clicking on a compact card will expand it to the full view and move it forward to a central position. Clicking it again will return it to compact mode and place it back where it started. I did this by using v-on
to respond to the click event with a Vue method.
<a-entity :position="currentPosition">
<a-box
class="clickable"
v-on:click="toggleCardMode()"
>
...
</a-box>
</a-entity>
// Toggle the display value in ItemCard3D.vue
// The card position will update using a Vue computed property
// The card display state is handled in the template code
toggleCardMode() {
this.display = !this.display;
},
Next, I turned my attention to creating a simple Control Panel to host several buttons that can drive features at the scene level. This went through a few revisions throughout the week before ending up with the one I demoed in the video above.
Each of the buttons in the control panel is using the same v-on
event to method process that I used for the cards. I like this design pattern for adding high-level features to the scene, but I think for some events, it would be better to do things the “A-Frame Way”. For example, I wanted a hover state for the buttons on the control panel. Instead of editing the geometry directly in a Vue component, I decided to handle this with an A-Frame Component.
AFRAME.registerComponent("control-button-hover", {
init: function () {
var el = this.el;
el.addEventListener("mouseenter", function () {
el.setAttribute("scale", "1.1 1.1 1.1");
});
el.addEventListener("mouseleave", function () {
el.setAttribute("scale", "1 1 1");
});
}
});
In future projects, I’ll need to consider when and where to handle events. I suspect that I’ll use v-on
with Vue methods anytime that I need an entity to interact with data or state, but I’ll use A-Frame Components when the event only needs to happen in the context of the scene, without having an impact on in the rest of the Vue app.
You can view the code and try out the scene from this sandbox.
At this point, I consider this “feature complete” for what I set out to do. There are a lot of other areas that I want to explore in the future, but for now I’m going to prepare for the next chapter of this series: using Babylon JS with Vue JS.