いきなり日曜大工

好きなことを好きなだけやる

Leaflet 拡張基礎編 #5 L.Control + 全画面表示切替

地図の全画面表示コントロール

前回 L.Control のサブクラス作成と使い方について見たので、今回は実装編です。

今回は地図の全画面表示切替機能を作ります。地図画面上にボタンを表示させ、そのボタンをスイッチとして地図を全画面表示させたり抜けたりするものです。全画面表示の切り替え部分は Fullscreen API を使います。

ゴールのイメージ

  • 地図画面上に「全画面表示にする」ボタンがある
  • デフォルト(全画面表示中でない)の状態でボタンを押すと地図が全画面表示になり、ボタンは「全画面表示を抜ける」ものに変わる
  • 全画面表示中にボタンを押すと地図の全画面表示モードを抜け、ボタンは「全画面表示する」ものに変わる

Fullscreen API

それでは Fullscreen API について調べます。

概要

Fullscreen API も History API と同じく所謂 HTML5 に含まれる比較的新しい機能で、指定した DOM 要素を全画面表示させたり抜けたりする機能を提供します。また表示の切り替え時にはイベントを発火します。

プロパティdocument.fullscreenEnabledFullscreen API が利用可能か
プロパティdocument.fullscreenElement全画面表示中の DOM 要素
メソッドelement.requestFullscreen()DOM 要素を全画面表示
メソッドdocument.exitFullscreen()全画面表示を抜ける
イベントfullscreenchange全画面表示に入る・抜けると発火
イベントfullscreenerror全画面表示に失敗すると発火

Leaflet でこの機能を使うとしたら、普通は地図コンテナ部分を全画面化することになると思います。地図コンテナの DOM 要素は以下のようなやり方で取得できます。

// 地図全体を含む div の取得(map は L.Map のインスタンス)
const mapDiv = map.getContainer();

非常にシンプルな動作テストとして、以前作った Hello, world のサンプルを改造して地図をクリックすると全画面表示を切り替えるようなものを作ってみました。

// 地図上をクリックしたときの処理
map.on('click', function() {
    if (document.fullscreenEnabled) {
        if (document.fullscreenElement) {
            // 全画面表示中
            document.exitFullscreen();   // 全画面表示を抜ける
        } else {
            // 全画面表示中でない
            const mapDiv = map.getContainer();
            mapDiv.requestFullscreen();  // 全画面表示をリクエスト
        }
    }
});

Leaflet Demo – Fullscreen API #1

このサンプルはたぶん Firefox と Google Chrome の最近のものでしか動かないでしょう。他のブラウザについては次の「ベンダプレフィクス」での話になります。

ベンダプレフィクス

実装にあたっては、ブラウザごとにベンダプレフィクスを考慮する必要があります。 少し前までは全ての主要ブラウザでベンダプレフィクスが必要でしたが、最近リリースされた Firefox 64 および Google Chrome 71 でそれぞれ Fullscreen API のベンダプレフィクスがとれ、現在は以下のようになりました。

Firefoxなし(最近まで moz)
Google Chromeなし(最近まで webkit)
Safariwebkit
IEms
Edgewebkit

大体 API の各プロパティにベンダプレフィクスを付ければ(付けるときはもとのプロパティとメソッドの頭は大文字にする)使えますが、IE の場合イベントが MSFullscreenChange、MSFullscreenError とさらに所々大文字になります。他にも古い Safari の場合とかあるみたいで詳しくは こちら が参考になります。

さきほど作ったサンプルに webkit 系を考慮したバージョンの一例です。

// 地図上をクリックしたときの処理
map.on('click', function() {
    if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
        if (document.fullscreenElement || document.webkitFullscreenElement) {
            // 全画面表示中
            if (document.fullscreenElement) {
                document.exitFullscreen();
            } else if (document.webkitFullscreenElement) {
                document.webkitExitFullscreen();
            }
        } else {
            // 全画面表示中でない
            const mapDiv = map.getContainer();
            if (mapDiv.requestFullscreen) {
                mapDiv.requestFullscreen();
            } else if (mapDiv.webkitRequestFullscreen) {
                mapDiv.webkitRequestFullscreen();
            }
        }
    }
});

Leaflet Demo – Fullscreen API #2

多くのブラウザに対応しようとすると書き方はより複雑になりますが、Web で探すといろんな書き方が見つかります。

参考

L.Control + Fullscreen API

では L.Control のサブクラスから Fullscreen API を使う、L.Fullscreen を作ります。

段取りを考える

  1. L.Control のサブクラスを作って全画面表示の切替ボタンを作成
  2. ボタンに Fullscreen API を使った地図コンテナの全画面表示切り替えを紐づける
  3. 全画面表示切替をすると発火するイベントを受けてボタンのアイコン等を状況に応じたものに変更する

L.Control のサブクラスの実体は HTML エレメントなので、この HTML エレメントを生成したときに一緒に各イベント処理も登録します。

複数ブラウザ対応前のあらまし

L.Control のサブクラスを作成し、自分で実装する onAdd は全画面表示の切り替えスイッチに使うボタンの DOM 要素を返します。
ここでクリックで画面表示を切り替える処理をイベントリスナーに登録し、さらに表示が切り替わったときに発火するイベントを受けて行う処理も登録します。

定義しているメソッドは以下のように分けられます。

  • onAdd(), onRemove() – L.Control のサブクラスに自分で実装する
  • イベントリスナーに登録するもの – ボタンクリック時、表示モード切替時(fullscreenchange)
  • _setContent() – 状況に応じてボタンのコンテンツを書き換える
  • Fullscreen API 呼び出し用 – あとで複数ブラウザ対応の処理を追加する時の差分吸収
L.Fullscreen = L.Control.extend({

    onAdd: function(map) {
        const div = L.DomUtil.create('div', 'leaflet-control-fullscreen');
        L.DomEvent.disableClickPropagation(div);
        L.DomEvent.on(div, 'click', this._onClick, this);
        L.DomEvent.on(document, this._fs.fullscreenchange, this._onFullscreenchange, this);
        this._button = div;
        this._setContent();
        return div;
    },

    onRemove: function() {
        L.DomEvent.off(this._button, 'click', this._onClick, this);
        L.DomEvent.off(document, this._fs.fullscreenchange, this._onFullscreenchange, this);
    },

    _onClick: function(e) {
        L.DomEvent.stop(e);
        if (this.isFullscreen()) {
            this.exitFullscreen();
        } else {
            this.requestFullscreen();
        }
    },

    _onFullscreenchange: function() {
        this._setContent();
    },

    _setContent: function() {
        // 状況に応じて切替ボタンのコンテンツを書き換え
    },

    exitFullscreen: function() {
        document.exitFullscreen();
    },

    isFullscreen: function() {
        return !!document.fullscreenElement;
    },

    requestFullscreen: function() {
        const mapDiv = this._map.getContainer();
        mapDiv.requestFullscreen();
    }
});

こんな感じで、あとは複数ブラウザで Fullscreen API を使えるようにする処理や、状況に応じて切替ボタンのコンテンツを書き換える処理などを追加していきます。

試作完成

ところで仮完成したものの挙動を Google Maps と見比べようと本物を見に行ってみたら、いつの間にか Google Maps から全画面表示のアイコンが無くなっているじゃありませんか。 まあもともとブラウザ 100% の表示だからブラウザを全画面化すればいいことだし、いらんっちゃあ確かにいらんのかもしれないのでした。

次回も L.Control 関係になる予定です。