[Mapbox]地図表示非表示の切り替えで、canvas横幅高さがデフォルト400px/300pxになる件を解決する

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ロードとカウントされ、使えるようになります。

無料枠が多いので使い勝手は良いですが、大規模になってくるとこの辺を考慮したほうが良さそうです。

よかったらシェアしてね!
目次