Orbitvu Public Support SiteVIEWER, ORBITTOUR, and 360° Presentations PL Przykłady integracjiNiestandardowy interfejs użytkownika dla przeglądarki korzystającej z API-samouczek

Niestandardowy interfejs użytkownika dla przeglądarki korzystającej z API-samouczek

Orbitvu VIEWER (wersje SUN, Free i Infinity360) obsługuje API, które umożliwia dostosowanie interfejsu użytkownika przeglądarki. W tym samouczku zaimplementujemy przyciski Viewer, aby wyglądały jak poniżej.

Prosimy zwrócić uwagę, że ten samouczek jest przeznaczony dla osób posiadających co najmniej podstawową wiedzę z zakresu tworzenia stron internetowych (HTML, JavaScript, CSS).

Układ strony

Zaczynamy od zdefiniowania układu naszej strony i naszych własnych przycisków. Będziemy pracować z prezentacją demo z serwera SUN.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!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/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>

Jest kilka ciekawych rzeczy do odnotowania w powyższym kodzie:

Linia 10: Ładujemy bibliotekę Font Awesome, aby uzyskać ładne ikony dla naszych przycisków.

Linie 16-23: Osadzony kod skopiowany z Orbitvu Sun

Linie 15, 25, 27-231: zauważ, że id tych elementów jest ustawiony na unikalny przy użyciu uid prezentacji z Orbitvu Sun (WZarBpyJ7hHX77zaF49Rb9 w tym przypadku). Będzie to przydatne, jeśli mamy wiele prezentacji na tej samej stronie.

Być może zastanawiasz się, dlaczego na najwyższym poziomie są dwa elementy <div>:

1
2
3
4
5
<div class="viewer-wrapper">
    <div class="viewer-container" id="viewer-container-WZarBpyJ7hHX77zaF49Rb9">
        ...
    </div>
</div>

Jest to wymagane, aby przycisk Fullscreen działał poprawnie na niektórych starszych przeglądarkach. Więcej szczegółów na ten temat znajdziesz póżniej, kiedy będziemy dodawać obsługę przycisków pełnoekranowych.

Jeżeli zamierzasz używać prezentacji hostowanej na własnym serwerze (nie z Orbitvu SUN), to musisz zamieścić swoją prezentację w sposób opisany w niniejszej dokumentacji.

Dodamy teraz kilka stylów, aby uzyskać pożądany układ strony.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<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 ją zobaczyć w osobnym oknie i sprawdzić źródło klikając tutaj.

Dostęp do API VIEWER

Mamy zdefiniowany układ strony, więc teraz nadszedł czas na na połączenie z API Viewer. Aby to zrobić, najpierw musimy zdefiniować funkcję callback, która będzie otrzymywała obiekt API zaraz po zainicjalizowaniu API VIEWER-a. Można to zrobić za pomocą parametru viewer_api_init. Zacznijmy od napisania naszej funkcji callback:

1
2
3
4
5
6
<!-- Put this at the end of the page, just before closing </body> tag -->
<script type="text/javascript">
    window['api_init'] = (api_obj) => {
        console.log('api initialized');
    };
</script>

Początkowo wyświetli tylko tekst 'api initialized'.

Teraz podłączymy naszą funkcję callback do VIEWER-a. Ponieważ używamy kodu osadzającego z Orbitvu SUN, do querystring dodamy po prostu viewer_api_init, w ten sposób: &viewer_api_init=api_init (zobacz podświetloną część kodu poniżej)

1
2
3
4
5
6
7
8
<!-- 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"></script>
<div class="orbitvu-viewer" style="width: 100%;height: 100%;">
    <div id="ovContent-WZarBpyJ7hHX77zaF49Rb9"></div>
</div>
<!-- Embed code from Orbitvu SUN ends here -->

Teraz powinieneś zobaczyć tekst 'api initialized', wysłany do konsoli. Jeśli to nie działa, sprawdź, czy w konsoli nie ma błędów, a w przypadku gdy nie używasz SUN, czy Twój VIEWER wspiera API.

Możliwość ponownego użycia kodu

Funkcja callback, którą zdefiniowaliśmy ma jeden mały problem. Jest ona zdefiniowana jako funkcja globalna, zwana api_init, więc jeśli chcemy dodać kolejną prezentację 360 na stronę, będziemy musieli upewnić się, że nazwa funkcji callback jest unikalna (tak, byśmy mogli połączyć się z właściwą instancją przeglądarki).

Będziemy używać uid prezentacji: WZarBpyJ7hHX77zaF49Rb9 jako części nazwy funkcji callback. Zmienimy także nasz kod w klasę, tak aby można go było łatwo ponownie wykorzystać.

Naszym identyfikatorem funkcji callback będzie teraz 'api_init_WZarBpyJ7hHX77zaF49Rb9'. A kod osadzający VIEWER będzie miał postać:

1
2
3
4
5
6
7
8
<!-- 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 -->

Nasza klasa JavaScript zostanie zdefiniowana w następujący sposób:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- Put this at the end of the page, just before closing </body> tag -->
<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 klas ES6 i arrow functions. Jeśli potrzebujesz wsparcia dla starszych przeglądarek upewnij się, że używasz kompilata, takiego jak Babel, aby kod był kompatybilny wstecz.

Korzystanie z API VIEWER

Teraz możemy zrobić coś użytecznego z naszym obiektem API. Być może zauważyłeś (zwłaszcza, jeśli korzystasz z wolniejszej sieci lub masz ograniczoną prędkość sieciową w Developer Tools), że po załadowaniu naszej strony niestandardowe przyciski są wyświetlane natychmiast, a VIEWER jest inicjalizowany nieco później.

Nie jest to pożądane zachowanie ponieważ nie możęmy użyć przycisków zanim VIEWER nie został zainicjowany. To co powinniśmy zrobić to ukryć na wstępie nasze przyciski i wyświetlać je dopiero po inicjalizacji VIEWER-a. Można to łatwo zrobić korzystając ze zdarzenia partially_initialized, które jest wyzwalane przez VIEWER w momencie gdy zostały załadowane pierwsze 4 klatki (jeśli partial_load jest włączony).

Istnieje również wywołanie zwrotne viewer_initialized, które jest wywoływane po załadowaniu wszystkich klatek.

Najpierw dodamy display: none do naszego elementu kontenera dla przycisków:

1
<div id="custom-ui-WZarBpyJ7hHX77zaF49Rb9" class="custom-ui" style="display: none;">

Następnie dodamy event handler, który pokaże przyciski w chwili inicjalizacji VIEWER.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!-- Put this at the end of the page, just before closing </body> tag -->
<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>

Linia 14: powiązanie wywołania zwrotnego ze zdarzeniem partially_initialized

Linia 19: definicja wywołania zwrotnego

Linia 20: przechowywanie obiektu API w zmiennej

Linia 24: pokazanie panelu przycisków poprzez zmianę stylu wyświetlania na flex

Powstała w ten sposób strona wygląda teraz jak poniżej. Możesz ją otworzyć w nowym oknie i sprawdzić kod źródłowy tutaj.

Ponowna implementacja panelu sterowania

Czas na zrobienie czegoś bardziej użytecznego. Zacznijmy ponownie implementować przyciski VIEWER-a.

Przycisk automatycznego obracania (autorotate)

Niektóre wymagania dotyczące implementacji przycisku Autorotate:

  • rozpocznij automatyczną rotację (jeśli nie jest uruchomiona)
  • zatrzymaj automatyczną rotację (jeśli jest uruchomiona)
  • zmień kolor przycisku, gdy uruchomiona jest autorotacja
  • zmień kolor przycisku, gdy automatyczna rotacja jest zatrzymana

Zdarzenia i wywołania API, z których będziemy korzystać:

  • autorotate - wywołanie API w celu rozpoczęcia/zatrzymania automatycznej rotacji
  • autorotate_stop - zdarzenie wyzwalane po zatrzymaniu autorotacji
  • autorotate_start - zdarzenie wyzwalane po uruchomieniu autorotacji

Nasz kod zdefiniuje funkcję callback dla zdarzeń autorotate_start oraz autorotate_stop w celu "poznania" aktualnego stanu autoobrotu - stan ten zostanie zapisany w zmiennej, oraz w celu zmiany koloru przycisku. Kliknięcie przycisku spowoduje automatyczne rozpoczęcie/zatrzymanie obrotu, w zależności od jego aktualnego stanu.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<!-- Put this at the end of the page, just before closing </body> tag -->
<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>
  • Linia 7: zapisuje aktualny stan autoobrotu
  • Linie 24-29: wiąże funkcję callback z VIEWER-em
  • Linie 36-43: wywołanie autorotate_start oraz autorotate_stop - zaktualizowanie bieżącego stanu i ponowne narysowanie przycisku
  • Linie 51-58: obsługa kliknięcia dla przycisku autorotate - wywołuje VIEWER API: autorotate
  • Linie 61-69: zmiana koloru przycisku w zależności od aktualnego stanu autoobrotu (używa aktywnej klasy CSS)

Zobacz jak to działa teraz. Pełnoekranowy przykład można znaleźć tutaj.

Przybliżanie i oddalanie

Przyciski Zoom in oraz Zoom out bedą używać następującego API:

Pierwsza, trywialna implementacja może być następująca (aby poprawić czytelność, poniższy kod nie zawiera implementacji przycisku autorotate):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!-- Put this at the end of the page, just before closing </body> tag -->
<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 (pełnoekranowy przykład można znaleźć tutaj):

Przyciski powiększania i pomniejszania działają, ale trzeba je wielokrotnie klikać za każdym razem, gdy chcesz przybliżyć lub oddalić obiekt. Byłoby lepiej (i zgodnie z oryginalną implementacją), gdybyśmy mogli kliknąć przycisk, przytrzymać go i aby powiększanie/pomniejszanie działało aż do momentu zwolnienia przycisku.

W tym celu użyjemy biblioteki Hammer.js do obsługi zdarzeń click and touch w ustandaryzowany sposób, następnie użyjemy setInterval na początku zdarzeń touch/click aby uzyskać ciągłość powiększania, aż do momentu zakończenia operacji click/touch (cleverInterval).

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<!-- Load hammer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>

<!-- Put this at the end of the page, just before closing </body> tag -->
<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 zagłębimy się tutaj w szczegóły biblioteki Hammer.js, wystarczy powiedzieć, że definiuje ona trzy zdarzenia, których używamy: tap, press i pressup. Po użyciu tap (czyli po prostu naciśnięciu i zwolnieniu przycisku) po prostu uruchamiamy akcję jednorazowego powiększenia. W przypadku zdarzenia press, które identyfikuje dłuższe naciśnięcie przycisku, uruchamiamy funkcję callback setInterval , które w sposób ciągły powiększa prezentację (do momentu zwolnienia przycisku - zdarzenie pressup).

  • Linie 20-25: funkcja pomocnicza do wyzerowania interwałów
  • Linie 28-30: nawet jeśli mamy wywołania zwrotne ustawione przez Hammer, nadal musimy zablokować domyślne zachowanie onClick
  • Linie 33-47: dodatkowe handlery dla zdarzeń mouseout/pointerout. Może się zdarzyć, ze użytkownik kliknie prawym przyciskiem myszy podczas powiększania  i w takiej sytuacji zdarzenie pressup nie zostanie wywołane. Z tego powodu nasłuchujemy tych dodatkowych zdarzeń, aby natychmiast przerwać powiększanie.
  • Linie 61, 67, 71: wywołanie API VIEWER w celu powiększenia
  • Linie 111, 117, 121: wywołanie API Viewer w celu pomniejszenia

Rezultat można zobaczyć na stronie tutaj:

Przeciągnij/obróć przycisk

Przeciągnij/obróć przycisk zmienia sposób w jaki działa przeciąganie myszą nad prezentacją. Może ono powodować obrót prezentacji lub przesuwanie bieżącej klatki. Domyślnie VIEWER będzie w trybie rotate, ale po powiększeniu automatycznie przełączy się w tryb drag (tylko jeśli auto_drag_switch jest włączony (domyślnie)).

Aby ponownie zaimplementować ten przycisk musimy zmienić jego wygląd w chwili zmiany trybu działania (zdarzenie mode_changed) i przełączyć tryb na kliknięcie.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<!-- Put this at the end of the page, just before closing </body> tag -->
<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>

Linia 9: zapisuje aktualną wartość trybu działania przycisku

Linie 26-28: funkcja callback dla zdarzenia mode_changed jest rejestrowana

Linie 32-35: funkcja callback aktualizuje bieżący stan i przycisk jest odświeżany

Linie 45-56:  handler dla kliknięcia na przycisku, wywołuje metodę API VIEWER

Tak to działa:

Przycisk wejścia w tryb pełnoekranowy

Ostatnim przyciskiem do wykonania jest przycisk Fullscreen. Zanim wgłębimy się w szczegóły, musimy zastanowić się nad kilkoma ważnymi kwestiami. Przede wszystkim należy zauważyć, że Fullscreen API nie jest obsługiwane przez każdą przeglądarkę. Oznacza to, że w niektórych przeglądarkach w ogóle nie będziemy używać pełnoekranowego trybu lub zaimplementujemy jakieś obejścia (np. zmiana rozmiaru niektórych elementów na stronie).

W tym tutorialu będziemy trzymać się tylko przeglądarek obsługujących Fullscreen API i w celu ułatwienia korzystania z niego będziemy używać biblioteki Screenfull.

Drugą rzeczą, nad która musimy się zastanowić jest element, który zostanie przełączony w tryb pełnoekranowy. Fullscreen API działa poprzez zmianę stylów (CSS) wybranego elementu i wprowadzenie go do Fullscreen. Oznacza to, że możemy przełączyć ORBITVU VIEWER w tryb pełnoekranowy, ale nasze przyciski, które są zdefiniowane poza VIEWER-em pozostaną poza trybem pełnoekranowym i tym samym pozostaną niewidoczne. Tak więc, to co możemy zrobić, to stworzyć własny element, zawierający zarówno VIEWER jak i nasze własne przyciski, oraz użyć na nim Fullscreen API. To właśnie zamierzamy zrobić w tym tutorialu. Elementem, który zostanie przełączony na tryb pełnoekranowym będzie: viewer-container-WZarBpyJ7hHX77zaF49Rb9.

Jest jeszcze jeden problem przy tym podejściu. Jeśli przełączymy zewnętrzny (dla VIEWER-a) element w tryb Fullscreen, to sam VIEWER "nie wie" o tym. Ważne jest, aby VIEWER "wiedział" o przejściu w tryb fullscreen ze względu na fakt, że ma zmienić swoje wymiary, nawet jeśli zostały one wymuszone np. poprzez ustawienie wyraźnej szerokości i wysokości (innymi słowy, możesz chcieć mieć małe okno VIEWER na stronie (np 200px x 350px), ale przełączając się na pełny ekran chcesz, aby skalować je do całej dostępnej przestrzeni. Z tego powodu będziemy musieli poinformować VIEWER o przejściu w tryb pełnoekranowy i będzie to wykonane przy użyciu wywołania metody fullscreen z API VIEWER-a.

Spójrz na kod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!-- Load Screenfull library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenfull.js/3.3.3/screenfull.min.js"></script>

<!-- Put this at the end of the page, just before closing </body> tag -->
<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>

Linia 13: zapamiętuje aktualny stan ekranu

Linia 32: w momencie zmiany trybu pełnoekranowego, jeśli włączony jest tryb pełnoekranowy, wywołuje metodę 'fullscreen: enter' z VIEWER API

Linia 35: w momencie zmiany trybu pełnoekranowego, jeśli tryb pełnoekranowy jest wyłączony, wywołuje metodę 'fullscreen: cancel' z VIEWER API

Linia 46: w chwili kliknięcia na przycisk fullscreen wywołuje metodę 'fullscreen:pre' z VIEWER API.

Linia 47: przełączanie trybu pełnoekranowego

Jak widać, użyliśmy tutaj trzech metod API dla przejścia w tryb pełnoekranowy

  • pre - przechowuje aktualne wymiary elementów VIEWER-a (dzieki czemu będziemy mogli je przywrócić)
  • enter - usuwa aktualnie wymuszone wymiary, np.  szerokość (powinno być wywołane po pre)
  • cancel - przywraca wymiary, które były zachowane przez pre - powinno być wywołane w chwili wyjścia z trybu pełnoekranowego.

Ostatnich kilka rzeczy do zrobienia to:

  • ukryć domyślne przyciski VIEWER
  • wyłączyć domyślne podwójne stuknięcie/podwójne kliknięcie VIEWER-a, które powoduje wejście w tryb pełnoekranowy (ponieważ nie chcemy wprowadzać samego tylko VIEWER-a w tryb pełnoekranowy).

Obie te rzeczy można łatwo zrobić dodając parametry do naszego kodu osadzenia prezentacji. Parametrem do usunięcia przycisków jest style a parametrem do zmiany zachowania podwójnego kliknięcia jest doubletap_mode.

Wynikiem tego jest następujący kod:

1
2
3
4
5
6
7
8
<!-- 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&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 można znaleźć tutaj: