Anpassen des MapLibre-Navigation-Control für verschiedene Geräte und Bildschirmgrößen
In diesem Beitrag überlege ich, wie ich das Navigation Control bzw. das Zoom Control von MapLibre in verschiedenen Szenarien anpassen kann – zum Beispiel abhängig davon, ob ein Zeiger vorhanden ist oder wie groß der Bildschirm ist. Außerdem stelle ich mir die Frage, ob ich dafür CSS oder JavaScript einsetzen sollte.
Mein Ziel ist es, das Navigation Control nur auf Geräten anzuzeigen, auf denen es sinnvoll und benutzerfreundlich ist. Da ich in diesem Bereich noch wenig Erfahrung habe, freue ich mich über Meinungen, Feedback oder Verbesserungsvorschläge.
Navigation Control oder Zoom Control in MapLibre
Abschnitt betitelt „Navigation Control oder Zoom Control in MapLibre“Ich beginne mit einem einfachen Beispiel:
<!DOCTYPE html><html lang="de"> <head> <title>Demo Navigation Control</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Demo Navication Control 1"> <link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet" > <script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js" ></script> <link rel="stylesheet" href="index.css"> <script type="module" src="index.js" defer></script> </head> <body> <div id="map"></div> </body></html>const map = new maplibregl.Map({ container: "map", center: [12, 50], zoom: 6, style: "https://tiles.versatiles.org/assets/styles/colorful/style.json",});
map.addControl(new maplibregl.NavigationControl({}));body { margin: 0; padding: 0;}
html,body,#map { height: 100%;}
Dieser Code bindet eine einfache MapLibre-Karte mit Navigation Control ein. Das Navigation Control ist jedoch nur wirklich sinnvoll, wenn das Gerät, an dem ich arbeite, einen Zeiger unterstützt, mit dem man die Buttons hovern und anklicken kann. Unter Umständen stört es auf schmalen Screens und möchte es lieber nicht nutzen.
CSS oder JavaScript
Abschnitt betitelt „CSS oder JavaScript“Als erstes frage ich mich: Soll ich dieses Problem über JavaScript oder CSS lösen?
Wenn man in MapLibre GL JS das NavigationControl nur auf Geräten mit Maus bzw. Hover einblenden möchte, betrifft das meiner Ansicht nach eher die Funktionalität als das Design.
- Design beschreibt aus meiner Sicht, wie etwas aussieht – Größe, Farben, Layout, Position.
- Funktion beschreibt, wie sich etwas verhält – wann ein Control vorhanden ist, wer es nutzen kann, welche Interaktionen möglich sind.
Die JavaScript-Zeile
map.addControl(new maplibregl.NavigationControl({}));macht mehr, als nur ein paar HTML-Elemente im DOM einzublenden, die man per CSS ausblenden könnte:
@media (max-width: 768px) { .maplibregl-ctrl-compass, .maplibregl-ctrl-zoom-out, .maplibregl-ctrl-zoom-in { display: none !important; }}Die Elemente existieren im DOM und binden Events, auch wenn sie unsichtbar sind.
Natürlich gibt es zwei Seiten. Ein Designer argumentiert sicher, dass die Platzierung und Sichtbarkeit des Controls Teil des Gesamtbildes ist. Außerdem kann es sinnvoll sein, CSS zu verwenden, weil es automatisch auf Änderungen reagiert – z. B. wenn die Bildschirmbreite sich ändert oder später eine USB-Maus angeschlossen wird. Im JavaScript muss sich selbst darum kümmern, wenn man diese Aktionen abfragen will.
Deshalb lohnt es sich, beide Ansätze genauer zu betrachten.
Wenn es darum geht, die Controls abhängig vom Eingabetyp (Touch vs. Maus) zu zeigen, sollte any-hover verwendet werden. Wenn es um das Layout und den verfügbaren Platz auf dem Bildschirm geht, sollte die Bildschirmbreite verwendet werden. Möglich ist die Kombination aus beiden. Dann werden die Controls nur auf großen Bildschirmen und mit Hover-Funktion angezeigt. Aber der Reihe nach.
Bildschirmbreite
Abschnitt betitelt „Bildschirmbreite“Zunächst überlege ich, in Maplibre das gesamte Control rechts oben auszublenden. Dann könnte ich dort allerdings keine weiteren Controls einfügen, ohne dass diese ebenfalls tangiert werden. Deshalb blende ich alle drei Buttons gezielt aus.
@media (max-width: 768px) { .maplibregl-ctrl-compass, .maplibregl-ctrl-zoom-out, .maplibregl-ctrl-zoom-in { display: none !important; }}Meiner Meinung nach ist das der beste CSS-only Ansatz. Ein bisschen Bauchweh habe ich bei der Verwendung von !important, aber in meinem Fall funktioniert es nur so. Ich bin mir nicht ganz sicher, warum das so ist, denn ich kann keine Inline-Styles finden. Ich vermute, dass mein CSS beim Aufruf von map.addControl(new maplibregl.NavigationControl({})) überschrieben wird.
body { margin: 0; padding: 0;}
html,body,#map { height: 100%;}
@media (max-width: 768px) { .maplibregl-ctrl-compass, .maplibregl-ctrl-zoom-out, .maplibregl-ctrl-zoom-in { display: none !important; }}Wenn es mir nicht nur darum geht, die Controls auf schmalen Displays aus Platzgründen auszublenden, sondern ich sie nur anzeigen möchte, wenn ein Pointer oder eine Maus vorhanden ist und kein Touch, dann nutze ich:
@media (any-hover: none), (pointer: coarse) { .maplibregl-ctrl-compass, .maplibregl-ctrl-zoom-out, .maplibregl-ctrl-zoom-in { display: none !important; }}Ich finde, dass Smashing Magazine den Grund warum es diese Media-Queries gibt sehr anschaulich erklärt.
Die CSS-Media-Query any-hover wird verwendet, um zu prüfen, ob ein verfügbares Eingabegerät in der Lage ist, über Elemente zu hovern (also den Mauszeiger oder Finger über ein Element zu bewegen).
Die pointer Media-Query überprüft, ob der Benutzer ein Zeigegerät wie eine Maus oder ein Touchpad verwendet und gibt an, wie präzise das primäre Zeigegerät ist.
Die Kombination
@media (any-hover: none), (pointer: coarse)prüft, ob mindestens eine der beiden Bedingungen zutrifft:
- Kein Eingabegerät unterstützt Hover: Typisch für Smartphones oder reine Touch-Geräte.
- Das primäre Zeigegerät ist grob: Zum Beispiel Touchscreens, bei denen eine pixelgenaue Steuerung nicht möglich ist und Navigation hauptsächlich über den Finger erfolgt.
Gerät und Bildschirmgröße
Abschnitt betitelt „Gerät und Bildschirmgröße“Die letzte Query kombieniert die beiden vorhergehenden Abschnitte:
@media (any-hover: none), (pointer: coarse), (max-width: 768px) { .maplibregl-ctrl-compass, .maplibregl-ctrl-zoom-out, .maplibregl-ctrl-zoom-in { display: none !important; }}JavaScript
Abschnitt betitelt „JavaScript“Wenn man das MapLibre-NavigationControl nur unter bestimmten Bedingungen anzeigen möchte – zum Beispiel abhängig von der Bildschirmbreite oder ob eine Maus angeschlossen ist – lässt sich das direkt mit JavaScript steuern.
if ( window.matchMedia("(any-hover: hover)").matches && !window.matchMedia("(pointer: coarse)").matches && window.matchMedia("(min-width: 769px)").matches) { map.addControl(new maplibregl.NavigationControl());}So vermeidet man CSS-Hacks wie display: none, bei denen das Control trotzdem initialisiert wird und Events bindet. Stattdessen wird das Control wirklich nur dann zur Karte hinzugefügt, wenn es benötigt wird.
Der bisherige Ansatz reagiert jedoch nicht dynamisch auf Änderungen. Das lässt sich ebenfalls mit JavaScript realisieren:
const navigationControl = new maplibregl.NavigationControl();
function updateNavigationControl() { const shouldShowNavigation = window.matchMedia("(any-hover: hover)").matches && !window.matchMedia("(pointer: coarse)").matches && window.matchMedia("(min-width: 769px)").matches;
const isAdded = navigationControl._container?.parentNode !== null;
if (shouldShowNavigation && !isAdded) { map.addControl(navigationControl, "top-right"); } else if (!shouldShowNavigation && isAdded) { map.removeControl(navigationControl); }}
updateNavigationControl();
window.matchMedia("(min-width: 769px)").addEventListener("change", updateNavigationControl);window.matchMedia("(any-hover: hover)").addEventListener("change", updateNavigationControl);window.matchMedia("(pointer: coarse)").addEventListener("change", updateNavigationControl);Gleich nach dem Laden wird geprüft, ob das Control angezeigt werden soll. Mit window.matchMedia werden Geräte-Eigenschaften abgefragt, z. B.:
- Bildschirmbreite
- Eingabemethode (Maus oder Touch)
Über addEventListener("change", …) reagiert der Code dynamisch auf:
- Fenstergröße (Resize, Orientation Change)
- Anschluss oder Entfernung einer Maus
- Wechsel zwischen Touch- und Maus-Eingabe
Die Zeile
const isAdded = navigationControl._container?.parentNode !== null;prüft direkt im DOM, ob das Control gerade in der Map aktiv ist. Das funktioniert meiner Erfahrung nach zuverlässig. Allerdings ist _container eine interne Property von MapLibre und nicht offiziell dokumentiert. Wer komplett auf interne Properties verzichten möchte, kann stattdessen ein eigenes Flag/eine eigene Variable verwenden.
touchZoomRotate: false` darf nicht gesetzt sein, weil sonst unter Umständen kein Zoomen mehr möglich ist.