Create a Hover Effect
Highlight GeoJSON features when the user hovers over them using setFeatureState.
🖱️ Hover over the circles to see the highlight effect.
How It Works
- Add a GeoJSON source with features that have
idfields - Use
feature-stateexpressions in layer paint to change appearance - Use
setFeatureStateonmouseenter/mouseleaveto toggle the state
Key Code Pattern
javascript
// Layer uses feature-state to change color/size on hover
map.addLayer({
id: 'my-layer',
type: 'circle',
source: 'my-source',
paint: {
// Changes based on hover state
'circle-color': ['case',
['boolean', ['feature-state', 'hover'], false],
'#ef4444', // color when hovered
'#3b82f6' // default color
],
'circle-radius': ['case',
['boolean', ['feature-state', 'hover'], false],
16, // radius when hovered
10 // default radius
]
}
});
let hoveredId = null;
map.on('mouseenter', 'my-layer', (e) => {
map.getCanvas().style.cursor = 'pointer';
if (e.features.length > 0) {
if (hoveredId !== null) {
map.setFeatureState({ source: 'my-source', id: hoveredId }, { hover: false });
}
hoveredId = e.features[0].id;
map.setFeatureState({ source: 'my-source', id: hoveredId }, { hover: true });
}
});
map.on('mouseleave', 'my-layer', () => {
map.getCanvas().style.cursor = '';
if (hoveredId !== null) {
map.setFeatureState({ source: 'my-source', id: hoveredId }, { hover: false });
}
hoveredId = null;
});Important: Features Need IDs
javascript
// Each feature must have an 'id' field for setFeatureState to work
const geojson = {
type: 'FeatureCollection',
features: [
{ type: 'Feature', id: 1, properties: { name: 'Paris' }, geometry: {...} },
{ type: 'Feature', id: 2, properties: { name: 'London' }, geometry: {...} },
]
};Complete Example
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
<script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
<style>
#map { height: 500px; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = new mapmetricsgl.Map({
container: 'map',
style: '<StyleFile_URL_with_Token>',
center: [2.349902, 48.852966],
zoom: 5
});
map.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
map.on('load', () => {
map.addSource('cities', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{ type: 'Feature', id: 1, properties: { name: 'Paris' }, geometry: { type: 'Point', coordinates: [2.349902, 48.852966] } },
{ type: 'Feature', id: 2, properties: { name: 'London' }, geometry: { type: 'Point', coordinates: [-0.1276, 51.5074] } },
{ type: 'Feature', id: 3, properties: { name: 'Berlin' }, geometry: { type: 'Point', coordinates: [13.405, 52.52] } },
]
}
});
map.addLayer({
id: 'cities-layer',
type: 'circle',
source: 'cities',
paint: {
'circle-radius': ['case', ['boolean', ['feature-state', 'hover'], false], 16, 10],
'circle-color': ['case', ['boolean', ['feature-state', 'hover'], false], '#ef4444', '#3b82f6'],
'circle-stroke-width': 2,
'circle-stroke-color': '#fff'
}
});
let hoveredId = null;
map.on('mouseenter', 'cities-layer', (e) => {
map.getCanvas().style.cursor = 'pointer';
if (e.features.length > 0) {
if (hoveredId !== null) map.setFeatureState({ source: 'cities', id: hoveredId }, { hover: false });
hoveredId = e.features[0].id;
map.setFeatureState({ source: 'cities', id: hoveredId }, { hover: true });
}
});
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = '';
if (hoveredId !== null) map.setFeatureState({ source: 'cities', id: hoveredId }, { hover: false });
hoveredId = null;
});
});
</script>
</body>
</html>jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const geojsonData = {
type: 'FeatureCollection',
features: [
{ type: 'Feature', id: 1, properties: { name: 'Paris' }, geometry: { type: 'Point', coordinates: [2.349902, 48.852966] } },
{ type: 'Feature', id: 2, properties: { name: 'London' }, geometry: { type: 'Point', coordinates: [-0.1276, 51.5074] } },
{ type: 'Feature', id: 3, properties: { name: 'Berlin' }, geometry: { type: 'Point', coordinates: [13.405, 52.52] } },
]
};
const HoverEffect = () => {
const mapContainer = useRef(null);
const map = useRef(null);
useEffect(() => {
if (map.current) return;
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
style: '<StyleFile_URL_with_Token>',
center: [2.349902, 48.852966],
zoom: 5
});
map.current.addControl(new mapmetricsgl.NavigationControl(), 'top-right');
map.current.on('load', () => {
map.current.addSource('cities', { type: 'geojson', data: geojsonData });
map.current.addLayer({
id: 'cities-layer',
type: 'circle',
source: 'cities',
paint: {
'circle-radius': ['case', ['boolean', ['feature-state', 'hover'], false], 16, 10],
'circle-color': ['case', ['boolean', ['feature-state', 'hover'], false], '#ef4444', '#3b82f6'],
'circle-stroke-width': 2,
'circle-stroke-color': '#fff'
}
});
let hoveredId = null;
map.current.on('mouseenter', 'cities-layer', (e) => {
map.current.getCanvas().style.cursor = 'pointer';
if (e.features.length > 0) {
if (hoveredId !== null) map.current.setFeatureState({ source: 'cities', id: hoveredId }, { hover: false });
hoveredId = e.features[0].id;
map.current.setFeatureState({ source: 'cities', id: hoveredId }, { hover: true });
}
});
map.current.on('mouseleave', 'cities-layer', () => {
map.current.getCanvas().style.cursor = '';
if (hoveredId !== null) map.current.setFeatureState({ source: 'cities', id: hoveredId }, { hover: false });
hoveredId = null;
});
});
return () => { map.current?.remove(); map.current = null; };
}, []);
return <div ref={mapContainer} style={{ height: '500px', width: '100%' }} />;
};
export default HoverEffect;For more information, visit the MapMetrics GitHub repository.