Mapbox Newsletter WEEKLY TIPSの解説 -「ホバー効果を作成」
はじめに
この記事は、先日配信されたMapbox NewsletterのWEEKLY TIPSで紹介されていた「ホバー効果を作成」についての解説です。このサンプルではeventの中でも、特にmousemove
とmouseleave
の使い方を紹介しています。また、Newsletterの購読はこちらからお申し込みいただけます。
コードを確認
まずExamplesのコードを見に行きましょう。
日本語サイト
英語サイト
基本的に同じコードですが、英語版はスタイルがMapbox Streets v12にアップグレードされているのでこちらを使用します。Mapbox Streets v11ではデフォルトのプロジェクションがWebメルカトルであるのに対し、Mapbox Streets v12ではGlobe(3D表示された地球)なので、印象がかなり異なります。
HTML/CSS
まずHTMLを見ていきましょう。
以下は地図を表示するエレメントを作成しています。
<div id="map"></div>
Mapの作成
次にJavaScriptのコードを見ていきます。以下のコードはいつも通り、Mapオブジェクトを作成しています。container
で地図を表示するHTMLエレメントのidを指定します。
const map = new mapboxgl.Map({
container: 'map',
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/streets-v12',
center: [-100.486052, 37.830348],
zoom: 2
});
変数の準備
選択されたPolygonのidを一次保存する変数を宣言しています。
let hoveredPolygonId = null;
ソース、レイヤーの作成
load
イベント(map.on('load', () => {})
の中身)で1つのソース、2つのレイヤーを追加しています。
まずソースを作成しています。アメリカ合衆国の州の形を表現したポリゴンが格納されたGeoJSONをstates
というidのソースとして読み込んでいます。
map.addSource('states', {
'type': 'geojson',
'data': 'https://docs.mapbox.com/mapbox-gl-js/assets/us_states.geojson'
});
次に1つ目のレイヤーをstate-fills
というidで作成しています。ソースは先程作成したstates
を使用します。このレイヤーは州のPolygonの色と不透明度を表現しています。マウスカーソルがある州の上に乗っかっている時には、その州だけ不透明度を1
にし、それ以外は0.5
にすることでマウスカーソルのある州を目立たせるホバー効果を実現しています。
map.addLayer({
'id': 'state-fills',
'type': 'fill',
'source': 'states',
'layout': {},
'paint': {
'fill-color': '#627BC1',
'fill-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
1,
0.5
]
}
});
fill-opacity
のExpressionsが少し複雑なので分解してみていきます。
-
case
: 第1引数に条件、第2引数に条件にマッチした際の値、第3引数に条件、第4引数に条件にマッチした際の値、...、第2n+1引数に全てにマッチしなかったときの値を記述します。ここでは第1引数が['boolean'...]
、第2引数が1
、第3引数が0.5
なので、「['boolean'...]
が真ならば1
、そうでないなら0.5
」がfill-opacity
の値になります。 -
boolean
: 引数の真偽値を返します。複数の引数があるときは前から評価していき、真偽値が得られたところで評価を終了します。ここでは['feature-state', 'hover']
がnull
になることがあるので、その場合は第2引数であるfalse
がboolean
の評価値となります。 -
feature-state
: feature stateの値を取得します。ここではhover
の値を取得します。あとから出てきますが、feature stateはsetFeatureState
で設定された値であって、ソースのプロパティとは無関係です。feature stateが付与されていないときはnull
が返ります。そのため、['boolean', ...]
を使う必要がありました。
2つ目のレイヤーは州の境界線を表現しています。
map.addLayer({
'id': 'state-borders',
'type': 'line',
'source': 'states',
'layout': {},
'paint': {
'line-color': '#627BC1',
'line-width': 2
}
});
マウスイベント
いよいよマウスイベントです。
mousemove
まず1つ目がmousemove
イベントです。このイベントは地図上でマウスが動く度に発火します。ただし、第2引数にレイヤーidが指定されている場合には、そのレイヤーが表示されている部分を動いたときのみ発火します。ここではstate-fills
が指定されているので、州のPolygon上を移動したときのみ発火します。
また、コールバック関数の引数e
にはqueryRenderedFeatures
で取得できるものと同じ値が入っています。そこでif (e.features.length > 0)
ではfeatureが1個以上取れているかどうかを確認しています。あとでe.features[0]
のようにインデックス0に直接アクセスするコードがあるので念のために1個以上あることを確認しています。state-fills
レイヤー上でしか発火しないことから必ずfeatureが1個はあるはずですが、念のために確認しているということでしょう。
map.on('mousemove', 'state-fills', (e) => {
if (e.features.length > 0) {
中身...
}
});
中身を見ていきます。if
は飛ばして続きから見ます。まず、hoveredPolygonId = e.features[0].id
でマウスカーソルが乗っかっているPolygonのid
をhoveredPolygonId
に格納しています。次にsetFeatureState
でfeature stateの値を設定しています。第1引数は値を設定するfeatureのソースとidを指定します。第2引数は実際の値を指定します。先程レイヤーのところで見たhover
というfeature stateがここで設定されます。
そして飛ばしたif
に戻ります。if (hoveredPolygonId !== null)
なので、hoveredPolygonId
に値が入っていれば必ず実行されます。先程見た通り、マウスカーソルが一度でもどこかの州に乗っかっていればこの値は設定されています。中身を見るとsetFeatureState
でhover
をfalse
にしています。これはある州から別の州にマウスカーソルが移動した際、前の州のホバー効果を消すために必要な処理です。同じ州内であれば一度hover
はfalse
になりますが、直後の処理でtrue
に戻るのでホバー効果に影響はありません。
if (hoveredPolygonId !== null) {
map.setFeatureState(
{ source: 'states', id: hoveredPolygonId },
{ hover: false }
);
}
hoveredPolygonId = e.features[0].id;
map.setFeatureState(
{ source: 'states', id: hoveredPolygonId },
{ hover: true }
);
mousemove
2つ目がmouseleave
イベントです。第2引数に指定されたレイヤーからマウスカーソルが出たタイミングで発火します。ここでは最後にホバー効果を得ていたPolygonを元に戻しています。
map.on('mouseleave', 'state-fills', () => {
if (hoveredPolygonId !== null) {
map.setFeatureState(
{ source: 'states', id: hoveredPolygonId },
{ hover: false }
);
}
hoveredPolygonId = null;
});
まとめ
マウスの移動に応じてホバー効果を実現する方法について確認しました。ご覧の通り、一般的なGUIプログラミングのようにオブジェクトのプロパティを直接変えることはできません。レイヤーとしてホバー状態を定義しておき、実際にホバー状態にしたいfeatureのプロパティやfeature stateを変更するという2段構えで効果を実現します。慣れないうちはわかりにくいですが、スタイルの書き方に習熟してくると違和感無くなってくるので大丈夫です。
Discussion