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`,
...