В Leaflet архитектура расширения основана на прототипном наследовании
и системе классов, реализованной поверх JavaScript-конструкций. Основная
идея заключается в том, что почти любой базовый компонент библиотеки
может быть расширен через extend, сохраняя при этом
поведение родительского класса и добавляя собственную логику.
Исторически в Leaflet использовалась система L.Class,
обеспечивающая единый подход к созданию расширяемых сущностей. Каждый
класс создаётся через вызов:
const MyClass = L.Class.extend({
initialize: function (options) {
this._options = options;
}
});
Метод extend формирует новый конструктор, наследующий
прототип родительского класса. Внутри такого объекта можно
переопределять методы и добавлять новые свойства.
Ключевые элементы механизма:
Наиболее часто расширяемым классом выступает L.Layer.
Любой визуальный элемент карты (маркер, слой, canvas-оверлей)
наследуется от него.
const CustomLayer = L.Layer.extend({
initialize: function (options) {
L.Util.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
this._container = L.DomUtil.create('div', 'custom-layer');
map.getPanes().overlayPane.appendChild(this._container);
this._update();
},
onRemove: function () {
L.DomUtil.remove(this._container);
this._map = null;
},
_update: function () {
if (!this._map) return;
const latLng = this._map.getCenter();
this._container.style.transform =
`translate(${latLng.lat}px, ${latLng.lng}px)`;
}
});
Методы onAdd и onRemove являются
обязательными для корректной интеграции слоя с жизненным циклом
карты.
Функция L.Util.setOptions служит для объединения
пользовательских параметров с дефолтными значениями класса:
const MyLayer = L.Layer.extend({
options: {
padding: 10,
visible: true
},
initialize: function (options) {
L.Util.setOptions(this, options);
}
});
Такой подход обеспечивает единообразное управление конфигурацией и предотвращает дублирование логики.
При расширении классов часто требуется сохранить поведение родительского метода. Это реализуется через явный вызов прототипа:
const MyMarker = L.Marker.extend({
initialize: function (latlng, options) {
L.Marker.prototype.initialize.call(this, latlng, options);
this._customState = true;
}
});
Подобная конструкция обеспечивает контроль над цепочкой вызовов без нарушения внутренней логики Leaflet.
Контролы представляют элементы интерфейса карты. Их расширение
требует реализации методов onAdd и
onRemove.
const CustomControl = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function (map) {
const container = L.DomUtil.create('div', 'custom-control');
container.innerHTML = 'Control';
L.DomEvent.disableClickPropagation(container);
return container;
}
});
Особенность контролов заключается в том, что они не привязаны к географическим координатам и взаимодействуют исключительно с DOM.
Современная архитектура Leaflet использует L.Evented,
предоставляющий систему событий.
const MyObject = L.Evented.extend({
triggerUpdate: function () {
this.fire('update', { time: Date.now() });
}
});
Подписка на события реализуется стандартным образом:
obj.on('update', function (e) {
console.log(e.time);
});
Расширение базовых классов позволяет создавать сложные визуальные сущности, включая canvas-слои:
const CanvasLayer = L.Layer.extend({
onAdd: function (map) {
this._canvas = L.DomUtil.create('canvas', '');
this._ctx = this._canvas.getContext('2d');
map.getPanes().overlayPane.appendChild(this._canvas);
this._reset();
},
_reset: function () {
const size = this._map.getSize();
this._canvas.width = size.x;
this._canvas.height = size.y;
},
draw: function () {
const ctx = this._ctx;
ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
const center = this._map.latLngToContainerPoint(this._map.getCenter());
ctx.fillRect(center.x, center.y, 10, 10);
}
});
Здесь важна синхронизация географических координат с пиксельной системой отображения.
Класс тайлового слоя часто используется для подключения кастомных источников картографических данных:
const CustomTileLayer = L.TileLayer.extend({
getTileUrl: function (coords) {
return `https://tiles.example.com/${coords.z}/${coords.x}/${coords.y}.png`;
}
});
Переопределение getTileUrl позволяет интегрировать любые
тайловые сервисы без изменения ядра карты.
Leaflet поддерживает механизм миксинов для повторного использования функциональности:
const ZoomMixin = {
zoomIn: function () {
this._map.setZoom(this._map.getZoom() + 1);
}
};
const MyControl = L.Control.extend({
includes: ZoomMixin
});
Миксины особенно полезны для разделения логики между несколькими классами без жёсткого наследования.
При расширении базовых классов применяется ряд соглашений:
_options_container,
_canvasТакая структура снижает связанность компонентов и упрощает сопровождение.
GeoJSON-слои позволяют переопределять логику создания объектов:
const CustomGeoJSON = L.GeoJSON.extend({
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 8
});
},
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.name);
}
});
Эти методы обеспечивают гибкую трансформацию геоданных в визуальные элементы.
Каждый расширённый компонент должен корректно взаимодействовать с картой через стандартные точки входа:
initializeonAddonRemoveНесоблюдение этих контрактов приводит к утечкам памяти и некорректному отображению слоёв.
При создании кастомных элементов используется API
L.DomUtil:
const el = L.DomUtil.create('div', 'custom-class');
L.DomUtil.addClass(el, 'active');
L.DomUtil.removeClass(el, 'hidden');
Для предотвращения нежелательного взаимодействия с картой применяется:
L.DomEvent.disableClickPropagation(el);
Это критично для интерактивных контролов и панелей управления.
Сложные системы строятся через комбинацию нескольких расширений:
Такой подход формирует модульную архитектуру, где каждый компонент отвечает за строго определённую область ответственности.