aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/ParkMap.astro124
-rw-r--r--src/components/ParkMapHelp.astro2
2 files changed, 111 insertions, 15 deletions
diff --git a/src/components/ParkMap.astro b/src/components/ParkMap.astro
index ac85c0e..4d5541c 100644
--- a/src/components/ParkMap.astro
+++ b/src/components/ParkMap.astro
@@ -32,6 +32,22 @@
border: 0;
border-radius: 0;
}
+ .park-map.is-maximized {
+ position: fixed;
+ inset: 0;
+ width: 100vw;
+ height: 100dvh;
+ margin: 0;
+ border: 0;
+ border-radius: 0;
+ z-index: 10000;
+ }
+ :global(body.park-map-maximized) {
+ overflow: hidden;
+ }
+ :global(.park-map.park-map-touch.leaflet-container) {
+ touch-action: none;
+ }
:global(.park-map-fullscreen button) {
width: 30px;
height: 30px;
@@ -457,7 +473,13 @@
if (el && !el.dataset.initialized) {
el.dataset.initialized = "1";
+ const isTouch =
+ L.Browser.mobile || window.matchMedia("(pointer: coarse)").matches;
const map = L.map(el, {
+ // タッチ端末では1本指のドラッグを無効にし、ページのスクロールを妨げない。
+ // 地図の移動・拡大は2本指(touchZoom)で行う。
+ dragging: !isTouch,
+ touchZoom: true,
scrollWheelZoom: true,
zoomSnap: 1,
zoomDelta: 1,
@@ -466,6 +488,55 @@
markerZoomAnimation: false,
}).setView([35.728, 139.477], 13);
+ // タッチ端末では touch-action: none にして2本指ジェスチャーをLeafletに届け、
+ // 1本指の縦ドラッグはページスクロールとして自前で処理する。
+ if (isTouch) {
+ el.classList.add("park-map-touch");
+ let tracking = false;
+ let lastX = 0;
+ let lastY = 0;
+ el.addEventListener(
+ "touchstart",
+ (e: TouchEvent) => {
+ if (e.touches.length !== 1) {
+ tracking = false;
+ return;
+ }
+ const target = e.target as Element | null;
+ if (target && target.closest(".leaflet-control")) {
+ tracking = false;
+ return;
+ }
+ tracking = true;
+ lastX = e.touches[0].clientX;
+ lastY = e.touches[0].clientY;
+ },
+ { passive: true },
+ );
+ el.addEventListener(
+ "touchmove",
+ (e: TouchEvent) => {
+ if (!tracking || e.touches.length !== 1) return;
+ const x = e.touches[0].clientX;
+ const y = e.touches[0].clientY;
+ const dx = x - lastX;
+ const dy = y - lastY;
+ if (Math.abs(dy) >= Math.abs(dx)) {
+ e.preventDefault();
+ window.scrollBy(0, -dy);
+ lastX = x;
+ lastY = y;
+ }
+ },
+ { passive: false },
+ );
+ const stopTracking = () => {
+ tracking = false;
+ };
+ el.addEventListener("touchend", stopTracking);
+ el.addEventListener("touchcancel", stopTracking);
+ }
+
// 帰属表示の「Leaflet」旗アイコンを消す(OSMの表示は残す)。
map.attributionControl.setPrefix(false);
@@ -484,29 +555,54 @@
button.setAttribute("aria-label", "全画面表示");
button.innerHTML = "⛶";
L.DomEvent.disableClickPropagation(container);
+ L.DomEvent.disableScrollPropagation(container);
+
+ const target = map.getContainer();
+ const isCssMax = () => target.classList.contains("is-maximized");
+ const update = () => {
+ const active = document.fullscreenElement === target || isCssMax();
+ button.innerHTML = active ? "✕" : "⛶";
+ button.title = active ? "全画面を終了" : "全画面表示";
+ button.setAttribute("aria-label", button.title);
+ window.setTimeout(() => map.invalidateSize(), 60);
+ };
+ const enterCssMax = () => {
+ target.classList.add("is-maximized");
+ document.body.classList.add("park-map-maximized");
+ update();
+ };
+ const exitCssMax = () => {
+ target.classList.remove("is-maximized");
+ document.body.classList.remove("park-map-maximized");
+ update();
+ };
+
L.DomEvent.on(button, "click", (ev: Event) => {
L.DomEvent.stop(ev);
- const target = map.getContainer();
- if (!document.fullscreenElement) {
- target.requestFullscreen?.().catch(() => {});
+ if (document.fullscreenElement === target) {
+ document.exitFullscreen?.().catch(() => exitCssMax());
+ return;
+ }
+ if (isCssMax()) {
+ exitCssMax();
+ return;
+ }
+ // Fullscreen API が使えない端末(iPhoneなど)はCSSで最大化する。
+ if (document.fullscreenEnabled && target.requestFullscreen) {
+ target.requestFullscreen().catch(() => enterCssMax());
} else {
- document.exitFullscreen?.().catch(() => {});
+ enterCssMax();
}
});
- const update = () => {
- const active = document.fullscreenElement === map.getContainer();
- button.innerHTML = active ? "✕" : "⛶";
- button.title = active ? "全画面を終了" : "全画面表示";
- button.setAttribute("aria-label", active ? "全画面を終了" : "全画面表示");
- map.invalidateSize();
- };
+
document.addEventListener("fullscreenchange", update);
+ document.addEventListener("keydown", (ev) => {
+ if (ev.key === "Escape" && isCssMax()) exitCssMax();
+ });
return container;
},
});
- if (document.fullscreenEnabled) {
- new FullscreenControl({ position: "topright" }).addTo(map);
- }
+ new FullscreenControl({ position: "topright" }).addTo(map);
const layersByName = new Map<string, any>();
const allLayers: any[] = [];
diff --git a/src/components/ParkMapHelp.astro b/src/components/ParkMapHelp.astro
index 4999389..d693a2d 100644
--- a/src/components/ParkMapHelp.astro
+++ b/src/components/ParkMapHelp.astro
@@ -20,7 +20,7 @@
</div>
<div class="park-play-legend" data-play-legend></div>
<p class="park-map-note">
- 公園の範囲を赤、市外の近い公園を青、公共施設を紫(市外はオレンジ)で表示しています。地図を拡大すると見やすくなります。PCでは地図上でスクロールするとズームできます。市外の近い公園のうち、範囲が取得できないものは丸で表示しています。トイレ・水飲み場・時計・消火栓・防火水槽のアイコンは拡大すると表示されます。さらに拡大すると、遊具アイコン(上の凡例)が表示されます。OpenStreetMapに公園として登録されている範囲を表示しているため、すべての公園を網羅しているわけではありません。現在地は端末内でのみ利用し、サーバーには送信しません。地図データ © <a
+ 公園の範囲を赤、市外の近い公園を青、公共施設を紫(市外はオレンジ)で表示しています。地図を拡大すると見やすくなります。PCでは地図上でスクロールするとズームできます。スマホでは1本指でページをスクロールし、2本指で地図を動かせます。市外の近い公園のうち、範囲が取得できないものは丸で表示しています。トイレ・水飲み場・時計・消火栓・防火水槽のアイコンは拡大すると表示されます。さらに拡大すると、遊具アイコン(上の凡例)が表示されます。OpenStreetMapに公園として登録されている範囲を表示しているため、すべての公園を網羅しているわけではありません。現在地は端末内でのみ利用し、サーバーには送信しません。地図データ © <a
href="https://www.openstreetmap.org/copyright"
target="_blank"
rel="noopener">OpenStreetMap</a