I am LAZY bones?
AN ancient AND boring SITE

网页定位踩坑记

之前我写过一个新加坡天气云图的小页面,照着官方网站糊的,补上了官方没有的”你所在的位置”,还能看历史。自己天天在用,挺顺手。

直到前两天,我在 Unihertz Titan 2 的 Chrome 上打开它,面板底部赫然一行红字:

奇怪的是,同一部手机上 Google Maps 定位好好的,Chrome 的位置权限我也手动给了,Mac 和 iPhone 上的 Safari 打开同一个页面也都正常。我意识到,不妙,又来活儿了~

这篇就把这次排查的结果记下来:一个把定位”悄悄”拦掉的响应头,一个 nginx 里关于 add_header 的老坑,还有后来发现的定位慢的问题。值得写一篇,分享给有需要的朋友。

权限明明给了,为什么还是失败?

先排除手机本身。系统定位是开的,Chrome 的定位权限也确实是已授予。连上电脑用 adb 确认:

那问题就在页面这边。看了一眼页面代码,取位置失败的回调里只有一句 console.log 加一个红字提示,没有任何有用的信息。于是我换个思路,直接看服务器给了什么响应头:

就是它!geolocation=() 的意思是:本站任何页面都不许使用定位。这是 Permissions-Policy 响应头,浏览器会在权限检查之前就把请求拒掉,所以不管你在浏览器里给了多少权限,页面里的 navigator.geolocation 都只会走到错误回调,也不会弹授权框。

那为什么 Safari 正常?据我所知,WebKit 对这个响应头基本不执行(它对 iframe 的 allow 属性支持得比较好),所以头里写了禁止,Safari 照样能弹框、能定位。Google Maps 是原生 App,更不走网页策略,当然更没事。

说起来,这个头还真不是什么神秘的东西,是我自己加的。九月底那阵子,博客的安全插件一直提示我不安全,扫描器里六个安全头全是 Not Found,我就照着博客安全头踩坑记里写的,在 nginx 里一口气补齐了。其中 Permissions-Policy 这一行的想法特别朴素:摄像头、麦克风、定位这些用不上的浏览器能力全关了。但没想到我之前偷懒挂在博客域名下的这个天气页面受到了影响。哈哈!

只放开一个路径:nginx 的 add_header 继承坑

当然不能整站都改成允许定位,我只想放开 /data/w/。第一反应是给它单独写个 location,再加一行 add_header。但是 nginx 有个很经典的坑(上面那篇里我也踩过一次):某个 location 里只要写了自己的 add_header,就不会再继承外层(server 级)的所有 add_header。这意味着我得把 HSTS、CSP 那一堆安全头在新 location 里全抄一遍,以后改一处要同步两处,迟早漏。

更省事的办法是用 map,让同一个头的值随路径变化。add_header 只保留 server 级那一行,其他什么都不用动:

map 必须放在 server 块外面、http 上下文里(我的配置文件第一个就是 server 块,说明它是被 http 块 include 进来的,放文件开头就行)。reload 之后验证一下,只有目标路径放开了:

手机上刷新,Chrome 弹出授权框,点允许,蓝点出现了~

蓝点出现得好慢?

问题解决了吗?没有,因为我发现蓝点要过好几秒才出来。我一开始的判断很干脆:页面里写的是 enableHighAccuracy: true,这会强制走 GPS,室内搜星慢,改成 false 用 Wi-Fi / 基站定位应该秒出。

但是光猜不行,得量。Chrome 在安卓上可以通过 adb 转发出 DevTools 端口:

然后通过 CDP 的 Page.addScriptToEvaluateOnNewDocument 在页面脚本之前注入一小段代码,包装 navigator.geolocation 的回调,记录每次定位什么时候返回、蓝点什么时候显示。这样量出来的是真实耗时,不靠肉眼。

先是普通刷新三次,蓝点 0.86 到 1.36 秒出现,其实不算慢,也没复现我肉眼看到的”好几秒”。后来我直接用 screenrecord 录下自己手动操作的过程,再用 ffmpeg 拆成每秒 10 帧、逐帧检测蓝点的颜色,终于抓到了:连续点三次刷新,蓝点出现的时间是 1.4 秒、2.1 秒、3.0 秒,一次比一次慢。

我用脚本模拟同样的快速刷新(间隔约 4 秒),也复现了:1.3 → 1.6 → 2.5 → 2.8 秒。间隔拉开到 8 秒以上就没有这个现象。开不开录屏结果都一样。

那是不是 enableHighAccuracy 的问题?我把三种写法放在同样的条件下对比(快速刷新 5 次,每组之间等 25 秒):

三种写法的波动规律完全一致。结论很清楚:慢不是页面参数造成的,是安卓系统和 Chrome 的定位层在短时间内连续请求时本身就时快时慢,页面这边改什么参数都没用。我最开始那个”改成低精度就秒出”的判断,被数据直接打脸了。。。

页面层能做的:缓存一个短时间的位置

系统定位快慢我控制不了,那就让蓝点不依赖它。思路很简单:把上一次拿到的位置连同时间戳存进 localStorage,打开页面先拿缓存画蓝点,同时照常发起真正的定位,成功后再更新。

有两个细节我是想清楚了才加的:

1. 缓存只认 3 分钟内的。位置过期了就不显示,免得定位一直失败的时候,蓝点永远停在旧地方。
2. 只有真正定位成功才更新缓存,失败了缓存不动。

我通过 DevTools 把线上页面临时换成本地新文件,在手机上连续刷新四次:蓝点出现的时间都是 0.01 秒。当然,缓存是空的时候(第一次打开,或者隔了三分钟以上),还是得老老实实等系统定位,这点没法改。

总结一下几个坑

1. Permissions-Policy 拦截在权限弹窗之前。浏览器里权限给了也没用,页面也不会弹框,只会静悄悄地走错误回调。遇到”权限明明给了却定位失败”,先 curl -I 看一眼响应头,比反复折腾手机设置快得多。
2. Safari 正常不代表没问题。这个响应头在不同浏览器上的执行力度不一样,测定位功能,Chrome 一定要测。
3. 测速要把刷新间隔拉开。连续快速刷新时,系统定位会越来越慢,拿这个当基线会得出错误结论。
4. 别光靠猜。“高精度会强制 GPS 所以慢”听起来天经地义,实际一量就不是那么回事。

全文完。

最后修改时间: 2026年11月02日 20:54

本文章发表于: 2026年11月02日 20:50 | 所属分类:流水帐. | 您可以在此订阅本文章的所有评论. | 您也可以发表评论, 或从您的网站trackback.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注