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
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
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:
- 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 twoNumber
arguments, representing the latitude and longitude, respectively, of the location on the map that the marker should be placed. - 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.
- 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
:
draw()
. This method will be used to draw the marker on the map.remove()
. This method will be used to remove the marker from the map.getPosition()
. This method will be used to keep track of where the location (latitude/longitude) that the marker has been placed.getDraggable()
. This method will dictate if our marker is draggable. Since it is not, this method will simply returnfalse
.
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.