Interaction and Events
The Mapbox GL JS interaction and events system provides a powerful framework for handling user interactions with maps and features. This page documents how to work with the event system, create custom interactions, target specific features, and manage feature state for visual feedback.
For information about UI controls that can be added to the map, see Controls.
Event System Overview
Mapbox GL JS wraps native browser events into map-specific event objects that provide geographic context and additional functionality. The events system is built on top of the Evented class, which provides methods for registering event listeners using on(), once(), and off().
Event Types and Objects
There are three main event object types:
- MapMouseEvent: For mouse interactions like
click,mousemove - MapTouchEvent: For touch interactions like
touchstart,touchend - MapWheelEvent: For wheel events (
wheel)
Basic Event Handling
To listen for map events, use the map.on() method:
// Listen for clicks anywhere on the map
map.on('click', (e) => {
console.log(`Clicked at ${e.lngLat.lng}, ${e.lngLat.lat}`);
});
// Listen for clicks on a specific layer
map.on('click', 'building-layer', (e) => {
console.log('Clicked building:', e.features[0].properties);
});Preventing Default Behaviors
Event objects include a preventDefault() method that stops the map's default handling of the event (such as panning, zooming, or rotation):
map.on('mousedown', (e) => {
// Prevent the map's drag behavior
e.preventDefault();
// Custom behavior...
});The Interaction System
Mapbox GL JS provides a high-level interaction system that extends beyond basic event handling, allowing for more declarative and feature-specific interactions. This system is built around the InteractionSet class and accessed through the map.addInteraction() method.
Interaction Flow
Adding Interactions
Interactions are configured using the map.addInteraction(id, config) method:
map.addInteraction('building-click', {
type: 'click', // Event type
target: {featuresetId: 'buildings'}, // Target descriptor
filter: ['>=', ['get', 'height'], 100], // Optional filter
handler: (e) => { // Event handler
map.setFeatureState(e.feature, {selected: true});
}
});The Interaction configuration object includes:
type: The event type ('click', 'mouseenter', etc.)target: Optional target descriptor (layer or featureset)filter: Optional expression to filter featureshandler: Function that receives anInteractionEvent
InteractionEvent
The InteractionEvent object passed to handlers includes:
- Standard event properties (
type,point,lngLat,originalEvent) feature: The specific feature that was interacted with (as aTargetFeature)id: The interaction IDinteraction: The original interaction configurationpreventDefault(): Method to stop event propagation
Feature Targeting
Mapbox GL JS has a flexible system for targeting specific features or collections of features for interactions.
Target Descriptors
A target descriptor can be either:
- A layer target:
{layerId: 'building-layer'} - A featureset target:
{featuresetId: 'buildings', importId?: 'basemap'}
Featuresets are collections of features defined in a style's featuresets property and can span multiple layers or imports.
Feature Variants
When querying features with target descriptors, the resulting features include a variants property that maps targets to feature variants. When these features are passed to interaction handlers, they're wrapped in a TargetFeature that represents the specific variant that matched the target.
Feature State Management
Feature state allows for dynamic styling and interaction feedback. States are per-feature key-value pairs that can be used in style expressions.
Managing Feature State
// Set a feature state
map.setFeatureState(feature, {selected: true, hover: false});
// Get current feature state
const state = map.getFeatureState(feature);
// Remove a specific state property
map.removeFeatureState(feature, 'selected');
// Remove all state properties
map.removeFeatureState(feature);Using Feature State in Styles
Feature states can be referenced in style expressions using the feature-state operator:
map.setPaintProperty('buildings', 'fill-color', [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000', // Selected color
['boolean', ['feature-state', 'hover'], false],
'#00ff00', // Hover color
'#0000ff' // Default color
]);Common Interaction Patterns
Hover Effects
// Add mouseenter interaction
map.addInteraction('building-hover', {
type: 'mouseenter',
target: {featuresetId: 'buildings'},
handler: (e) => {
map.setFeatureState(e.feature, {hover: true});
}
});
// Add mouseleave interaction
map.addInteraction('building-unhover', {
type: 'mouseleave',
target: {featuresetId: 'buildings'},
handler: (e) => {
map.setFeatureState(e.feature, {hover: false});
}
});Selection
let selectedFeature = null;
map.addInteraction('building-select', {
type: 'click',
target: {featuresetId: 'buildings'},
handler: (e) => {
// Deselect previous feature
if (selectedFeature) {
map.setFeatureState(selectedFeature, {selected: false});
}
// Select new feature
selectedFeature = e.feature;
map.setFeatureState(selectedFeature, {selected: true});
// Show popup or details
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`<h3>${e.feature.properties.name || 'Building'}</h3>`)
.addTo(map);
}
});Working with Imports
When working with imported styles, you can target featuresets in specific imports:
map.addInteraction('basemap-building-click', {
type: 'click',
target: {featuresetId: 'buildings', importId: 'basemap'},
handler: (e) => {
// Handle click on buildings from the basemap import
console.log('Clicked on building in basemap:', e.feature);
}
});InteractionSet Implementation
Under the hood, the interaction system is managed by an InteractionSet instance that handles:
- Registering and managing interactions
- Delegating events to appropriate handlers
- Tracking hover state for mouseenter/mouseleave events
- Querying features for interaction targets
- Preventing interaction duplication
The InteractionSet.handleType() method is responsible for handling all interaction events. For mouseenter/mouseleave events, handleMove() and handleOut() are used to track state changes and fire the appropriate events.
Advanced Topics
Stopping Event Propagation
Interaction handlers can control whether other interactions should process an event by returning a boolean:
map.addInteraction('exclusive-click', {
type: 'click',
target: {layerId: 'important-layer'},
handler: (e) => {
// Handle the click
console.log('Handling click exclusively');
// Return true to stop propagation to other interactions
// Return false (or undefined) to allow other interactions
return true;
}
});Feature Filters
Interactions can include filters to limit which features respond to interactions:
map.addInteraction('tall-building-click', {
type: 'click',
target: {featuresetId: 'buildings'},
// Only trigger for buildings taller than 100 meters
filter: ['>', ['get', 'height'], 100],
handler: (e) => {
console.log('Clicked on tall building:', e.feature);
}
});The filter syntax uses the same expression system as Mapbox GL styles.
Feature Namespaces
For complex applications, you can use namespaces to organize features:
map.addInteraction('city-building-click', {
type: 'click',
target: {featuresetId: 'buildings'},
namespace: 'city',
handler: (e) => {
console.log('City building namespace:', e.feature.namespace);
// Handle click on city buildings
}
});Supported Event Types
| Event Type | Description | Based On |
|---|---|---|
mousedown | Mouse button pressed | MouseEvent |
mouseup | Mouse button released | MouseEvent |
click | Mouse click | MouseEvent |
dblclick | Double click | MouseEvent |
mousemove | Mouse movement | MouseEvent |
mouseenter | Mouse enters a feature | MouseEvent (delegated) |
mouseleave | Mouse leaves a feature | MouseEvent (delegated) |
mouseover | Alias for mouseenter | MouseEvent (delegated) |
mouseout | Alias for mouseleave | MouseEvent (delegated) |
touchstart | Touch begins | TouchEvent |
touchend | Touch ends | TouchEvent |
touchcancel | Touch is canceled | TouchEvent |
wheel | Mouse wheel movement | WheelEvent |
Implementation Details
The interaction and events system is implemented through several key components:
- Map events (in
src/ui/events.ts) wrap DOM events with map-specific context - InteractionSet (in
src/ui/interactions.ts) manages interactions and delegated events - Feature targeting (in
src/util/vectortile_to_geojson.ts) handles feature variants and target features - Query features (in
src/source/query_features.ts) performs feature lookups with target descriptors - Feature state is stored in the style system and used for rendering
When an interaction is added with map.addInteraction(), the system:
- Creates an entry in the InteractionSet
- Sets up event listeners if needed
- For hover-related events, sets up mousemove/mouseout delegation
When an event occurs, the system:
- Creates a MapMouseEvent/MapTouchEvent
- Delegates to InteractionSet.handleType()
- Queries features based on target descriptors
- Applies filters
- Creates TargetFeature instances for matched features
- Calls handlers with InteractionEvent objects