Vue3 + TypeScript で Mapbox の マップ をレスポンシブに画面全体に表示する サンプル を紹介します。 以前紹介した方法では シンプル に実装することを重要視しすぎた結果、汎用性が乏しいものになってしまいました。そこで、今回はより汎用性が高いと思われる サンプルコード を説明、紹介していこうと思います。
macOS: 13.1
"dependencies": {
"@types/mapbox-gl": "^2.7.10",
"mapbox-gl": "^2.13.0",
"vue": "^3.2.47"
},
"devDependencies": {
"@types/node": "^18.14.2",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/tsconfig": "^0.1.3",
"npm-run-all": "^4.1.5",
"typescript": "~4.8.4",
"vite": "^4.1.4",
"vue-tsc": "^1.2.0"
}
Vue3 + TypeScript プロジェクト の作成, Mapbox GL JS インストール
プロジェクト の作成と Mapbox GL JS の インストール は以前の記事と同様です。 ここでは、コマンドを列挙しておきます。 既に作成、インストール 済みの場合は読み飛ばしてください。
% npm init vue@latest
プロジェクト の設定は 以下のように、 TypeScript のみ Yes とし、他はすべて No としています。
✔ Project name: … [プロジェクト名]
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes
初期設定として、以下を実行し、デフォルトのプロジェクトが問題なく動作することを確認します。
% cd プロジェクトディレクトリ
% npm install
% npm run dev
以下のコマンドで mapbox-gl
と @types/mapbox-gl
をインストールします。
% npm install --save mapbox-gl
% npm install --save @types/mapbox-gl
マップ 表示用の コンポーネント の準備
それでは本題の マップ 表示用の コンポーネント を実装していきます。 今回も 新規に作成する MyMap.vue に マップ 表示部分を実装し App.vue から MyMap.vue を呼び出す構成にしたいと思います。 まずは最小限の コード 量で ファイル を作成しておきます。
src/App.vue
今回新規に作成する MyMap.vue
を インポートし、テンプレートに設定します。
<script setup lang="ts">
import MyMap from './components/MyMap.vue'
</script>
<template>
<div>
<MyMap />
</div>
</template>
src/components/MyMap.vue
この段階では、コンポーネントの箱だけを用意するに留めます。
<script setup lang="ts">
</script>
<template></template>
src/main.ts
プロジェクト作成時に デフォルト で src/assets/main.css
をインポートする設定が入っています。今回は混同を防ぐためにコメントアウトすることにします。
import { createApp } from 'vue'
import App from './App.vue'
// import './assets/main.css'
createApp(App).mount('#app')
ここまでで マップ を表示させるための プロジェクト の準備は完了となります。 次に 実際に マップ の表示部分を実装していきましょう。
マップ 表示の実装
Mapbox Access Token の準備
以前の記事同様、Mapbox の アクセストークン を準備します。 以前の記事や公式サイトを参考に準備してください。
マップ 表示 コンポーネント の実装
それでは 本記事の本題である マップ 表示 コンポーネント の実装を進めていきます。MyMap.vue の script タグ に以下の内容を記載していきます。 今回も前回同様 国立競技場 を中心とした マップ を表示していきます。
script
部分は以下のようにします。
<script setup lang="ts">
import { onMounted } from 'vue'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
mapboxgl.accessToken = '<your access token here>'
onMounted(() => {
const mapObj = new mapboxgl.Map({
// 国立競技場
container: 'map', // container ID
style: 'mapbox://styles/mapbox/streets-v11', // style URL
center: [139.714590, 35.678184], // starting position [lng, lat]
zoom: 14 // starting zoom
})
})
</script>
- 4 行目: 公式 サイト の手順に従って mapbox-gl で使用する CSS を インポート します。
- 6 行目: ‘<your access token here>’ の部分には 上述した Default Public Token の値を指定してください。
誤ってアクセストークンを公開してしまわないように、Vite の環境変数を用いる方法が推奨されます。詳細は、以下の記事を参考にしてください。
- 8 行目: Vue3 の ライフサイクルフック の onMounted の タイミング で Map オブジェクト を生成する必要があります。
template
部分は以下のようにします。
<template>
<div class="map-container">
<div id="map"></div>
</div>
</template>
マップ 表示部分を map-container
という要素内に配置していきます。
続いて、呼び出し部分である App.vue に呼び出し処理などを実装していきます。
マップ 呼び出し部分の実装 (template in App.vue)
今回は、以下のような階層でオブジェクトを配置していきます。
階層 | オブジェクト | 説明 |
---|---|---|
1 | div | Vue.js アプリ配置場所 |
2 | div class="container" | Flexbox を使ったコンテナ |
3 | MyMap | MyMap コンポーネント配置場所 |
template
部分の説明<template>
<div>
<div class="container">
<MyMap />
</div>
</div>
</template>
マップ 呼び出し部分の実装 (style in App.vue)
以前の記事と大きく異なるのがこの部分です。 具体的には、 CSS をしっかり活用して実用性のあるコードにしています。 app
, container
, content
という階層の中に MyMap
を配置していますので、それぞれの オブジェクトごとに CSS を定義していきます。
#app
の CSS
以下のように実装します。
#app {
min-height: calc(100vh - 40px);
min-width: calc(100vw - 40px);
display: flex;
justify-content: center;
align-items: center;
position: relative;
box-sizing: border-box;
}
なお、 なぜ #app
のCSSを定義するかというと、以下のように プロジェクトルートディレクトリに存在する index.html
に以下の記載があり、<div id="app">
と定義しているからです。
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
min-height: calc(100vh - 40px), min-width: calc(100vw - 40px)
この設定で、#app
の表示領域を画面全体を基準に少しだけ (40px) 小さいものにしています。
display: flex
表示を整えるために CSS Flex Box Layout を活用することにします。
アイテムにサイズがない場合は、コンテンツのサイズがフレックスベースとして使用されます。このため、親にdisplay: flexを宣言してフレックスアイテムを作成すると、アイテムはすべて一列になり、コンテンツを表示するのに必要なだけのスペースを確保することができます。
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox
DEEPLによる翻訳
justify-content: center
justify-content
は Flexbox で利用可能な パラメータ の一つですが、だんだんこの辺りから難しくなってくるので、少し関連情報を整理します。
The justify-content property aligns flex items along the main axis of the current line of the flex container.
https://www.w3.org/TR/css-flexbox-1/#justify-content-property
https://www.w3.org/TR/css-flexbox-1/images/flex-direction-terms.svg An illustration of the various directions and sizing terms as applied to a row flex container.
justify-content
は main axis
に沿って、コンテナを整頓させることができるということです。 つまり、上の図で言うところの、赤線の右方向、つまり水平方向に整える効果があるということですね。
今回は center
を指定して、 左右中央に配置させています。
align-items: center
align-itmes
も Flexbox で利用可能な パラーメータ の一つで、cross axis
に沿って整列させることができます。つまり、先ほどの図で言うところの垂直方向を整える効果があります。
The CSS
https://developer.mozilla.org/en-US/docs/Web/CSS/align-itemsalign-items
property sets thealign-self
value on all direct children as a group. In Flexbox, it controls the alignment of items on the Cross Axis.
今回は center
を指定して、上下中央に配置させています。
position: relative
今回、 position: relative
設定は #app
の見た目に直接の影響を与えませんが、 #app
の中に 表示させたい マップ を相対位置で配置したいため、設定しています。詳細は .container
で触れます。
参考:
box-sizing: border-box
最後に、 box-sizing
を用いて汎用的にオブジェクトの配置を整えていきまます。 border-box
を指定することで、各オブジェクトのサイズを計算する際に、パディング (padding) や枠線(border)、マージン(margin) を細かく考慮する必要がないため、とても便利な設定です。
box-sizing
の設定は他にもありますが、以下にもあるように、box-sizing
は border-box
を指定するのが一般的のようですので、ここでは border-box
を採用することにします。
It is often useful to set
https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizingbox-sizing
toborder-box
to lay out elements. This makes dealing with the sizes of elements much easier, and generally eliminates a number of pitfalls you can stumble on while laying out your content.
参考:
ここまでの #app
の状態
ここまで設定したCSSで#app
がどのようになっているのか確認してみます。 確認のため、以下のコードを追記し、実際に見てみます。
* MyMap
呼び出し部分は コメントアウト しています。
border: 4px dashed pink;
min-width
, min-height
で調整した分だけ余白を残して画面全体に広がっていることがわかります。
min-width
, min-height
で調整した分だけ余白を残して画面全体に広がっていることがわかります。
.container
の CSS
続いて、 .container
の CSS を追加していきます。
.container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
position: absolute, top: 0, left: 0
#app
で position: relative
を設定したのは、ここでこの設定を利用したいためでした。
こうすることで container
は常に #app
の表示領域と同じ top
, left
を起点に配置されるため、 #app
内に綺麗におさまるようになります。
width: 100%, height: 100%
この設定で、container
の領域を #app
と同じサイズにすることができます。
ここまでの #app
, container
の状態
それでは、実際にどのように オブジェクト が配置されているか確認してみます。
#app
に追記した border
の設定を削除し、 container
の スタイル に以下を追記して表示してみます。
border: 4px solid green;
ピンクの点線で示した #app
の内側いっぱいに グリーンの実線でしめした .container
が配置されていることがわかります。 box-sizing: border-box
を用いているため、これらの border
の線の太さも自動調整してくれています。
マップ 表示部分の実装 (style in MyMap.vue)
最後に、 MyMap.vue
内に Mapbox
用の スタイル を定義していきます。
.map-container
マップオブジェクト を格納する コンテナ 部分の設定をしていきます。 呼び出し元 (App.vue) で定義した #app
と似たような設定にしていきます。
.map-container {
display: flex;
justify-content: center;
box-sizing: border-box;
align-items: center;
position: relative;
width: 100%;
height: 100%;
}
display: flex
, justify-contet: center
, align-items: center
, box-sizing: border-box
配置するマップのスタイルや個数が変化する場合に備え、#app
同様の設定を施しています。
positon: relative
#app
と同様の理由で relative
設定にしています。
width: 100%
, height: 100%
この設定によって、呼び出し元の #app
と同じサイズの領域を確保します。
ここまでの オブジェクトの状態
それでは、ここでも以下のコードを追記して、実際の配置を確認していきます。
* <div id="map">
部分を コメントアウト しています。
border: 4px dashed red;
順調に、オブジェクトの配置が進んでいることがわかります。
map
の CSS
最後に、 Mapbox
オブジェクト配置用のスタイルを定義していきます。 App.vue
の .container
と全く同じ設定にしています。
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
以下のように マップ が表示されることを確認できます。
最後に、各所に設定した border
を削除して完成です。
レスポンシブ の確認
最後に各種解像度で期待通りに レスポンシブ となっているか確認してみます。
iPhone SE
iPad Mini
Nest Hub Max
まとめ
App.vue
, MyMap.vue
それぞれで オブジェクト の スタイル を定義することで、意図通りの配置を実現する
#app
はdisplay: flex
に設定し、 Flexbox を活用し、position: relative
とすることで、子コンポーネントを相対位置で配置できるようにする- .
container
はposition: absolute
で 親コンポーネント#app
の内部に配置 .map-container
は#app
同様の設定- #
map
は.container
同様の設定
考察
親子構造を使うと、綺麗に配置できる反面、親オブジェクトのスタイルが子オブジェクトに影響してくるので適切な場所でスタイルを定義する必要があり、難しい部分ですね。
今回使用したコード
src
/App.vue
<script setup lang="ts">
import MyMap from './components/MyMap.vue'
</script>
<template>
<div>
<div class="container">
<MyMap />
</div>
</div>
</template>
<style>
#app {
min-width: calc(100vw - 40px);
min-height: calc(100vh - 40px);
display: flex;
justify-content: center;
align-items: center;
position: relative;
box-sizing: border-box;
}
.container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
</style>
src
/components
/MyMap.vue
<script setup lang="ts">
import { onMounted } from 'vue'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
mapboxgl.accessToken = '<your access token here>'
onMounted(() => {
const mapObj = new mapboxgl.Map({
// 国立競技場
container: 'map', // container ID
style: 'mapbox://styles/mapbox/streets-v11', // style URL
center: [139.714590, 35.678184], // starting position [lng, lat]
zoom: 14 // starting zoom
})
})
</script>
<template>
<div class="map-container">
<div id="map"></div>
</div>
</template>
<style>
.map-container {
display: flex;
justify-content: center;
box-sizing: border-box;
align-items: center;
position: relative;
width: 100%;
height: 100%;
}
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
</style>