みなさん、こんにちは。新卒エンジニアの id:matsuda0528 です。 今日は、Mapbox GL JS を使用して地図の描画領域を変更するアニメーションを実装する方法についてお話します。
TL;DR
以下のように、setInterval()
関数を用いて resize()
関数を繰り返し実行する方法で実装しました。
const onClickMapResizeButton = () => { clearInterval(mapResizer) mapResizer = setInterval(() => { map.value.resize() }) } const onTransitionend = () => { clearInterval(mapResizer) }
駅メモの地図について
駅メモでは、Mapbox GL JS を使用していくつかの地図表示機能を実装しています。 最近ではスタンプラリーイベントで新たに地図表示機能が追加されました。 今回は、スタンプラリーイベントで新しく実装した地図の大きさを変更するアニメーションについて解説します。
地図のサイズ変更アニメーション
下記のような実装を用意します。
その上で <div class="map">
の大きさを変更する処理を追加すれば、地図のサイズ変更アニメーションを実装することが可能です。
<div class="map"> <!-- 地図を描画するコンポーネント --> <v-map ... /> </div>
.map { transition: height 0.5s ease; }
しかし、Mapbox GL JS の地図は描画領域に関する情報を保持していて、CSS アニメーションだけでは地図本体が追従しません。 実際に行ってみると、以下の図のように地図を囲む要素の大きさは変わるものの、地図自体の描画領域は変化しません。
地図の描画領域も同時に動かすためには、resize()
関数を用いて随時地図領域を更新する必要があります。
今回は、地図の外枠に対して transition でアニメーションを行いつつ、その間中に resize()
を実行し続けることで対応しました。
以下に Vue3 での実装例を挙げています:
<template> <main> <div class="map" :class="mapSize" @transitionend="onTransitionend"> <v-map ref="map" ... /> </div> <v-button @click="onClickMapResizeButton">サイズ変更</v-button> </main> </template> <script setup> import { ref } from 'vue'; const map = ref(null); const mapSize = ref('map-size-normal'); const mapResizer; const onClickMapResizeButton = () => { mapSize.value = 'map-size-large'; mapResizer = setInterval(() => { map.value.resize(); }); }; const onTransitionend = () => { clearInterval(mapResizer); }; </script> <style> .map { transition: height 0.5s ease; } .map-size-normal { height: 50px; } .map-size-large { height: 100px; } </style>
clearInterval に届かない可能性を考える
この実装の懸念点は、setInterval()
から clearInterval()
までに別のイベント( onTransitionend
)を経由するため、clearInterval()
が必ず実行される保証がないことです。
たとえば、一度「サイズ変更ボタン」を押した後、 transition の途中で「サイズ変更ボタン」をもう一度押してしまうと、onTransitionend
が発火しないまま新しく setInterval()
が実行されてしまいます。
これによって、最初の setInterval()
の intervalID
が失われてしまい、インターバルの処理が終わらなくなってしまう可能性があります。
この問題への対応策として、transition が中断する可能性のあるアクションの前に clearInterval()
を実行します。
const onClickMapResizeButton = () => { mapSize.value = "map-size-large" clearInterval(mapResizer) mapResizer = setInterval(() => { map.value.resize() }) }
まとめ
今回紹介した方法では、setInterval()
を使用して resize()
関数を繰り返し実行することで、地図のサイズ変更アニメーションを実現しました。
ただし、この方法では clearInterval()
が常に適切に実行される保証がないため、使う際には注意が必要です。
参考サイト
- Mapbox GL JS | Mapbox. (https://docs.mapbox.com/mapbox-gl-js)
- javascript - MapBox Smooth Transition of Resizing Map - Stack Overflow. (https://stackoverflow.com/questions/61490901/mapbox-smooth-transition-of-resizing-map)