В библиотеке MapLibre GL JS позиционный маркер чаще всего ассоциируется с отображением текущего местоположения пользователя на карте. В базовой реализации он может быть представлен через встроенный механизм геолокации или через полностью кастомный DOM-элемент, управляемый вручную.
Основная особенность позиционного маркера заключается в его динамической природе: координаты обновляются во времени, а визуальное представление должно отражать изменения положения, точности и, при необходимости, направления движения.
Стандартный способ отображения позиции основан на контроле:
import maplibregl from "maplibre-gl";
const map = new maplibregl.Map({
container: "map",
style: "https://demotiles.maplibre.org/style.json",
center: [0, 0],
zoom: 2
});
const geolocate = new maplibregl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true,
showAccuracyCircle: true
});
map.addControl(geolocate);
Встроенный контрол создаёт несколько визуальных элементов:
При этом стилизация через CSS ограничена, поскольку элементы встроены в внутреннюю структуру MapLibre.
Встроенный маркер удобен для быстрого использования, но имеет ряд ограничений:
Для сложных интерфейсов используется ручная реализация маркера.
Более гибкий подход основан на использовании
maplibregl.Marker с HTML-элементом.
const el = document.createElement("div");
el.className = "position-marker";
const marker = new maplibregl.Marker({
element: el,
anchor: "center"
})
.setLngLat([30.5, 50.45])
.addTo(map);
CSS-стилизация позволяет полностью управлять визуалом:
.position-marker {
width: 18px;
height: 18px;
border-radius: 50%;
background: #2e7cff;
border: 3px solid white;
box-shadow: 0 0 12px rgba(46, 124, 255, 0.6);
}
Такой подход открывает возможность:
Для динамического перемещения маркера используется метод
setLngLat:
function updatePosition(lng, lat) {
marker.setLngLat([lng, lat]);
}
Часто источник данных — браузерный API геолокации:
navigator.geolocation.watchPosition((pos) => {
const { longitude, latitude } = pos.coords;
updatePosition(longitude, latitude);
});
При высокой частоте обновлений важно учитывать производительность. Оптимизация достигается через:
Круг точности не является частью Marker API и реализуется через
GeoJSON source и слой circle.
map.addSource("accuracy", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "Point",
coordinates: [30.5, 50.45]
},
properties: {
radius: 50
}
}
});
Отрисовка слоя:
map.addLayer({
id: "accuracy-circle",
type: "circle",
source: "accuracy",
paint: {
"circle-radius": [
"interpolate",
["linear"],
["get", "radius"],
0, 0,
100, 1000
],
"circle-color": "rgba(46, 124, 255, 0.15)",
"circle-stroke-color": "rgba(46, 124, 255, 0.4)",
"circle-stroke-width": 1
}
});
Обновление данных:
function updateAccuracy(lng, lat, radius) {
const source = map.getSource("accuracy");
source.setData({
type: "Feature",
geometry: {
type: "Point",
coordinates: [lng, lat]
},
properties: {
radius
}
});
}
Один из распространённых UX-паттернов — пульсация точки текущего положения.
HTML:
const el = document.createElement("div");
el.className = "pulse-marker";
CSS:
.pulse-marker {
width: 14px;
height: 14px;
border-radius: 50%;
background: #2e7cff;
position: relative;
}
.pulse-marker::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 14px;
height: 14px;
border-radius: 50%;
transform: translate(-50%, -50%);
background: rgba(46, 124, 255, 0.4);
animation: pulse 1.8s infinite;
}
@keyframes pulse {
0% {
width: 14px;
height: 14px;
opacity: 0.8;
}
100% {
width: 60px;
height: 60px;
opacity: 0;
}
}
Такой маркер визуально обозначает активную точку без необходимости использования canvas-слоёв.
При наличии данных GPS можно визуализировать направление движения через поворот DOM-элемента:
function updateHeading(bearing) {
el.style.transform = `rotate(${bearing}deg)`;
}
Если используется вложенная структура:
<div class="marker">
<div class="arrow"></div>
</div>
CSS:
.arrow {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 12px solid #2e7cff;
transform-origin: center;
}
В сценариях навигации маркер часто остаётся в центре экрана:
map.setCenter([lng, lat]);
или с плавной анимацией:
map.easeTo({
center: [lng, lat],
duration: 500
});
В режиме следования (tracking) важно минимизировать конфликт между пользовательским управлением камерой и автоматическим обновлением позиции.
При большом количестве обновлений или сложных визуальных эффектов
DOM-маркер может стать узким местом. В таких случаях используется
circle или symbol layer.
Пример GeoJSON источника:
map.addSource("user-position", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "Point",
coordinates: [30.5, 50.45]
}
}
});
Слой:
map.addLayer({
id: "user-position-layer",
type: "circle",
source: "user-position",
paint: {
"circle-radius": 8,
"circle-color": "#2e7cff",
"circle-stroke-width": 2,
"circle-stroke-color": "#ffffff"
}
});
Обновление:
function setPosition(lng, lat) {
const source = map.getSource("user-position");
source.setData({
type: "Feature",
geometry: {
type: "Point",
coordinates: [lng, lat]
}
});
}
В реальных интерфейсах позиционный маркер часто состоит из трёх компонентов:
Синхронизация всех элементов:
function updateUserState({ lng, lat, accuracy, bearing }) {
updatePosition(lng, lat);
updateAccuracy(lng, lat, accuracy);
if (bearing !== undefined) {
updateHeading(bearing);
}
map.easeTo({
center: [lng, lat],
duration: 300
});
}
GPS-данные часто шумные, поэтому применяется сглаживание:
let last = null;
function smoothPosition(newPos) {
if (!last) {
last = newPos;
return newPos;
}
const alpha = 0.2;
const lng = last.lng + (newPos.lng - last.lng) * alpha;
const lat = last.lat + (newPos.lat - last.lat) * alpha;
last = { lng, lat };
return last;
}
Сглаживание уменьшает “дрожание” маркера и улучшает визуальную стабильность.
При изменении масштаба карты DOM-маркер остаётся неизменным в пикселях. Для корректного визуального восприятия учитывается:
anchor: "center" для симметричного
позиционирования;Пример адаптации:
map.on("zoom", () => {
const zoom = map.getZoom();
const scale = Math.min(2, Math.max(0.8, zoom / 10));
el.style.transform += ` scale(${scale})`;
});
При отсутствии обновлений позиция может:
Пример:
function markStale() {
el.style.opacity = "0.5";
el.style.filter = "grayscale(1)";
}
Сброс состояния:
function markFresh() {
el.style.opacity = "1";
el.style.filter = "none";
}