Mapboxの地図を表示、非表示すると地図のwidth、heightが意図した通りに動かなかったので整理をした。
起こった現象
最初に表示するページでは、Mapboxの地図を非表示(display:none)にして、特定ページに遷移したタイミングで表示(display:block)しようとしていた。
コードではstyleにプロパティを設定し、切り替える想定をしている。
import Map from 'react-map-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
import { useEffect, useState } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
/**
* Mapbox
*/
const Mapbox2 = () => {
const [mapDisplay, setMapDisplay] = useState<boolean>(false)
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
const showPage = ['/test-page']
if (showPage.includes(pathname)) {
setMapDisplay(true)
} else {
setMapDisplay(false)
}
}, [pathname, searchParams])
return (
<Map
id='map'
initialViewState={{
longitude: 139.636814,
latitude: 35.443098,
zoom: 15
}}
style={{ width: '100%', height: '100vh', display: mapDisplay ? 'block' : 'none' }}
mapStyle={'mapbox://styles/xxx/yyy'}
mapboxAccessToken={"Mapbox Access Token"}
/>
)
}
export default Mapbox2
該当ページには、next/linkを使ってsingle page applicationの遷移を実現しようとしていた。
しかしながら、ページ遷移後、Mapboxの地図はheightに100vhを指定してても常にwidth:400、height:300で表示された。
ブラウザのリサイズが起こるとstyleの設定通りになることも確認できた。
Mapboxのresizeイベントを呼び出すことで解決
結論、Mapboxのresizeイベントで解決します。
https://docs.mapbox.com/mapbox-gl-js/api/map/#map#resize
ただし、Nextjsでreact-map-glを使っている場合は、ライフサイクルを意識しながら実装をする必要がありました。
単純にresize()イベントを呼ぶだけではマップサイズは変わりませんでした。
調査していくと、地図ロードが完了したタイミングで呼び出せばresizeイベントが動作することがわかりました。
コードの全体像はこちらです。
import Map, { useMap } from 'react-map-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
import { useEffect, useState } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
/**
* Mapbox
*/
const Mapbox2 = () => {
const { map } = useMap()
// 地図の表示非表示制御
const [mapDisplay, setMapDisplay] = useState<boolean>(false)
const pathname = usePathname()
const searchParams = useSearchParams()
// 地図がロードされたタイミングでresize
const mapLoaded = () => {
const set_interval_id = setInterval(mapResize, 200)
function mapResize() {
if (map?.loaded()) {
map.resize()
clearInterval(set_interval_id)
}
}
}
useEffect(() => {
const showPage = ['/test-page']
if (showPage.includes(pathname)) {
setMapDisplay(true)
// ここで呼び出してもresizeされない
// map?.resize()
// mapのloadedのタイミング後にresizeイベントが走る
mapLoaded()
} else {
setMapDisplay(false)
}
}, [pathname, searchParams])
return (
<Map
id='map'
initialViewState={{
longitude: 139.636814,
latitude: 35.443098,
zoom: 15
}}
style={{ width: '100%', height: '100vh', display: mapDisplay ? 'block' : 'none' }}
mapStyle={'mapbox://styles/xxx/yyy'}
mapboxAccessToken={"Mapbox Access Token"}
/>
)
}
export default Mapbox2
mapLoaded関数を作成して、map?.loaded()が完了したらresie()を起こすようにします。
この実装で、next/linkを利用した画面遷移でも、Mapboxの地図が設定したstyleで表示されるようになります。
Summary
わざわざresizeイベントを起こさなくても、ページごとでMapboxを呼べば解決なんですが、呼ぶたびにnewされることが課題でした。
呼ぶたびにnewする=Mapboxのロード数にあたります。
ページ遷移するたびにロード数が上がると、Mapbox GL JSの月間ロード数が累乗的に上がり、従量課金の金額があがります。
そのため、ロード数を考慮しながら設計する必要がありました。
一度newしてしまえば再描画が走らない限り12時間は1ロードとカウントされ、使えるようになります。
無料枠が多いので使い勝手は良いですが、大規模になってくるとこの辺を考慮したほうが良さそうです。