frame academy

Learn how to create immersive, cross-platform webxr sites.

project 4: create a webxr photosphere gallery with javascript

part 5: creating the photosphere gallery

I hope you took a crack at the challenge for Part 4! The skills you practice in the challenges are the same ones you will use to build your own VR gallery.

Remember that if you have questions or anything you want to share, please post on our online community or on Twitter with the hashtag #frameacademy

Quick Review

In part 4, we saw how to create a disappear component that caused entities in your scene to disappear as soon as they were clicked. We wrote our first JavaScript function and learned a bit about:

this -
a special word in JavaScript that lets you reference whatever entity a component is attached to. It's super handy for making sure your component does what it should no matter what entity it's attached to. You can use this.el to make sure you're referencing the underlying element so that you can use things like .setAttribute and .addEventListener

.setAttribute - a JavaScript function that lets you create or modify the attributes on your HTML elements dynamically. You can use .setAttribute to modify and add components to your entities.

.addEventListener - a JavaScript function that lets you listen for events and then run a function as soon as the event goes off. We used it to listen for the "click" event and then run the disappearfunc function we made.

Our disappear component is pretty cool, but it's not super practical for an immersive museum unless you want the user to be able to make stuff vanish. I had us build it because it gave us a chance to learn a few things that we can now use to build what I really want to show you: a way to make a photosphere gallery in your museum that lets users view any pick and view photospheres in your museum.

Below, I'll show you how I built a component that lets you click on the sphere in the museum to change the background photosphere of the scene, and the component that lets you click on the home button to go back to the museum. With these components, you can bring a cool, cross-platform photosphere gallery into your project. Here are the components in action:

ID and class in HTML

The first thing we should check out is the HTML in the starter project. Notice that I've taken the disappear component off the entities because I don't want things to vanish when the user clicks on them anymore.

Take a look at the entities on line 33 and 35. On line 33, there's an a-sky entity and on line 35 there's an a-entity that is the parent for the floor, ceiling, and wall entities. There are some attributes on these entities that are quite important: id and class. I've given the a-sky id="sky" and the entity below it has id="architecture" and class="homeworld":

Why do I have these attributes? Well, if your HTML elements have an id or a class, it's very easy to reference those elements from your JavaScript files. This gives you a handy way to let your components find and modify any element in your HTML! Instead of JavaScript's this, which only lets you reference the specific element a component is attached to, id and class will let you select any element or group of elements in your HTML document. So, what's the difference between id and class?

An id needs to be unique - two elements can't have the same id. Glitch will even throw an error if it detects that you've tried to give multiple elements the same unique id. See what happens when I try to give two of my walls an id of "wallpart". Glitch throws up the red circle on the left letting me know there's a problem.

Multiple elements can have the same class, though. That's why a lot of elements in the scene have class="homeworld".

In your JavaScript, you can use an id to reference a specific HTML element, but if you want to reference more than one element at once, you can give a group of elements the same class and use class instead. When you press the sphere in the corner to make it the background, all of the elements with class="homeworld" disappears! That's because I'm able to select them all at once and then modify them - all because they have the same class.

So, that's why I've got the id and class attributes on elements throughout my HTML - I expect I'll need to reference those elements in my JavaScript. Next, I'll show you how to actually reference elements using id and class in your JavaScript.

.querySelector

I'm going to make a new JavaScript file in Glitch called public/js/sphereexpand.js and I'll put my new component in here. I want to name it sphereexpand, so my starter code to register and name the component looks like this. Note: you can also find this component starter code in the public/js/startercomponent.js file in your project:

AFRAME.registerComponent('sphereexpand', {           

   init: function () {  
           
    }

 });

Let's think through what this sphereexpand should do. When the user clicks on the sphere in the scene, we want two things to happen:

- we want most of the entities in our scene to disappear so that the photosphere is in full view.
- instead of the starry night sky, we want to make a different photosphere the background of the a-sky element.

First, the component needs a way to reference all of the elements that we want to have disappear when the user clicks. I gave all of those elements a class="homeworld" in my HTML document, and we can reference all of these elements with a document.querySelectorAll. Check this out:

let homeworldelements = document.querySelectorAll(".homeworld");

What document.querySelectorAll does is search through your HTML document for all elements that have a certain class. Notice the syntax: you put the class you're searching for in the parenthesis, inside of quotes, and after a dot. I'm storing the reference to all those entities in a variable that I've named homeworldelements, but you could name it whatever you want (see exceptions here).

That's pretty powerful. We've used the class attribute in our HTML to make it so that our JavaScript components can reference multiple elements from our HTML document at once. Now our sphereexpand component has a reference to all of the things in our scene that should disappear when the user clicks on the sphere.

Now, we need a reference to our a-sky element so that we can modify its src to display a different photosphere. There is only one a-sky element in our HTML document, so we don't need to use class - we can just use its unique id, which I've set as id="sky". Instead of. .querySelectorAll, just use .querySelector like this to reference a single element in your HTML document using its id:

let sky = document.querySelector("#sky");

Instead of the dot that you used to select elements of a certain class, you use the # sign to select an element by id. I've saved the reference to our a-sky element in a variable simply called sky, but again, you could name this whatever you want.

How can you test out that you've set your querySelectors the right way? Go ahead and log your new variables to the console to see what shows up. Inside the init function of my sphereexpand component, you could have this:

let homeworldelements = document.querySelectorAll(".homeworld");      
let sky = document.querySelector("#sky");    
console.log(homeworldelements);    
console.log(sky);

Reminder: in order for this to work, you need to make sure the JavaScript file with your component is imported in your HTML document with a script element and that you attach the sphereexpand component to some entity in your scene. You also already have my completed sphereexpand.js file already in the starter project, so you could just add the console.logs to it.

Here's what I see now in the browser console when I load the page (load the console with control-shift-i):

The first thing we see is homeworldelements - a list of all your elements with class="homeworld". We used querySelectorAll for this.
The second thing we see is the a-sky element, which we found by referencing its unique id. We used querySelector for this.

So far so good!

.forEach

Now that we have references to the elements we need in our component, it's time to make the function that actually does what we want. I'm going to call this function sphereloader so I'll start it off like this:

let sphereloader = () => {

}

Just as with our disappear function, there's nothing in the first parentheses yet. Inside the curly braces is where we put the code that will run when the sphereloader function is called. First, let's have this function make the photosphere on our a-sky element change. How would we do this? Take a peek at the a-sky element in the HTML (line 33):

<a-sky id="sky" src="#starsky"></a-sky>

It's the src attribute on a-sky that determines which photosphere it sets as the background. Right now, the src attribute for a-sky is set as one of the photospheres we've pre-loaded and stored in our a-assets (to review the Asset Management System, see this project).

So, if we change the value of the src attribute so that it references a different photosphere in our a-assets, then we should be set. We've already seen how to do this in the last part of this project - we can use .setAttribute to modify or add attributes to our HTML elements! Take a look at how I change the src of my a-sky element so that it uses a different photosphere from my a-assets:

let homeworldelements = document.querySelectorAll(".homeworld");      
let sky = document.querySelector("#sky");

let sphereloader = () => {
sky.setAttribute("src", "#bordeauxtheater");
}

Remember, inside the parentheses for .setAttribute, first you put the name of an attribute in quotes, a comma, and then the value you want to set for the attribute. Our function is halfway there! Now, to make our homeworldelements disappear, we can re-use some of the code in the disappear component we made in the last part of this project.

There's only one snag. As we saw in our console log, the homeworldelements we made with .querySelectorAll is a list of elements, it's not just one element like a-sky. It's all of the elements in our HTML document with class="homeworld". We can't do a .setAttribute on the whole list of elements because well, it's a list of elements. You can only do .setAttribute on a particular element at once. Thankfully, there's a pretty easy way in JavaScript to loop through each of the elements and run the same bit of code on each one: .forEach

Inside the parentheses after homeworldelements.forEach, we can write a function that will run for every element gathered in our homeworldelements list. Here's how my function looks:

homeworldelements.forEach((homeworldelement) => {      
homeworldelement.setAttribute("visible", false);
})

Notice that now we have a value, homeworldelement, inside the parentheses before the => sign of our function. That could be called anything you want; we're simply providing a name so that as .forEach runs on each item in our homeworldelements list, the function has a way to reference the item itself - not the whole list. So, the code in the curly braces will execute for every item in the homeworldelements list, and every time it does, homeworldelement will refer to each particular item, one after the other.

This code basically says "go through each one of the elements in the homeworldelements list, and for each homeworldelement, set its visible attribute to false." Now my sphereloader function looks like this:

let sphereloader = () => {      
sky.setAttribute("src", "#bordeauxtheater");      
homeworldelements.forEach((homeworldelement) => {      
homeworldelement.setAttribute("visible", false)
})      
}


Looks good, but remember that so far we've only defined the function. To actually run the function, we need to call it. Specifically, we want to call this sphereloader function whenever the user "clicks" on the sphere element in the back left corner of the gallery. So, just as we did with the disappear component in the previous part, below the function we'll set up an eventListener to listen for a click event and then call our function like this: 

this.el.addEventListener("click", sphereloader);

We're going to put our whole component on the sphere entity itself in our HTML document, so "this" will refer to the sphere. When the user clicks the sphere, the function will be called, our sky will change its background, and our homeworld elements will disappear. Take a look at the entire component in the public/js/sphereexpand.js file and test it out by clicking on the photosphere in the scene. In the HTML, notice that the sphereexpand component is on the a-sphere element on line 66.

challenge #5: bring it all home, add more spheres

For the challenge, I want you to take a peek at the backhome.js component in the starter project to see if you can make sense of how I set up the button that will take you back to the gallery after a photosphere has been expanded. Then, try to bring another sphere or two into your gallery and make it so that when the user clicks on them, they expand out just like the one already in the starter project. If you'd like, share your scene to the Frame Gallery so that we can check it out and learn from it using the form below! Also, see the button below if you'd like to donate to support Frame Academy and the creation of future projects.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Did you get value from this Frame Academy project? Do you want support the creation of more?

< go back to part #4
Modify your scene with javascript
see other frame academy projectsadvance to project 5 >
using sound and video in your webxr sites