Yandex Metrika
sanches.free 21 просмотр

Интерактивная стилизованная карта Google Maps: точки из PHP и проекты рядом с 1С‑Битрикс

Смысл на проде рядом с Битрикс

В каталогах, разделах «Где купить» или лендингах блок «карта + список магазинов» живёт одинаково: ядро отдаёт активные элементы с числовым id и координатами, браузер рисуем виджет. Идентификатор здесь уже есть в данных из инфоблока или пользовательской таблицы до отображения виджета; он нужен связать обводку маркера, клики в списке и ссылку на детальную страницу. Логику «нет стабильного id, пока не выполнен Add(), а после сохранения в ответе отдаём id и url карточки» лучше смотреть отдельно в материале про первая публикация элемента и JSON для карты.

Без хака с пустым meta name="point"

Древний приём — прятать каждую строку координат в фиктивный meta и затем считывать его из jQuery — формально кривый HTML и плохо для доступности и SEO. Спокойнее вынести данные один раз во встроенный JSON (например <script type="application/json" id="map-stores-json">…</script>) или в data-* на контейнере списка, а уже оттуда массив читает сценарий карты.

PHP: блок карты и список

Отрисовать контейнер с центром и перечень ссылок, по которым фронт переходит на ту же точку без перезагрузки.

$centerLat = 55.748238;
$centerLng = 37.63897;
echo '<div id="stores-map"'
    . ' data-center-lat="' . htmlspecialchars((string) $centerLat, ENT_QUOTES, 'UTF-8') . '"'
    . ' data-center-lng="' . htmlspecialchars((string) $centerLng, ENT_QUOTES, 'UTF-8') . '"'
    . ' style="width:100%;height:460px;border-radius:8px"
></div>';

foreach ($stores as $row) {
    $id = (int) ($row['id'] ?? $row['ID'] ?? 0);
    $name = htmlspecialchars((string) ($row['name'] ?? $row['NAME'] ?? ''), ENT_QUOTES, 'UTF-8');
    $addr = htmlspecialchars((string) ($row['address'] ?? $row['description'] ?? ''), ENT_QUOTES, 'UTF-8');
    $lat = isset($row['lat']) ? (float) $row['lat'] : (float) ($row['PROPERTY_MAP_LAT_VALUE'] ?? 0);
    $lng = isset($row['lng']) ? (float) $row['lng'] : (float) ($row['PROPERTY_MAP_LNG_VALUE'] ?? 0);

    printf(
        '<p><a class="js-map-point" href="#"'
            . ' data-id="%d" data-lat="%s" data-lng="%s" data-address="%s">%s</a></p>',
        $id,
        htmlspecialchars((string) $lat, ENT_QUOTES, 'UTF-8'),
        htmlspecialchars((string) $lng, ENT_QUOTES, 'UTF-8'),
        $addr,
        $name
    );
}

Для «живых» свойств высоких нагрузочных элементов см. уже опубликованные разборы геокодирования; здесь считаем, что долгота и широта уже сохранены.

Стилизованная подложка

Старый визард на googlecode.com закрыт. Актуальная отправная точка документации — официальный гид по стилям; JSON вы передаёте в mapId либо в устаревающей, но ещё встречающейся в полях связке через StyledMapType, если поддерживаете старый сценарий как в кодовой базе до рефакторинга.

Инициализация после загрузки API

Подключайте ключ через параметр скрипт‑лоадера (ограничьте referrer или IP вашего домена и отдельный ключ для локального Cron). После создания экземпляра карты пройдитесь по .js-map-point, сохраняя маркеры в объекте { [id]: marker } и расширяя LatLngBounds, когда точек несколько.

const mapDiv = document.getElementById('stores-map');
const center = {
  lat: Number(mapDiv.dataset.centerLat),
  lng: Number(mapDiv.dataset.centerLng),
};
const map = new google.maps.Map(mapDiv, { zoom: 10, center, mapId: 'YOUR_VECTOR_MAP_ID' });
const bounds = new google.maps.LatLngBounds();
const byId = {};
let count = 0;
document.querySelectorAll('.js-map-point').forEach((el) => {
    const pt = {
      id: Number(el.dataset.id),
      lat: Number(el.dataset.lat),
      lng: Number(el.dataset.lng),
      title: el.textContent,
      snippet: el.dataset.address || '',
    };
    const marker = new google.maps.marker.AdvancedMarkerElement({
      map,
      position: { lat: pt.lat, lng: pt.lng },
      title: pt.title,
    });
    byId[pt.id] = { marker };
    bounds.extend({ lat: pt.lat, lng: pt.lng });
    count++;
    el.addEventListener('click', (e) => {
      e.preventDefault();
      map.setCenter({ lat: pt.lat, lng: pt.lng });
      map.setZoom(14);
    });
});
if (count > 1) map.fitBounds(bounds);

Точную сигнатуру библиотеки уточните под ваш билд: в кодовых базах вокруг Битрикс до сих пор встречается классический google.maps.Marker; для новых проектов Google рекомендует advanced markers.

Кластеризация

На плотных кластерах маркеры объедините через пакет @googlemaps/markerclusterer; смысл тот же, что и десять лет назад, меняются только версии сборщика модулей (RequireJS сменился на статический или Vite-бандлер в шаблоне сайта).

Одновременно только одно InfoWindow

Чтобы не оставаться с «стеком» всплывающих окон, сохраните ссылку на последний открытый экземпляр и перед открытием нового закрывайте предыдущий — паттерн с переменной previousInfoWindow или единственным объектом окна те же самые десять лет всё так же здравы на фронте вокруг любого решения каталога.

let activeWindow = null;
function attachInfo(marker, html) {
  const iw = new google.maps.InfoWindow({ content: html });
  marker.addListener('click', () => {
    if (activeWindow) activeWindow.close();
    iw.open({ map, anchor: marker });
    activeWindow = iw;
  });
}

Маршруты и промежуточные POI

Задачи вроде «покажи заправки вдоль маршрута» решаются через Directions/Places совместно; стартовые ссылки есть в классической ветке SO по точкам вдоль пути — здесь ограничимся отправной точкой, без копирования длинной дискуссии.

В сторону эксплуатации

  • Ключ ограничен по домену (публичный виджет) и отделён от серверного для Geocoding, если два разных ограничения.
  • Каждое имя адреса и подпись в шаблоне проходят через экранирование; не смешиваем «сырой» вывод точек на карте с недоверенным текстом пользователя.
  • Выбор между лёгкой JSON-подложкой точек на странице и отдельным AJAX с /local/ajax/map.php — по нагрузке; для SEO списком иногда лучше серверная отрисовка, показанная выше.

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии