title: Using custom popups instead of info windows on Google Maps with multiple markers
body: <p>Customizing info windows on Google Maps has limitations/risks as Google could change how they implement the CSS of the info windows in the future. The recommended approach if you want to significantly change the design is to use custom popups.</p>
  
  <p>However, Google's own documentation for using custom popups doesn't address how to do a few key things that I think most users would want to know.</p>
  
  <p>1) How do I use the custom popup with multiple markers?</p>
  
  <p>2) How do I toggle/hide/show the custom popup associated with a particular marker?</p>
  
  <p>Here is the documentation for creating custom popups I am working off of:
  <a href="https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup" rel="nofollow noreferrer">https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup</a></p>
  
  <p>Any help is greatly appreciated.</p>
  
  <p>EDIT - adding code sample and image:</p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html&gt;
    &lt;head&gt;
      &lt;meta name="viewport" content="initial-scale=1.0, user-scalable=no"&gt;
      &lt;meta charset="utf-8"&gt;
      &lt;title&gt;Custom Popups&lt;/title&gt;
      &lt;style&gt;
        /* Always set the map height explicitly to define the size of the div
         * element that contains the map. */
        #map {
          height: 100%;
        }
        /* Optional: Makes the sample page fill the window. */
        html, body {
          height: 100%;
          margin: 0;
          padding: 0;
        }
  
        /* The popup bubble styling. */
        .popup-bubble {
          /* Position the bubble centred-above its parent. */
          display: none;
          position: absolute;
          top: 0;
          left: 0;
          transform: translate(-50%, -100%);
          /* Style the bubble. */
          background-color: white;
          /* padding: 5px; */
          border-radius: 5px;
          font-family: sans-serif;
          overflow-y: auto;
          /* height: 250px; */
          /* width: 250px; */
          box-shadow: 0px 2px 10px 1px rgba(0,0,0,0.5);
        }
        /* The parent of the bubble. A zero-height div at the top of the tip. */
        .popup-bubble-anchor {
          /* Position the div a fixed distance above the tip. */
          position: absolute;
          width: 100%;
          bottom: /* TIP_HEIGHT= */ 8px;
          left: 0;
        }
        /* This element draws the tip. */
        .popup-bubble-anchor::after {
          content: "";
          position: absolute;
          top: 0;
          left: 0;
          /* Center the tip horizontally. */
          transform: translate(-50%, 0);
          /* The tip is a https://css-tricks.com/snippets/css/css-triangle/ */
          width: 0;
          height: 0;
          /* The tip is 8px high, and 12px wide. */
          border-left: 6px solid transparent;
          border-right: 6px solid transparent;
          border-top: /* TIP_HEIGHT= */ 8px solid white;
        }
        /* JavaScript will position this div at the bottom of the popup tip. */
        .popup-container {
          cursor: auto;
          height: 0;
          position: absolute;
          /* The max width of the info window. */
          width: 200px;
        }
        .info-window-img {
          height: 170px;
          width: 250px;
        }
        .info-window-text {
          padding: 5px;
        }
        .info-window h1 {
          font-size: 18px;
          text-align: left;
          margin: 0px;
        }
        .info-window a {
          color: #E85A71;
          decoration: none;
        }
      &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
      &lt;div id="map"&gt;&lt;/div&gt;
  &lt;script&gt;
    var stories = [
      {
        "name": "First place",
        "url": "www.example.com",
        "category": "first category",
        "lat": 53.3498,
        "lng": 6.2603
      },
      {
        "name": "Second place",
        "url": "www.example2.com",
        "category": "second category",
        "lat": 53.0,
        "lng": 6.2603
      }
    ];
    var map, popup, Popup;
    var popups = [];
  
    function addMarkerInfo() {
      var bounds = new google.maps.LatLngBounds();
      var markers = []
      var clusterStyles = [
      {
        textColor: 'white',
        url: `{{ url_for('static', filename='images/') }}` + 'clusterimage.png',
        height: 50,
        width: 50
      }
      ];
      var mcOptions = {
        styles: clusterStyles,
      }
        for (var i = 0; i &lt; stories.length; i++) {
            var story = stories[i]
            var url = story.url;
            var storyname = story.name;
            var storycategory = story.category;
            var contentString =`
              &lt;h1&gt;&lt;a href="${url}"&gt;${storyname}&lt;/a&gt;&lt;/h1&gt;
              &lt;p&gt;${storycategory}&lt;/p&gt;`;
            const marker = new google.maps.Marker({
                position: {lat: story.lat, lng: story.lng},
                map: map
            });
            markers.push(marker)
            bounds.extend(marker.position);
            map.fitBounds(bounds, 50);
            var opt = {
              maxZoom: 17,
              mapTypeControl: false,
              streetViewControl: false
             };
            map.setOptions(opt);
            // POPUP STARTS HERE
            Popup = createPopupClass();
            popup = new Popup(
                new google.maps.LatLng(story.lat, story.lng),
                contentString
                );
            popup.setMap(map);
            // popup.draw();
            popups[0] = popup;
            console.log(popup.containerDiv);
  
            marker.addListener('click', function () {
  
            });
            // POPUP ENDS HERE
        }
        var markerCluster = new MarkerClusterer(map, markers, mcOptions);
    }
  
    /** Initializes the map and the custom popup. */
    function initMap() {
      map = new google.maps.Map(document.getElementById('map'), {
      });
  
      addMarkerInfo();
    }
  
    /**
     * Returns the Popup class.
     *
     * Unfortunately, the Popup class can only be defined after
     * google.maps.OverlayView is defined, when the Maps API is loaded.
     * This function should be called by initMap.
     */
    function createPopupClass() {
      /**
       * A customized popup on the map.
       * @param {!google.maps.LatLng} position
       * @param {!Element} content The bubble div.
       * @constructor
       * @extends {google.maps.OverlayView}
       */
      function Popup(position, string) {
        this.position = position;
        var content = document.createElement('div');
        content.classList.add('popup-bubble');
        content.classList.add('info-window');
        content.innerHTML = string;
  
        // This zero-height div is positioned at the bottom of the bubble.
        var bubbleAnchor = document.createElement('div');
        bubbleAnchor.classList.add('popup-bubble-anchor');
        bubbleAnchor.appendChild(content);
  
        // This zero-height div is positioned at the bottom of the tip.
        this.containerDiv = document.createElement('div');
        this.containerDiv.classList.add('popup-container');
        this.containerDiv.appendChild(bubbleAnchor);
  
        // Optionally stop clicks, etc., from bubbling up to the map.
        google.maps.OverlayView.preventMapHitsAndGesturesFrom(this.containerDiv);
      }
      // ES5 magic to extend google.maps.OverlayView.
      Popup.prototype = Object.create(google.maps.OverlayView.prototype);
  
      /** Called when the popup is added to the map. */
      Popup.prototype.onAdd = function() {
        this.getPanes().floatPane.appendChild(this.containerDiv);
      };
  
      /** Called when the popup is removed from the map. */
      Popup.prototype.onRemove = function() {
        if (this.containerDiv.parentElement) {
          this.containerDiv.parentElement.removeChild(this.containerDiv);
        }
      };
  
      /** Called each frame when the popup needs to draw itself. */
      Popup.prototype.draw = function() {
        var divPosition = this.getProjection().fromLatLngToDivPixel(this.position);
  
        // Hide the popup when it is far out of view.
        var display =
            Math.abs(divPosition.x) &lt; 4000 &amp;&amp; Math.abs(divPosition.y) &lt; 4000 ?
            'block' :
            'none';
  
        if (display === 'block') {
          this.containerDiv.style.left = divPosition.x + 'px';
          this.containerDiv.style.top = divPosition.y + 'px';
        }
        if (this.containerDiv.style.display !== display) {
          this.containerDiv.style.display = display;
        }
      };
  
      return Popup;
    }
      &lt;/script&gt;
      &lt;script src="https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js"&gt;
      &lt;/script&gt;
      &lt;script async defer
      src="https://maps.googleapis.com/maps/api/js?key={{GOOGLE_KEY}}&amp;callback=initMap"&gt;
      &lt;/script&gt;
    &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/1cAB1.png" rel="nofollow noreferrer">map image with markers and popup</a></p>