Place in a hidden container

Open on CodeSandbox

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script src="./common.js"></script>

    <script>
      window.map = null;

      let isLoading = false;
      let activeTab = '';

      const createMap = async () => {
        await mappable.ready;
        const {MMap, MMapDefaultSchemeLayer, MMapControls} = mappable;
        const {MMapZoomControl} = await mappable.import('@mappable-world/mappable-controls@0.0.1');

        map = new MMap(document.getElementById('map'), {location: LOCATION, zoomStrategy: 'zoomToCenter'});
        map.addChild((scheme = new MMapDefaultSchemeLayer()));
        map.addChild(new MMapControls({position: 'right'}).addChild(new MMapZoomControl({})));
      };

      const destroyMap = () => {
        map.destroy();
        map = null;
      };

      const toggleLoader = () => {
        document.querySelector('.loader-container').classList.toggle('_hide', !isLoading);
      };

      const fetchScript = async () => {
        try {
          isLoading = true;
          toggleLoader();
          await loadMapScript();
        } catch (error) {
          console.error(error);
          return 'error';
        } finally {
          isLoading = false;
          toggleLoader();
        }
      };

      const onChangeTab = (e) => {
        activeTab = e.target.id;

        if (e.target.id === 'tab2') {
          if (typeof mappable === 'undefined' && !isLoading) {
            fetchScript().then((res) => {
              if (res === 'error') return;

              activeTab === 'tab2' && createMap();
            });
          }

          typeof mappable !== 'undefined' && createMap();
        } else {
          map && destroyMap();
        }
      };

      document.addEventListener('DOMContentLoaded', () => {
        document.getElementById('tab1').addEventListener('change', onChangeTab);
        document.getElementById('tab2').addEventListener('change', onChangeTab);
      });
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app">
      <div class="tabs">
        <input type="radio" name="tabs" id="tab1" checked />
        <label for="tab1">Description</label>
        <input type="radio" name="tabs" id="tab2" />
        <label for="tab2">Map</label>
        <div class="tab content1"><p>In the Map tab there is a map of the city</p></div>
        <div class="tab content2">
          <div id="map">
            <div class="loader-container _hide"><div class="loader" /></div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
input {
  position: absolute;

  display: none;

  width: 0;
}

input + label {
  position: relative;

  display: inline-block;

  margin-top: 8px;
  padding: 0.5em 1em;

  cursor: pointer;

  border: 1px solid #aaa;
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
  background-color: #fff;
}

input:first-child + label {
  margin-left: 4px;
}

input + label:hover {
  background-color: #eee;
}

input:checked + label {
  z-index: 1;

  border-color: #428bca;
  background-color: #fff;
  box-shadow: 0 3px 0 -1px #fff, inset 0 5px 0 -1px #949494;
}

.tab {
  display: none;

  padding: 8px;

  border-top: 1px solid #ccc;
}

.visible,
#tab1:checked ~ .tab.content1,
#tab2:checked ~ .tab.content2 {
  display: block;
}

#map {
  height: 500px;
}

.loader-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 90vh;
}

.loader-container._hide {
  display: none;
}

.loader {
  border: 8px solid #cccccc80;
  border-top: 8px solid #949494;
  border-radius: 50%;
  width: 30px;
  height: 30px;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
const LOCATION = {center: [55.44279, 25.24613], zoom: 9};

function loadMapScript() {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src =
      'https://js.api.mappable.world/v3/?apikey=pk_jGLPsUVXSzwQimgFDNwlofchPIdEhdsdKaearbKtwUgDiYNsZGGTuVXNCrxSsXmd&lang=en_US';
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <script src="./common.js"></script>

    <script type="text/babel">
      window.map = null;

      main();
      async function main() {
        const createMapComponent = async () => {
          const [mappableReact] = await Promise.all([
            mappable.import('@mappable-world/mappable-reactify'),
            mappable.ready
          ]);
          const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
          const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapListener} = reactify.module(mappable);

          const {MMapZoomControl} = reactify.module(await mappable.import('@mappable-world/mappable-controls@0.0.1'));

          return function () {
            const [location, setLocation] = React.useState(LOCATION);

            const onUpdate = React.useCallback(({location, mapInAction}) => {
              // Animation not happening
              if (!mapInAction) {
                setLocation({
                  center: location.center,
                  zoom: location.zoom
                });
              }
            }, []);

            return (
              <MMap location={location} zoomStrategy="zoomToCenter" ref={React.useCallback((x) => (map = x), [])}>
                <MMapListener onUpdate={onUpdate} />
                <MMapDefaultSchemeLayer />
                <MMapControls position="right">
                  <MMapZoomControl />
                </MMapControls>
              </MMap>
            );
          };
        };

        // when the api is initialized, it will become a map component
        let Map;

        function Loader() {
          return (
            <div className={'loader-container'}>
              <div className={'loader'} />
            </div>
          );
        }

        function App() {
          const [tab, setTab] = React.useState('tab1');
          const [isLoading, setIsLoading] = React.useState(false);

          const onChangeTab = React.useCallback(
            async (e) => {
              setTab(e.target.id);

              if (e.target.id === 'tab2') {
                if (typeof mappable === 'undefined' && !isLoading) {
                  try {
                    setIsLoading(true);
                    await loadMapScript();
                    Map = await createMapComponent();
                  } catch (error) {
                    console.error(error);
                  } finally {
                    setIsLoading(false);
                  }
                }
              }
            },
            [isLoading]
          );

          return (
            <div className="tabs">
              <input type="radio" name="tabs" id="tab1" checked={tab === 'tab1'} onChange={onChangeTab} />
              <label htmlFor="tab1">Description</label>
              <input type="radio" name="tabs" id="tab2" checked={tab === 'tab2'} onChange={onChangeTab} />
              <label htmlFor="tab2">Map</label>
              {tab === 'tab1' && (
                <div className="tab visible">
                  <p>In the Map tab there is a map of the city</p>
                </div>
              )}
              {tab === 'tab2' && (
                <div id="map" className="tab visible">
                  {isLoading ? <Loader /> : typeof Map !== 'undefined' && <Map />}
                </div>
              )}
            </div>
          );
        }

        ReactDOM.render(
          <React.StrictMode>
            <App />
          </React.StrictMode>,
          document.getElementById('app')
        );
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
    <script src="./common.js"></script>

    <script>
      window.map = null;

      main();
      async function main() {
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);
        const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapListener} = vuefy.module(mappable);
        const {MMapZoomControl} = vuefy.module(await mappable.import('@mappable-world/mappable-controls@0.0.1'));

        const app = Vue.createApp({
          components: {MMap, MMapDefaultSchemeLayer, MMapControls, MMapListener, MMapZoomControl},
          setup() {
            const tab = Vue.ref('tab1');
            const location = Vue.ref(LOCATION);
            const onUpdate = ({location, mapInAction}) => {
              if (!mapInAction) {
                location.value = {center: location.center, zoom: location.zoom};
              }
            };
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            return {location, refMap, tab, onUpdate};
          },
          template: `
                        <div class="tabs">
                            <input type="radio" name="tabs" id="tab1" v-model="tab" value="tab1"/>
                            <label for="tab1">Description</label>
                            <input type="radio" name="tabs" id="tab2" v-model="tab" value="tab2"/>
                            <label for="tab2">Map</label>
                            <div v-if="tab === 'tab1'" class="tab visible">
                                <p>In the Map tab there is a map of the city</p>
                            </div>
                            <div v-if="tab === 'tab2'" id="map" className="tab visible">
                                <MMap :location="location" zoomStrategy="zoomToCenter" :ref="refMap" >
                                    <MMapListener :onUpdate="onUpdate" />
                                    <MMapDefaultSchemeLayer />
                                    <MMapControls position="right">
                                        <MMapZoomControl />
                                    </MMapControls>
                                </MMap>
                            </div>
                        </div>`
        });
        app.mount('#app');
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>