Реализация интерактивных карт (Google Maps/Яндекс.Карты) на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация интерактивных карт (Google Maps/Яндекс.Карты) на сайте
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1227
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1164
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    859
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1074
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    829
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    839

Реализация интерактивных карт (Google Maps/Яндекс.Карты) на сайте

Выбор между Google Maps и Яндекс.Картами определяется аудиторией: для российского рынка Яндекс покрывает детали, недоступные в Google. Обе платформы требуют API-ключей, биллинга и внимания к производительности.

Google Maps: базовая интеграция

<!-- Загружаем API асинхронно -->
<script>
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
({key: "YOUR_API_KEY", v: "weekly"});
</script>
async function initMap(containerId: string) {
  const { Map, InfoWindow } = await google.maps.importLibrary('maps') as google.maps.MapsLibrary
  const { AdvancedMarkerElement } = await google.maps.importLibrary('marker') as google.maps.MarkerLibrary

  const map = new Map(document.getElementById(containerId)!, {
    center: { lat: 55.7558, lng: 37.6173 },  // Москва
    zoom: 14,
    mapId: 'YOUR_MAP_ID',   // для AdvancedMarker и Cloud Styling
    gestureHandling: 'cooperative',  // Ctrl+scroll для зума — не мешает скроллу страницы
    disableDefaultUI: false,
    zoomControl: true,
    mapTypeControl: false,
    streetViewControl: false,
    fullscreenControl: true,
    restriction: {
      latLngBounds: { north: 85, south: -85, west: -180, east: 180 },
      strictBounds: false,
    },
  })

  return { map, InfoWindow, AdvancedMarkerElement }
}

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

import { MarkerClusterer } from '@googlemaps/markerclusterer'

interface Location {
  lat: number
  lng: number
  title: string
  description: string
  category: string
}

async function addMarkersWithClustering(map: google.maps.Map, locations: Location[]) {
  const { AdvancedMarkerElement } = await google.maps.importLibrary('marker') as google.maps.MarkerLibrary
  const infoWindow = new google.maps.InfoWindow()

  const markers = locations.map(loc => {
    // Кастомный DOM-элемент вместо дефолтного маркера
    const pin = document.createElement('div')
    pin.className = `map-pin map-pin--${loc.category}`
    pin.innerHTML = `<span class="map-pin__label">${loc.title}</span>`

    const marker = new AdvancedMarkerElement({
      map,
      position: { lat: loc.lat, lng: loc.lng },
      content: pin,
      title: loc.title,
    })

    marker.addListener('click', () => {
      infoWindow.setContent(`
        <div class="map-popup">
          <h3>${loc.title}</h3>
          <p>${loc.description}</p>
          <a href="/locations/${loc.title}" target="_blank">Подробнее</a>
        </div>
      `)
      infoWindow.open(map, marker)
    })

    return marker
  })

  // Кластеризация близких маркеров
  new MarkerClusterer({
    map,
    markers,
    algorithm: new SuperClusterAlgorithm({ radius: 60 }),
    renderer: {
      render: ({ count, position }) => new google.maps.Marker({
        label: { text: String(count), color: '#fff', fontSize: '12px' },
        position,
        icon: { url: '/icons/cluster.svg', scaledSize: new google.maps.Size(40, 40) },
        zIndex: 1000 + count,
      }),
    },
  })
}

Яндекс.Карты 3.0 (современный API)

npm install @yandex/ymaps3-types
// Инициализация через module import (Яндекс.Карты 3.0)
async function initYandexMap(containerId: string) {
  await ymaps3.ready

  const { YMap, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer } = ymaps3

  const map = new YMap(
    document.getElementById(containerId)!,
    {
      location: {
        center: [37.6173, 55.7558],  // В Яндексе порядок: [lng, lat]
        zoom: 14,
      },
      behaviors: ['drag', 'scrollZoom', 'pinchZoom'],
    }
  )

  map.addChild(new YMapDefaultSchemeLayer({ theme: 'light' }))
  map.addChild(new YMapDefaultFeaturesLayer())

  return map
}

// Маркеры в Яндекс.Картах 3.0
async function addYandexMarkers(map: any, locations: Location[]) {
  const { YMapMarker, YMapClusterer, clusterByGrid } = await ymaps3.import('@yandex/[email protected]')

  const getMarkerElement = (loc: Location) => {
    const el = document.createElement('div')
    el.className = `ymap-pin ymap-pin--${loc.category}`
    el.textContent = loc.title
    return el
  }

  const markers = locations.map(loc => ({
    id: loc.title,
    geometry: { type: 'Point', coordinates: [loc.lng, loc.lat] },
    properties: loc,
  }))

  const clusterer = new YMapClusterer({
    method: clusterByGrid({ gridSize: 64 }),
    features: markers,
    marker: (feature) => new YMapMarker(
      { coordinates: feature.geometry.coordinates },
      getMarkerElement(feature.properties)
    ),
    cluster: (coordinates, features) => {
      const el = document.createElement('div')
      el.className = 'ymap-cluster'
      el.textContent = String(features.length)
      return new YMapMarker({ coordinates }, el)
    },
  })

  map.addChild(clusterer)
}

Производительность: lazy-инициализация

Подключение Google Maps SDK добавляет ~240KB JS. Загружаем только при взаимодействии:

import { useState, useRef } from 'react'

export function LazyMap({ center, zoom = 14 }: { center: { lat: number; lng: number }; zoom?: number }) {
  const [loaded, setLoaded] = useState(false)
  const containerRef = useRef<HTMLDivElement>(null)

  function loadMap() {
    if (loaded) return
    setLoaded(true)
  }

  return (
    <div className="map-wrapper" style={{ position: 'relative', height: 400 }}>
      {!loaded && (
        <div
          className="map-placeholder"
          onClick={loadMap}
          style={{
            position: 'absolute', inset: 0,
            background: `url(https://maps.googleapis.com/maps/api/staticmap?center=${center.lat},${center.lng}&zoom=${zoom}&size=800x400&key=${API_KEY}) center/cover`,
            cursor: 'pointer',
          }}
        >
          <button className="map-load-btn" aria-label="Загрузить интерактивную карту">
            Нажмите для загрузки карты
          </button>
        </div>
      )}
      {loaded && (
        <div ref={containerRef} style={{ height: '100%' }} id="map" />
      )}
    </div>
  )
}

Static Maps API — дешевле и быстрее для статичного превью.

Геокодирование: адрес → координаты

// Google Geocoding API
async function geocode(address: string): Promise<{ lat: number; lng: number }> {
  const { Geocoder } = await google.maps.importLibrary('geocoding') as google.maps.GeocodingLibrary
  const geocoder = new Geocoder()

  const result = await geocoder.geocode({ address, region: 'ru', language: 'ru' })
  if (!result.results.length) throw new Error(`Address not found: ${address}`)

  const { lat, lng } = result.results[0].geometry.location
  return { lat: lat(), lng: lng() }
}

// Серверное геокодирование через Laravel (не тратим клиентские запросы)
// composer require spatie/geocoder
class GeoController extends Controller
{
    public function geocode(Request $request): JsonResponse
    {
        $address = $request->validate(['address' => 'required|string'])['address'];

        $result = Geocoder::getCoordinatesForAddress($address);

        return response()->json([
            'lat' => $result['lat'],
            'lng' => $result['lng'],
            'accuracy' => $result['accuracy'],
        ]);
    }
}

Роутинг и маршруты

// Отображение маршрута между двумя точками
async function showRoute(map: google.maps.Map, origin: string, destination: string) {
  const { DirectionsService, DirectionsRenderer, TravelMode } = await google.maps.importLibrary('routes') as any

  const service = new DirectionsService()
  const renderer = new DirectionsRenderer({
    map,
    suppressMarkers: false,
    polylineOptions: { strokeColor: '#6366f1', strokeWeight: 4 },
  })

  const result = await service.route({
    origin,
    destination,
    travelMode: TravelMode.DRIVING,
    region: 'ru',
    language: 'ru',
    provideRouteAlternatives: true,
  })

  renderer.setDirections(result)

  // Информация о маршруте
  const route = result.routes[0].legs[0]
  return {
    distance: route.distance?.text,
    duration: route.duration?.text,
  }
}

CSP для Google Maps

// Laravel: разрешаем Google Maps в Content Security Policy
$csp = [
    "script-src 'self' https://maps.googleapis.com https://maps.gstatic.com",
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
    "img-src 'self' data: https://*.googleapis.com https://*.gstatic.com",
    "connect-src 'self' https://maps.googleapis.com",
    "frame-src https://www.google.com",
];

Сроки

Статичная карта с маркером и popup — 1 день. С кластеризацией, lazy-загрузкой, геокодированием и кастомными стилями — 2–3 дня. С маршрутами, поиском адресов, несколькими типами маркеров и мобильной оптимизацией — 1 неделя.