Zum Inhalt

Advertisement Carousel

Einsteiger SmartMaps GL Marker Popup Lazy Loading

Dieser Anwendungsfall zeigt ein Werbekarussell für ein Einzelhandelsgeschäft. Das Karussell durchläuft mehrere Folien mit saisonalen Rabattaktionen und Neuheiten, wobei die letzte Folie eine integrierte SmartMaps-GL-Karte enthält, die den Standort des Geschäfts zusammen mit den Kontaktdaten anzeigt. Die Karte wird erst dann geladen (Lazy Loading), wenn der Nutzer zu dieser Folie navigiert, um eine optimale Seitenleistung sicherzustellen.

Funktionen & APIs

  • SmartMaps GL Map -- Interaktive Karte, gerendert über die SmartMaps GL JS-Bibliothek mit dem Kartenstil essential
  • Custom Marker -- Ein gestalteter, kreisförmiger Marker, der den Standort des Geschäfts auf der Karte markiert
  • Popup -- Ein an den Marker angehängtes Pop-up für zusätzlichen Kontext
  • Navigation Control -- Integrierte Zoom- und Rotations-Controls, die der Karte hinzugefügt werden
  • Lazy Loading -- Die Karte wird erst initialisiert, wenn der Nutzer zur Karten-Folie navigiert, was die anfängliche Ladeleistung verbessert
  • Scroll-Zoom deaktiviert -- Das Zoomen per Mausrad ist auf der Karte deaktiviert, damit es das Scrollen der Seite nicht stört

Wie es funktioniert

Das Karussell verwendet CSS-translateX-Transformationen, um zwischen Inhaltsfeldern zu wechseln. Die Karte wird nicht beim Laden der Seite initialisiert -- stattdessen wird sie erst dann erstellt, wenn der Nutzer zur letzten Folie navigiert. Dies vermeidet unnötige API-Aufrufe und das Laden von Tiles, wenn die Karte möglicherweise nie angezeigt wird.

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>