Custom UI for Viewer using API - tutorial
Orbitvu VIEWER (SUN, Free and Infinity 360 versions) supports API that makes it possible to customize Viewer UI. In this tutorial we will reimplement Viewer buttons to have them look like below.
We will start by defining the layout for our page and our custom buttons. We will work with the demo presentation from the SUN server.
There are few interesting things to note in the above code:
Line 10: We're loading Font Awesome library in order to get nice icons for our buttons
Lines 16-23: Embed code copied from Orbitvu SUN
Lines 15, 25, 27-231: note that id of these elements is set to be unique using uid of the presentation from Orbitvu SUN (WZarBpyJ7hHX77zaF49Rb9 in this case). It will be useful if we have multiple presentations on the same page.
You might be wondering why there are two <div> elements at the top level:
This is required for the Fullscreen button to work properly on some older browsers. You'll find more details about this later, when we will be adding Fullscreen button support.
If you're going to use presentation hosted on your own server (not with Orbitvu SUN), then you need to embed your presentation as described in this documentation.
We will now add some styles in order to have the desired layout of the page
Our page now looks like below. You can see it in separate window and check the source by clicking here.
Accessing the Viewer API
We have our page layout defined so now its time to connect to the Viewer API. In order to do so, first we have to define a callback that will receive API object as soon as the Viewer API is initialized. This can be done with viewer_api_init parameter. Let's start with writing our callback function:
Initially it will just output 'api initialized' text.
Now, let's connect our callback to the VIEWER. As we're using Orbitvu SUN embed code we will just add viewer_api_init to the querystring, like: &viewer_api_init=api_init (see highlighted part of the code below)
Now you should see the 'api initialized' text outputted to the console. If it doesn't work check if there are any errors in the console and, in case you're not using SUN, if your VIEWER does support the API.
Making the code reusable
Callback function we've defined has one small problem. It is defined as a global function called api_init, so if we want to add another 360 degree presentation to the page, we will have to make sure the callback name is unique (so that we can connect to the proper viewer instance).
We will use presentation uid: WZarBpyJ7hHX77zaF49Rb9 as a part of the callback name. We will also make our code embedded into the class so that it can be easily reused.
Our callback identifier will now be 'api_init_WZarBpyJ7hHX77zaF49Rb9'. And the VIEWER embed code will be:
We're using ES6 classes and arrow functions here. If you need to support older browsers make sure to use some compilers like Babel to make it backward compatible.
Using VIEWER API
Now we can do something useful with our API object. You might have noticed (especially if you're on a slower network or have network speed limited in Developer Tools) that when our page is loaded, custom buttons are shown immediately, and the VIEWER is initialized a bit later.
This is not good as we cannot use the buttons before VIEWER is properly intialized. What we would like to do is to hide our buttons initially and show these after VIEWER initialization. This can be easily done by using the partially_initialized event that is triggered by VIEWER as soon as it has loaded at least 4 frames (if partial_load is enabled).
There is also viewer_initialized callback that is called when all frames are loaded.
First we will add display: none to our buttons container:
Then we will add event handler that will show the buttons on VIEWER initialization:
Line 14: binding a callback to partially_initialized event.
Line 19: definition of the callback
Line 20: store the API object in the variable
Line 24: show buttons panel by changing its display style to flex
The resulting page now looks like below. You can open it in new window and check the source code by clicking here.
Reimplementing control panel
Time to do something more useful. Let's start reimplementing the VIEWER buttons.
Some requirements for Autorotate button implementation:
- start the autorotation (if not running)
- stop the autorotation (if running)
- change the button color when autorotation is running
- change the button color when autorotation is stopped
Events and API calls we will use:
- autorotate - API call to start/stop autorotation
- autorotate_stop - event triggered when autorotation is stopped
- autorotate_start - event triggered when autorotation is started
Our code will define callback for autorotate_start and autorotate_stop events in order to "know" the current autorotation state - state will be stored in the variable, and to change the color of the button. Click handler for the button will trigger autorotate call to start/stop autorotation, depending on its current state.
- Line 7: store current autorotation state
- Lines 24-29: bind callbacks to VIEWER
- Lines 36-43: autorotate_start and autorotate_stop callbacks - update current state and redraw the button
- Lines 51-58: click handler for autorotate button - calls VIEWER api method: autorotate
- Lines 61-69: change color of the button depending on current autorotation state (uses active CSS class)
Have a llook how it works now. Fullscreen example can be found here.
Zoom in and Zoom out
First, trivial implementation can be as follows (for clarity, the code below doesn't include autorotate button implementation):
See below how it works (full page example can be found here):
Zoom in and zoom out buttons do work but you have to click these repeatedly each time you want to zoom in/out more. Would be better (and consistent with original implementation) if we can click the button and it will zoom in/out until we release the button.
In order to do it we will use Hammer.js library to handle click and touch events in a standardized way, then we will use setInterval on touch/click start to continuously zoom until click/touch is ended (clearInterval).
We will not go into details of Hammer.js library here, it is enough to say that it defines three events that we're using: tap, press and pressup. On tap (that is just a quick press and release) we simply trigger single zoom action. For the press event which identifies longer button press, we start setInterval callback that continuously zooms the presentation (until pressup).
- Lines 20-25: helper function to clear interval
- Lines 28-30: even though we have callbacks set by Hammer we still have to prevent default onClick behaviour.
- Lines 33-47: additional handlers for mouseout/pointerout events. It might happen that user clicks right mouse button while zooming and in such situation pressup event will not be triggered. Because of this we listen to these extra events to break zooming immediately.
- Lines 61, 67, 71: call VIEWER API to zoom in
- Lines 111, 117, 121: call VIEWER API to zoom out
Resulting page can be seen here:
Drag / Rotate button
Drag / Rotate button changes the way dragging the mouse over presentation works. It can either cause presentation rotation or movement of the current frame. By default VIEWER will be in rotate mode but when zoomed in it will automatically switch to drag mode (if only auto_drag_switch is enabled (default)).
Line 9: we're storing current mode value
Lines 26-28: callback for mode_changed event is being registered
Lines 32-35: callback updates the current state and rerenders button
Lines 45-56: click handler for the button, calls VIEWER API method
This is how it works:
The last button to implement is the Fullscreen button. Before we got into details we have to think about some important issues. First of all, it has to be noted that Fullscreen API is not supported by every browser. This means that on some browsers we will not use fullscreen at all or we will implement some workaround ourselves (eg. resize some elements on a page).
In this tutorial, we will only stick to the browsers supporting Fullscreen API and in order to facilitate using it, we will use Screenfull library.
Second thing we have to wonder about is the element that will be switched into Fullscreen mode. Fullscreen API works by changing the styles (CSS) of the selected element and bringing it into Fullscreen. This means that we can switch Orbitvu VIEWER into the fullscreen mode but our buttons, that are defined outside the VIEWER, will be left out of fullscreen and thus invisible. What we can do is to create our own element, containing both VIEWER and our custom buttons, and use Fullscreen API on it. This is what we're going to do in this tutorial. Element that will be switched into fullscreen will be viewer-container-WZarBpyJ7hHX77zaF49Rb9.
There is one more problem with such approach. If we switch external (to the VIEWER) element into Fullscreen, then VIEWER itself doesn't "know" about this. It is important for the VIEWER to "know" about this due to the fact it is supposed to change its own dimensions even though these might have been forced, eg. by setting explicit widht and height (in other words, you might want to have small VIEWER window on page (eg. 200px x 350px) but when going to fullscreen you want it to scale up to the available space). Because of that we will have to inform VIEWER about the Fullscreen switch and it will be done using fullscreen VIEWER API call.
Have a look at the code:
Line 13: remember current fullscreen state
Line 32: on fullscreen change, if fullscreen is enabled, call 'fullscreen: enter' method of the VIEWER API
Line 35: on fullscreen change, if fullscreen is disabled, call 'fullscreen: cancel' method of the VIEWER API
Line 46: on fullscreen button click call 'fullscreen:pre' method of the VIEWER API.
Line 47: toggle fullscreen
As you can se we've used three API methods here, for the fullscreen call:
- pre - stores current dimensions of the viewer elements (so that we will be able to bring these back)
- enter - removes currently forced dimensions, eg. width (should be called after pre)
- cancel - brings back dimensions that were stored by pre - should be called on fullscreen exit.
The last few things to do are:
- hide default VIEWER buttons
- disable default doubletap/doubleclick behaviour of the VIEWER that causes entering Fullscreen (as we do not want to bring just the VIEWER into the fullscreen mode)
Resulting code is:
Complete example can be found here: