1. 程式人生 > >How to Create Custom HTML Markers on Google Maps

How to Create Custom HTML Markers on Google Maps

The default method of creating markers on a Google Map (when using the Google Map’s JavaScript API) is to use an image or SVG path, which limits development possibilities. This tutorial will cover using HTML and CSS for creating map markers on a Google Map. The technique involves extending the Google Map’s library class OverlayView

, which introduces its own challenges, so we’ll also cover how to asynchronously extend our new class in order to avoid build problems, race conditions, and duplicate dependencies.

How Maps and Markers are Made

In modern apps, most dependencies will be added via a package manager (like npm or yarn), used in code via import

statements, and bundled into performant code chunks at build time via a build tool (like webpack). Google Maps, on the other hand, is usually loaded somewhere in a webapp’s HTML file via a script tag, which then places the Google Maps JavaScript library on the global object (window.google) for consumption at runtime.

Each feature in the Google Maps JavaScript API has it’s own class/namespace under the parent namespace of google.maps. The simplest way to add a marker to the map is to use Marker class, which lives at google.maps.Marker. This interface for creating markers on a map is limited to accepting only two types of entities for the marker’s content: an image or an SVG path. This works well for many use cases, but also introduces limitations to what a developer can do with the markers placed on a map.

The Google Maps JavaScript API also has a more generic class for creating entities on the map: the OverlayView class, at google.maps.OverlayView. The docs call these Custom Overlays, and defines them as “objects on the map that are tied to latitude/longitude coordinates, so they move when you drag or zoom the map.”

That sure sounds a lot like a map marker defined using the Marker class! And as it turns out, google.maps.Marker is simply a predefined overlay type. Therefore, by utilizing the OverlayView class, a developer can create map markers using any HTML content fit for a given project. And since the content of a Custom Overlay is just HTML, both CSS and JavaScript can target a Custom Overlay via the element’s id and/or class attributes, allowing for more granular control over behavior, more flexible content, and CSS animations!

Creating the HTMLMapMarker Class

We’ll be creating an ES6 class called HTMLMapMarker which extends the OverlayView class. The constructor will take three arguments:

  1. A Latitude/Longitude object. This will be an instance of another Google Maps class, the LatLng class (google.maps.LatLng). This class’s constructor takes two Number arguments, representing the latitude and longitude, respectively, of the location on the map that the marker should be placed.
  2. The HTML that should be used as the content of the marker. This is HTML represented as a string. For more complicated apps built with webpack, this can be an HTML file that is imported via html-loader.
  3. The Google Map instance that the marker will be placed on.

Our class will also need to implement four methods to satisfy the requirements of an OverlayView:

  1. draw(). This method will be used to draw the marker on the map.
  2. remove(). This method will be used to remove the marker from the map.
  3. getPosition(). This method will be used to keep track of where the location (latitude/longitude) that the marker has been placed.
  4. getDraggable(). This method will dictate if our marker is draggable. Since it is not, this method will simply return false.

Let’s begin with the class definition and the constructor. We’ll be passing the arguments into the constructor via an args object with the three properties discussed above:

As you can see, the LatLng and the HTML passed into the constructor are just saved as instance properties of the same name ( this.latlng and this.html). The instance of map however can be used right away. In the constructor we call the setMap method inherited from extending OverlayView, passing in the map instance, effectively linking the new marker to the current map instance on the page.

Next, let’s move on to the draw method. This method will be doing most of the heavy lifting for the class.

The first step is to check if this.div is defined. This is the instance property that will be holding the <div> element that will ultimately end up placed on the map. If it is not, we create a new <div> via document.createElement(“div”) and store it in this.div, and set it’s position to absolute.

The HTML assigned to this.html from the constructor is then set to the innerHTML property of the <div>, defining the element’s HTML content.

On line 8 of the method, we use the google.maps.event class method addDomLister to add an event listener to our new div. The method takes this.div as the first argument, the event name (‘click’) as the second argument, and a callback to be executed when the event occurs as the third argument. In our case, the callback argument simply needs to pass along the click event to this, which becomes the created marker once we are using our class. More on this later.

Line 10 of the draw method uses the getPanes method inherited from OverlayView to get the panes for the current map instance. Panes are what Google Maps calls the different layers where entities can be placed for displaying on the map. For more information on the available panes, you can read the docs here. We use the appendChild method of the overlayImage pane to append our <div> element stored in this.div.

The last step in the draw method is positioning our newly placed <div> to the proper latitude/longitude. This can be done by grabbing the map projection with the inherited method getProjection, then calling fromLatLngToDivPixel method on the returned projection. This method converts the provided latitude/longitude to the corresponding x, y position on the screen. We then position the div’s left and top properties to the x and y values, respectively. This would also be the perfect place for adding a pixel based offset for granularly adjusting the positioning of the rendered element. In the demo at the bottom of this article, you’ll notice I’ve added an offset of 25px to the height and width, since the image is 50px squared, and the desired effect was centering the image over the provided latitude/longitude.

Our class’s draw method is complete, but a little refactoring could really improve readability. I chose to refactor the function’s logic into three helper methods named for what they do: createDiv, appendDivToOverlay, and positionDiv. Here’s the refactored method, along with the helper methods it uses:

We’re ready to move on to the remove method:

This method simply checks for the existence of an HTML element stored in this.div. If it exists, we can remove it from the DOM by calling removeChild from the div’s parentNode. Once removed from the DOM, we also set this.div to null so that the instance reference is also removed.

The remaining methods, getPosition and getDraggable are both very simple:

The getPosition method just returns the latitude/longitude that was saved to this.latlng in the constructor, and getDraggable just returns the boolean false, since our marker will not be draggable.

We’ve finished defining the class. The final class definition should look something like this:

Defining OverlayView at Runtime

Since we’re extending the OverlayView class that is provided by the Google Maps JavaScript library, we need to guarantee that google.maps.OverlayView is defined by the time we extend it.

If you’re building an app that does not use a build process, you may be able to define the HTMLMapMarker class as extending the OverlayView directly. If the globalgoogle.maps object is defined by the time you’re defining the class, you can extend the class directly:

class HTMLMapMarker extends google.maps.OverlayView {

However, if you’re working in a project that has any kind of build process, there’s a very good chance that google.maps.OverlayView will NOT be defined by the time you’re trying to use it to extend the HTMLMapMarker class. And while adding the OverlayView class directly to your project’s code somehow might resolve the problem, it will result in duplicate code, since OverlayView will always be loaded when the Google Maps library is loaded.

Thanks to the power of closures, we can wrap our class definition such that google.maps.OverlayView is guaranteed to be defined by the time it’s being used. No race conditions, no duplicate code.

Let’s name our closure function createHTMLMapMarker. This function takes an object with two properties: an OverlayView property with a default value of google.maps.OverlayView, and a rest operator (…args) to collect the arguments passed into the createHTMLMapMarker when it is called.

With this small change, we can guarantee that google.maps.OverlayView will be defined by the time we call createHTMLMapMarker, since we have full control over when createHTMLMapMarker is called.

Now that we have our class and our closure in place, let’s use them! The remainder of the tutorial will assume the following folder structure:

/index.html/src/index.js/src/html-map-marker.js <-- Our closure + class definition/src/style.css

For our tutorial purposes, the index.html needs simply load our CSS, load the Google Maps JavaScript library, and load the app’s entry point at src/index.js. The only element on the page is a singe <div> with an ID of map, which will be used to display the Google Map.

Our index.js will define a new Google Maps map instance centered on the latitude and longitude for Timbuktu. It will then define a marker using the imported createHTMLMapMarker closer function we defined previously, passing along the same latlng, the map instance, and the html to be rendered as the marker content. We’ll be using a party parrot in an image tag, with an ID attribute of parrot for styling purposes.

Remember when we defined the DOM event of ‘click’ in the draw method of our HTMLMapMarker class? As stated before, the click event is just passed along to the marker instance. This allows us to add an event listener directly to our newly created marker via the addListener method, as seen in lines 16–18 of index.js.

The styles in style.css just fill the page with the map <div>, and apply border, height, and width to the marker as a proof of concept. Since the HTML element we created the marker with has an ID of parrot, we can style it with the #parrot selector.

That’s it! With our new createHTMLMapMarker function, we’re now able to easily add any HTML to a google map instance. You can see the final result of tutorial in the codesandbox below.

Note that the CodeSandbox will also have an alert from Google saying “This page can’t load Google Maps correctly,” since no API key is being used. The app should still work as intended.