{"id":5055,"date":"2025-01-29T14:16:33","date_gmt":"2025-01-29T19:16:33","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=5055"},"modified":"2025-01-29T14:16:34","modified_gmt":"2025-01-29T19:16:34","slug":"mapping-with-leaflet","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/mapping-with-leaflet\/","title":{"rendered":"Mapping with Leaflet"},"content":{"rendered":"\n<p>It <em>seems<\/em> relatively simple: you have some data that involves location and you want to display those locations on a map. <\/p>\n\n\n\n<p><em>But how do you choose which service to use? <\/em><\/p>\n\n\n\n<p>Most folks default to Google Maps, which is a very powerful platform, but also comes with probably way more than you need, and has a cost involved at a certain level. What if there were a simpler, smaller, and dare I say, <em>free and open source<\/em> alternative?&nbsp;<a href=\"https:\/\/leafletjs.com\/\">Leaflet.js<\/a>&nbsp;is exactly that. <\/p>\n\n\n\n<p>Leaflet is incredibly simple and flexible. It lets you quickly add interactive maps to your web pages with little overhead.<\/p>\n\n\n\n<p>Before we jump into code, let&#8217;s consider a simple, but realistic example. You run a website that helps prospective cat owners find cats that need a new home. Your data comes from an API (which for our sake will be a JSON file) and includes information like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Characterstics of the cat<\/li>\n\n\n\n<li>How to contact the current owner<\/li>\n\n\n\n<li>Where the cat could be picked up, a location in latitude and longitude.<\/li>\n<\/ul>\n\n\n\n<p>What we want to do here is see how much effort is required to add this to a web page. Let&#8217;s get started!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"including-the-library\">Including the Leaflet Library<\/h2>\n\n\n\n<p>We&#8217;ll start with a simple bit of HTML. I&#8217;m using CodePen for the samples so this will be a subset of the entire page of course. First, just some introductory content and a place for our map.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Cat Connector<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n  Use the map below to help find cats who need a new home. \n  Every cat deserves a loving home!\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"map\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The map will need a size of some sort, so I used the following CSS:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-id\">#map<\/span> {\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">100%<\/span>;\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">500px<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you follow the&nbsp;<a href=\"https:\/\/leafletjs.com\/examples\/quick-start\/\">Leaflet Quick Start<\/a>, you can see two dependencies that need adding, first, a CSS resource:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> \n  <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"stylesheet\"<\/span> \n  <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css\"<\/span>\n  <span class=\"hljs-attr\">integrity<\/span>=<span class=\"hljs-string\">\"sha256-p4NxAoJBhIIN+hmNHrzRCf9tD\/miZyoHS5obTRR9BMY=\"<\/span>\n  <span class=\"hljs-attr\">crossorigin<\/span>=<span class=\"hljs-string\">\"\"<\/span>\n\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And then the library itself:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">&lt;<span class=\"hljs-selector-tag\">script<\/span> \n  <span class=\"hljs-selector-tag\">src<\/span>=\"<span class=\"hljs-selector-tag\">https<\/span>:\/\/<span class=\"hljs-selector-tag\">unpkg<\/span><span class=\"hljs-selector-class\">.com<\/span>\/<span class=\"hljs-selector-tag\">leaflet<\/span><span class=\"hljs-keyword\">@1<\/span>.9.4\/dist\/leaflet.js\"\n  integrity=<span class=\"hljs-string\">\"sha256-20nQCchB9co0qIjJZRGuk2\/Z9VM+kNiyxNV1lvTlZBo=\"<\/span>\n  crossorigin=<span class=\"hljs-string\">\"\"<\/span>&gt;\n&lt;\/script&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For CodePen, these  was done in the Pen Settings in the External Stylesheets and External Scripts sections.<\/p>\n\n\n\n<p>Now, we need to actually add the map, and here&#8217;s where you need to think a bit. In order to create a map, you&#8217;ll need a location to begin with. What this location is depends on a lot of factors. If I were rendering maps in the continental United States, I could center it on America. If I were mapping castles in Germany, obviously I&#8217;d pick a location somewhere in the country.<\/p>\n\n\n\n<p>Another factor you have to consider is the initial zoom. How close, or how far, the map initially shows itself will also depend on the kind of data you&#8217;re rendering.<\/p>\n\n\n\n<p>For our pretend cat rehoming website, we&#8217;ll assume Lafayette, Louisiana, which just so happens to be where I live. The latitude of Lafayette is 30.216667 and the longitude is -92.033333.<\/p>\n\n\n\n<p>This can be done with one line:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> map = L.map(<span class=\"hljs-string\">'map'<\/span>).setView(&#91;<span class=\"hljs-number\">30.216667<\/span>, <span class=\"hljs-number\">-92.033333<\/span>], <span class=\"hljs-number\">12<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you were to run this right now, you would see:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"650\" height=\"512\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/01\/l1.png?resize=650%2C512&#038;ssl=1\" alt=\"Big gray box, useless!\" class=\"wp-image-5059\" style=\"width:424px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/01\/l1.png?w=650&amp;ssl=1 650w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/01\/l1.png?resize=300%2C236&amp;ssl=1 300w\" sizes=\"auto, (max-width: 650px) 100vw, 650px\" \/><\/figure>\n\n\n\n<p>Where&#8217;s the map? The final part you need is a tile provider. Map tiles are the parts that make up any interactive map. As you go to different locations, and different zoom levels, the map is rendered from a set of tiles, based on those factors (location and zoom). <a href=\"https:\/\/www.openstreetmap.org\/#map=4\/38.01\/-95.84\">OpenStreetMap<\/a> provides free map tiles (with attribution!) and can be added to Leaflet like so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">L.tileLayer(<span class=\"hljs-string\">'https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png'<\/span>, {\n  <span class=\"hljs-attr\">maxZoom<\/span>: <span class=\"hljs-number\">19<\/span>,\n  <span class=\"hljs-attr\">attribution<\/span>: <span class=\"hljs-string\">'&amp;copy; &lt;a href=\"http:\/\/www.openstreetmap.org\/copyright\"&gt;OpenStreetMap&lt;\/a&gt;'<\/span>\n}).addTo(map);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let&#8217;s take a look at what we have so far:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_VYZdqZL\" src=\"\/\/codepen.io\/anon\/embed\/VYZdqZL?height=700&amp;theme-id=47434&amp;slug-hash=VYZdqZL&amp;default-tab=result\" height=\"700\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed VYZdqZL\" title=\"CodePen Embed VYZdqZL\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We haven&#8217;t actually added any data yet and it&#8217;s possible we may need to tweak multiple things. Maybe the map needs to be bigger? Maybe our centered location isn&#8217;t quite right? Our default zoom may not be optimal either. These are all things that will come into play as we start adding data \u2014 so let&#8217;s do that next!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"our-data\">Our Data<\/h2>\n\n\n\n<p>Before we get into how Leaflet can show data, let&#8217;s look at our sample data. Our cats will be returned as static data, an array of results. Here&#8217;s a portion of the data:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> availableCats = &#91;\n  {\n    <span class=\"hljs-attr\">cat<\/span>: {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Fluffy\"<\/span>,\n      <span class=\"hljs-attr\">age<\/span>: <span class=\"hljs-number\">5<\/span>,\n      <span class=\"hljs-attr\">gender<\/span>: <span class=\"hljs-string\">\"male\"<\/span>,\n      <span class=\"hljs-attr\">breed<\/span>: <span class=\"hljs-string\">\"calico\"<\/span>\n    },\n    <span class=\"hljs-attr\">owner<\/span>: {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Raymond Camden\"<\/span>,\n      <span class=\"hljs-attr\">contactEmail<\/span>: <span class=\"hljs-string\">\"raymondcamden@gmail.com\"<\/span>,\n      <span class=\"hljs-attr\">contactPhone<\/span>: <span class=\"hljs-string\">\"555-555-5555\"<\/span>\n    },\n    <span class=\"hljs-attr\">location<\/span>: {\n      <span class=\"hljs-attr\">latitude<\/span>: <span class=\"hljs-number\">30.227394<\/span>,\n      <span class=\"hljs-attr\">longitude<\/span>: <span class=\"hljs-number\">-92.02909<\/span>\n    }\n  },\n  {\n    <span class=\"hljs-attr\">cat<\/span>: {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Pig\"<\/span>,\n      <span class=\"hljs-attr\">age<\/span>: <span class=\"hljs-number\">12<\/span>,\n      <span class=\"hljs-attr\">gender<\/span>: <span class=\"hljs-string\">\"female\"<\/span>,\n      <span class=\"hljs-attr\">breed<\/span>: <span class=\"hljs-string\">\"calico\"<\/span>\n    },\n    <span class=\"hljs-attr\">owner<\/span>: {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Lindy Camden\"<\/span>,\n      <span class=\"hljs-attr\">contactEmail<\/span>: <span class=\"hljs-string\">\"lindyjcamden@gmail.com\"<\/span>,\n      <span class=\"hljs-attr\">contactPhone<\/span>: <span class=\"hljs-string\">\"555-555-5555\"<\/span>\n    },\n    <span class=\"hljs-attr\">location<\/span>: {\n      <span class=\"hljs-attr\">latitude<\/span>: <span class=\"hljs-number\">30.231695<\/span>,\n      <span class=\"hljs-attr\">longitude<\/span>: <span class=\"hljs-number\">-92.007103<\/span>\n    }\n  }\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>To emulate an API call, we can wrap this up in a function like so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getAvailableCats<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-comment\">\/\/ list of cats here...<\/span>\n  ]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And then update our code to let us call this asynchronously:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">\"DOMContentLoaded\"<\/span>, init, <span class=\"hljs-literal\">false<\/span>);\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">init<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> cats = <span class=\"hljs-keyword\">await<\/span> getAvailableCats();\n\n  <span class=\"hljs-keyword\">let<\/span> map = L.map(<span class=\"hljs-string\">\"map\"<\/span>).setView(&#91;<span class=\"hljs-number\">30.216667<\/span>, <span class=\"hljs-number\">-92.033333<\/span>], <span class=\"hljs-number\">12<\/span>);\n\n  L.tileLayer(<span class=\"hljs-string\">\"https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png\"<\/span>, {\n    <span class=\"hljs-attr\">maxZoom<\/span>: <span class=\"hljs-number\">19<\/span>,\n    <span class=\"hljs-attr\">attribution<\/span>:\n      <span class=\"hljs-string\">'&amp;copy; &lt;a href=\"http:\/\/www.openstreetmap.org\/copyright\"&gt;OpenStreetMap&lt;\/a&gt;'<\/span>\n  }).addTo(map);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>At this point, we&#8217;ve got data, but how to add it to the map? Leaflet lets you &#8216;draw&#8217; on a map in many different ways, but the simplest is by adding a marker:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> marker = L.marker(&#91;latitude, longitude]).addTo(map);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Since we got the cats earlier in the function, we just need to loop over them:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">cats.forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">c<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">let<\/span> marker = L.marker(&#91;c.location.latitude, c.location.longitude]).addTo(map);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can see this version below:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_QwLxzEr\" src=\"\/\/codepen.io\/anon\/embed\/QwLxzEr?height=700&amp;theme-id=47434&amp;slug-hash=QwLxzEr&amp;default-tab=result\" height=\"700\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed QwLxzEr\" title=\"CodePen Embed QwLxzEr\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Notice that it&#8217;s possible that changing the zoom may help here, there&#8217;s a lot of space around the data and we could get clodser in. However, maybe we know our organization could, at any time, get more data farther out. This is exactly what I was talking about in terms of understanding your data, both what&#8217;s immediately available and what may come later.<\/p>\n\n\n\n<p>Leaflet also supports the idea of taking a set of locations and ensuring they all &#8216;fit&#8217; by default. That&#8217;s just one example of how powerful the API is, but we&#8217;ll keep it simple for now.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"adding-interactivity\">Adding Interactivity<\/h2>\n\n\n\n<p>Now for the last part of the puzzle. In order for the information on the map to actually be useful, we need to associate the data for those markers and provide some form of UI to share that with the users. In it&#8217;s simplest form, Leaflet lets you bind popups to markers in, literally, one simple method call.<\/p>\n\n\n\n<p>You take this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> marker = L.marker(&#91;latitude, longitude]).addTo(map);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And add:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> marker = L.marker(&#91;latitude, longitude]).addTo(map).bindPopup(<span class=\"hljs-string\">'Set some HTML or just regular text here.'<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That&#8217;s it! So given our loop over the cat data, here&#8217;s a new version with that. I created a template string to more nicely format the information:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">cats.forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">c<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">let<\/span> desc = <span class=\"hljs-string\">`\n    &lt;h3&gt;<span class=\"hljs-subst\">${c.cat.name}<\/span>&lt;\/h3&gt;\n    &lt;p&gt;\n      <span class=\"hljs-subst\">${c.cat.name}<\/span> is a <span class=\"hljs-subst\">${c.cat.gender}<\/span> <span class=\"hljs-subst\">${c.cat.breed}<\/span>.\n    &lt;\/p&gt;\n    &lt;p&gt;\n      Owner: <span class=\"hljs-subst\">${c.owner.name}<\/span>&lt;br&gt;\n      Email: <span class=\"hljs-subst\">${c.owner.contactEmail}<\/span>&lt;br&gt;\n      Tel: <span class=\"hljs-subst\">${c.owner.contactPhone}<\/span>&lt;br&gt;\n    &lt;\/p&gt;\n  `<\/span>;\n  L.marker(&#91;c.location.latitude, c.location.longitude]).addTo(map).bindPopup(desc);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This one method handles the click interaction, creating the popup, even automatically closing another popup if you forget to close one. Here&#8217;s the complete demo:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_zxOJNEP\" src=\"\/\/codepen.io\/anon\/embed\/zxOJNEP?height=700&amp;theme-id=47434&amp;slug-hash=zxOJNEP&amp;default-tab=result\" height=\"700\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed zxOJNEP\" title=\"CodePen Embed zxOJNEP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-else\">What Else?<\/h2>\n\n\n\n<p>This introduction only scratches the surface of what&#8217;s possible and you should peruse the&nbsp;<a href=\"https:\/\/leafletjs.com\/reference.html\">docs<\/a>&nbsp;for a full idea. Leaflet is great, and free, but, you should also be aware of some of the things you&nbsp;<em>don&#8217;t<\/em>&nbsp;get that you would with Google Maps or <a href=\"https:\/\/maps.here.com\/?map=44.09044,-120.73045,8.44\">HERE<\/a>. These include the various &#8216;services&#8217;, like<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Routing<\/li>\n\n\n\n<li>Traffic information<\/li>\n\n\n\n<li>Geocoding (and Reverse Geocoding)<\/li>\n<\/ul>\n\n\n\n<p>This doesn&#8217;t mean you can&#8217;t make use of those services&nbsp;<em>along<\/em>&nbsp;with Leaflet. I discussed an example of this late last year:&nbsp;<a href=\"https:\/\/www.raymondcamden.com\/2024\/10\/04\/using-geocoding-with-leaflet\">&#8220;Using Geocoding with Leaflet&#8221;<\/a>. That being said, keep this in mind when planning your projects as it may impact your decision on whether or not to use the library.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Leaflet.js is a free open-source mapping library. We&#8217;ll look at how to use it to create a basic map with location points of cats up for adoption. <\/p>\n","protected":false},"author":29,"featured_media":5065,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[3,292,291],"class_list":["post-5055","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-javascript","tag-leaflet","tag-maps"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/01\/leaflet.png?fit=1324%2C784&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/5055","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=5055"}],"version-history":[{"count":8,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/5055\/revisions"}],"predecessor-version":[{"id":5074,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/5055\/revisions\/5074"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/5065"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=5055"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=5055"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=5055"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}