Comprehensive event system for handling user interactions, map state changes, and data loading events throughout the Mapbox GL JS library.
Base class that provides event handling capabilities to Map, Marker, Popup, and other interactive components.
/**
* Event system base class providing event handling capabilities
*/
class Evented {
/**
* Add event listener
* @param type - Event type name
* @param listener - Event handler function
* @returns Instance for chaining
*/
on(type: string, listener: Function): this;
/**
* Remove event listener
* @param type - Event type name (optional, removes all if not specified)
* @param listener - Event handler function (optional, removes all for type if not specified)
* @returns Instance for chaining
*/
off(type?: string, listener?: Function): this;
/**
* Add one-time event listener
* @param type - Event type name
* @param listener - Event handler function
* @returns Instance for chaining
*/
once(type: string, listener: Function): this;
/**
* Fire an event
* @param type - Event type name
* @param data - Event data object
* @returns Instance for chaining
*/
fire(type: string, data?: any): this;
/**
* Check if has listeners for event type
* @param type - Event type name
* @returns True if has listeners
*/
listens(type: string): boolean;
}Base event interfaces used throughout the library.
/**
* Base event object
*/
interface Event {
type: string;
target: any;
originalEvent?: any;
}
/**
* Error event with error information
*/
interface ErrorEvent extends Event {
error: Error;
}Mouse interaction events fired by the Map class.
/**
* Mouse event fired by map interactions
*/
interface MapMouseEvent extends Event {
type: 'mousedown' | 'mouseup' | 'click' | 'dblclick' | 'mousemove' | 'mouseover' | 'mouseout' | 'contextmenu';
target: Map;
originalEvent: MouseEvent;
point: Point;
lngLat: LngLat;
preventDefault(): void;
defaultPrevented: boolean;
}
/**
* Touch event fired by map interactions
*/
interface MapTouchEvent extends Event {
type: 'touchstart' | 'touchend' | 'touchmove' | 'touchcancel';
target: Map;
originalEvent: TouchEvent;
point: Point;
lngLat: LngLat;
points: Point[];
lngLats: LngLat[];
preventDefault(): void;
defaultPrevented: boolean;
}
/**
* Wheel event fired by map scroll interactions
*/
interface MapWheelEvent extends Event {
type: 'wheel';
target: Map;
originalEvent: WheelEvent;
preventDefault(): void;
defaultPrevented: boolean;
}Usage Examples:
import mapboxgl from 'mapbox-gl';
// Mouse event handling
map.on('click', (e: mapboxgl.MapMouseEvent) => {
console.log(`Clicked at: ${e.lngLat.lng}, ${e.lngLat.lat}`);
console.log(`Screen coordinates: ${e.point.x}, ${e.point.y}`);
});
map.on('mousemove', (e: mapboxgl.MapMouseEvent) => {
// Update cursor coordinates display
document.getElementById('coordinates').textContent =
`${e.lngLat.lng.toFixed(4)}, ${e.lngLat.lat.toFixed(4)}`;
});
// Touch event handling
map.on('touchstart', (e: mapboxgl.MapTouchEvent) => {
console.log(`Touch started with ${e.points.length} fingers`);
e.points.forEach((point, index) => {
console.log(`Touch ${index}: ${point.x}, ${point.y}`);
});
});
// Double-click to zoom
map.on('dblclick', (e: mapboxgl.MapMouseEvent) => {
e.preventDefault(); // Prevent default zoom
map.flyTo({
center: e.lngLat,
zoom: map.getZoom() + 1
});
});
// Context menu
map.on('contextmenu', (e: mapboxgl.MapMouseEvent) => {
e.preventDefault();
showContextMenu(e.point, e.lngLat);
});Events related to data loading, style changes, and rendering.
/**
* Data-related events fired during map operations
*/
interface MapDataEvent extends Event {
type: 'data' | 'dataloading' | 'sourcedata' | 'sourcedataloading' | 'styledata' | 'styledataloading';
target: Map;
dataType?: 'source' | 'style';
isSourceLoaded?: boolean;
sourceId?: string;
sourceDataType?: 'metadata' | 'content' | 'visibility' | 'idle';
tile?: any;
coord?: { x: number; y: number; z: number };
}
/**
* Box zoom event
*/
interface MapBoxZoomEvent extends Event {
type: 'boxzoomstart' | 'boxzoomend' | 'boxzoomcancel';
target: Map;
originalEvent?: MouseEvent;
boxZoomBounds?: LngLatBounds;
}Usage Examples:
// Data loading events
map.on('sourcedata', (e: mapboxgl.MapDataEvent) => {
if (e.sourceId && e.isSourceLoaded) {
console.log(`Source ${e.sourceId} finished loading`);
}
});
map.on('data', (e: mapboxgl.MapDataEvent) => {
if (e.dataType === 'source') {
console.log('Source data changed');
} else if (e.dataType === 'style') {
console.log('Style data changed');
}
});
// Style loading
map.on('styledata', (e: mapboxgl.MapDataEvent) => {
console.log('Style data loaded');
// Add custom layers after style loads
if (!map.getLayer('custom-layer')) {
map.addLayer({
id: 'custom-layer',
type: 'fill',
source: 'custom-source',
paint: { 'fill-color': '#088' }
});
}
});
// Box zoom events
map.on('boxzoomstart', (e: mapboxgl.MapBoxZoomEvent) => {
console.log('Box zoom started');
});
map.on('boxzoomend', (e: mapboxgl.MapBoxZoomEvent) => {
console.log('Box zoom ended:', e.boxZoomBounds);
});Events related to map view changes, loading states, and errors.
/**
* Map lifecycle and state events
*/
type MapEventType =
// Lifecycle
| 'load' | 'idle' | 'remove' | 'render'
// View changes
| 'movestart' | 'move' | 'moveend'
| 'dragstart' | 'drag' | 'dragend'
| 'zoomstart' | 'zoom' | 'zoomend'
| 'rotatestart' | 'rotate' | 'rotateend'
| 'pitchstart' | 'pitch' | 'pitchend'
// Resize
| 'resize'
// Errors
| 'error' | 'webglcontextlost' | 'webglcontextrestored';Usage Examples:
// Map lifecycle events
map.on('load', () => {
console.log('Map fully loaded');
// Safe to add sources and layers
initializeMapData();
});
map.on('idle', () => {
console.log('Map finished rendering and is idle');
// All tiles loaded, animations complete
});
// View change events
map.on('movestart', () => {
console.log('Map started moving');
showLoadingIndicator();
});
map.on('moveend', () => {
console.log('Map finished moving');
hideLoadingIndicator();
updateVisibleData();
});
map.on('zoomend', () => {
const zoom = map.getZoom();
console.log(`Zoom level: ${zoom.toFixed(2)}`);
// Show/hide layers based on zoom
if (zoom > 10) {
map.setLayoutProperty('detailed-layer', 'visibility', 'visible');
} else {
map.setLayoutProperty('detailed-layer', 'visibility', 'none');
}
});
// Error handling
map.on('error', (e: mapboxgl.ErrorEvent) => {
console.error('Map error:', e.error);
showErrorMessage(e.error.message);
});Events specific to layer interactions and feature queries.
/**
* Layer-specific mouse events
*/
interface MapLayerMouseEvent extends MapMouseEvent {
features?: MapboxGeoJSONFeature[];
}
/**
* Layer-specific touch events
*/
interface MapLayerTouchEvent extends MapTouchEvent {
features?: MapboxGeoJSONFeature[];
}Usage Examples:
// Layer-specific events
map.on('click', 'points-layer', (e: mapboxgl.MapLayerMouseEvent) => {
if (e.features && e.features.length > 0) {
const feature = e.features[0];
console.log('Clicked feature:', feature.properties);
// Show popup with feature info
new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`<h3>${feature.properties.name}</h3>`)
.addTo(map);
}
});
// Hover effects
map.on('mouseenter', 'interactive-layer', (e: mapboxgl.MapLayerMouseEvent) => {
map.getCanvasContainer().style.cursor = 'pointer';
if (e.features && e.features.length > 0) {
const feature = e.features[0];
map.setFeatureState(
{ source: 'data-source', id: feature.id },
{ hover: true }
);
}
});
map.on('mouseleave', 'interactive-layer', () => {
map.getCanvasContainer().style.cursor = '';
// Clear all hover states
map.removeFeatureState({ source: 'data-source' }, 'hover');
});You can fire custom events on Evented objects.
/**
* Fire custom events with data
*/
interface CustomEventData {
[key: string]: any;
}Usage Examples:
// Custom event handling
class CustomMapController extends mapboxgl.Evented {
private map: mapboxgl.Map;
constructor(map: mapboxgl.Map) {
super();
this.map = map;
}
performCustomAction(data: any) {
// Do custom work
console.log('Performing custom action with:', data);
// Fire custom event
this.fire('customaction', { data, timestamp: Date.now() });
}
}
// Usage
const controller = new CustomMapController(map);
controller.on('customaction', (e) => {
console.log('Custom action fired:', e.data, 'at', e.timestamp);
});
controller.performCustomAction({ type: 'analysis', value: 42 });// Proper event cleanup
class MapManager {
private map: mapboxgl.Map;
private handlers: { [key: string]: Function } = {};
constructor(container: string) {
this.map = new mapboxgl.Map({ container });
this.setupEventHandlers();
}
private setupEventHandlers() {
this.handlers.click = (e: mapboxgl.MapMouseEvent) => {
console.log('Map clicked:', e.lngLat);
};
this.handlers.move = () => {
console.log('Map moved');
};
// Add handlers
this.map.on('click', this.handlers.click);
this.map.on('move', this.handlers.move);
}
destroy() {
// Remove all handlers
Object.keys(this.handlers).forEach(eventType => {
this.map.off(eventType, this.handlers[eventType]);
});
this.map.remove();
}
}// Handle events for multiple layers
const layerIds = ['layer1', 'layer2', 'layer3'];
function handleLayerClick(e: mapboxgl.MapLayerMouseEvent) {
const layerId = e.target.id;
console.log(`Clicked on layer: ${layerId}`);
if (e.features && e.features.length > 0) {
const feature = e.features[0];
handleFeatureClick(feature, layerId);
}
}
// Add same handler to multiple layers
layerIds.forEach(layerId => {
map.on('click', layerId, handleLayerClick);
});// Event type definitions
type MapEventType =
// Mouse events
| 'mousedown' | 'mouseup' | 'click' | 'dblclick' | 'mousemove' | 'mouseover' | 'mouseout' | 'contextmenu'
// Touch events
| 'touchstart' | 'touchend' | 'touchmove' | 'touchcancel'
// Wheel events
| 'wheel'
// Map lifecycle
| 'load' | 'idle' | 'remove' | 'render'
// View changes
| 'movestart' | 'move' | 'moveend'
| 'dragstart' | 'drag' | 'dragend'
| 'zoomstart' | 'zoom' | 'zoomend'
| 'rotatestart' | 'rotate' | 'rotateend'
| 'pitchstart' | 'pitch' | 'pitchend'
// Data events
| 'data' | 'dataloading' | 'sourcedata' | 'sourcedataloading' | 'styledata' | 'styledataloading'
// Box zoom
| 'boxzoomstart' | 'boxzoomend' | 'boxzoomcancel'
// Resize and errors
| 'resize' | 'error' | 'webglcontextlost' | 'webglcontextrestored';
// Event object type mapping
type MapEventOf<T extends MapEventType> =
T extends 'click' | 'dblclick' | 'mousedown' | 'mouseup' | 'mousemove' | 'mouseover' | 'mouseout' | 'contextmenu'
? MapMouseEvent
: T extends 'touchstart' | 'touchend' | 'touchmove' | 'touchcancel'
? MapTouchEvent
: T extends 'wheel'
? MapWheelEvent
: T extends 'data' | 'dataloading' | 'sourcedata' | 'sourcedataloading' | 'styledata' | 'styledataloading'
? MapDataEvent
: T extends 'boxzoomstart' | 'boxzoomend' | 'boxzoomcancel'
? MapBoxZoomEvent
: T extends 'error'
? ErrorEvent
: Event;