Mapbox Newsletter WEEKLY TIPSの解説 -「マップ間のスワイプ」
はじめに
この記事は、先日配信されたMapbox NewsletterのWEEKLY TIPSで紹介されていた「マップ間のスワイプ」についての解説です。このサンプルはmapbox-gl-compare
プラグインの使い方に関して例示しています。また、Newsletterの購読はこちらからお申し込みいただけます。
以下が本サンプルのデモです。
コードを確認
まずExamplesのコードを見に行きましょう。
日本語サイト
英語サイト
基本的に同じコードですが、英語版はスタイルがMapbox Light/Dark v11にアップグレードされているのでこちらを使用します。Mapbox Light/Dark v10ではデフォルトのプロジェクションがWebメルカトルであるのに対し、Mapbox Light v11ではGlobe(3D表示された地球)なので、印象がかなり異なります。また、英語版はMapbox GL JS v3が使用されています。
HTML/CSS
まずHTMLを見ていきましょう。
mapbox-gl-compare
プラグインの読み込みを行います。
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-compare/v0.4.0/mapbox-gl-compare.js"></script>
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-compare/v0.4.0/mapbox-gl-compare.css" type="text/css">
次に地図を表示するエレメントを作成しています。2つの地図を比較するということで、地図を表示するdiv
を2つ、そしてそれらをまとめるdiv
を作成します。
<div id="comparison-container">
<div id="before" class="map"></div>
<div id="after" class="map"></div>
</div>
Mapの作成
次にJavaScriptのコードを見ていきます。以下のコードはいつも通り、Mapオブジェクトを作成しています。container
で地図を表示するHTMLエレメントのidを指定します。ただし、今回は2個作成しています。
const beforeMap = new mapboxgl.Map({
container: 'before',
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/light-v11',
center: [0, 0],
zoom: 0
});
const afterMap = new mapboxgl.Map({
container: 'after',
style: 'mapbox://styles/mapbox/dark-v11',
center: [0, 0],
zoom: 0
});
mapbox-gl-compareの作成
Compare
をインスタンス化します。第1、第2引数が作成したMapオブジェクト、第3引数が2つの地図のコンテナのセレクタ、第4引数はオプションです。第3引数はセレクタということに注意が必要です。今回はdiv
エレメントのidを指定しているので先頭に#
がついています。このコンテナの直下にmapbox-gl-compareの本体が配置されます。
const container = '#comparison-container';
const map = new mapboxgl.Compare(beforeMap, afterMap, container, {
// Set this to enable comparing two maps by mouse movement:
// mousemove: true
});
ちなみに、コメントを外してmousemove
オプションを有効化するとドラッグではなくマウスの移動で境目が動きます。
まとめ
非常に簡単に2つの地図を比較することができることがわかりました。また、せっかくなのでMapbox StandardとMapbox Streets v12を比較するものも作ってみました。東京タワー周辺の地図です。ぜひ2つのスタイルを比較してみてください。
おまけ
mapbox-gl-compareはどうやって2つの地図を合成しているのでしょうか。
compareの作成処理
Compare
のインスタンス化の方法を見ると、あたかもmapbox-gl-compareが2つの地図を内部に抱え込んでいるように思えます。しかし、実際にコードを見てみるとmapboxgl-compare
というクラス名のdiv
エレメントをコンテナの子要素として追加しているだけです。
このクラスのスタイルは以下のとおりですが、これが真ん中に表示される仕切りの部分です。
そしてmapboxgl-compare
にcompare-swiper-vertical
というクラス名のdiv
エレメントが追加されます。
このクラスのスタイルは以下のとおりですが、これが真ん中に表示される丸いマウスでドラッグするパーツです。
つまり、以下のような構造になります。地図を内包しているどころか、2つの地図の下に棒と丸が配置されているだけです。
<div id="comparison-container">
<div id="before" class="map"></div>
<div id="after" class="map"></div>
<!-- ここ -->
<div class="mapboxgl-compare">
<div class="compare-swiper-vertical"></div>
</div>
</div>
css
cssの定義を見て実際の表示を確認します。
2つの地図については以下の通り、abusolute
が指定されているのでピッタリ重なっています。
.map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
mapboxgl-compare
とcompare-swiper-vertical
もabusolute
なので、地図2枚の上に棒と丸が重なっているという配置になります。
ドラッグ
compare-swiper-vertical
をドラッグすると以下の処理が実行されます。
ここではマウスの座標に合わせてclip
を使って切り出しています。1つ目のMapはマウスより左側の矩形、2つ目のMapはマウスより右側の矩形が表示されることになります。
var clipA = this._horizontal
? 'rect(0, 999em, ' + x + 'px, 0)'
: 'rect(0, ' + x + 'px, ' + this._bounds.height + 'px, 0)';
var clipB = this._horizontal
? 'rect(' + x + 'px, 999em, ' + this._bounds.height + 'px,0)'
: 'rect(0, 999em, ' + this._bounds.height + 'px,' + x + 'px)';
this._mapA.getContainer().style.clip = clipA;
this._mapB.getContainer().style.clip = clipB;
つまり、ドラッグに合わせてclip
が連動するので2つの地図の見える範囲が変わるということになります。
2つの地図が同期して動く仕組み
mapbox-gl-sync-moveというライブラリを使用して同期しています。
まず、以下の部分でmapbox-gl-sync-moveのsyncMaps
関数を呼び出しています。
syncMaps
関数は何個でもMapオブジェクトを受け取れます。
少しややこしいですが各Mapオブジェクトに対してsync
関数を呼び出す処理を割り当てています。この割り当てられた処理はMapのmove
イベントが発火した際に実行されます。また、引数として自分と自分以外のMapを渡しています。
sync
関数はマウスイベントを受け取ったMapオブジェクトをmaster
、それ以外のMapオブジェクトをclone
としてmoveToMapPosition
関数を実行します。
moveToMapPosition
関数は以下の通り、clone
をmaster
のカメラに合わせるような処理となります。
すべてのMapについて、操作されたMapがmaster
、それ以外がclone
となっていたので、どのマップを操作してもそれ以外のマップが同じ動きをするようになります。
以下はmapbox-gl-sync-moveのデモです。すべてのマップが同期していますね。
Discussion