Стилизация контролов

MapLibre GL JS использует DOM-контролы, которые поверх WebGL-карты накладываются как обычные HTML-элементы. Это делает стилизацию контролов полностью управляемой через CSS, без необходимости вмешиваться в рендеринг карты. Архитектура контролов построена вокруг единого контейнера .maplibregl-ctrl-top-right / top-left / bottom-right / bottom-left, внутри которого располагаются группы .maplibregl-ctrl-group и отдельные элементы управления.

Каждый встроенный контрол в MapLibre GL JS реализует интерфейс с методами onAdd(map) и onRemove(map). При добавлении на карту создаётся DOM-узел, который автоматически помещается в один из контейнеров:

  • .maplibregl-ctrl-top-left
  • .maplibregl-ctrl-top-right
  • .maplibregl-ctrl-bottom-left
  • .maplibregl-ctrl-bottom-right

Контролы группируются в контейнер .maplibregl-ctrl-group, который задаёт базовую визуальную рамку, тень и вертикальное/горизонтальное выравнивание элементов.

Ключевая особенность заключается в том, что визуальный стиль полностью отделён от логики: сам контрол не знает, как он выглядит, он лишь создаёт структуру DOM.

Базовые CSS-классы и их роль

Стилизация начинается с переопределения стандартных классов:

  • .maplibregl-ctrl — базовый контейнер любого контрола
  • .maplibregl-ctrl-group — группа кнопок
  • .maplibregl-ctrl button — интерактивные элементы
  • .maplibregl-ctrl-icon — иконки внутри кнопок

Типовая структура кнопки:

<div class="maplibregl-ctrl maplibregl-ctrl-group">
  <button class="maplibregl-ctrl-icon maplibregl-ctrl-zoom-in"></button>
  <button class="maplibregl-ctrl-icon maplibregl-ctrl-zoom-out"></button>
</div>

Именно через эти классы происходит основная стилизация: размеры, фон, hover-состояния, активные состояния и отключение.

Переопределение базовой темы

Стандартный стиль контролов ориентирован на универсальную светлую тему с полупрозрачным фоном и мягкой тенью. Для полной кастомизации обычно переопределяются следующие свойства:

  • background-color
  • box-shadow
  • border-radius
  • border
  • color
  • fill (для SVG-иконок)
  • opacity при hover/active

Пример изменения базового контейнера:

.maplibregl-ctrl-group {
  background-color: rgba(20, 20, 20, 0.85);
  border-radius: 12px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

Такой подход позволяет интегрировать карту в тёмные интерфейсы без визуального конфликта.

Стилизация кнопок управления

Кнопки внутри контролов имеют фиксированную модель поведения: квадратный hit-area, центрированная иконка, состояния hover и active.

Основные точки кастомизации:

  • размеры кнопок (width, height)
  • отступы (padding)
  • эффекты наведения (:hover)
  • активное состояние (:active)
  • отключённое состояние (:disabled)
.maplibregl-ctrl button {
  width: 36px;
  height: 36px;
  transition: background-color 0.15s ease;
}

.maplibregl-ctrl button:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.maplibregl-ctrl button:active {
  transform: scale(0.96);
}

Важно учитывать, что трансформации (transform) применяются к интерактивным элементам без влияния на layout карты.

Работа с иконками

Иконки контролов в MapLibre GL JS реализованы через SVG или CSS background-image.

Подходы к кастомизации:

Замена SVG через CSS

.maplibregl-ctrl-zoom-in {
  background-image: url('/icons/zoom-in.svg');
  background-size: 18px 18px;
}

Инлайновые SVG-манипуляции

.maplibregl-ctrl-icon svg {
  fill: #ffffff;
  stroke: none;
}

Использование sprite-sheet

В высоконагруженных интерфейсах применяется спрайт:

.maplibregl-ctrl-icon {
  background-image: url('/sprites/controls.png');
  background-repeat: no-repeat;
}

Такой подход уменьшает количество HTTP-запросов и ускоряет загрузку интерфейса.

Позиционирование контролов

Контролы располагаются через flex-контейнеры в углах карты. Каждый контейнер имеет собственную модель выравнивания:

  • top-left: вертикальная колонка
  • top-right: вертикальная колонка
  • bottom-left: вертикальная колонка
  • bottom-right: часто используется для attribution и кастомных панелей

Пример переопределения позиции:

map.addControl(new maplibregl.NavigationControl(), 'bottom-right');

Стилизация контейнеров позволяет изменить поведение группировки:

.maplibregl-ctrl-bottom-right {
  display: flex;
  flex-direction: row;
  gap: 8px;
}

Это превращает стандартную вертикальную колонку в горизонтальную панель инструментов.

Адаптация под тёмные и светлые темы

Контролы легко адаптируются под системные темы через prefers-color-scheme:

@media (prefers-color-scheme: dark) {
  .maplibregl-ctrl-group {
    background-color: rgba(30, 30, 30, 0.9);
    border-color: rgba(255, 255, 255, 0.1);
  }

  .maplibregl-ctrl button {
    color: #ffffff;
  }
}

В сложных интерфейсах используется токенизация:

:root {
  --ctrl-bg: #ffffff;
  --ctrl-border: #e5e5e5;
}

.dark-theme {
  --ctrl-bg: #1f1f1f;
  --ctrl-border: #333;
}

И затем:

.maplibregl-ctrl-group {
  background-color: var(--ctrl-bg);
  border: 1px solid var(--ctrl-border);
}

Кастомные контролы и их стилизация

Создание собственного контрола требует реализации DOM-структуры вручную. При этом стилизация полностью ложится на разработчика.

Базовая структура:

class CustomControl {
  onAdd(map) {
    this.container = document.createElement('div');
    this.container.className = 'maplibregl-ctrl maplibregl-ctrl-group custom-control';

    const button = document.createElement('button');
    button.textContent = 'A';

    this.container.appendChild(button);
    return this.container;
  }

  onRemove() {
    this.container.remove();
  }
}

Стилизация:

.custom-control {
  padding: 4px;
  border-radius: 10px;
}

.custom-control button {
  width: 40px;
  height: 40px;
  font-weight: bold;
}

Ключевой момент: кастомные контролы наследуют только позиционирование, всё остальное полностью контролируется CSS.

Управление состояниями контролов

Контролы часто требуют отображения состояния:

  • включено/выключено
  • активный режим
  • загрузка данных

Используются классы-модификаторы:

.custom-control.is-active {
  background-color: rgba(0, 150, 255, 0.2);
}

Или data-атрибуты:

.custom-control[data-state="loading"] {
  opacity: 0.5;
  pointer-events: none;
}

Такой подход хорошо сочетается с реактивными фреймворками.

Интеграция с UI-фреймворками

При использовании React/Vue/Svelte контролы часто создаются как мост между DOM MapLibre и компонентной системой.

Особенность заключается в том, что контрол живёт вне виртуального DOM. Поэтому стилизация должна быть изолированной:

  • scoped CSS
  • CSS Modules
  • BEM-нейминг

Пример BEM:

.map-ctrl {}
.map-ctrl__button {}
.map-ctrl--disabled {}

Это предотвращает конфликт со стилями приложения.

Проблемы масштабирования и плотности интерфейса

На малых экранах контролы требуют адаптации:

  • уменьшение размеров кнопок
  • сокращение групп
  • перенос контролов в одну панель
@media (max-width: 600px) {
  .maplibregl-ctrl button {
    width: 30px;
    height: 30px;
  }
}

Также применяется сворачивание групп:

.maplibregl-ctrl-group.compact {
  flex-direction: row;
}

Стилизация attribution-контрола

Отдельного внимания требует attribution:

.maplibregl-ctrl-attrib {
  font-size: 11px;
  opacity: 0.8;
}

Он часто размещается в углу и требует минимальной визуальной нагрузки, чтобы не конкурировать с основным интерфейсом карты.

Изоляция стилей и предотвращение конфликтов

Поскольку контролы являются частью DOM страницы, возможны конфликты с глобальными стилями приложения. Для предотвращения используются:

  • строгие селекторы .maplibregl-ctrl ...
  • префиксы
  • CSS reset только внутри контейнера карты
.map-container .maplibregl-ctrl button {
  all: unset;
}

Этот подход требует осторожности, но обеспечивает полную предсказуемость внешнего вида контролов.