Newer
Older
2025-shino / fog.js
(() => {
  'use strict';

  document.addEventListener("DOMContentLoaded", () => {

    const START_POS = [38.891, 139.824];
    const REVEAL_RADIUS = 40; // meters

    const titleEl = document.getElementById("title");
    const startBtn = document.getElementById("start");
    const stopBtn = document.getElementById("stop");

    let map;
    let playerMarker;
    let watchId = null;

    // ===============================
    // Canvas Fog
    // ===============================
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    canvas.style.position = "absolute";
    canvas.style.top = 0;
    canvas.style.left = 0;
    canvas.style.pointerEvents = "none";

    const revealedPoints = [];

    function resizeCanvas() {
      const size = map.getSize();
      canvas.width = size.x;
      canvas.height = size.y;
      redrawFog();
    }

    function redrawFog() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // 霧
      ctx.fillStyle = "rgba(0,0,0,0.85)";
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      ctx.globalCompositeOperation = "destination-out";
      ctx.shadowColor = "rgba(0,0,0,1)";
      ctx.shadowBlur = 20;

      revealedPoints.forEach(latlng => {
        const p = map.latLngToContainerPoint(latlng);
        const r = metersToPixels(REVEAL_RADIUS, latlng.lat);

        ctx.beginPath();
        ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
        ctx.fill();
      });

      ctx.globalCompositeOperation = "source-over";
    }

    function metersToPixels(m, lat) {
      return m / 40075017 * 256 * Math.pow(2, map.getZoom()) / Math.cos(lat * Math.PI / 180);
    }

    // ===============================
    // Map Init
    // ===============================
    function initMap() {
      map = L.map("locationmap").setView(START_POS, 16);

      L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
        attribution: '© OpenStreetMap contributors'
      }).addTo(map);

      map.getPanes().overlayPane.appendChild(canvas);

      resizeCanvas();
      map.on("resize move zoom", redrawFog);

      playerMarker = L.marker(map.getCenter()).addTo(map);
      playerMarker.bindPopup("STARTで探索開始").openPopup();

      map.on("click", e => updateLocation(e.latlng));
    }

    // ===============================
    // Game Logic
    // ===============================
    function reveal(latlng) {
      revealedPoints.push(latlng);
      redrawFog();
    }

    function updateLocation(latlng) {
      map.panTo(latlng, { animate: false });
      playerMarker.setLatLng(latlng);
      reveal(latlng);
      playerMarker.setPopupContent("探索中…").openPopup();
    }

    function onSuccess(pos) {
      updateLocation(L.latLng(
        pos.coords.latitude,
        pos.coords.longitude
      ));
    }

    function startWatch() {
      stopWatch();
      watchId = navigator.geolocation.watchPosition(
        onSuccess,
        null,
        { enableHighAccuracy: true }
      );
      titleEl.textContent = "探索中";
    }

    function stopWatch() {
      if (!watchId) return;
      navigator.geolocation.clearWatch(watchId);
      watchId = null;
      titleEl.textContent = "STOP";
    }

    startBtn.addEventListener("click", startWatch);
    stopBtn.addEventListener("click", stopWatch);

    initMap();
  });
})();