Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions samples/control-replacement/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Google Maps JavaScript Sample

## control-replacement

This sample demonstrates replacing default map controls with custom controls.

## Setup

### Before starting run:

`npm i`

### Run an example on a local web server

`cd samples/control-replacement`
`npm start`

### Build an individual example

`cd samples/control-replacement`
`npm run build`

From 'samples':

`npm run build --workspace=control-replacement/`

### Build all of the examples.

From 'samples':

`npm run build-all`

### Run lint to check for problems

`cd samples/control-replacement`
`npx eslint index.ts`

## Feedback

For feedback related to this sample, please open a new issue on
[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues).
51 changes: 51 additions & 0 deletions samples/control-replacement/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!doctype html>
<!--
@license
Copyright 2026 Google LLC. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
-->
<!-- [START maps_control_replacement] -->
<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>
<!-- [END maps_control_replacement] -->
98 changes: 98 additions & 0 deletions samples/control-replacement/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* @license
* Copyright 2026 Google LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

// [START maps_control_replacement]
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', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the "?" here?

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', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need "?" here?

mapTypeControlDiv.classList.add('maptype-control-is-map');
mapTypeControlDiv.classList.remove('maptype-control-is-satellite');
innerMap.setMapTypeId('roadmap');
});

btnSatellite?.addEventListener('click', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

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();
// [END maps_control_replacement]
14 changes: 14 additions & 0 deletions samples/control-replacement/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@js-api-samples/control-replacement",
"version": "1.0.0",
"scripts": {
"build": "tsc && bash ../jsfiddle.sh control-replacement && bash ../app.sh control-replacement && bash ../docs.sh control-replacement && npm run build:vite --workspace=. && bash ../dist.sh control-replacement",
"test": "tsc && npm run build:vite --workspace=.",
"start": "tsc && vite build --base './' && vite",
"build:vite": "vite build --base './'",
"preview": "vite preview"
},
"dependencies": {

}
}
102 changes: 102 additions & 0 deletions samples/control-replacement/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @license
* Copyright 2026 Google LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* [START maps_control_replacement] */

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

.controls {
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;
}

/* [END maps_control_replacement] */
14 changes: 14 additions & 0 deletions samples/control-replacement/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022", "DOM"],
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"types": ["google.maps"]
},
"include": ["./*.ts", "./types/*.d.ts"]
}
Loading