Documentation menu
Example
Hover highlight with feature-state
Highlight the polygon under the pointer without touching the data. feature-state flips a flag on one feature and the paint expression reads it, so hover costs no setData and no re-tiling, even on large sources.
ts
import { createMap } from "@mapmap/maps";
const map = createMap({
container: "map",
apiKey: "snk_...",
attributionPosition: "bottom-left",
center: [-0.12, 51.5],
zoom: 10.3,
});
await map.whenReady();
const cells: GeoJSON.Feature[] = [];
for (let x = 0; x < 18; x++) {
for (let y = 0; y < 12; y++) {
const west = -0.42 + x * 0.034, south = 51.36 + y * 0.025;
cells.push({
type: "Feature",
properties: { cell: x * 100 + y },
geometry: {
type: "Polygon",
coordinates: [[
[west, south], [west + 0.034, south], [west + 0.034, south + 0.025],
[west, south + 0.025], [west, south],
]],
},
});
}
}
map.map.addSource("areas", {
type: "geojson",
data: { type: "FeatureCollection", features: cells },
// feature-state needs a stable id per feature: promote one of yours.
promoteId: "cell",
});
// Slots arrive in the SDK 0.17: map.slot("middle") replaces "road-labels".
map.map.addLayer({
id: "areas-fill",
type: "fill",
source: "areas",
paint: {
"fill-color": "#1a6bff",
"fill-opacity": [
"case", ["boolean", ["feature-state", "hover"], false], 0.55, 0.12,
],
},
}, "road-labels");
map.map.addLayer({
id: "areas-outline",
type: "line",
source: "areas",
paint: { "line-color": "#1a6bff", "line-width": 0.6 },
}, "road-labels");
let hovered: string | number | undefined;
const setHover = (id: string | number | undefined) => {
if (hovered !== undefined) {
map.map.setFeatureState({ source: "areas", id: hovered }, { hover: false });
}
hovered = id;
if (id !== undefined) {
map.map.setFeatureState({ source: "areas", id }, { hover: true });
}
};
map.map.on("mousemove", "areas-fill", (e) => {
setHover(e.features?.[0]?.id);
map.map.getCanvas().style.cursor = "pointer";
});
map.map.on("mouseleave", "areas-fill", () => {
setHover(undefined);
map.map.getCanvas().style.cursor = "";
});Worth knowing
- feature-state is only readable from paint properties, never from layout or filter. Use it for colour, opacity and width; use a filter or setData for anything that changes which features draw.
- Ids must be stable. On a GeoJSON source use promoteId (a property) or feature.id; generateId: true works but renumbers on every setData. On a vector source pass sourceLayer to setFeatureState, and the id must be the same on every tile the feature crosses.
- MapMap basemap features carry tile-assigned ids, not OpenStreetMap ids, so hover on basemap layers highlights per tile and an id never matches an OSM way.