Replace Default Controls

  • This example demonstrates how to replace the default map controls with custom ones.

  • It covers creating custom zoom, map type, and fullscreen controls.

  • The sample provides code in both JavaScript and TypeScript.

  • Users can interact with a live demo through JSFiddle or Google Cloud Shell.

  • Instructions for cloning and running the sample locally are included.

Read the documentation.

TypeScript

const mapElement = document.querySelector('gmp-map') as google.maps.MapElement;
let innerMap: google.maps.Map;

async function initMap() {
    // Load the needed libraries.
    await google.maps.importLibrary('maps');

    innerMap = mapElement.innerMap;

    // Disable the default controls.
    innerMap.setOptions({
        disableDefaultUI: true,
    });

    initZoomControl(innerMap);
    initMapTypeControl(innerMap);
    initFullscreenControl(innerMap);
}

function initZoomControl(map: google.maps.Map) {
    const zoomInButton = document.querySelector(
        '.zoom-control-in'
    ) as HTMLButtonElement;
    const zoomOutButton = document.querySelector(
        '.zoom-control-out'
    ) as HTMLButtonElement;

    zoomInButton.addEventListener('click', () => {
        map.setZoom((map.getZoom() || 0) + 1);
    });

    zoomOutButton.addEventListener('click', () => {
        map.setZoom((map.getZoom() || 0) - 1);
    });
}

async function initMapTypeControl(innerMap: google.maps.Map) {
    const mapTypeControlDiv = document.querySelector(
        '.maptype-control'
    ) as HTMLElement;
    const btnMap = document.querySelector(
        '.maptype-control-map'
    ) as HTMLButtonElement;
    const btnSatellite = document.querySelector(
        '.maptype-control-satellite'
    ) as HTMLButtonElement;

    btnMap.addEventListener('click', () => {
        mapTypeControlDiv.classList.add('maptype-control-is-map');
        mapTypeControlDiv.classList.remove('maptype-control-is-satellite');
        innerMap.setMapTypeId('roadmap');
    });

    btnSatellite.addEventListener('click', () => {
        mapTypeControlDiv.classList.add('maptype-control-is-satellite');
        mapTypeControlDiv.classList.remove('maptype-control-is-map');
        innerMap.setMapTypeId('hybrid');
    });
}

async function initFullscreenControl(innerMap: google.maps.Map) {
    // Get the UI elements for the fullscreen control.
    const btnFullscreen = document.querySelector(
        '#fullscreen-button'
    ) as HTMLButtonElement;

    btnFullscreen.addEventListener('click', () => {
        toggleFullScreen(mapElement);
    });
}

async function toggleFullScreen(element: google.maps.MapElement) {
    const fullScreenIcon = document.querySelector(
        '#fullscreen-button .material-icons'
    ) as HTMLElement;

    try {
        if (!document.fullscreenElement) {
            element.requestFullscreen();
            fullScreenIcon.innerText = 'fullscreen_exit';
        } else {
            document.exitFullscreen();
            fullScreenIcon.innerText = 'fullscreen';
        }
    } catch (error) {
        console.error('Error toggling fullscreen:', error);
    }
}

initMap();

JavaScript

const mapElement = document.querySelector('gmp-map');
let innerMap;
async function initMap() {
    // Load the needed libraries.
    await google.maps.importLibrary('maps');
    innerMap = mapElement.innerMap;
    // Disable the default controls.
    innerMap.setOptions({
        disableDefaultUI: true,
    });
    initZoomControl(innerMap);
    initMapTypeControl(innerMap);
    initFullscreenControl(innerMap);
}
function initZoomControl(map) {
    const zoomInButton = document.querySelector('.zoom-control-in');
    const zoomOutButton = document.querySelector('.zoom-control-out');
    zoomInButton.addEventListener('click', () => {
        map.setZoom((map.getZoom() || 0) + 1);
    });
    zoomOutButton.addEventListener('click', () => {
        map.setZoom((map.getZoom() || 0) - 1);
    });
}
async function initMapTypeControl(innerMap) {
    const mapTypeControlDiv = document.querySelector('.maptype-control');
    const btnMap = document.querySelector('.maptype-control-map');
    const btnSatellite = document.querySelector('.maptype-control-satellite');
    btnMap.addEventListener('click', () => {
        mapTypeControlDiv.classList.add('maptype-control-is-map');
        mapTypeControlDiv.classList.remove('maptype-control-is-satellite');
        innerMap.setMapTypeId('roadmap');
    });
    btnSatellite.addEventListener('click', () => {
        mapTypeControlDiv.classList.add('maptype-control-is-satellite');
        mapTypeControlDiv.classList.remove('maptype-control-is-map');
        innerMap.setMapTypeId('hybrid');
    });
}
async function initFullscreenControl(innerMap) {
    // Get the UI elements for the fullscreen control.
    const btnFullscreen = document.querySelector('#fullscreen-button');
    btnFullscreen.addEventListener('click', () => {
        toggleFullScreen(mapElement);
    });
}
async function toggleFullScreen(element) {
    const fullScreenIcon = document.querySelector('#fullscreen-button .material-icons');
    try {
        if (!document.fullscreenElement) {
            element.requestFullscreen();
            fullScreenIcon.innerText = 'fullscreen_exit';
        }
        else {
            document.exitFullscreen();
            fullScreenIcon.innerText = 'fullscreen';
        }
    }
    catch (error) {
        console.error('Error toggling fullscreen:', error);
    }
}
initMap();

CSS

/* Optional: Makes the sample page fill the window. */
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}

.controls {
    margin: 10px;
    background-color: white;
    box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 4px -1px;
    box-sizing: border-box;
    border-radius: 2px;
    display: flex;
    user-select: none;
    background-clip: padding-box;
    overflow: hidden; /* Keeps button backgrounds inside the rounded corners */
}

.controls button {
    border: 0;
    background-color: white;
    color: rgba(0, 0, 0, 0.6);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: color 0.2s ease;
}

.controls button:hover {
    color: rgba(0, 0, 0, 0.9);
}

.controls.zoom-control {
    flex-direction: column;
    width: 40px;
}

.controls.zoom-control button {
    height: 40px;
    width: 40px;
    font-size: 24px;
}

.controls.maptype-control {
    flex-direction: row;
    height: 40px;
}

.controls.maptype-control button {
    padding: 0 12px;
    font-size: 14px;
    font-family: Roboto, Arial, sans-serif;
    text-transform: uppercase;
    height: 100%;
}

.controls.maptype-control.maptype-control-is-map .maptype-control-map {
    font-weight: 700;
}

.controls.maptype-control.maptype-control-is-satellite
    .maptype-control-satellite {
    font-weight: 700;
}

.controls.fullscreen-control {
    width: 40px;
    height: 40px;
}

.controls.fullscreen-control button {
    background: none;
    border: none;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    width: 100%;
    height: 100%;
}

.controls.fullscreen-control {
    width: 40px;
    height: 40px;
}

#fullscreen-button .material-icons {
    font-size: 28px;
}

HTML

<html>
    <head>
        <title>Replacing Default Controls</title>

        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <link
            href="https://fonts.googleapis.com/icon?family=Material+Icons"
            rel="stylesheet" />
        <!-- prettier-ignore -->
        <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
        ({key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "weekly"});</script>
    </head>
    <body>
        <gmp-map center="-34.397,150.644" zoom="8">
            <div
                class="controls zoom-control"
                slot="control-inline-end-block-end">
                <button class="zoom-control-in" title="Zoom In">+</button>
                <button class="zoom-control-out" title="Zoom Out">-</button>
            </div>
            <div
                class="controls maptype-control maptype-control-is-map"
                slot="control-block-start-inline-start">
                <button class="maptype-control-map" title="Show road map">
                    Map
                </button>
                <button
                    class="maptype-control-satellite"
                    title="Show satellite imagery">
                    Satellite
                </button>
            </div>
            <div
                class="controls fullscreen-control"
                slot="control-block-start-inline-end">
                <button id="fullscreen-button" title="Toggle Fullscreen">
                    <span class="material-icons">fullscreen</span>
                </button>
            </div>
        </gmp-map>
    </body>
</html>

Try Sample

Clone Sample

Git and Node.js are required to run this sample locally. Follow these instructions to install Node.js and NPM. The following commands clone, install dependencies and start the sample application.

  git clone https://github.com/googlemaps-samples/js-api-samples.git
  cd samples/control-replacement
  npm i
  npm start