Ленивая загрузка карты

Ленивая загрузка Google Maps JavaScript API основана на принципе отложенного подключения тяжёлого внешнего скрипта и инициализации карты только в момент, когда она действительно становится необходимой. Такой подход снижает время первоначальной загрузки страницы, уменьшает объём блокирующих ресурсов и улучшает показатели производительности, включая Core Web Vitals.

Google Maps JavaScript API является одним из наиболее ресурсоёмких внешних SDK: он загружает десятки модулей, шрифты, изображения тайлов, сервисы геокодирования и рендеринга. Если подключать его синхронно при старте страницы, это приводит к увеличению First Contentful Paint и задержке интерактивности.


Базовая стратегия отложенной загрузки API

Ключевая идея заключается в том, чтобы не подключать скрипт API в <head> статически, а создавать его динамически:

function loadGoogleMapsApi(apiKey) {
  return new Promise((resolve, reject) => {
    if (window.google && window.google.maps) {
      resolve(window.google.maps);
      return;
    }

    const script = document.createElement('script');
    script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=initMap`;
    script.async = true;
    script.defer = true;

    window.initMap = () => resolve(window.google.maps);
    script.oner ror = reject;

    document.head.appendChild(script);
  });
}

Такой подход обеспечивает:

  • отсутствие блокировки парсинга HTML;
  • загрузку API только при необходимости;
  • контроль момента инициализации карты;
  • возможность повторного использования промиса.

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

После загрузки API карта создаётся стандартным образом, но строго внутри callback-логики:

function createMap() {
  const map = new google.maps.Map(document.getElementById("map"), {
    center: { lat: 49.8064, lng: 73.0855 },
    zoom: 12,
  });

  new google.maps.Marker({
    position: { lat: 49.8064, lng: 73.0855 },
    map,
  });
}

Связка с ленивой загрузкой:

loadGoogleMapsApi("API_KEY").then(() => {
  createMap();
});

Отложенная загрузка по видимости блока (IntersectionObserver)

Наиболее эффективная стратегия — загрузка карты только тогда, когда контейнер попадает в область видимости.

const mapContainer = document.getElementById("map");

const observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    observer.disconnect();

    loadGoogleMapsApi("API_KEY").then(() => {
      createMap();
    });
  }
});

observer.observe(mapContainer);

Такой механизм предотвращает загрузку API, если пользователь никогда не прокручивает страницу до карты.


Заглушка (placeholder) вместо карты

До момента загрузки API контейнер карты обычно заменяется статическим содержимым:

  • изображение карты;
  • серый блок с индикатором загрузки;
  • SVG-превью района;
  • минимальный интерактивный placeholder.
<div id="map">
  <div class="map-placeholder">
    Загрузка карты...
  </div>
</div>

После инициализации placeholder удаляется:

function createMap() {
  const container = document.getElementById("map");
  container.innerHTML = "";

  const map = new google.maps.Map(container, {
    center: { lat: 49.8064, lng: 73.0855 },
    zoom: 12,
  });

  return map;
}

Контроль повторной загрузки и кеширование

Повторное подключение API недопустимо. Поэтому используется глобальный кеш промиса:

let googleMapsPromise = null;

function getGoogleMaps(apiKey) {
  if (googleMapsPromise) return googleMapsPromise;

  googleMapsPromise = new Promise((resolve, reject) => {
    const script = document.createElement("script");

    script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=__gmapsInit`;
    script.async = true;
    script.defer = true;

    window.__gmapsInit = () => resolve(window.google.maps);
    script.oner ror = reject;

    document.head.appendChild(script);
  });

  return googleMapsPromise;
}

Это исключает:

  • повторное создание <script>;
  • гонки инициализации;
  • дублирование глобального объекта google.maps.

Ленивая загрузка в SPA (Single Page Applications)

В SPA архитектуре карта часто монтируется динамически при переходе на маршрут. В этом случае важно:

  1. загружать API только при входе на страницу карты;
  2. уничтожать экземпляр карты при выходе;
  3. не держать DOM-узел активным без необходимости.
let mapInstance = null;

function mountMap() {
  getGoogleMaps("API_KEY").then(() => {
    mapInstance = new google.maps.Map(document.getElementById("map"), {
      center: { lat: 49.8064, lng: 73.0855 },
      zoom: 12,
    });
  });
}

function unmountMap() {
  if (!mapInstance) return;

  mapInstance = null;
  document.getElementById("map").innerHTML = "";
}

Разделение загрузки API и библиотек

Google Maps API поддерживает подзагрузку библиотек через параметр libraries. При ленивой загрузке важно избегать избыточного набора:

script.src =
  "https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places,geometry";

Оптимальная стратегия:

  • загружать только базовый API при первом рендере;
  • подключать places, geometry, drawing только при необходимости;
  • при сложных интерфейсах — разделять по функциональным зонам.

Асинхронная инициализация и предотвращение race conditions

Проблема возникает при одновременном вызове инициализации из разных компонентов.

Решение — единый менеджер загрузки:

class GoogleMapsLoader {
  static promise = null;

  static load(apiKey) {
    if (this.promise) return this.promise;

    this.promise = new Promise((resolve, reject) => {
      const script = document.createElement("script");

      script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=__initGMaps`;
      script.async = true;
      script.defer = true;

      window.__initGMaps = () => resolve(window.google.maps);
      script.oner ror = reject;

      document.head.appendChild(script);
    });

    return this.promise;
  }
}

Оптимизация поведения при медленном интернете

При нестабильном соединении ленивую загрузку дополняют:

  • тайм-аутом ожидания;
  • fallback на статичное изображение;
  • повторной попыткой загрузки;
  • деградацией функциональности (без интерактива).
function loadWithTimeout(ms) {
  return Promise.race([
    getGoogleMaps("API_KEY"),
    new Promise((_, reject) =>
      setTimeout(() => reject("timeout"), ms)
    ),
  ]);
}

Многоразовые карты на одной странице

При наличии нескольких контейнеров карта не должна грузиться повторно. Вместо этого используется общий API и отдельные экземпляры:

loadGoogleMapsApi("API_KEY").then(() => {
  document.querySelectorAll(".map").forEach((el) => {
    new google.maps.Map(el, {
      center: { lat: 49.8, lng: 73.1 },
      zoom: 10,
    });
  });
});

Связка ленивой загрузки с пользовательскими событиями

Помимо прокрутки применяются дополнительные триггеры:

  • клик по кнопке «Показать на карте»;
  • открытие модального окна;
  • hover на карточке объекта;
  • раскрытие аккордеона.
button.addEventListener("click", () => {
  loadGoogleMapsApi("API_KEY").then(createMap);
});

Типичные ошибки реализации

Частые проблемы при ленивой загрузке:

  • повторное добавление <script> без кеширования;
  • вызов new google.maps.Map до загрузки API;
  • отсутствие очистки window.initMap;
  • утечки памяти при SPA-переходах;
  • отсутствие проверки window.google.maps.

Корректная архитектура всегда строится вокруг единственного источника загрузки и строгого контроля состояния инициализации.