watchPosition は「動いたらそのたびに現在地を教えてもらう」仕組み
前回の getCurrentPosition は「今どこ?」を一回だけ聞く API でした。watchPosition はそれの“連続版”です。
ユーザーが移動するたびに、
「今ここにいるよ」と位置情報を何度も通知してくれる仕組み
ランニングの軌跡を記録したい
配達員の位置をリアルタイムに追いたい
マップ上の自分のアイコンをスーッと動かしたい
こういう「動き続ける位置」を扱いたいときに使うのが watchPosition です。
基本の形をまず押さえる
getCurrentPosition との違いをコードで見る
getCurrentPosition:
navigator.geolocation.getCurrentPosition(success, error, options);
JavaScriptwatchPosition:
const watchId = navigator.geolocation.watchPosition(success, error, options);
// 監視をやめる
navigator.geolocation.clearWatch(watchId);
JavaScript一番の違いはここです。
getCurrentPosition… 成功コールバックが「1 回だけ」呼ばれるwatchPosition… 位置が変わるたびに「何度も」成功コールバックが呼ばれる
そして watchPosition は「監視 ID(watchId)」を返します。
この ID を clearWatch に渡すことで、「もう追跡しなくていいよ」と止められます。
最小の「現在位置を追跡して表示する」例
HTML と JavaScript の全体像
HTML:
<button id="startWatchBtn">追跡開始</button>
<button id="stopWatchBtn" disabled>追跡停止</button>
<pre id="log">まだ追跡していません</pre>
JavaScript:
const startWatchBtn = document.querySelector("#startWatchBtn");
const stopWatchBtn = document.querySelector("#stopWatchBtn");
const log = document.querySelector("#log");
let watchId = null;
startWatchBtn.addEventListener("click", () => {
if (!("geolocation" in navigator)) {
log.textContent = "このブラウザは位置情報に対応していません";
return;
}
if (watchId !== null) {
log.textContent = "すでに追跡中です";
return;
}
log.textContent = "位置情報の追跡を開始します…";
watchId = navigator.geolocation.watchPosition(
(position) => {
const c = position.coords;
log.textContent =
`緯度: ${c.latitude}\n` +
`経度: ${c.longitude}\n` +
`精度: 約 ${c.accuracy} m\n` +
`取得時刻: ${new Date(position.timestamp).toLocaleString()}`;
},
(error) => {
log.textContent = `エラー: ${error.message}`;
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
startWatchBtn.disabled = true;
stopWatchBtn.disabled = false;
});
stopWatchBtn.addEventListener("click", () => {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
log.textContent = "追跡を停止しました";
startWatchBtn.disabled = false;
stopWatchBtn.disabled = true;
}
});
JavaScriptここでやっていることを日本語で整理すると、
- 「追跡開始」ボタンで
watchPositionを呼び、watchIdを保存 - 位置が更新されるたびに success コールバックが呼ばれ、ログを書き換える
- 「追跡停止」ボタンで
clearWatch(watchId)を呼び、監視を終了
という流れです。
watchPosition の“連続性”をちゃんとイメージする
「1 回の結果」ではなく「時間とともに変わる値」を扱う
getCurrentPosition は「1 回だけ値をもらって終わり」なので、
普通の関数の戻り値に近い感覚で扱えます。
一方 watchPosition は、
- いつコールバックが呼ばれるか分からない
- 何回呼ばれるかも分からない
- ユーザーが止まっていればしばらく呼ばれないこともある
という「時間に開いたイベント」です。
だからこそ、こういう書き方になります。
let lastPosition = null;
watchId = navigator.geolocation.watchPosition((position) => {
if (lastPosition) {
// 前回からどれだけ移動したかを計算できる
}
lastPosition = position;
});
JavaScript「最新の位置をどこかに保持しておく」
「前回との差分を使って距離を計算する」
といった“継続的な状態管理”が、watchPosition を使うときのポイントです。
ランニング距離をざっくり計算するミニ例
「追跡するなら、何か意味のあることをしたいよね」ということで、
超ざっくりですが「移動距離」を足し上げる例を見てみます。
※ 正確な距離計算ではなく、イメージ用の簡易版です。
HTML:
<button id="startRunBtn">ラン開始</button>
<button id="stopRunBtn" disabled>ラン停止</button>
<pre id="runLog">まだ開始していません</pre>
JavaScript:
const startRunBtn = document.querySelector("#startRunBtn");
const stopRunBtn = document.querySelector("#stopRunBtn");
const runLog = document.querySelector("#runLog");
let watchId = null;
let lastCoords = null;
let totalDistance = 0; // メートル
function toRad(deg) {
return (deg * Math.PI) / 180;
}
function distanceInMeters(lat1, lng1, lat2, lng2) {
const R = 6371000; // 地球の半径(m)
const dLat = toRad(lat2 - lat1);
const dLng = toRad(lng2 - lng1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(lat1)) *
Math.cos(toRad(lat2)) *
Math.sin(dLng / 2) ** 2;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
startRunBtn.addEventListener("click", () => {
if (!("geolocation" in navigator)) {
runLog.textContent = "このブラウザは位置情報に対応していません";
return;
}
if (watchId !== null) {
runLog.textContent = "すでに計測中です";
return;
}
totalDistance = 0;
lastCoords = null;
runLog.textContent = "ラン開始。位置情報を追跡中…";
watchId = navigator.geolocation.watchPosition(
(position) => {
const c = position.coords;
if (lastCoords) {
const d = distanceInMeters(
lastCoords.latitude,
lastCoords.longitude,
c.latitude,
c.longitude
);
totalDistance += d;
}
lastCoords = c;
runLog.textContent =
`現在の位置:\n` +
`緯度: ${c.latitude}\n` +
`経度: ${c.longitude}\n` +
`累計距離: ${(totalDistance / 1000).toFixed(3)} km`;
},
(error) => {
runLog.textContent = `エラー: ${error.message}`;
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
startRunBtn.disabled = true;
stopRunBtn.disabled = false;
});
stopRunBtn.addEventListener("click", () => {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
runLog.textContent += "\n計測を終了しました。";
startRunBtn.disabled = false;
stopRunBtn.disabled = true;
}
});
JavaScriptここでの大事なポイントは、
lastCoordsに「前回の位置」を保存しておく- 新しい位置が来るたびに「前回との差」を距離として足し上げる
- 監視を止めるタイミングをボタンで制御する
という「連続データを扱う思考」です。
options が watchPosition で特に重要になる理由
enableHighAccuracy とバッテリーの関係
watchPosition は「ずっと位置を取り続ける」ので、enableHighAccuracy: true のまま長時間動かすと、
スマホのバッテリーをかなり食います。
だからこそ、用途に応じて考える必要があります。
- ランニング計測 →
enableHighAccuracy: true(短時間・高精度) - 配達員の位置をざっくり追う →
falseでもよい(長時間・省電力)
「とりあえず true」ではなく、
“どれくらいの精度が本当に必要か”を考える癖 をつけると、設計が一段上がります。
maximumAge で「どこまで古い位置を許すか」を決める
watchPosition でも maximumAge は効きます。
0… 毎回最新を取りに行く5000… 5 秒以内の位置ならキャッシュを使ってよい
例えば、1 秒ごとに位置が更新されれば十分なアプリなら、maximumAge を少し大きめにしても体感は変わりません。
その代わり、位置取得の回数が減ってバッテリーが節約できます。
「どれくらい“リアルタイム”である必要があるか」を考えて、maximumAge を調整するのも、watchPosition 設計の一部です。
監視を“止める”ことまで含めて設計する
clearWatch を忘れるとどうなるか
watchPosition は、呼んだ瞬間から「ずっと動き続ける」タイプの API です。clearWatch を呼ばない限り、位置情報の取得は続きます。
その結果、
- バッテリーを無駄に消費する
- 位置情報アイコンがずっと点灯し続ける
- ユーザーに「このサイト怪しくない?」と思われる
といったことが起こります。
だからこそ、
- ページを離れるとき
- 特定の操作が終わったとき
- 一定時間経ったとき
など、どこかで必ず clearWatch を呼ぶ設計にしておくべきです。
ページ離脱時に自動で止める例
window.addEventListener("beforeunload", () => {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
}
});
JavaScriptこうしておくと、
ユーザーがページを閉じたりリロードしたときに、
位置情報の監視がちゃんと止まります。
「開始」と「停止」をセットで考えること。
これが watchPosition を扱ううえでの重要なマインドセットです。
初心者として「watchPosition」で本当に掴んでほしいこと
watchPosition の本質は、
「位置が変わるたびに、何度も現在地を教えてもらう仕組み」
です。
そのうえで、まずこの 4 つをしっかり押さえてください。
watchPositionは「連続的な位置更新」、clearWatchで必ず止める- コールバックは何度も呼ばれるので、「前回の位置」などの状態を自分で持つ
enableHighAccuracyやmaximumAgeで「精度 vs バッテリー」を調整する- 追跡開始・停止の UI をきちんと用意して、ユーザーに分かる形で制御させる
練習としては、
- 追跡開始/停止ボタン付きで、現在地をログ表示する
- 前回位置との差分を使って「ざっくり距離」を足し上げてみる
この 2 段階を書けるようになるだけで、
位置情報を「一回取る」世界から「動きを扱う」世界に、一歩踏み込めます。
そこまで行くと、
地図上でアイコンを動かす
ランニングアプリのプロトタイプを作る
みたいなアイデアが、急に現実味を帯びてきます。
watchPosition は、その扉を開けるためのキーみたいな存在です。
