パントレ開発部

【Leaflet/MapLibre】Gesture handling を実装する

Map

はじめに

 Gesture handling とは、マップをスマートフォンで閲覧する際に2本指でしかスクロールできないようにしたり、PCで閲覧する際にCtrlキーを押さないとズームできないようにする機能の事です。今回は、この Gesture handling 機能を Leaflet/MapLibre で実装する方法について説明したいと思います。

 なお、当サイト(Leaflet Maps Maker)で Leaflet のプログラムを生成した場合、Gesture handling を自動で実装しています(OFFにすることもできます)。使い方はこちらにあるので、興味のある方は使ってみてください。

 

Leaflet で実装する

 Leaflet の場合はプラグインを使って、Gesture handling を実装することが出来ます。

 Leaflet.GestureHandling の JavaScript および CSS は GitHub にあります。なお、サーバーにダウンロードしておかずとも、下記のファイルパスで直接アクセスも出来ます。

<link rel="stylesheet" href="//unpkg.com/leaflet-gesture-handling/dist/leaflet-gesture-handling.min.css" type="text/css">
<script src="//unpkg.com/leaflet-gesture-handling"></script>

 全体のソースコードと実装例は以下の通りです。

<!DOCTYPE html>
<html>
<head>
<title>htmlMap</title>
<meta http-equiv='content-type' charset='utf-8'>
<meta name='viewport' content='width=device-width'>
</head>
<body>
<!-- 埋め込みマップのdivタグ。マップサイズはwidth(幅)とheight(高さ)で決まる -->
<div id='mapcontainer' style='width:100%; height:300px; z-index:0;'></div>
<!-- 以下LeafletのJavaScriptとCSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<!-- Gesture handling のプラグイン -->
<link rel="stylesheet" href="//unpkg.com/leaflet-gesture-handling/dist/leaflet-gesture-handling.min.css" type="text/css">
<script src="//unpkg.com/leaflet-gesture-handling"></script>

<script>
function init_map() {
    const map = L.map('mapcontainer', {
        //Gesture handling を ON
        gestureHandling: true
    });

    //初期の中心位置とズームレベルを設定
    map.setView([43.66, 142.146], 6); 

    //マップタイルを読み込み、引用元を記載する
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
        {attribution: "<a href='http://osm.org/copyright'> ©OpenStreetMap </a>"}
    ).addTo(map);
}

//ダウンロード時に初期化する
window.addEventListener('DOMContentLoaded', init_map());

</script>
</body>
</html>

実装例

 

MapLibre で実装する

 MapLibre の場合、プラグインを追加しなくても Gesture handling を ON にできます。なお、MapLibre では Cooperative gestures と呼ばれているようです。

const map = new maplibregl.Map({
    container: 'mapcontainer',
    style: 'https://gsi-cyberjapan.github.io/gsivectortile-mapbox-gl-js/std.json',
    center: [142.14, 43.65], 
    cooperativeGestures: true,
    locale: {
        'CooperativeGesturesHandler.WindowsHelpText': '地図をズームするには、Ctrl キーを押しながらスクロールして下さい',
        'CooperativeGesturesHandler.MacHelpText': '地図をズームするには、Command キーを押しながらスクロールして下さい',
        'CooperativeGesturesHandler.MobileHelpText': '地図を移動させるには指2本で操作します'
    },
    zoom: 5, 
    pitch: 0 
});

 全体のソースコードと実装例は以下の通りです。

<!DOCTYPE html>
<html>
<head>
<title>htmlMap</title>
<meta http-equiv='content-type' charset='utf-8'>
<meta name='viewport' content='width=device-width'>
</head>
<body>
<!-- 埋め込みマップのdivタグ。マップサイズはwidth(幅)とheight(高さ)で決まる -->
<div id='mapcontainer' style='width:100%; height:300px; z-index:0; border:1px solid #333;'></div>

<!-- 以下 MapLibre のJavaScriptとCSS -->
<link rel='stylesheet' href='https://unpkg.com/maplibre-gl/dist/maplibre-gl.css' />
<script src='https://unpkg.com/maplibre-gl/dist/maplibre-gl.js'></script>

<script>
function init_map() {
    const map = new maplibregl.Map({
        container: 'mapcontainer',
        style: 'https://gsi-cyberjapan.github.io/gsivectortile-mapbox-gl-js/std.json',
        center: [142.14, 43.65], 
        cooperativeGestures: true,
        locale: {
            'CooperativeGesturesHandler.WindowsHelpText': '地図をズームするには、Ctrl キーを押しながらスクロールして下さい',
            'CooperativeGesturesHandler.MacHelpText': '地図をズームするには、Command キーを押しながらスクロールして下さい',
            'CooperativeGesturesHandler.MobileHelpText': '地図を移動させるには指2本で操作します'
        },
        zoom: 5, 
        pitch: 0 
    });
}

//ダウンロード時に初期化する
window.addEventListener('DOMContentLoaded', init_map());

</script>
</body>
</html>

実装例

  

JavaScript で自作する

 JavaScript で自作 Gesture handling を実装するには、マップの上からマスクとメッセージの要素を作成しておいて、イベントに応じて表示と非表示を切り替えることによって実現します。

 まず、HTML と CSS は以下のようになります。

<div id='container'>
    <div id='mapcontainer'></div>
    <div id='mapmask' class='mapmask'>
        <div id='mapmessage'></div>
    </div>
</div>
#container{
    position:relative; 
}

#mapcontainer{
    width:100%;
    height:300px;
    z-index:0;
}

.mapmask{
    width:100%;
    height:300px;
    position:absolute;
    top:0; 
    left:0;
    z-index:1; 
    pointer-events:none;
    background:rgba(0, 0, 0, 0.5);
    opacity:0; 
    transition:0.5s; 
}

.mapshow{
    opacity:1;
}

#mapmessage{
    width:90%;
    margin-left:5%;
    position:absolute;
    text-align:center;
    top:50%;
    -webkit-transform:translateY(-50%);
    -ms-transform:translateY(-50%);
    transform:translateY(-50%);
    color:#ffffff;
}

 イベントを付加する JavaScript は以下のようになります。なお、例では Leaflet になっていますが、基本は MapLibre や OpenLayers の場合も同じです。

function init_map() {

    //マップを表示する
    const map = L.map('mapcontainer',{
        center: [43.659, 142.141], 
        zoom: 6, 
        dragging: false,                 
        scrollWheelZoom: false                    
    });

    //マップタイルを読み込み、引用元を記載する
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: "<a href='http://osm.org/copyright'> ©OpenStreetMap</a> contributors"
    }).addTo(map);

    //ブラウザの言語を取得する
    const MaskLanguage = (window.navigator.languages&&window.navigator.languages[0])
        ||window.navigator.language
        ||window.navigator.userLanguage
        ||window.navigator.browserLanguage;
    
    let ctrl_down = 0;
    let Timeout;

    //キーボードのキーイベント
    document.body.addEventListener('keydown', function(e){
        if(e.ctrlKey||e.metaKey){ 
            ctrl_down = 1; //Ctrl もしくは command キーを押した場合、ctrl_down = 1 とする
            document.getElementById('mapmask').classList.remove('mapshow'); //マスクを表示する
        }
    });

    document.body.addEventListener('keyup', function(e){
        ctrl_down = 0; //キーを上げた場合、ctrl_down = 0 とする
    });

    //地図のマウスホイールイベント
    document.getElementById('mapcontainer').addEventListener('wheel', function(e){
        //ブラウザの言語に合わせて、メッセージの表示内容を切り替える
        if((MaskLanguage=='ja')||(MaskLanguage=='ja-JP')||(MaskLanguage=='ja-JP-mac')){
            document.getElementById('mapmessage').innerText = '地図をズームするには、Ctrl (Command) キーを押しながらスクロールして下さい';
        }else{
            document.getElementById('mapmessage').innerText = 'Use Ctrl (Command) + Scroll to zoom the map';
        }
        //Ctrlキーを押下している場合の処理
        if(ctrl_down ==1){
            e.returnValue = false; //ブラウザズームを妨害する
            map.scrollWheelZoom.enable(); //スクロールホイールズームを許可する
            document.getElementById('mapmask').classList.remove('mapshow'); //マスクを非表示する
        //Ctrlキーを押下していない場合の処理
        }else{
            clearTimeout(Timeout); //一回リセット
            map.scrollWheelZoom.disable(); //スクロールホイールズームをキャンセルする
            document.getElementById('mapmask').classList.add('mapshow'); //マスクを表示する
            Timeout = setTimeout(function(){
                document.getElementById('mapmask').classList.remove('mapshow'); //3秒後にマスクを非表示にする
            }, 3000);
        }
    })

    //マウスポインタがマップから離れた時のイベント
    document.getElementById('mapcontainer').addEventListener('mouseleave', function(e){
        document.getElementById('mapmask').classList.remove('mapshow'); //マスクを非表示にする
        map.dragging.disable(); //マップのドラッグをキャンセルする
    });

    //マウスポインタがマップ上で動いたときのイベント
    document.getElementById('mapcontainer').addEventListener('mousemove', function(e){
        map.dragging.enable(); //マップのドラッグを許可する
    });

    //マップを指でタッチした時のイベント
    document.getElementById('mapcontainer').addEventListener('touchmove', function(e){
        //ブラウザの言語に合わせて、メッセージの表示内容を切り替える
        if((MaskLanguage=='ja')||(MaskLanguage=='ja-JP')||(MaskLanguage=='ja-JP-mac')){
            document.getElementById('mapmessage').innerText = '地図を移動させるには\n指2本で操作します';
        }else{
            document.getElementById('mapmessage').innerText = 'Use two fingers to move the map';
        }
        //タッチした時の指が二本指だった時の処理
        if(e.touches.length == 2){
            e.preventDefault(); //デフォルトのイベントを妨害する
            map.dragging.enable(); //マップのドラッグを許可する 
            if (map.tap) map.tap.enable(); //マップのタップを許可する
            document.getElementById('mapmask').classList.remove('mapshow'); //マスクを非表示にする
        //タッチした時の指の本数が二本以外の処理
        }else{
            clearTimeout(Timeout); //一回リセット
            map.dragging.disable(); //マップのドラッグをキャンセルする 
            document.getElementById('mapmask').classList.add('mapshow'); //マスクを表示する
            Timeout = setTimeout(function(){
                document.getElementById('mapmask').classList.remove('mapshow'); //3秒後にマスクを非表示にする
            }, 3000);
        }
    })
}

window.addEventListener('DOMContentLoaded', init_map());

実装例

 

関連記事

 GUI で Leaflet の HTML/JavaScript を生成するツールはこちら。コード書くのがめんどくさいな~って人は使ってみてください。WordPress の場合、生成されたコードをカスタム HTML に貼り付ければ動きます。Gesture handling をつけることもできます。

 Leaflet、MapLibre、OpenLayers の比較はこちら。

 Leaflet についてはこちら。

 MapLibre についてはこちら。

 

ちょっと宣伝

 当サイトは Web 地図を積極的に用いた海外旅ブログまとめサイトとなっています。トップページに地図がありますが、地名を押すと画像が開き、画像を押すと関連記事一覧(クリック数順)が開きます。記事数が多い国ほど赤く、地名もその国で記事が多い都市の文字が大きくなるようにしています。ぜひ覗いてみてください。
 当サイトで海外旅ブログを執筆することも可能です(もちろん無料です)! また既にブログをお持ちの方も、当サイトからリンクを貼ることができるようになっています。パントレ開発部までお気軽にお問い合わせください。


 このページが皆様のプログラミングの一助となりますことをお祈りいたします

パントレ開発部