JSFiddle - React, Tailwind, and code Playground

by fxi

HTML

<script src="https://unpkg.com/split-grid/dist/split-grid.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"></script>
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css">
<div class="main">

<div class="header"> 
  <h1>Map comparator</h1>
  <p> Use the cursor bars to swipe between years to compare layers</p>
   <p> Click and drag the map to ajust position </p>
</div>
  <div id='base' class="grid">
    <div class="container a">
      <div class="year"><span>2016</span></div>
      <div id="a" class="map"></div>
    </div>
    <div class="gutter-col gutter-col-1">


    </div>
    <div class="container b">
      <div class="year"><span>2017</span></div>
      <div id="b" class="map"></div>
    </div>
    <div class="container c">
      <div class="year"><span>2018</span></div>
      <div id="c" class="map"></div>

    </div>
    <div class="gutter-row gutter-row-1">


    </div>
    <div class="container d">
      <div class="year"><span>2019</span></div>
      <div id="d" class="map"></div>
    </div>
  </div>

</div>

CSS

html,
body {
  font-family: Helvetica, sans-serif;
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;

}
.header {
   padding:20px;
}

.main {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
}

.grid {
  flex-grow: 1;
  display: grid;
  min-height:1px;
  grid-template-rows: 1fr 10px 1fr;
  grid-template-columns: 1fr 10px 1fr;
  border:10px solid #efefef;
}

.gutter-col,
.gutter-row {
  z-index: 2;
  background-repeat: no-repeat;
  background-position: center;
  background-color: #efefef;
}

.gutter-col {
  grid-row: 1/-1;
  cursor: col-resize;
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
}


.gutter-row {
  grid-column: 1/-1;
  cursor: row-resize;
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
}

.gutter-col-1 {
  grid-column: 2;

}
.gutter-row-1 {
  grid-row: 2;

}

.container {
  top: 0;
  left: 0;
  z-index: 1;
  overflow: hidden;
  position: relative;
}

.year {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  padding: 10px;
  z-index: 1;
  background: #fff;
}

.d .year,
.c .year {
  bottom: 0;
  top: auto;
}

.year span {
  font-size: 1.2em;
}

.map {
  width: 100vw;
  height: 100vh;
  border: 10px solid #fff;
}

JavaScript

class MapboxSynchronizer {
  constructor(config) {
    const ms = this;
    ms.token = config.token;
    ms.bounds = config.bounds;
    ms.pitch = config.pitch;
    ms.projection = config.projection;
    ms.ids = config.ids;
    ms.style = config.style;
    ms.layers = config.layers;
    ms.disableUnify = config.disableUnify;
    ms.updating = false;

    mapboxgl.accessToken = ms.token;

    ms.maps = ms.initializeMaps();
    ms.addTerrain();
    ms.addLayers();
    ms.initializeSplit();
  }

  initializeMaps() {
    const ms = this;
    let maps = {};
    for (const id of ms.ids) {
      const others = [...ms.ids];
      others.splice(others.indexOf(id), 1);
      maps[id] = new mapboxgl.Map({
        container: id,
        style: ms.style,
        bounds: ms.bounds,
        pitch: ms.pitch,
        projection: ms.projection
      });
      maps[id].on('move', ms.updateMaps(id, others));
    }
    return maps;
  }

  addTerrain() {
    const ms = this;
    for (const id in ms.maps) {
      ms.maps[id].on('style.load', () => {
        ms.maps[id].addSource('mapbox-dem', {
          'type': 'raster-dem',
          'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
          'tileSize': 512,
          'maxzoom': 14
        });
        ms.maps[id].setTerrain({
          'source': 'mapbox-dem',
          'exaggeration': 1.5
        });
      });
    }
  }


  addLayers() {
    const ms = this;
    ms.layers.forEach((l) => {
      if (ms.maps[l.map].loaded()) {
        ms.addLayerToMap(l, ms.maps[l.map]);
      } else {
        ms.maps[l.map].on('load', () => {
          ms.addLayerToMap(l, ms.maps[l.map]);
        });
      }
    });
  }

  addLayerToMap(layerConfig, map) {
    map.addSource(`${layerConfig.layer}-src`, {
      "type": "raster",
      "tiles": [layerConfig.tilesUrl.replace('{layer}', layerConfig.layer)],
      tileSize: 256
    });
    map.addLayer({
      'id': layerConfig.layer,
      'type': 'raster',
      'source': `${layerConfig.layer}-src`,
   ...