<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: korywka</title>
    <description>The latest articles on DEV Community by korywka (@korywka).</description>
    <link>https://dev.to/korywka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F391075%2Fa8bd2945-12d2-4449-8327-99e0cc354705.png</url>
      <title>DEV Community: korywka</title>
      <link>https://dev.to/korywka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/korywka"/>
    <language>en</language>
    <item>
      <title>The most native image gallery</title>
      <dc:creator>korywka</dc:creator>
      <pubDate>Mon, 23 Nov 2020 10:03:01 +0000</pubDate>
      <link>https://dev.to/korywka/the-most-native-image-gallery-360i</link>
      <guid>https://dev.to/korywka/the-most-native-image-gallery-360i</guid>
      <description>&lt;p&gt;Hi, I want to share with you an approach to create the most native web gallery. By native, I mean a lot of work is done by the browser itself with minimum code (&lt;a href="https://bundlephobia.com/result?p=native-gallery@0.0.4" rel="noopener noreferrer"&gt;630B gzip&lt;/a&gt;). The gallery relies on some of the features that are supported by the latest versions of browsers (excluding experimental ones). It also corresponds to the principle of graceful degradation: to work in older browsers but lose some functionality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F143qmtn52nujwqag01oi.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F143qmtn52nujwqag01oi.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gallery is named &lt;a href="https://github.com/korywka/native-gallery" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;native-gallery&amp;gt;&lt;/code&gt;&lt;/a&gt; to take advantage of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements" rel="noopener noreferrer"&gt;Custom Elements&lt;/a&gt; in the future. For now, it is just a custom HTML tag.&lt;/p&gt;

&lt;p&gt;There are no strict requirements for HTML markup, so let's keep it as simple as possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;native-gallery&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"1.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1600"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"900"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"2.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"675"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"900"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"3.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1600"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"900"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"4.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1600"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"900"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"5.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1600"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"900"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/native-gallery&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lazy loading 🚀
&lt;/h2&gt;

&lt;p&gt;The first modern feature used here is &lt;a href="https://web.dev/browser-level-image-lazy-loading/" rel="noopener noreferrer"&gt;Native lazy loading&lt;/a&gt; with &lt;code&gt;loading="lazy"&lt;/code&gt; attribute. There was some strange behaviour for a horizontal container (not &lt;code&gt;window&lt;/code&gt;) scroll that all images were loading at the beginning anyway. I got around this by hiding and showing images back after page renders with these few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* -loaded class is set at initialization */&lt;/span&gt;
&lt;span class="nt"&gt;native-gallery&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.-loaded&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;loading&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"lazy"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the &lt;code&gt;loading&lt;/code&gt; attribute is omitted in the first image tag for rendering it before the initialization of the plugin.&lt;/p&gt;

&lt;p&gt;Since lazy loading is an important feature for production usage, &lt;a href="https://github.com/aFarkas/lazysizes" rel="noopener noreferrer"&gt;lazysizes.js&lt;/a&gt; is supported as a polyfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Magnetic behaviour 🧲
&lt;/h2&gt;

&lt;p&gt;The magnetic behaviour is implemented using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type" rel="noopener noreferrer"&gt;Scroll Snap&lt;/a&gt; CSS property. Only a single image is visible after scroll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;native-gallery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;scroll-snap-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;mandatory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;scroll-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;smooth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;native-gallery&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;scroll-snap-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mixed sizes images are supported too 🧙‍♂️&lt;/p&gt;

&lt;p&gt;Any browser that does not support this feature will scroll the gallery with the standard behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controls and events ⌨️
&lt;/h2&gt;

&lt;p&gt;Controls and events are almost the only reason why this gallery contains javascript code. I tried to keep them as simple as possible. To navigate to the next image we need to preload it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;preloadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`can't load image: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then just to scroll container to the new position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;preloadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrollTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;toIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To listen to change events, &lt;a href="https://github.com/korywka/native-gallery/blob/master/src/index.js#L14-L18" rel="noopener noreferrer"&gt;we subscribe to the container's &lt;code&gt;scroll&lt;/code&gt; event through the &lt;code&gt;throttle&lt;/code&gt;&lt;/a&gt; function for better performance.&lt;/p&gt;

&lt;p&gt;Controls demo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw0pstm4furenrstw2fiu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw0pstm4furenrstw2fiu.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR 🏃‍♂️
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CSS property &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type" rel="noopener noreferrer"&gt;scroll-snap-type&lt;/a&gt; for snapping&lt;/li&gt;
&lt;li&gt;CSS property &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior" rel="noopener noreferrer"&gt;scroll-behavior&lt;/a&gt; for smooth JS scrolling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loading&lt;/code&gt; attribute for &lt;a href="https://web.dev/native-lazy-loading/" rel="noopener noreferrer"&gt;Native Lazy Loading&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Compatible with &lt;a href="https://github.com/aFarkas/lazysizes" rel="noopener noreferrer"&gt;lazysizes.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events" rel="noopener noreferrer"&gt;Custom Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;It was named as &lt;code&gt;native-gallery&lt;/code&gt; to become &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements" rel="noopener noreferrer"&gt;Custom Element&lt;/a&gt; in the future&lt;/li&gt;
&lt;li&gt;Disadvantages: circular scrolling is &lt;a href="https://github.com/korywka/native-gallery/pulls" rel="noopener noreferrer"&gt;not implemented yet&lt;/a&gt; 🤷‍♀️&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bundlephobia.com/result?p=native-gallery@0.0.4" rel="noopener noreferrer"&gt;630B gzip&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/korywka/native-gallery" rel="noopener noreferrer"&gt;Code repository&lt;/a&gt; / &lt;a href="https://korywka.github.io/native-gallery/example/" rel="noopener noreferrer"&gt;Example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any feedback is much appreciated ❤️&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>css</category>
    </item>
    <item>
      <title>From Google Maps to MapBox</title>
      <dc:creator>korywka</dc:creator>
      <pubDate>Tue, 26 May 2020 22:17:20 +0000</pubDate>
      <link>https://dev.to/korywka/from-google-maps-to-mapbox-4nfk</link>
      <guid>https://dev.to/korywka/from-google-maps-to-mapbox-4nfk</guid>
      <description>&lt;p&gt;Hi. Over the past two years, I have been working with maps and I want to share some knowledge writing maps from the ground up for a large real estate project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem 😏
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4pthH7qY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g0hfqb3le4r1mftfy0h6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4pthH7qY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g0hfqb3le4r1mftfy0h6.png" alt="Our first Google Map"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was our map as of two years ago. All of the data would load at page load and Google Maps would render basic pins and polygons.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;first problem&lt;/strong&gt; with this implementation was that we couldn't render thousands of markers at the same time. The size of the data to load on the client was over 10MB.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;second problem&lt;/strong&gt; was the lack of UI performance - rendering speed, inertia when dragging, and smoothness while zooming.&lt;/p&gt;

&lt;p&gt;And our dream was to render 3D buildings 🏢&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt #1 – Stick to Google Maps 🤔
&lt;/h2&gt;

&lt;p&gt;To solve the first problem we considered clusterization. It looked less like a solution, and more like "hey, we are not able to render all the data you want to see, so here is your circle with a number, and try to zoom in more." Admission of defeat. Not an option 😎&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y6k9v0mM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f5miyhb6e84wevtqy28c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y6k9v0mM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f5miyhb6e84wevtqy28c.png" alt="Clusterization example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we moved towards &lt;a href="https://developers.google.com/maps/documentation/javascript/kml"&gt;KML&lt;/a&gt;. Despite the fact that KML is an XML format, debugging can be time-consuming: you send a KML payload to the Google server and get nothing in the response. No errors, just blank tiles. Having assembled a working prototype, we got the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mM2mMyxw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gyqs1ixgecotsoxmwrw0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mM2mMyxw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gyqs1ixgecotsoxmwrw0.gif" alt="KML blinking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We thought "cool, now we’re only rendering markers in the viewport as many as we want!" But with this came two new problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Flickering while zooming. Any new zoom level is a new request to the Google tile server. Tiles are raster images so they are reloaded, not scaled.&lt;/li&gt;
&lt;li&gt;Markers were blurry on high-resolution displays. And there was no way to fix this problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Attempt #2 – MapBox 😳
&lt;/h2&gt;

&lt;p&gt;While searching for solutions we found MapBox. Unlike Google Maps, MapBox works with vector tiles and provides a very flexible API with &lt;a href="https://github.com/mapbox/mapbox-gl-js"&gt;&lt;code&gt;mapbox-gl-js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead of downloading all of the GeoJSON data to the client, we could now prepare our data to be distributed in the vector &lt;code&gt;.pbf&lt;/code&gt; tiles format:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert GeoJSON file to &lt;code&gt;.mbtiles&lt;/code&gt; with &lt;a href="https://github.com/mapbox/tippecanoe"&gt;tippecanoe&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Serve this file with a tile server - &lt;a href="https://www.mapbox.com/mapbox-studio/"&gt;MapBox Studio&lt;/a&gt; or &lt;a href="https://github.com/maptiler/tileserver-gl"&gt;tileserver-gl&lt;/a&gt;, if you prefer a self-hosted solution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result exceeded all expectations 😱&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---D5uT0U8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v5aatp1faac040lqym12.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---D5uT0U8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v5aatp1faac040lqym12.gif" alt="MapBox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Smoothly. Tiles are rendered without any flickering between zoom levels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering 3D Models 🙀
&lt;/h2&gt;

&lt;p&gt;Not so long ago MapBox added support for &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/custom-style-layer/"&gt;Custom Layer&lt;/a&gt;, with a low-level API to WebGL context. This makes it possible to render 3D models.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/mapbox-3d-model-5p080"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;As of now, nothing like this can be done with Google Maps 🤷‍♀️&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic of Extrusion 🧙‍♂️
&lt;/h2&gt;

&lt;p&gt;Extrusion is another powerful feature of &lt;code&gt;mapbox-gl-js&lt;/code&gt;. It helps us render &lt;a href="https://docs.mapbox.com/mapbox-gl-js/example/3d-buildings/"&gt;3D buildings on the fly&lt;/a&gt;. Unlike the 3D model example above, &lt;code&gt;fill-extrusion&lt;/code&gt; works without any &lt;code&gt;.glb&lt;/code&gt; or &lt;code&gt;.obj&lt;/code&gt; files, rendering is based on provided GeoJSON properties. For example, the &lt;code&gt;height&lt;/code&gt; property from the OpenStreetMaps database. Since we render tile by tile on the fly, this approach turned out to be very scaleable. The property &lt;code&gt;fill-extrusion&lt;/code&gt; comes in handy when we want to render a single apartment or even an entire floor 😯&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/mapbox-3d-floor-9d2t6"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This example is not interactive, without &lt;code&gt;hover&lt;/code&gt; and &lt;code&gt;click&lt;/code&gt; events, but they are super easy to add.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Enough Minerals 💎
&lt;/h2&gt;

&lt;p&gt;MapBox Studio and MapBox as a service are brilliant. Without any deep knowledge of what’s under the hood of the map or tile server, it helps create fast beautiful maps. But this service costs a pretty penny. So if you explore using it, it’s important to weigh out whether or not it will have a direct impact on your bottom line. In our case, it didn’t. 💸&lt;/p&gt;

&lt;p&gt;But it is possible to rid yourself of the payments. Let’s a look at how to do that.&lt;/p&gt;

&lt;p&gt;The first step is to move tile distribution to our end. For both map tiles and data tiles (if you do not render GeoJSON data directly).&lt;/p&gt;

&lt;p&gt;First, you need to create a &lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/"&gt;Style JSON file&lt;/a&gt;. It has at least 3 important fields you need to host by yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/sprite/"&gt;Sprite&lt;/a&gt; - all icons that are used on the map. Take a look at the &lt;a href="https://labs.mapbox.com/maki-icons/"&gt;Maki Icons&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/glyphs/"&gt;Glyphs&lt;/a&gt; - fonts. All fonts that are used on the map must be converted to &lt;code&gt;.*pbf&lt;/code&gt;. For the most common font families, you check out &lt;a href="https://github.com/korywka/fonts.pbf"&gt;fonts.pbf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/"&gt;Sources&lt;/a&gt; - the most challenging part. First, you need to find out OpenStreetMaps &lt;code&gt;.mbtiles&lt;/code&gt;. There are two main options that we have found: buy them at &lt;a href="https://openmaptiles.org/"&gt;OpenMapTiles&lt;/a&gt; (the latest data is not free for commercial use) or download OSM &lt;code&gt;.pbf&lt;/code&gt; data at &lt;a href="https://download.geofabrik.de/"&gt;Geofabrik&lt;/a&gt; (free and updated quite often), and convert it to tiles with &lt;a href="https://github.com/openmaptiles/openmaptiles"&gt;openmaptiles&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After hosting all of these style assets (I just have to mention &lt;a href="https://maputnik.github.io/"&gt;maputnik&lt;/a&gt; - worthwhile layers editing tool) your map can be rendered by &lt;code&gt;mapbox-gl-js&lt;/code&gt; without any Access Tokens. &lt;/p&gt;

&lt;p&gt;And then it’s time to think about how to render the vector data on top of it.&lt;/p&gt;

&lt;p&gt;What have we tried?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export MySQL data to GeoJSON file, convert it to &lt;code&gt;*.mptiles&lt;/code&gt; file with &lt;a href="https://github.com/mapbox/tippecanoe"&gt;tippecanoe&lt;/a&gt; and upload it to MapBox Studio.&lt;/li&gt;
&lt;li&gt;Once we did this, we had to give up with Studio, we set up &lt;a href="https://github.com/maptiler/tileserver-gl"&gt;tileserver-gl&lt;/a&gt; from MapTiler. And it’s great – all we have to do is set up a single config file.&lt;/li&gt;
&lt;li&gt;Coming up in a minute 🙊&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So we ended up with a cheap, fully self-hosted map that makes no concessions to the ones you have to pay for 💰&lt;/p&gt;

&lt;h2&gt;
  
  
  Tiles on The Fly 🚀
&lt;/h2&gt;

&lt;p&gt;In the end, our weakest pipeline was a dynamic data update - MySQL =&amp;gt; PHP =&amp;gt; GeoJSON =&amp;gt; tippecanoe =&amp;gt; MBTiles =&amp;gt; upload tiles to &lt;code&gt;tileserver-gl&lt;/code&gt; =&amp;gt; reload Docker container 🤯&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/urbica/martin"&gt;MARTIN&lt;/a&gt; comes to the rescue here ✨ &lt;/p&gt;

&lt;p&gt;It is a tile server written in Rust and works on top of PostGIS. I don't know who came up with the idea, but it is genius.&lt;/p&gt;

&lt;p&gt;You just have to create a database table (we were using PostgreSQL), fill in geo data, and point MARTIN to serve this table as a source. It works out of the box, without any config files. If you need to perform geospatial operations at the moment of tile creation (e.g. do not render geometries that &lt;a href="https://postgis.net/docs/ST_Intersects.html"&gt;intersect&lt;/a&gt;) then &lt;a href="https://github.com/urbica/martin#function-sources"&gt;function sources&lt;/a&gt; is the right place to do it. With a caching proxy (e.g. NGINX-based) in front of MARTIN, you will get a blazingly fast tile server ⚡&lt;/p&gt;

&lt;h2&gt;
  
  
  The TL;DR 💡
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Switching from raster to vector tiles decreases loading time - often vector tiles are lighter with the ability to &lt;a href="https://docs.mapbox.com/help/glossary/overzoom/"&gt;overzoom&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Vector maps are easy to customize with &lt;a href="https://www.mapbox.com/mapbox-studio/"&gt;MapBox Studio&lt;/a&gt; or &lt;a href="https://github.com/maputnik/editor"&gt;maputnik&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;With OpenStreetMaps it is easy to add or update data like streets or POI, unlike Google Maps.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.here.com/documentation/map-tile/dev_guide/topics/example-satellite-map.html"&gt;HERE&lt;/a&gt; have Satellite tiles with a significant free of charge limit.&lt;/li&gt;
&lt;li&gt;It’s not a good idea to use GeoJSON source for heavy data visualization. Serve it with tiles and let your users download only the data, that is visible in the viewport.&lt;/li&gt;
&lt;li&gt;Best not to hold a lot of data in &lt;code&gt;property&lt;/code&gt; keys of GeoJSON Feature, it will make tiles heavier. If you need any properties after a click, you can load it later with an XHR request.&lt;/li&gt;
&lt;li&gt;If you can afford to pay for MapBox service, they certainly deserve every dollar. But if not, a step by step self-hosted tile server is not hard to implement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;mapbox-gl-js&lt;/code&gt; is definitely the future of geo data visualization on the web 🌏&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mapbox</category>
      <category>maps</category>
    </item>
  </channel>
</rss>
