Skip to content

Advertisement Carousel

Beginner SmartMaps GL Marker Popup Lazy Loading

This use case demonstrates a promotional advertisement carousel for a retail store. The carousel cycles through multiple slides showcasing seasonal sales and new arrivals, with the final slide featuring an integrated SmartMaps GL map that displays the store location along with contact details. The map is lazy-loaded only when the user navigates to that slide, ensuring optimal page performance.

Features & APIs

  • SmartMaps GL Map -- Interactive map rendered via the SmartMaps GL JS library using the essential map style
  • Custom Marker -- A styled circular marker pinpointing the store location on the map
  • Popup -- A popup attached to the marker for additional context
  • Navigation Control -- Built-in zoom and rotation controls added to the map
  • Lazy Loading -- The map is initialized only when the user navigates to the map slide, improving initial load performance
  • Scroll Zoom Disabled -- Scroll-based zoom is disabled on the map to prevent interference with page scrolling

How it works

The carousel uses CSS translateX transforms to slide between content panels. The map is not initialized on page load -- instead, it is created lazily when the user navigates to the last slide. This avoids unnecessary API calls and tile loading when the map may never be viewed.

Code

// =============================================================
// ADVERTISEMENT CAROUSEL WITH MAP
// A promotional ad widget with multiple slides. The final slide
// contains a lazy-loaded SmartMaps map showing the store location.
//
// APIs used:
//   - SmartMaps GL JS (Map, Marker, Popup, NavigationControl)
// =============================================================

// --- CAROUSEL LOGIC ---
const slider = document.getElementById("slider");
const slides = document.querySelectorAll(".carousel-slide");
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");

let currentIndex = 0;
const totalSlides = slides.length;
let mapInitialized = false;
let map;

function goToSlide(index) {
  // Clamp index to be within bounds
  currentIndex = Math.max(0, Math.min(index, totalSlides - 1));

  slider.style.transform = `translateX(-${currentIndex * 100}%)`;

  // Lazy-load the map only when its slide is active
  if (currentIndex === totalSlides - 1 && !mapInitialized) {
    initializeMap();
  }
}

nextBtn.addEventListener("click", () => {
  goToSlide(currentIndex + 1);
});

prevBtn.addEventListener("click", () => {
  goToSlide(currentIndex - 1);
});

// --- MAP LOGIC ---
function initializeMap() {
  // Prevent re-initialization
  mapInitialized = true;

  const storeLocation = {
    lng: 9.1853, // Longitude for Markwiesenstr. 38, Reutlingen
    lat: 48.5042, // Latitude for Markwiesenstr. 38, Reutlingen
  };

  const smartMapsApiKey =
    "[INSERT API-KEY]";

  map = new smartmapsgl.Map({
    apiKey: smartMapsApiKey,
    container: "map-container",
    style: "essential",
    center: [storeLocation.lng, storeLocation.lat],
    zoom: 14,
    scrollZoom: false, // Disable scroll zoom to not interfere with page scrolling
  });

  map.addControl(new smartmapsgl.NavigationControl());

  // Create a custom marker element
  const markerEl = document.createElement("div");
  markerEl.className = "store-marker";

  // Create popup for the store location
  const popup = new smartmapsgl.Popup({ offset: 25, closeButton: false })
    .setText("SMO Reutlingen");

  // Add marker and popup to the map
  new smartmapsgl.Marker(markerEl)
    .setLngLat([storeLocation.lng, storeLocation.lat])
    .setPopup(popup)
    .addTo(map)
    .togglePopup(); // Open it by default
}

// Initialize with the first slide
goToSlide(0);
<body>
  <div class="ad-container">
    <!-- Header -->
    <header class="p-3 border-b flex justify-between items-center">
      <span class="text-xs font-semibold text-gray-500">ANZEIGE</span>
      <div class="flex items-center space-x-6">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
        </svg>
        <div class="font-black text-2xl tracking-tighter">SMO</div>
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="currentColor" viewBox="0 0 20 20">
          <path fill-rule="evenodd"
            d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z"
            clip-rule="evenodd" />
        </svg>
      </div>
    </header>

    <!-- Navigation Tabs -->
    <nav class="flex justify-around text-sm font-semibold text-gray-600 border-b">
      <a href="#" class="py-3 px-2 border-b-2 border-teal-500 text-teal-500">KONTAKT</a>
      <a href="#" class="py-3 px-2 border-b-2 border-transparent hover:border-gray-300">STARTSEITE</a>
      <a href="#" class="py-3 px-2 border-b-2 border-transparent hover:border-gray-300">SUMMER SALE</a>
    </nav>

    <!-- Carousel -->
    <main class="relative overflow-hidden">
      <div id="slider" class="carousel-slider">
        <!-- Slide 1: Spring Sale Ad -->
        <div class="carousel-slide p-6 text-center">
          <img src="/assets/examples/img/ph-spring-sale.svg" alt="Spring Sale"
            class="w-full h-auto rounded-lg mb-4" />
          <h2 class="text-2xl font-bold text-gray-800">
            Spring Sale is Here!
          </h2>
          <p class="text-gray-600 mt-2">
            Get up to 50% off on selected items. Don't miss out on our best
            deals of the season.
          </p>
        </div>

        <!-- Slide 2: New Arrivals Ad -->
        <div class="carousel-slide p-6 text-center">
          <img src="/assets/examples/img/ph-new-arrivals.svg" alt="New Arrivals"
            class="w-full h-auto rounded-lg mb-4" />
          <h2 class="text-2xl font-bold text-gray-800">
            Check Out New Arrivals
          </h2>
          <p class="text-gray-600 mt-2">
            Fresh styles have just landed. Be the first to wear our latest
            collection.
          </p>
        </div>

        <!-- Slide 3: Find Us (Map) -->
        <div class="carousel-slide">
          <div id="map-container"></div>
          <div class="p-4">
            <p class="text-sm font-semibold text-gray-500">Adresse</p>
            <p class="text-gray-800 mt-1">
              Reutlingen<br />Markwiesenstr. 38<br />72770 Reutlingen
            </p>
            <p class="text-sm font-semibold text-gray-500 mt-4">Telefon:</p>
            <p class="text-gray-800 mt-1">071219190</p>
            <p class="text-sm font-semibold text-gray-500 mt-4">Email:</p>
            <p class="text-gray-800 mt-1">info@bmcreutlingen.de</p>
          </div>
        </div>
      </div>

      <!-- Carousel Controls -->
      <button id="prevBtn"
        class="absolute top-1/2 left-2 -translate-y-1/2 bg-white/70 rounded-full p-2 shadow-md hover:bg-white">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
        </svg>
      </button>
      <button id="nextBtn"
        class="absolute top-1/2 right-2 -translate-y-1/2 bg-white/70 rounded-full p-2 shadow-md hover:bg-white">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
        </svg>
      </button>
    </main>

    <!-- Footer -->
    <footer class="p-3 bg-gray-800 text-white font-bold text-center flex justify-between items-center">
      <span>Aktionsbedingungen</span>
      <span>Impressum</span>
      <span>©</span>
    </footer>
  </div>
</body>
@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(../fonts/MaterialIcons-Regular.woff2) format('woff2');
}

body {
  font-family: "Inter", sans-serif;
  background-color: #e5e7eb;
  /* bg-gray-200 */
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 1rem;
}

/* The main container for the ad widget */
.ad-container {
  width: 100%;
  max-width: 380px;
  /* Constrain width on larger screens */
  background-color: white;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1),
    0 8px 10px -6px rgba(0, 0, 0, 0.1);
  border: 1px solid #d1d5db;
  /* border-gray-300 */
}

/* Carousel styles */
.carousel-slider {
  display: flex;
  transition: transform 0.5s ease-in-out;
}

.carousel-slide {
  width: 100%;
  flex-shrink: 0;
}

#map-container {
  height: 250px;
  /* Fixed height for the map */
  width: 100%;
}

#map {
  height: 100%;
  width: 100%;
}

/* Custom marker for the store location */
.store-marker {
  width: 30px;
  height: 30px;
  background-color: #1f2937;
  /* gray-800 */
  border: 3px solid white;
  border-radius: 50%;
  box-shadow: 0 0 0 3px #1f2937;
  cursor: pointer;
}

/* Custom popup for the route planner */
.maplibregl-popup-content {
  background-color: #fef08a;
  /* yellow-200 */
  color: #1f2937;
  /* gray-800 */
  border-radius: 9999px;
  /* rounded-full */
  padding: 6px 14px;
  font-weight: 600;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.maplibregl-popup-tip {
  display: none;
}

/* Hide scrollbars */
.no-scrollbar::-webkit-scrollbar {
  display: none;
}

.no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Advertisement</title>

  <!-- Tailwind CSS for styling -->
  <link rel="stylesheet" href="css/tailwind.min.css" />

  <!-- MapLibre GL JS -->
  <script src="https://cdn.smartmaps.cloud/packages/smartmaps/smartmaps-gl/v2/umd/smartmaps-gl.min.js"></script>

  <style>
    @font-face {
      font-family: 'Inter';
      src: url('../fonts/Inter-Regular.woff2') format('woff2');
      font-weight: 400;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: 'Inter';
      src: url('../fonts/Inter-Medium.woff2') format('woff2');
      font-weight: 500;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: 'Inter';
      src: url('../fonts/Inter-Bold.woff2') format('woff2');
      font-weight: 700;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: 'Material Icons';
      font-style: normal;
      font-weight: 400;
      src: url(../fonts/MaterialIcons-Regular.woff2) format('woff2');
    }

    body {
      font-family: "Inter", sans-serif;
      background-color: #e5e7eb;
      /* bg-gray-200 */
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      padding: 1rem;
    }

    /* The main container for the ad widget */
    .ad-container {
      width: 100%;
      max-width: 380px;
      /* Constrain width on larger screens */
      background-color: white;
      box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1),
        0 8px 10px -6px rgba(0, 0, 0, 0.1);
      border: 1px solid #d1d5db;
      /* border-gray-300 */
    }

    /* Carousel styles */
    .carousel-slider {
      display: flex;
      transition: transform 0.5s ease-in-out;
    }

    .carousel-slide {
      width: 100%;
      flex-shrink: 0;
    }

    #map-container {
      height: 250px;
      /* Fixed height for the map */
      width: 100%;
    }

    #map {
      height: 100%;
      width: 100%;
    }

    /* Custom marker for the store location */
    .store-marker {
      width: 30px;
      height: 30px;
      background-color: #1f2937;
      /* gray-800 */
      border: 3px solid white;
      border-radius: 50%;
      box-shadow: 0 0 0 3px #1f2937;
      cursor: pointer;
    }

    /* Custom popup for the route planner */
    .maplibregl-popup-content {
      background-color: #fef08a;
      /* yellow-200 */
      color: #1f2937;
      /* gray-800 */
      border-radius: 9999px;
      /* rounded-full */
      padding: 6px 14px;
      font-weight: 600;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }

    .maplibregl-popup-tip {
      display: none;
    }

    /* Hide scrollbars */
    .no-scrollbar::-webkit-scrollbar {
      display: none;
    }

    .no-scrollbar {
      -ms-overflow-style: none;
      scrollbar-width: none;
    }
  </style>
</head>

<body>
  <div class="ad-container">
    <!-- Header -->
    <header class="p-3 border-b flex justify-between items-center">
      <span class="text-xs font-semibold text-gray-500">ANZEIGE</span>
      <div class="flex items-center space-x-6">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
            d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
        </svg>
        <div class="font-black text-2xl tracking-tighter">SMO</div>
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="currentColor" viewBox="0 0 20 20">
          <path fill-rule="evenodd"
            d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z"
            clip-rule="evenodd" />
        </svg>
      </div>
    </header>

    <!-- Navigation Tabs -->
    <nav class="flex justify-around text-sm font-semibold text-gray-600 border-b">
      <a href="#" class="py-3 px-2 border-b-2 border-teal-500 text-teal-500">KONTAKT</a>
      <a href="#" class="py-3 px-2 border-b-2 border-transparent hover:border-gray-300">STARTSEITE</a>
      <a href="#" class="py-3 px-2 border-b-2 border-transparent hover:border-gray-300">SUMMER SALE</a>
    </nav>

    <!-- Carousel -->
    <main class="relative overflow-hidden">
      <div id="slider" class="carousel-slider">
        <!-- Slide 1: Spring Sale Ad -->
        <div class="carousel-slide p-6 text-center">
          <img src="/assets/examples/img/ph-spring-sale.svg" alt="Spring Sale"
            class="w-full h-auto rounded-lg mb-4" />
          <h2 class="text-2xl font-bold text-gray-800">
            Spring Sale is Here!
          </h2>
          <p class="text-gray-600 mt-2">
            Get up to 50% off on selected items. Don't miss out on our best
            deals of the season.
          </p>
        </div>

        <!-- Slide 2: New Arrivals Ad -->
        <div class="carousel-slide p-6 text-center">
          <img src="/assets/examples/img/ph-new-arrivals.svg" alt="New Arrivals"
            class="w-full h-auto rounded-lg mb-4" />
          <h2 class="text-2xl font-bold text-gray-800">
            Check Out New Arrivals
          </h2>
          <p class="text-gray-600 mt-2">
            Fresh styles have just landed. Be the first to wear our latest
            collection.
          </p>
        </div>

        <!-- Slide 3: Find Us (Map) -->
        <div class="carousel-slide">
          <div id="map-container"></div>
          <div class="p-4">
            <p class="text-sm font-semibold text-gray-500">Adresse</p>
            <p class="text-gray-800 mt-1">
              Reutlingen<br />Markwiesenstr. 38<br />72770 Reutlingen
            </p>
            <p class="text-sm font-semibold text-gray-500 mt-4">Telefon:</p>
            <p class="text-gray-800 mt-1">071219190</p>
            <p class="text-sm font-semibold text-gray-500 mt-4">Email:</p>
            <p class="text-gray-800 mt-1">info@bmcreutlingen.de</p>
          </div>
        </div>
      </div>

      <!-- Carousel Controls -->
      <button id="prevBtn"
        class="absolute top-1/2 left-2 -translate-y-1/2 bg-white/70 rounded-full p-2 shadow-md hover:bg-white">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
        </svg>
      </button>
      <button id="nextBtn"
        class="absolute top-1/2 right-2 -translate-y-1/2 bg-white/70 rounded-full p-2 shadow-md hover:bg-white">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700" fill="none" viewBox="0 0 24 24"
          stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
        </svg>
      </button>
    </main>

    <!-- Footer -->
    <footer class="p-3 bg-gray-800 text-white font-bold text-center flex justify-between items-center">
      <span>Aktionsbedingungen</span>
      <span>Impressum</span>
      <span>©</span>
    </footer>
  </div>

  <script>
    // =============================================================
    // ADVERTISEMENT CAROUSEL WITH MAP
    // A promotional ad widget with multiple slides. The final slide
    // contains a lazy-loaded SmartMaps map showing the store location.
    //
    // APIs used:
    //   - SmartMaps GL JS (Map, Marker, Popup, NavigationControl)
    // =============================================================

    // --- CAROUSEL LOGIC ---
    const slider = document.getElementById("slider");
    const slides = document.querySelectorAll(".carousel-slide");
    const prevBtn = document.getElementById("prevBtn");
    const nextBtn = document.getElementById("nextBtn");

    let currentIndex = 0;
    const totalSlides = slides.length;
    let mapInitialized = false;
    let map;

    function goToSlide(index) {
      // Clamp index to be within bounds
      currentIndex = Math.max(0, Math.min(index, totalSlides - 1));

      slider.style.transform = `translateX(-${currentIndex * 100}%)`;

      // Lazy-load the map only when its slide is active
      if (currentIndex === totalSlides - 1 && !mapInitialized) {
        initializeMap();
      }
    }

    nextBtn.addEventListener("click", () => {
      goToSlide(currentIndex + 1);
    });

    prevBtn.addEventListener("click", () => {
      goToSlide(currentIndex - 1);
    });

    // --- MAP LOGIC ---
    function initializeMap() {
      // Prevent re-initialization
      mapInitialized = true;

      const storeLocation = {
        lng: 9.1853, // Longitude for Markwiesenstr. 38, Reutlingen
        lat: 48.5042, // Latitude for Markwiesenstr. 38, Reutlingen
      };

      const smartMapsApiKey =
        "[INSERT API-KEY]";

      map = new smartmapsgl.Map({
        apiKey: smartMapsApiKey,
        container: "map-container",
        style: "essential",
        center: [storeLocation.lng, storeLocation.lat],
        zoom: 14,
        scrollZoom: false, // Disable scroll zoom to not interfere with page scrolling
      });

      map.addControl(new smartmapsgl.NavigationControl());

      // Create a custom marker element
      const markerEl = document.createElement("div");
      markerEl.className = "store-marker";

      // Create popup for the store location
      const popup = new smartmapsgl.Popup({ offset: 25, closeButton: false })
        .setText("SMO Reutlingen");

      // Add marker and popup to the map
      new smartmapsgl.Marker(markerEl)
        .setLngLat([storeLocation.lng, storeLocation.lat])
        .setPopup(popup)
        .addTo(map)
        .togglePopup(); // Open it by default
    }

    // Initialize with the first slide
    goToSlide(0);
  </script>
</body>

</html>