跳至主要內容
Skip to content

掌握使用者位置:Geolocation API 深度解析

在開發地圖應用、外送服務或即時天氣查詢時,獲取使用者的地理位置是不可或缺的功能。瀏覽器原生的 Geolocation API 提供了標準化的介面,讓我們能安全且精確地取得裝置的經緯度資訊。

本篇將深入探討如何使用 Geolocation API,並將其封裝成一個強大的 Vue 3 Composable,支援單次定位與即時追蹤。


一、 Geolocation API 核心基礎

1.1 隱私與權限

這是一個高度隱私敏感的 API。

  • HTTPS 限制:現代瀏覽器強制要求網站必須使用 HTTPS 協定(localhost 除外)才能呼叫此 API。
  • 使用者同意:瀏覽器會自動彈出視窗詢問使用者是否允許分享位置。如果被拒絕,將無法再次自動彈出,需使用者手動至設定開啟。

1.2 核心方法

API 掛載於 navigator.geolocation 物件下:

  1. getCurrentPosition(success, error, options):獲取「目前」的單次位置。
  2. watchPosition(success, error, options):監聽位置變化,當裝置移動時持續觸發。回傳一個 watchId
  3. clearWatch(watchId):停止監聽。

1.3 設定選項 (PositionOptions)

javascript
const options = {
  enableHighAccuracy: true, // 是否要求高精確度(會較耗電)
  timeout: 5000,            // 等待最長毫秒數 (預設 Infinity)
  maximumAge: 0             // 接受的快取位置時間 (0 代表強制作新定位)
};

二、 封裝 useGeolocation Composable

我們將實作一個 useGeolocation,讓它具備響應式的座標狀態,並提供 resume (開始追蹤) 與 pause (停止追蹤) 的控制能力。

2.1 介面定義

typescript
export interface UseGeolocationReturn {
  coords: Ref<{ latitude: number; longitude: number } | null>;
  locatedAt: Ref<number | null>; // 最後更新時間
  error: Ref<GeolocationPositionError | null>;
  isSupported: boolean;
  resume: () => void;
  pause: () => void;
}

2.2 完整實作

typescript
// composables/useGeolocation.ts
import { ref, onMounted, onUnmounted } from 'vue';

export function useGeolocation(options: PositionOptions = {}) {
  const isSupported = typeof navigator !== 'undefined' && 'geolocation' in navigator;
  
  const coords = ref<{ latitude: number; longitude: number } | null>(null);
  const locatedAt = ref<number | null>(null);
  const error = ref<GeolocationPositionError | null>(null);
  
  let watcherId: number | null = null;

  const updatePosition = (position: GeolocationPosition) => {
    coords.value = {
      latitude: position.coords.latitude,
      longitude: position.coords.longitude
    };
    locatedAt.value = position.timestamp;
    error.value = null;
  };

  const onError = (err: GeolocationPositionError) => {
    error.value = err;
  };

  const resume = () => {
    if (!isSupported) return;
    if (watcherId) return; // 已經在監聽中

    watcherId = navigator.geolocation.watchPosition(
      updatePosition, 
      onError, 
      options
    );
  };

  const pause = () => {
    if (watcherId !== null) {
      navigator.geolocation.clearWatch(watcherId);
      watcherId = null;
    }
  };

  onMounted(() => resume());
  onUnmounted(() => pause());

  return {
    isSupported,
    coords,
    locatedAt,
    error,
    resume,
    pause
  };
}

三、 實戰範例:即時座標儀表板

這個範例展示如何顯示使用者的即時經緯度,並提供一個 Google Maps 的連結。

vue
<script setup lang="ts">
import { computed } from 'vue';
import { useGeolocation } from '@/composables/useGeolocation';

const { coords, error, locatedAt, pause, resume } = useGeolocation({
  enableHighAccuracy: true
});

const mapLink = computed(() => {
  if (!coords.value) return '#';
  return `https://www.google.com/maps?q=${coords.value.latitude},${coords.value.longitude}`;
});
</script>

<template>
  <div class="geo-panel">
    <h3>📍 即時位置追蹤</h3>
    
    <div v-if="error" class="error-msg">
      定位失敗: {{ error.message }}
      (Code: {{ error.code }})
    </div>

    <div v-else-if="coords" class="coords-info">
      <div class="stat">
        <label>緯度 (Lat)</label>
        <span>{{ coords.latitude.toFixed(6) }}</span>
      </div>
      <div class="stat">
        <label>經度 (Lng)</label>
        <span>{{ coords.longitude.toFixed(6) }}</span>
      </div>
      <div class="time">
        更新時間: {{ new Date(locatedAt!).toLocaleTimeString() }}
      </div>
      
      <a :href="mapLink" target="_blank" class="map-btn">
        在 Google Maps 開啟
      </a>
    </div>

    <div v-else class="loading">
      正在獲取位置...
    </div>
    
    <div class="controls">
      <button @click="pause">暫停追蹤</button>
      <button @click="resume">繼續追蹤</button>
    </div>
  </div>
</template>

<style scoped>
.geo-panel {
  padding: 20px;
  background: #f8f9fa;
  border-radius: 8px;
  max-width: 400px;
}
.stat {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
  font-family: monospace;
  font-size: 1.1em;
}
.error-msg {
  color: #dc3545;
  background: #ffe6e6;
  padding: 10px;
  border-radius: 4px;
}
.map-btn {
  display: block;
  text-align: center;
  background: #4285f4;
  color: white;
  padding: 8px;
  border-radius: 4px;
  text-decoration: none;
  margin-top: 15px;
}
</style>

四、 常見錯誤代碼對照表

onError 被觸發時,error.code 會告訴我們失敗的原因:

Code常數說明
1PERMISSION_DENIED使用者拒絕提供位置權限。
2POSITION_UNAVAILABLE無法獲取位置(例如 GPS 訊號不良)。
3TIMEOUT在指定時間內無法獲取位置。

總結

Geolocation API 雖然簡單,但細節在於「權限處理」與「錯誤回饋」。透過 watchPosition 搭配 Vue 的生命週期管理,我們能輕鬆打造出對電池友善且使用者體驗良好的定位功能。


延伸閱讀