HTML Viewer Examples
Interactive HTML examples demonstrating esri-gl with different mapping libraries and service types. Click the links below to view live examples in your browser.
Live Examples
🚀 Basic MapLibre GL Viewer
A complete interactive viewer with:
- Service type selector (Dynamic, Feature, Tiled, Vector)
- Custom URL input with working examples
- Click-to-identify functionality
- Service loading/clearing controls
- Professional styling with control panels
⚡ Minimal Example
A bare-bones setup showing:
- Simple esri-gl integration
- Just 30 lines of code
- Perfect for getting started quickly
📊 Dashboard Example
An advanced dashboard-style interface featuring:
- Professional sidebar with service buttons
- Multiple predefined ArcGIS services
- Service management and layer controls
- Loading states and info panels
- Interactive identify functionality
Code Examples
Below are the complete source code examples you can copy and modify for your own projects.
Check out the live examples above to see esri-gl in action before diving into the code!
Basic MapLibre GL Setup
A complete HTML page using esri-gl with MapLibre GL JS:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>esri-gl with MapLibre GL</title>
<!-- MapLibre GL CSS -->
<link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
#map {
width: 100vw;
height: 100vh;
}
.controls {
position: absolute;
top: 10px;
right: 10px;
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 1000;
}
.control-group {
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, select, button {
width: 100%;
padding: 5px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 3px;
}
button {
background: #007cbf;
color: white;
border: none;
cursor: pointer;
font-weight: bold;
}
button:hover {
background: #005a87;
}
.info-popup {
background: white;
padding: 10px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
max-width: 300px;
}
.info-popup h3 {
margin: 0 0 10px 0;
color: #007cbf;
}
.info-popup p {
margin: 5px 0;
font-size: 14px;
}
</style>
</head>
<body>
<div id="map"></div>
<div class="controls">
<div class="control-group">
<label>Service Type:</label>
<select id="serviceType">
<option value="dynamic">Dynamic Map Service</option>
<option value="feature">Feature Service</option>
<option value="tiled">Tiled Map Service</option>
<option value="vector">Vector Tile Service</option>
</select>
</div>
<div class="control-group">
<label>Service URL:</label>
<input type="text" id="serviceUrl"
value="https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer"
placeholder="Enter ArcGIS service URL">
</div>
<div class="control-group">
<button onclick="loadService()">Load Service</button>
<button onclick="clearMap()">Clear Map</button>
</div>
<div class="control-group">
<label>
<input type="checkbox" id="enableIdentify" onchange="toggleIdentify()">
Enable Click to Identify
</label>
</div>
</div>
<!-- MapLibre GL JavaScript -->
<script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script>
<!-- esri-gl Library -->
<script src="https://unpkg.com/esri-gl@latest/dist/esri-gl.js"></script>
<script>
// Initialize MapLibre GL map
const map = new maplibregl.Map({
container: 'map',
style: {
version: 8,
sources: {
'raster-tiles': {
type: 'raster',
tiles: [
'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
],
tileSize: 256,
attribution: '© OpenStreetMap contributors'
}
},
layers: [
{
id: 'background',
type: 'raster',
source: 'raster-tiles'
}
]
},
center: [-95, 40],
zoom: 4
});
// Global variables
let currentService = null;
let identifyEnabled = false;
let currentPopup = null;
// Wait for map to load
map.on('load', () => {
console.log('Map loaded successfully');
});
// Load service based on type
function loadService() {
const serviceType = document.getElementById('serviceType').value;
const serviceUrl = document.getElementById('serviceUrl').value.trim();
if (!serviceUrl) {
alert('Please enter a service URL');
return;
}
// Clear existing service
clearMap();
try {
const sourceId = 'esri-service-source';
switch (serviceType) {
case 'dynamic':
currentService = new DynamicMapService(sourceId, map, {
url: serviceUrl
});
// Add raster layer
map.addLayer({
id: 'dynamic-layer',
type: 'raster',
source: sourceId
});
console.log('Dynamic Map Service loaded');
break;
case 'feature':
currentService = new FeatureService(sourceId, map, {
url: serviceUrl,
useVectorTiles: true,
useBoundingBox: true
});
// Add vector layers after service is ready
setTimeout(() => {
addFeatureServiceLayers(sourceId);
}, 1000);
console.log('Feature Service loaded');
break;
case 'tiled':
currentService = new TiledMapService(sourceId, map, {
url: serviceUrl
});
// Add raster layer
map.addLayer({
id: 'tiled-layer',
type: 'raster',
source: sourceId
});
console.log('Tiled Map Service loaded');
break;
case 'vector':
currentService = new VectorTileService(sourceId, map, {
url: serviceUrl
});
console.log('Vector Tile Service loaded');
break;
default:
alert('Unknown service type');
return;
}
// Update URL examples based on service type
updateUrlExample(serviceType);
} catch (error) {
console.error('Error loading service:', error);
alert('Error loading service. Please check the URL and try again.');
}
}
// Add layers for Feature Service
function addFeatureServiceLayers(sourceId) {
try {
// Get the source to check if it's loaded
const source = map.getSource(sourceId);
if (!source) {
console.warn('Source not ready yet');
return;
}
// Add fill layer for polygons
if (!map.getLayer('features-fill')) {
map.addLayer({
id: 'features-fill',
type: 'fill',
source: sourceId,
paint: {
'fill-color': '#088',
'fill-opacity': 0.6
},
filter: ['==', '$type', 'Polygon']
});
}
// Add line layer
if (!map.getLayer('features-line')) {
map.addLayer({
id: 'features-line',
type: 'line',
source: sourceId,
paint: {
'line-color': '#000',
'line-width': 2
},
filter: ['any', ['==', '$type', 'LineString'], ['==', '$type', 'Polygon']]
});
}
// Add circle layer for points
if (!map.getLayer('features-circle')) {
map.addLayer({
id: 'features-circle',
type: 'circle',
source: sourceId,
paint: {
'circle-color': '#ff0000',
'circle-radius': 6,
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff'
},
filter: ['==', '$type', 'Point']
});
}
} catch (error) {
console.error('Error adding feature layers:', error);
}
}
// Clear map
function clearMap() {
if (currentService && typeof currentService.remove === 'function') {
currentService.remove();
}
// Remove layers
const layersToRemove = ['dynamic-layer', 'tiled-layer', 'features-fill', 'features-line', 'features-circle'];
layersToRemove.forEach(layerId => {
if (map.getLayer(layerId)) {
map.removeLayer(layerId);
}
});
// Remove sources
const sourcesToRemove = ['esri-service-source'];
sourcesToRemove.forEach(sourceId => {
if (map.getSource(sourceId)) {
map.removeSource(sourceId);
}
});
currentService = null;
// Close popup
if (currentPopup) {
currentPopup.remove();
currentPopup = null;
}
console.log('Map cleared');
}
// Toggle identify functionality
function toggleIdentify() {
identifyEnabled = document.getElementById('enableIdentify').checked;
if (identifyEnabled) {
map.on('click', handleMapClick);
map.getCanvas().style.cursor = 'crosshair';
} else {
map.off('click', handleMapClick);
map.getCanvas().style.cursor = '';
if (currentPopup) {
currentPopup.remove();
currentPopup = null;
}
}
}
// Handle map click for identify
async function handleMapClick(e) {
if (!identifyEnabled || !currentService) return;
try {
const serviceType = document.getElementById('serviceType').value;
if (serviceType === 'dynamic' && currentService.identify) {
// Use service identify method
const results = await currentService.identify(e.lngLat);
showIdentifyResults(e.lngLat, results);
} else if (serviceType === 'feature') {
// Query features at click point
const features = map.queryRenderedFeatures(e.point, {
layers: ['features-fill', 'features-line', 'features-circle']
});
if (features.length > 0) {
showFeatureInfo(e.lngLat, features[0]);
} else {
showNoResults(e.lngLat);
}
} else {
showMessage(e.lngLat, 'Identify not supported for this service type');
}
} catch (error) {
console.error('Identify error:', error);
showMessage(e.lngLat, 'Error identifying features');
}
}
// Show identify results
function showIdentifyResults(lngLat, results) {
if (currentPopup) {
currentPopup.remove();
}
if (!results || !results.features || results.features.length === 0) {
showNoResults(lngLat);
return;
}
const feature = results.features[0];
const props = feature.properties || {};
let content = '<div class="info-popup"><h3>Feature Information</h3>';
Object.keys(props).slice(0, 5).forEach(key => {
if (props[key] !== null && props[key] !== undefined) {
content += `<p><strong>${key}:</strong> ${props[key]}</p>`;
}
});
content += '</div>';
currentPopup = new maplibregl.Popup()
.setLngLat(lngLat)
.setHTML(content)
.addTo(map);
}
// Show feature information
function showFeatureInfo(lngLat, feature) {
if (currentPopup) {
currentPopup.remove();
}
const props = feature.properties || {};
let content = '<div class="info-popup"><h3>Feature Properties</h3>';
Object.keys(props).slice(0, 8).forEach(key => {
if (props[key] !== null && props[key] !== undefined) {
content += `<p><strong>${key}:</strong> ${props[key]}</p>`;
}
});
content += '</div>';
currentPopup = new maplibregl.Popup()
.setLngLat(lngLat)
.setHTML(content)
.addTo(map);
}
// Show no results message
function showNoResults(lngLat) {
showMessage(lngLat, 'No features found at this location');
}
// Show message popup
function showMessage(lngLat, message) {
if (currentPopup) {
currentPopup.remove();
}
currentPopup = new maplibregl.Popup()
.setLngLat(lngLat)
.setHTML(`<div class="info-popup"><p>${message}</p></div>`)
.addTo(map);
}
// Update URL example based on service type
function updateUrlExample(serviceType) {
const urlInput = document.getElementById('serviceUrl');
const examples = {
dynamic: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer',
feature: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/0',
tiled: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/WorldTimeZones/MapServer',
vector: 'https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer'
};
if (examples[serviceType] && urlInput.value.includes('sampleserver6')) {
urlInput.value = examples[serviceType];
}
}
// Initialize service type change handler
document.getElementById('serviceType').addEventListener('change', (e) => {
updateUrlExample(e.target.value);
});
// Add keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'c' && e.ctrlKey) {
clearMap();
e.preventDefault();
}
if (e.key === 'l' && e.ctrlKey) {
loadService();
e.preventDefault();
}
});
console.log('esri-gl HTML viewer loaded successfully');
console.log('Available services:', Object.keys(esrigl));
</script>
</body>
</html>
Mapbox GL JS Version
A complete HTML page using esri-gl with Mapbox GL JS:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>esri-gl with Mapbox GL JS</title>
<!-- Mapbox GL CSS -->
<link href="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
#map {
width: 100vw;
height: 100vh;
}
.attribution {
position: absolute;
bottom: 0;
right: 0;
background: rgba(255, 255, 255, 0.8);
padding: 2px 5px;
font-size: 12px;
z-index: 1000;
}
</style>
</head>
<body>
<div id="map"></div>
<div class="attribution">
Powered by <a href="https://github.com/muimsd/esri-gl" target="_blank">esri-gl</a>
</div>
<!-- Mapbox GL JavaScript -->
<script src="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js"></script>
<!-- esri-gl Library -->
<script src="https://unpkg.com/esri-gl@latest/dist/esri-gl.js"></script>
<script>
// Set your Mapbox access token (required for Mapbox GL JS)
mapboxgl.accessToken = 'your-mapbox-access-token';
// Initialize Mapbox GL map
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11',
center: [-95, 40],
zoom: 4
});
// Add navigation control
map.addControl(new mapboxgl.NavigationControl());
map.on('load', () => {
// Add Dynamic Map Service
const dynamicService = new DynamicMapService('usa-service', map, {
url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer'
});
// Add the layer to map
map.addLayer({
id: 'usa-layer',
type: 'raster',
source: 'usa-service'
});
// Add Feature Service
const featureService = new FeatureService('cities-service', map, {
url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/0',
useVectorTiles: false
});
// Add feature layers
setTimeout(() => {
map.addLayer({
id: 'cities-points',
type: 'circle',
source: 'cities-service',
paint: {
'circle-radius': 8,
'circle-color': '#ff6b35',
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff'
}
});
}, 2000);
console.log('Mapbox GL map with esri-gl services loaded');
});
</script>
</body>
</html>
Minimal Example
A minimal HTML example for quick testing:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minimal esri-gl Example</title>
<link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet">
<style>
body { margin: 0; padding: 0; }
#map { width: 100vw; height: 100vh; }
</style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script>
<script src="https://unpkg.com/esri-gl@latest/dist/esri-gl.js"></script>
<script>
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [-95, 40],
zoom: 4
});
map.on('load', () => {
// Add ArcGIS Dynamic Map Service
new DynamicMapService('esri-service', map, {
url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer'
});
// Add the layer
map.addLayer({
id: 'esri-layer',
type: 'raster',
source: 'esri-service'
});
});
</script>
</body>
</html>
Interactive Dashboard Example
A more advanced dashboard-style HTML example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>esri-gl Dashboard</title>
<link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet">
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 0; font-family: 'Segoe UI', Arial, sans-serif; }
.container {
display: flex;
height: 100vh;
}
.sidebar {
width: 300px;
background: #2c3e50;
color: white;
padding: 20px;
overflow-y: auto;
}
.sidebar h2 {
margin-top: 0;
color: #3498db;
}
.service-group {
margin-bottom: 25px;
padding: 15px;
background: rgba(52, 152, 219, 0.1);
border-radius: 5px;
}
.service-group h3 {
margin: 0 0 15px 0;
color: #ecf0f1;
}
.service-item {
margin-bottom: 10px;
}
.service-item button {
width: 100%;
padding: 8px 12px;
background: #3498db;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
}
.service-item button:hover {
background: #2980b9;
}
.service-item button.active {
background: #e74c3c;
}
#map {
flex: 1;
position: relative;
}
.map-info {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 14px;
z-index: 1000;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 5px;
z-index: 2000;
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<h2>esri-gl Dashboard</h2>
<div class="service-group">
<h3>Map Services</h3>
<div class="service-item">
<button onclick="loadService('dynamic', 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer', 'USA Map')">USA Dynamic Service</button>
</div>
<div class="service-item">
<button onclick="loadService('tiled', 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/WorldTimeZones/MapServer', 'World Time Zones')">World Time Zones</button>
</div>
<div class="service-item">
<button onclick="loadService('dynamic', 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer', 'Census Data')">Census Data</button>
</div>
</div>
<div class="service-group">
<h3>Feature Services</h3>
<div class="service-item">
<button onclick="loadService('feature', 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/0', 'USA Cities')">USA Cities</button>
</div>
<div class="service-item">
<button onclick="loadService('feature', 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/2', 'USA States')">USA States</button>
</div>
</div>
<div class="service-group">
<h3>Vector Services</h3>
<div class="service-item">
<button onclick="loadService('vector', 'https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer', 'World Basemap')">World Basemap</button>
</div>
</div>
<div class="service-group">
<h3>Actions</h3>
<div class="service-item">
<button onclick="clearAllServices()">Clear All</button>
</div>
<div class="service-item">
<button onclick="toggleIdentify()">Toggle Identify</button>
</div>
</div>
</div>
<div id="map">
<div class="loading" id="loading">Loading service...</div>
<div class="map-info" id="mapInfo">Click a service to load</div>
</div>
</div>
<script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script>
<script src="https://unpkg.com/esri-gl@latest/dist/esri-gl.js"></script>
<script>
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [-95, 40],
zoom: 4
});
// Add controls
map.addControl(new maplibregl.NavigationControl());
map.addControl(new maplibregl.ScaleControl());
let services = new Map();
let layerCounter = 0;
let identifyEnabled = false;
function showLoading() {
document.getElementById('loading').style.display = 'block';
}
function hideLoading() {
document.getElementById('loading').style.display = 'none';
}
function updateMapInfo(text) {
document.getElementById('mapInfo').textContent = text;
}
async function loadService(type, url, name) {
showLoading();
updateMapInfo(`Loading ${name}...`);
try {
const serviceId = `service-${layerCounter++}`;
const layerId = `layer-${layerCounter}`;
let service;
switch (type) {
case 'dynamic':
service = new DynamicMapService(serviceId, map, { url });
map.addLayer({
id: layerId,
type: 'raster',
source: serviceId
});
break;
case 'tiled':
service = new TiledMapService(serviceId, map, { url });
map.addLayer({
id: layerId,
type: 'raster',
source: serviceId
});
break;
case 'feature':
service = new FeatureService(serviceId, map, {
url,
useVectorTiles: true
});
setTimeout(() => {
// Add feature layers based on geometry
const source = map.getSource(serviceId);
if (source) {
map.addLayer({
id: `${layerId}-fill`,
type: 'fill',
source: serviceId,
paint: {
'fill-color': '#3498db',
'fill-opacity': 0.6
},
filter: ['==', '$type', 'Polygon']
});
map.addLayer({
id: `${layerId}-line`,
type: 'line',
source: serviceId,
paint: {
'line-color': '#2c3e50',
'line-width': 2
}
});
map.addLayer({
id: `${layerId}-circle`,
type: 'circle',
source: serviceId,
paint: {
'circle-radius': 6,
'circle-color': '#e74c3c',
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff'
},
filter: ['==', '$type', 'Point']
});
}
}, 1000);
break;
case 'vector':
service = new VectorTileService(serviceId, map, { url });
break;
}
services.set(serviceId, { service, name, type, layerId });
updateMapInfo(`Loaded: ${name} (${services.size} services active)`);
} catch (error) {
console.error('Error loading service:', error);
updateMapInfo(`Error loading ${name}`);
}
hideLoading();
}
function clearAllServices() {
services.forEach((serviceInfo, serviceId) => {
if (serviceInfo.service && typeof serviceInfo.service.remove === 'function') {
serviceInfo.service.remove();
}
// Remove layers
const layersToRemove = [
serviceInfo.layerId,
`${serviceInfo.layerId}-fill`,
`${serviceInfo.layerId}-line`,
`${serviceInfo.layerId}-circle`
];
layersToRemove.forEach(layerId => {
if (map.getLayer(layerId)) {
map.removeLayer(layerId);
}
});
// Remove source
if (map.getSource(serviceId)) {
map.removeSource(serviceId);
}
});
services.clear();
updateMapInfo('All services cleared');
}
function toggleIdentify() {
identifyEnabled = !identifyEnabled;
if (identifyEnabled) {
map.on('click', handleMapClick);
map.getCanvas().style.cursor = 'crosshair';
updateMapInfo('Identify mode enabled - click on features');
} else {
map.off('click', handleMapClick);
map.getCanvas().style.cursor = '';
updateMapInfo(`${services.size} services active`);
}
}
function handleMapClick(e) {
if (!identifyEnabled) return;
// Query all rendered features at click point
const features = map.queryRenderedFeatures(e.point);
if (features.length > 0) {
const feature = features[0];
const props = feature.properties || {};
let content = '<h3>Feature Properties</h3>';
Object.keys(props).slice(0, 5).forEach(key => {
if (props[key] !== null && props[key] !== undefined) {
content += `<p><strong>${key}:</strong> ${props[key]}</p>`;
}
});
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(content)
.addTo(map);
}
}
map.on('load', () => {
updateMapInfo('Map ready - click a service to load');
});
</script>
</body>
</html>
Usage Instructions
CDN Links
All examples use CDN links for easy setup:
- MapLibre GL:
https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js - Mapbox GL:
https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js - esri-gl:
https://unpkg.com/esri-gl@latest/dist/esri-gl.js
Local Development
For local development, you can:
- Download the files and reference local paths
- Use npm/yarn to install packages and bundle
- Build esri-gl locally and reference the dist files
Key Features Demonstrated
- Multiple Service Types: Dynamic Map, Feature, Tiled Map, and Vector Tile services
- Interactive Controls: Service switching, layer management, identify functionality
- Responsive Design: Mobile-friendly layouts
- Error Handling: Graceful handling of service loading errors
- Performance: Efficient layer management and cleanup
Save any of these HTML files and open them in a web browser to see esri-gl in action!