Własny interfejs VIEWER-a z użyciem API – samouczek
Orbitvu VIEWER (wersje SUN, Free i Infinity360) udostępnia API, które pozwala dostosować interfejs VIEWER-a. W tym samouczku zaimplementujemy od nowa przyciski VIEWER-a.
Orbitvu VIEWER (wersje SUN, Free i Infinity360) udostępnia API, które pozwala dostosować interfejs VIEWER-a. W tym samouczku zaimplementujemy od nowa przyciski VIEWER-a tak, aby wyglądały jak poniżej.
Ten samouczek jest przeznaczony dla osób mających przynajmniej podstawową wiedzę o tworzeniu stron internetowych (HTML, JavaScript, CSS).
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
/* make viewer window smaller on big screens */
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
position: relative; /* required to properly position custom-ui inside this element */
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none; /* remove pointer events so that we can interact with viewer through it */
z-index: 99; /* bring buttons over the Viewer container */
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200, 200, 200, 0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0 5px #222222;
}
div.custom-ui-buttons a {
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active {
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
mode: 'rotate',
fullscreen: false
};
this.btn_zoom_interval_id = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
api_obj.addCallback(
'autorotate_start_' + this.uid,
'autorotate_start');
api_obj.addCallback(
'autorotate_stop_' + this.uid,
'autorotate_stop');
api_obj.addCallback(
'mode_changed_' + this.uid,
'mode_changed');
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
window['autorotate_start_' + this.uid] = () => {
this.state.autorotate = 'yes';
this.refresh_autorotate_btn();
};
window['autorotate_stop_' + this.uid] = () => {
this.state.autorotate = 'no';
this.refresh_autorotate_btn();
};
window['mode_changed_' + this.uid] = (mode) => {
this.state.mode = mode;
this.refresh_drag_rotate_btn();
};
}
setup_button_handlers() {
// button event handlers
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
let btn_zoom_in = document.getElementById('ui-zoom-in-' + this.uid);
let btn_zoom_out = document.getElementById('ui-zoom-out-' + this.uid);
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
let btn_fullscreen = document.getElementById('ui-fullscreen-' + this.uid);
let viewer_container = document.getElementById('viewer-container-' + this.uid);
// AUTOROTATE BUTTON
btn_autorotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({autorotate: this.state.autorotate === 'yes' ? 'no' : 'yes'})
}
});
let clear_zoom_interval = () => {
if (this.btn_zoom_interval_id) {
clearInterval(this.btn_zoom_interval_id);
this.btn_zoom_interval_id = null;
}
};
// ZOOM IN BUTTON
btn_zoom_in.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_in.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_in.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_in.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_in_hammer_mgr = new Hammer.Manager(
btn_zoom_in,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_in_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
}
});
zoom_in_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleUp: 1});
}, 50);
}
});
zoom_in_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// ZOOM OUT BUTTON
btn_zoom_out.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_out.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_out.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_out.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_out_hammer_mgr = new Hammer.Manager(
btn_zoom_out,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_out_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
}
});
zoom_out_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleDown: 1});
}, 50);
}
});
zoom_out_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// DRAG / ROTATE button
btn_drag_rotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
if (this.state.mode === 'rotate') {
this.viewer_api_obj.setScene({mode: 'drag'});
}
else {
this.viewer_api_obj.setScene({mode: 'rotate'});
}
}
});
// FULLSCREEN button
// add evt listener to screenful;
if (screenfull.enabled) {
screenfull.on('change', (e) => {
if (e.target.id === 'viewer-container-' + this.uid) {
if (screenfull.isFullscreen) {
this.viewer_api_obj.setScene({fullscreen: 'enter'});
}
else {
this.viewer_api_obj.setScene({fullscreen: 'cancel'});
}
}
});
}
btn_fullscreen.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (screenfull.enabled) {
this.viewer_api_obj.setScene({fullscreen: 'pre'});
screenfull.toggle(viewer_container);
}
});
}
refresh_drag_rotate_btn() {
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
if (this.state.mode === 'rotate') {
btn_drag_rotate.innerHTML = '<i class="fas fa-arrows-alt"></i>';
}
else if (this.state.mode === 'drag') {
btn_drag_rotate.innerHTML = '<i class="fas fa-sync-alt"></i>';
}
}
refresh_autorotate_btn() {
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
if (this.state.autorotate === 'yes') {
btn_autorotate.classList.add('active');
}
else {
btn_autorotate.classList.remove('active');
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
document.addEventListener("DOMContentLoaded", (event) => {
vui.setup_callbacks();
vui.setup_button_handlers()
});
</script>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&style=4&doubletap_mode=zoom&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>
Układ strony
Zaczniemy od zdefiniowania układu strony i naszych własnych przycisków. Będziemy pracować na prezentacji demonstracyjnej z serwera SUN.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css" integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/share/WZarBpyJ7hHX77zaF49Rb9/20838/360/script?width=auto&height=auto&content2=yes&partial_load=yes"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>W powyższym kodzie warto zwrócić uwagę na kilka rzeczy:
Wiersz 10: Ładujemy bibliotekę Font Awesome, aby uzyskać ładne ikony dla naszych przycisków
Wiersze 16-23: Embed code skopiowany z Orbitvu SUN
Wiersze 15, 25, 27-231: zwróć uwagę, że id tych elementów jest unikalne dzięki użyciu uid prezentacji z Orbitvu SUN (w tym przypadku WZarBpyJ7hHX77zaF49Rb9). Przyda się to, gdy na jednej stronie mamy kilka prezentacji.
Możesz się zastanawiać, dlaczego na najwyższym poziomie są dwa elementy <div>:
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
...
</div>
</div>Jest to potrzebne, aby przycisk Fullscreen działał prawidłowo w niektórych starszych przeglądarkach. Więcej szczegółów znajdziesz dalej, gdy będziemy dodawać obsługę przycisku Fullscreen.
Jeśli zamierzasz użyć prezentacji hostowanej na własnym serwerze (a nie w Orbitvu SUN), musisz osadzić ją zgodnie z tą dokumentacją.
Dodamy teraz style, aby uzyskać zamierzony układ strony
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
/* make viewer window smaller on big screens */
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
position: relative; /* required to properly position custom-ui inside this element */
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none; /* remove pointer events so that we can interact with viewer through it */
z-index: 99; /* bring buttons over the Viewer container */
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200,200,200,0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0 5px #222222;
}
div.custom-ui-buttons a{
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active{
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>Nasza strona wygląda teraz jak poniżej. Możesz zobaczyć ją w osobnym oknie i sprawdzić źródło, klikając tutaj.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
/* make viewer window smaller on big screens */
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
position: relative; /* required to properly position custom-ui inside this element */
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none; /* remove pointer events so that we can interact with viewer through it */
z-index: 99; /* bring buttons over the Viewer container */
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200,200,200,0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0 5px #222222;
}
div.custom-ui-buttons a{
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active{
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>
Dostęp do API VIEWER-a
Mamy już zdefiniowany układ strony, więc czas połączyć się z API VIEWER-a. Aby to zrobić, najpierw musimy zdefiniować callback, który otrzyma obiekt API zaraz po zainicjowaniu API VIEWER-a. Służy do tego parametr viewer_api_init. Zacznijmy od napisania funkcji zwrotnej:
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
window['api_init'] = (api_obj) => {
console.log('api initialized');
};
</script>Na początek będzie ona jedynie wypisywać tekst 'api initialized'.
Teraz podłączmy nasz callback do VIEWER-a. Ponieważ korzystamy z embed code z Orbitvu SUN, wystarczy dodać viewer_api_init do query stringa, w postaci: &viewer_api_init=api_init (zobacz podświetlony fragment kodu poniżej)
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/share/WZarBpyJ7hHX77zaF49Rb9/20838/360/script?width=auto&height=auto&content2=yes&partial_load=yes&viewer_api_init=api_init"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->Teraz w konsoli powinieneś zobaczyć tekst 'api initialized'. Jeśli to nie działa, sprawdź, czy w konsoli nie ma błędów, a jeśli nie korzystasz z SUN — czy Twój VIEWER w ogóle obsługuje API.
Kod wielokrotnego użytku
Zdefiniowana przez nas funkcja zwrotna ma jeden drobny problem. Jest zdefiniowana jako funkcja globalna o nazwie api_init, więc jeśli będziemy chcieli dodać na stronę kolejną prezentację 360 stopni, będziemy musieli zadbać o unikalność nazwy callbacku (aby połączyć się z właściwą instancją VIEWER-a).
Jako część nazwy callbacku wykorzystamy uid prezentacji: WZarBpyJ7hHX77zaF49Rb9. Umieścimy też nasz kod w klasie, aby dało się go łatwo wykorzystać ponownie.
Nasz identyfikator callbacku będzie teraz brzmiał 'api_init_WZarBpyJ7hHX77zaF49Rb9'. A embed code VIEWER-a będzie wyglądał tak:
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->Naszą klasę JavaScript wielokrotnego użytku zdefiniujemy w następujący sposób:
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
console.log('api initialized');
};
}
}
// pass unique UID to the CustomViewerUI constructor
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
vui.setup_callbacks();
</script>Używamy tutaj klas ES6 i funkcji strzałkowych. Jeśli musisz obsłużyć starsze przeglądarki, użyj kompilatora takiego jak Babel, aby zapewnić wsteczną kompatybilność.
Korzystanie z API VIEWER-a
Teraz możemy zrobić coś użytecznego z naszym obiektem API. Być może zauważyłeś (zwłaszcza przy wolniejszym łączu lub z ograniczoną prędkością sieci w Narzędziach deweloperskich), że po załadowaniu strony własne przyciski pojawiają się natychmiast, a VIEWER inicjalizuje się nieco później.
Nie jest to dobre, bo nie możemy używać przycisków, zanim VIEWER nie zostanie prawidłowo zainicjowany. Chcielibyśmy początkowo ukryć nasze przyciski i pokazać je po inicjalizacji VIEWER-a. Można to łatwo zrobić, korzystając ze zdarzenia partially_initialized, wyzwalanego przez VIEWER, gdy tylko załaduje co najmniej 4 klatki (jeśli partial_load jest włączony).
Istnieje również callback viewer_initialized, wywoływany po załadowaniu wszystkich klatek.
Najpierw dodamy display: none do kontenera naszych przycisków:
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">Następnie dodamy procedurę obsługi zdarzenia, która pokaże przyciski po inicjalizacji VIEWER-a:
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
vui.setup_callbacks();
</script>Wiersz 14: podpięcie callbacku do zdarzenia partially_initialized.
Wiersz 19: definicja callbacku
Wiersz 20: zapisanie obiektu API w zmiennej
Wiersz 24: pokazanie panelu przycisków przez zmianę stylu display na flex
Powstała strona wygląda teraz jak poniżej. Możesz otworzyć ją w nowym oknie i sprawdzić kod źródłowy, klikając tutaj.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
/* necessary to properly position custom-ui inside this element */
position: relative;
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none;
z-index: 99;
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200,200,200,0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0px 5px #222222;
}
div.custom-ui-buttons a{
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active{
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
document.addEventListener("DOMContentLoaded", (event) => {
vui.setup_callbacks();
});
</script>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>
Odtwarzanie panelu sterowania
Czas na coś bardziej użytecznego. Zacznijmy implementować od nowa przyciski VIEWER-a.
Przycisk Autorotate
Wymagania dla implementacji przycisku Autorotate:
- uruchomienie automatycznego obrotu (jeśli nie działa)
- zatrzymanie automatycznego obrotu (jeśli działa)
- zmiana koloru przycisku, gdy automatyczny obrót działa
- zmiana koloru przycisku, gdy automatyczny obrót jest zatrzymany
Zdarzenia i wywołania API, których użyjemy:
- autorotate – wywołanie API uruchamiające/zatrzymujące automatyczny obrót
- autorotate_stop – zdarzenie wyzwalane po zatrzymaniu automatycznego obrotu
- autorotate_start – zdarzenie wyzwalane po uruchomieniu automatycznego obrotu
Nasz kod zdefiniuje callbacki dla zdarzeń autorotate_start i autorotate_stop, aby „wiedzieć”, jaki jest bieżący stan automatycznego obrotu — stan zapiszemy w zmiennej — oraz aby zmieniać kolor przycisku. Procedura obsługi kliknięcia przycisku wywoła autorotate, uruchamiając lub zatrzymując obrót w zależności od bieżącego stanu.
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
};
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
api_obj.addCallback(
'autorotate_start_' + this.uid,
'autorotate_start');
api_obj.addCallback(
'autorotate_stop_' + this.uid,
'autorotate_stop');
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
window['autorotate_start_' + this.uid] = () => {
this.state.autorotate = 'yes';
this.refresh_autorotate_btn();
};
window['autorotate_stop_' + this.uid] = () => {
this.state.autorotate = 'no';
this.refresh_autorotate_btn();
};
}
setup_button_handlers(){
// button event handlers
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
// AUTOROTATE BUTTON
btn_autorotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({autorotate: this.state.autorotate === 'yes' ? 'no' : 'yes'})
}
});
}
refresh_autorotate_btn(){
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
if (this.state.autorotate === 'yes') {
btn_autorotate.classList.add('active');
}
else {
btn_autorotate.classList.remove('active');
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
vui.setup_callbacks();
vui.setup_button_handlers()
</script>- Wiersz 7: zapisanie bieżącego stanu automatycznego obrotu
- Wiersze 24-29: podpięcie callbacków do VIEWER-a
- Wiersze 36-43: callbacki autorotate_start i autorotate_stop — aktualizują bieżący stan i przerysowują przycisk
- Wiersze 51-58: procedura obsługi kliknięcia przycisku autorotate — wywołuje metodę API VIEWER-a: autorotate
- Wiersze 61-69: zmiana koloru przycisku w zależności od bieżącego stanu automatycznego obrotu (używa klasy CSS active)
Zobacz, jak to teraz działa. Przykład pełnoekranowy znajdziesz tutaj.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
/* necessary to properly position custom-ui inside this element */
position: relative;
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none;
z-index: 99;
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200,200,200,0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0px 5px #222222;
}
div.custom-ui-buttons a{
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active{
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
};
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
api_obj.addCallback(
'autorotate_start_' + this.uid,
'autorotate_start');
api_obj.addCallback(
'autorotate_stop_' + this.uid,
'autorotate_stop');
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
window['autorotate_start_' + this.uid] = () => {
this.state.autorotate = 'yes';
this.refresh_autorotate_btn();
};
window['autorotate_stop_' + this.uid] = () => {
this.state.autorotate = 'no';
this.refresh_autorotate_btn();
};
}
setup_button_handlers(){
// button event handlers
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
let viewer_container = document.getElementById('viewer-container-' + this.uid);
// AUTOROTATE BUTTON
btn_autorotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({autorotate: this.state.autorotate === 'yes' ? 'no' : 'yes'})
}
});
}
refresh_autorotate_btn(){
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
if (this.state.autorotate === 'yes') {
btn_autorotate.classList.add('active');
}
else {
btn_autorotate.classList.remove('active');
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
document.addEventListener("DOMContentLoaded", (event) => {
vui.setup_callbacks();
vui.setup_button_handlers()
});
</script>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>
Zoom in i Zoom out
Przyciski Zoom in i Zoom out będą korzystać z następującego API:
Pierwsza, trywialna implementacja może wyglądać tak (dla czytelności poniższy kod nie zawiera implementacji przycisku autorotate):
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
class CustomViewerUI {
// (...)
setup_button_handlers(){
// button event handlers
// (...)
let btn_zoom_in = document.getElementById('ui-zoom-in-' + this.uid);
let btn_zoom_out = document.getElementById('ui-zoom-out-' + this.uid);
// ZOOM IN BUTTON
btn_zoom_in.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
}
});
// ZOOM OUT BUTTON
btn_zoom_out.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
}
});
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
vui.setup_callbacks();
vui.setup_button_handlers()
</script>Zobacz poniżej, jak to działa (przykład na pełnej stronie znajdziesz tutaj):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
/* necessary to properly position custom-ui inside this element */
position: relative;
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none;
z-index: 99;
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200,200,200,0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0px 5px #222222;
}
div.custom-ui-buttons a{
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active{
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
mode: 'rotate',
fullscreen: false
};
this.btn_zoom_interval_id = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
api_obj.addCallback(
'autorotate_start_' + this.uid,
'autorotate_start');
api_obj.addCallback(
'autorotate_stop_' + this.uid,
'autorotate_stop');
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
window['autorotate_start_' + this.uid] = () => {
this.state.autorotate = 'yes';
this.refresh_autorotate_btn();
};
window['autorotate_stop_' + this.uid] = () => {
this.state.autorotate = 'no';
this.refresh_autorotate_btn();
};
}
setup_button_handlers(){
// button event handlers
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
let btn_zoom_in = document.getElementById('ui-zoom-in-' + this.uid);
let btn_zoom_out = document.getElementById('ui-zoom-out-' + this.uid);
let viewer_container = document.getElementById('viewer-container-' + this.uid);
// AUTOROTATE BUTTON
btn_autorotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({autorotate: this.state.autorotate === 'yes' ? 'no' : 'yes'})
}
});
// ZOOM IN BUTTON
btn_zoom_in.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
}
});
// ZOOM OUT BUTTON
btn_zoom_out.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
}
});
}
refresh_autorotate_btn(){
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
if (this.state.autorotate === 'yes') {
btn_autorotate.classList.add('active');
}
else {
btn_autorotate.classList.remove('active');
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
document.addEventListener("DOMContentLoaded", (event) => {
vui.setup_callbacks();
vui.setup_button_handlers()
});
</script>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>
Przyciski zoom in i zoom out działają, ale trzeba je klikać wielokrotnie, gdy chcesz przybliżyć lub oddalić bardziej. Lepiej byłoby (i zgodnie z oryginalną implementacją), gdyby po naciśnięciu przycisku obraz przybliżał się aż do jego zwolnienia.
Aby to osiągnąć, użyjemy biblioteki Hammer.js do ujednoliconej obsługi zdarzeń kliknięcia i dotyku, a następnie użyjemy setInterval przy rozpoczęciu dotyku/kliknięcia, aby przybliżać w sposób ciągły aż do zakończenia kliknięcia/dotyku (clearInterval).
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
// (...)
this.btn_zoom_interval_id = null;
}
// (...)
setup_button_handlers() {
// button event handlers
let btn_zoom_in = document.getElementById('ui-zoom-in-' + this.uid);
let btn_zoom_out = document.getElementById('ui-zoom-out-' + this.uid);
// (...)
let clear_zoom_interval = () => {
if (this.btn_zoom_interval_id) {
clearInterval(this.btn_zoom_interval_id);
this.btn_zoom_interval_id = null;
}
};
// ZOOM IN BUTTON
btn_zoom_in.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_in.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_in.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_in.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_in_hammer_mgr = new Hammer.Manager(
btn_zoom_in,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_in_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
}
});
zoom_in_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleUp: 1});
}, 50);
}
});
zoom_in_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// ZOOM OUT BUTTON
btn_zoom_out.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_out.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_out.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_out.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_out_hammer_mgr = new Hammer.Manager(
btn_zoom_out,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_out_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
}
});
zoom_out_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleDown: 1});
}, 50);
}
});
zoom_out_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
vui.setup_callbacks();
vui.setup_button_handlers()
</script>Nie będziemy tu wchodzić w szczegóły biblioteki Hammer.js; wystarczy powiedzieć, że definiuje ona trzy zdarzenia, z których korzystamy: tap, press i pressup. Przy tap (czyli szybkim naciśnięciu i zwolnieniu) wyzwalamy po prostu pojedynczą akcję zoomu. Przy zdarzeniu press, które oznacza dłuższe przytrzymanie przycisku, uruchamiamy callback setInterval, który w sposób ciągły przybliża prezentację (aż do pressup).
- Wiersze 20-25: funkcja pomocnicza czyszcząca interwał
- Wiersze 28-30: mimo że mamy callbacki ustawione przez Hammer, wciąż musimy zablokować domyślne zachowanie onClick.
- Wiersze 33-47: dodatkowe procedury obsługi zdarzeń mouseout/pointerout. Może się zdarzyć, że użytkownik kliknie prawym przyciskiem myszy podczas przybliżania — w takiej sytuacji zdarzenie pressup nie zostanie wyzwolone. Dlatego nasłuchujemy tych dodatkowych zdarzeń, aby natychmiast przerwać zoom.
- Wiersze 61, 67, 71: wywołanie API VIEWER-a w celu przybliżenia
- Wiersze 111, 117, 121: wywołanie API VIEWER-a w celu oddalenia
Powstałą stronę zobaczysz tutaj:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
/* make viewer window smaller on big screens */
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
position: relative; /* required to properly position custom-ui inside this element */
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none; /* remove pointer events so that we can interact with viewer through it */
z-index: 99; /* bring buttons over the Viewer container */
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200, 200, 200, 0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0 5px #222222;
}
div.custom-ui-buttons a {
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active {
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no'
};
this.btn_zoom_interval_id = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
api_obj.addCallback(
'autorotate_start_' + this.uid,
'autorotate_start');
api_obj.addCallback(
'autorotate_stop_' + this.uid,
'autorotate_stop');
api_obj.addCallback(
'mode_changed_' + this.uid,
'mode_changed');
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
window['autorotate_start_' + this.uid] = () => {
this.state.autorotate = 'yes';
this.refresh_autorotate_btn();
};
window['autorotate_stop_' + this.uid] = () => {
this.state.autorotate = 'no';
this.refresh_autorotate_btn();
};
}
setup_button_handlers() {
// button event handlers
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
let btn_zoom_in = document.getElementById('ui-zoom-in-' + this.uid);
let btn_zoom_out = document.getElementById('ui-zoom-out-' + this.uid);
let viewer_container = document.getElementById('viewer-container-' + this.uid);
// AUTOROTATE BUTTON
btn_autorotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({autorotate: this.state.autorotate === 'yes' ? 'no' : 'yes'})
}
});
let clear_zoom_interval = () => {
if (this.btn_zoom_interval_id) {
clearInterval(this.btn_zoom_interval_id);
this.btn_zoom_interval_id = null;
}
};
// ZOOM IN BUTTON
btn_zoom_in.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_in.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_in.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_in.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_in_hammer_mgr = new Hammer.Manager(
btn_zoom_in,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_in_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
}
});
zoom_in_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleUp: 1});
}, 50);
}
});
zoom_in_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// ZOOM OUT BUTTON
btn_zoom_out.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_out.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_out.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_out.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_out_hammer_mgr = new Hammer.Manager(
btn_zoom_out,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_out_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
}
});
zoom_out_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleDown: 1});
}, 50);
}
});
zoom_out_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
}
refresh_autorotate_btn() {
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
if (this.state.autorotate === 'yes') {
btn_autorotate.classList.add('active');
}
else {
btn_autorotate.classList.remove('active');
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
document.addEventListener("DOMContentLoaded", (event) => {
vui.setup_callbacks();
vui.setup_button_handlers()
});
</script>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&style=4&doubletap_mode=zoom&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>
Przycisk Drag / Rotate
Przycisk Drag / Rotate zmienia sposób działania przeciągania myszą po prezentacji. Może ono powodować obrót prezentacji albo przesuwanie bieżącej klatki. Domyślnie VIEWER działa w trybie rotate, ale po przybliżeniu automatycznie przełącza się w tryb drag (o ile auto_drag_switch jest włączony — a jest domyślnie).
Aby zaimplementować ten przycisk od nowa, musimy zmieniać jego wygląd przy zmianach trybu (zdarzenie mode_changed) i przełączać mode po kliknięciu.
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
mode: 'rotate'
};
this.btn_zoom_interval_id = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
// (...)
api_obj.addCallback(
'mode_changed_' + this.uid,
'mode_changed');
};
// (...)
window['mode_changed_' + this.uid] = (mode) => {
this.state.mode = mode;
this.refresh_drag_rotate_btn();
};
}
setup_button_handlers() {
// button event handlers
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
// (...)
// DRAG / ROTATE button
btn_drag_rotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
if (this.state.mode === 'rotate') {
this.viewer_api_obj.setScene({mode: 'drag'});
}
else {
this.viewer_api_obj.setScene({mode: 'rotate'});
}
}
});
}
refresh_drag_rotate_btn() {
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
if (this.state.mode === 'rotate') {
btn_drag_rotate.innerHTML = '<i class="fas fa-arrows-alt"></i>';
}
else if (this.state.mode === 'drag') {
btn_drag_rotate.innerHTML = '<i class="fas fa-sync-alt"></i>';
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
vui.setup_callbacks();
vui.setup_button_handlers()
</script>Wiersz 9: zapisujemy bieżącą wartość trybu
Wiersze 26-28: rejestrowany jest callback dla zdarzenia mode_changed
Wiersze 32-35: callback aktualizuje bieżący stan i przerysowuje przycisk
Wiersze 45-56: procedura obsługi kliknięcia przycisku, wywołuje metodę API VIEWER-a
Tak to działa:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
/* make viewer window smaller on big screens */
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
position: relative; /* required to properly position custom-ui inside this element */
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none; /* remove pointer events so that we can interact with viewer through it */
z-index: 99; /* bring buttons over the Viewer container */
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200, 200, 200, 0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0 5px #222222;
}
div.custom-ui-buttons a {
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active {
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
mode: 'rotate'
};
this.btn_zoom_interval_id = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
api_obj.addCallback(
'autorotate_start_' + this.uid,
'autorotate_start');
api_obj.addCallback(
'autorotate_stop_' + this.uid,
'autorotate_stop');
api_obj.addCallback(
'mode_changed_' + this.uid,
'mode_changed');
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
window['autorotate_start_' + this.uid] = () => {
this.state.autorotate = 'yes';
this.refresh_autorotate_btn();
};
window['autorotate_stop_' + this.uid] = () => {
this.state.autorotate = 'no';
this.refresh_autorotate_btn();
};
window['mode_changed_' + this.uid] = (mode) => {
this.state.mode = mode;
this.refresh_drag_rotate_btn();
};
}
setup_button_handlers() {
// button event handlers
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
let btn_zoom_in = document.getElementById('ui-zoom-in-' + this.uid);
let btn_zoom_out = document.getElementById('ui-zoom-out-' + this.uid);
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
let viewer_container = document.getElementById('viewer-container-' + this.uid);
// AUTOROTATE BUTTON
btn_autorotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({autorotate: this.state.autorotate === 'yes' ? 'no' : 'yes'})
}
});
let clear_zoom_interval = () => {
if (this.btn_zoom_interval_id) {
clearInterval(this.btn_zoom_interval_id);
this.btn_zoom_interval_id = null;
}
};
// ZOOM IN BUTTON
btn_zoom_in.addEventListener('click', (e) => {
e.preventDefault();
});
let zoom_in_hammer_mgr = new Hammer.Manager(
btn_zoom_in,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_in_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
}
});
zoom_in_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleUp: 1});
}, 50);
}
});
zoom_in_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// ZOOM OUT BUTTON
btn_zoom_out.addEventListener('click', (e) => {
e.preventDefault();
});
let zoom_out_hammer_mgr = new Hammer.Manager(
btn_zoom_out,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_out_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
}
});
zoom_out_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleDown: 1});
}, 50);
}
});
zoom_out_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// DRAG / ROTATE button
btn_drag_rotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
if (this.state.mode === 'rotate') {
this.viewer_api_obj.setScene({mode: 'drag'});
}
else {
this.viewer_api_obj.setScene({mode: 'rotate'});
}
}
});
}
refresh_drag_rotate_btn() {
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
if (this.state.mode === 'rotate') {
btn_drag_rotate.innerHTML = '<i class="fas fa-arrows-alt"></i>';
}
else if (this.state.mode === 'drag') {
btn_drag_rotate.innerHTML = '<i class="fas fa-sync-alt"></i>';
}
}
refresh_autorotate_btn() {
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
if (this.state.autorotate === 'yes') {
btn_autorotate.classList.add('active');
}
else {
btn_autorotate.classList.remove('active');
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
document.addEventListener("DOMContentLoaded", (event) => {
vui.setup_callbacks();
vui.setup_button_handlers()
});
</script>
</body>
</html>
Przycisk Fullscreen
Ostatnim przyciskiem do zaimplementowania jest przycisk Fullscreen. Zanim przejdziemy do szczegółów, trzeba przemyśleć kilka ważnych kwestii. Po pierwsze, Fullscreen API nie jest obsługiwane przez każdą przeglądarkę. Oznacza to, że w niektórych przeglądarkach albo w ogóle nie skorzystamy z trybu pełnoekranowego, albo sami zaimplementujemy jakieś obejście (np. zmianę rozmiaru pewnych elementów na stronie).
W tym samouczku ograniczymy się do przeglądarek obsługujących Fullscreen API, a żeby ułatwić sobie korzystanie z niego, użyjemy biblioteki Screenfull.
Druga kwestia to element, który zostanie przełączony w tryb pełnoekranowy. Fullscreen API działa przez zmianę stylów (CSS) wybranego elementu i wyświetlenie go na pełnym ekranie. Oznacza to, że możemy przełączyć w tryb pełnoekranowy sam Orbitvu VIEWER, ale nasze przyciski, zdefiniowane poza VIEWER-em, zostaną poza pełnym ekranem, a więc będą niewidoczne. Możemy zamiast tego utworzyć własny element zawierający zarówno VIEWER, jak i nasze przyciski, i użyć Fullscreen API na nim. Tak właśnie zrobimy w tym samouczku. Elementem przełączanym w tryb pełnoekranowy będzie viewer-container-WZarBpyJ7hHX77zaF49Rb9.
Z takim podejściem wiąże się jeszcze jeden problem. Jeśli przełączymy w tryb pełnoekranowy element zewnętrzny wobec VIEWER-a, sam VIEWER nic o tym „nie wie”. Jest to dla niego istotne, ponieważ powinien zmienić własne wymiary, nawet jeśli zostały one wymuszone, np. przez jawne ustawienie width i height (innymi słowy: możesz chcieć mieć na stronie małe okno VIEWER-a, np. 200px x 350px, ale po przejściu na pełny ekran chcesz, aby powiększyło się do dostępnej przestrzeni). Dlatego musimy poinformować VIEWER o przełączeniu na pełny ekran — zrobimy to wywołaniem API VIEWER-a fullscreen.
Spójrz na kod:
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<!-- Put this before the SUN embed code -->
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
mode: 'rotate',
fullscreen: false
};
this.btn_zoom_interval_id = null;
}
// (...)
setup_button_handlers() {
// button event handlers
let btn_fullscreen = document.getElementById('ui-fullscreen-' + this.uid);
// (...)
// FULLSCREEN button
// add evt listener to screenful;
if (screenfull.enabled) {
screenfull.on('change', (e) => {
if (e.target.id === 'viewer-container-' + this.uid) {
if (screenfull.isFullscreen) {
this.viewer_api_obj.setScene({fullscreen: 'enter'});
}
else {
this.viewer_api_obj.setScene({fullscreen: 'cancel'});
}
}
});
}
btn_fullscreen.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (screenfull.enabled) {
this.viewer_api_obj.setScene({fullscreen: 'pre'});
screenfull.toggle(viewer_container);
}
});
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
vui.setup_callbacks();
vui.setup_button_handlers()
</script>
</body>
</html>Wiersz 13: zapamiętanie bieżącego stanu trybu pełnoekranowego
Wiersz 32: przy zmianie trybu pełnoekranowego, jeśli jest on włączony, wywołanie metody 'fullscreen: enter' API VIEWER-a
Wiersz 35: przy zmianie trybu pełnoekranowego, jeśli jest on wyłączony, wywołanie metody 'fullscreen: cancel' API VIEWER-a
Wiersz 46: po kliknięciu przycisku fullscreen wywołanie metody 'fullscreen:pre' API VIEWER-a.
Wiersz 47: przełączenie trybu pełnoekranowego
Jak widać, użyliśmy tu trzech metod API w wywołaniu fullscreen:
- pre – zapisuje bieżące wymiary elementów VIEWER-a (abyśmy mogli je później przywrócić)
- enter – usuwa aktualnie wymuszone wymiary, np. width (należy wywołać po pre)
- cancel – przywraca wymiary zapisane przez pre — należy wywołać przy wyjściu z trybu pełnoekranowego.
Zostało jeszcze kilka rzeczy do zrobienia:
- ukrycie domyślnych przycisków VIEWER-a
- wyłączenie domyślnego zachowania VIEWER-a przy doubletap/doubleclick, które powoduje wejście w tryb pełnoekranowy (nie chcemy bowiem przełączać na pełny ekran samego VIEWER-a)
Obie rzeczy można łatwo zrobić, dodając parametry do embed code naszej prezentacji. Parametr usuwający przyciski to style, a parametr zmieniający zachowanie doubletap to doubletap_mode.
Wynikowy kod to:
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/share/WZarBpyJ7hHX77zaF49Rb9/20838/360/script?width=auto&height=auto&content2=yes&partial_load=yes&viewer_api_init=api_init&style=4&doubletap_mode=zoom"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->Kompletny przykład znajdziesz tutaj:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Orbitvu VIEWER custom UI</title>
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Load Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css"
integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>
<style type="text/css">
body {
margin: 0;
}
@media only screen and (min-width: 500px) {
/* make viewer window smaller on big screens */
.viewer-wrapper {
width: 50%;
margin: 0 auto;
}
}
.viewer-container {
/* width and height settings are required for fullscreen to work on this element in older Chrome */
height: 100%;
width: 100%;
min-height: 200px;
position: relative; /* required to properly position custom-ui inside this element */
display: flex;
align-items: center;
}
div.custom-ui {
position: absolute;
top: 0;
left: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
pointer-events: none; /* remove pointer events so that we can interact with viewer through it */
z-index: 99; /* bring buttons over the Viewer container */
}
div.custom-ui-buttons {
font-size: 2em;
text-align: center;
background-color: rgba(200, 200, 200, 0.4);
padding: 3px;
display: flex;
flex-direction: column;
border-radius: 0 5px 5px 0;
box-shadow: 2px 0 5px #222222;
}
div.custom-ui-buttons a {
pointer-events: all;
color: #00A4B6;
}
div.custom-ui-buttons a:active {
color: orange;
}
div.custom-ui-buttons a.active {
color: orange;
}
</style>
<script type="text/javascript">
class CustomViewerUI {
constructor(uid) {
this.uid = uid;
this.viewer_api_obj = null;
this.state = {
autorotate: 'no',
mode: 'rotate',
fullscreen: false
};
this.btn_zoom_interval_id = null;
}
setup_callbacks() {
/* we're using presentation uid to easily differentiate between presentations
* useful if there are more than 1 presentation on the same page as callbacks are global
* */
window['api_init_' + this.uid] = (api_obj) => {
api_obj.addCallback(
'viewer_partially_initialized_' + this.uid,
'partially_initialized');
};
window['viewer_partially_initialized_' + this.uid] = (api_obj) => {
this.viewer_api_obj = api_obj;
api_obj.addCallback(
'autorotate_start_' + this.uid,
'autorotate_start');
api_obj.addCallback(
'autorotate_stop_' + this.uid,
'autorotate_stop');
api_obj.addCallback(
'mode_changed_' + this.uid,
'mode_changed');
// display custom button panels
let custom_ui = document.getElementById('custom-ui-' + this.uid);
custom_ui.style.display = 'flex';
};
window['autorotate_start_' + this.uid] = () => {
this.state.autorotate = 'yes';
this.refresh_autorotate_btn();
};
window['autorotate_stop_' + this.uid] = () => {
this.state.autorotate = 'no';
this.refresh_autorotate_btn();
};
window['mode_changed_' + this.uid] = (mode) => {
this.state.mode = mode;
this.refresh_drag_rotate_btn();
};
}
setup_button_handlers() {
// button event handlers
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
let btn_zoom_in = document.getElementById('ui-zoom-in-' + this.uid);
let btn_zoom_out = document.getElementById('ui-zoom-out-' + this.uid);
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
let btn_fullscreen = document.getElementById('ui-fullscreen-' + this.uid);
let viewer_container = document.getElementById('viewer-container-' + this.uid);
// AUTOROTATE BUTTON
btn_autorotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({autorotate: this.state.autorotate === 'yes' ? 'no' : 'yes'})
}
});
let clear_zoom_interval = () => {
if (this.btn_zoom_interval_id) {
clearInterval(this.btn_zoom_interval_id);
this.btn_zoom_interval_id = null;
}
};
// ZOOM IN BUTTON
btn_zoom_in.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_in.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_in.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_in.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_in_hammer_mgr = new Hammer.Manager(
btn_zoom_in,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_in_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
}
});
zoom_in_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleUp: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleUp: 1});
}, 50);
}
});
zoom_in_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// ZOOM OUT BUTTON
btn_zoom_out.addEventListener('click', (e) => {
e.preventDefault();
});
// stop interval when cursor leaves the button (eg. on mouse right click and context menu popup)
if (window.PointerEvent) {
btn_zoom_out.addEventListener('pointerout', (e) => {
clear_zoom_interval();
});
}
else if (window.MSPointerEvent){
btn_zoom_out.addEventListener('MSPointerOut', (e) => {
clear_zoom_interval();
});
}
else{
btn_zoom_out.addEventListener('mouseout', (e) => {
clear_zoom_interval();
});
}
let zoom_out_hammer_mgr = new Hammer.Manager(
btn_zoom_out,
{
recognizers: [
[Hammer.Tap, {timeout: 80}],
[Hammer.Press, {time: 100}]
]
}
);
zoom_out_hammer_mgr.on('tap', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
}
});
zoom_out_hammer_mgr.on('press', (e) => {
clear_zoom_interval();
if (this.viewer_api_obj) {
this.viewer_api_obj.setScene({scaleDown: 1});
// start setInterval for continuous zooming while button is pressed
this.btn_zoom_interval_id = setInterval(() => {
this.viewer_api_obj.setScene({scaleDown: 1});
}, 50);
}
});
zoom_out_hammer_mgr.on('pressup', (e) => {
clear_zoom_interval();
});
// DRAG / ROTATE button
btn_drag_rotate.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.viewer_api_obj) {
if (this.state.mode === 'rotate') {
this.viewer_api_obj.setScene({mode: 'drag'});
}
else {
this.viewer_api_obj.setScene({mode: 'rotate'});
}
}
});
// FULLSCREEN button
// add evt listener to screenful;
if (screenfull.enabled) {
screenfull.on('change', (e) => {
if (e.target.id === 'viewer-container-' + this.uid) {
if (screenfull.isFullscreen) {
this.viewer_api_obj.setScene({fullscreen: 'enter'});
}
else {
this.viewer_api_obj.setScene({fullscreen: 'cancel'});
}
}
});
}
btn_fullscreen.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (screenfull.enabled) {
this.viewer_api_obj.setScene({fullscreen: 'pre'});
screenfull.toggle(viewer_container);
}
});
}
refresh_drag_rotate_btn() {
let btn_drag_rotate = document.getElementById('ui-drag-rotate-' + this.uid);
if (this.state.mode === 'rotate') {
btn_drag_rotate.innerHTML = '<i class="fas fa-arrows-alt"></i>';
}
else if (this.state.mode === 'drag') {
btn_drag_rotate.innerHTML = '<i class="fas fa-sync-alt"></i>';
}
}
refresh_autorotate_btn() {
let btn_autorotate = document.getElementById('ui-autorotate-' + this.uid);
if (this.state.autorotate === 'yes') {
btn_autorotate.classList.add('active');
}
else {
btn_autorotate.classList.remove('active');
}
}
}
vui = new CustomViewerUI('WZarBpyJ7hHX77zaF49Rb9');
document.addEventListener("DOMContentLoaded", (event) => {
vui.setup_callbacks();
vui.setup_button_handlers()
});
</script>
</head>
<body>
<div class="viewer-wrapper">
<div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
<!-- Embed code from Orbitvu SUN starts here -->
<script type="text/javascript"
async
src="//orbitvu.co/001/WZarBpyJ7hHX77zaF49Rb9/ov3601/3/script?width=auto&height=auto&content2=yes&partial_load=yes&style=4&doubletap_mode=zoom&viewer_api_init=api_init_WZarBpyJ7hHX77zaF49Rb9"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
<div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">
<div class="custom-ui-buttons">
<a href="" id="ui-autorotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-play-circle"></i></a>
<a href="" id="ui-zoom-in-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-plus"></i></a>
<a href="" id="ui-zoom-out-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-search-minus"></i></a>
<a href="" id="ui-drag-rotate-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-arrows-alt"></i></a>
<a href="" id="ui-fullscreen-WZarBpyJ7hHX77zaF49Rb9"><i class="fas fa-expand-arrows-alt"></i></a>
</div>
</div>
</div>
</div>
</body>
</html>
Osadzanie za pomocą embed codes z ORBITVU SUN
ORBITVU SUN to platforma, która pozwala bez wysiłku zarządzać prezentacjami ORBITVU, ponownie ich używać i je rozpowszechniać. Potrzebne parametry ustawisz w jej interfejsie, a gotowy embed code wystarczy wkleić do witryny.
Jak dostosować ORBITVU VIEWER za pomocą parametrów
Zachowanie ORBITVU VIEWER można dostosować na wiele sposobów za pomocą parametrów. Parametry można stosować w kodzie JavaScript albo w embed codes / adresach preview z ORBITVU SUN.