253 lines
8.5 KiB
Markdown
253 lines
8.5 KiB
Markdown
# Earth Mobile Center Country Highlight Plan
|
||
|
||
## Goal
|
||
|
||
移动端打开 Earth 国界图层后,用屏幕中心,也就是当前镜头正对的地球表面位置,自动识别所在国家,并高亮该国家国界。
|
||
|
||
桌面端仍保持现有 hover 行为。移动端不引入新的国界渲染体系,而是复用已有 `country-boundaries.js` 的 GeoJSON 命中和 hover 高亮能力。
|
||
|
||
## Criteria for success
|
||
|
||
1. 移动端 `layout-mode-mobile` 下,国界图层开启后,屏幕中心所在国家会自动高亮。
|
||
2. 移动端旋转、缩放、巡航或自动旋转地球时,高亮会跟随镜头中心更新。
|
||
3. 屏幕中心落在海洋或没有命中地球时,国家高亮会清除。
|
||
4. 国界图层关闭时,不执行中心国家识别,也不显示残留高亮。
|
||
5. 桌面端 pointer hover 行为保持不变。
|
||
6. 移动端抽屉、搜索、设置、媒体、详情等前景 UI 打开时,不因为用户操作 UI 产生明显误高亮或抖动。
|
||
7. 中心识别有节流或状态缓存,不把 GeoJSON point-in-polygon 检测放到无条件每帧高频执行。
|
||
8. 实现后能通过本地静态检查或前端构建,并用移动端 viewport 手动或 Playwright 验证核心场景。
|
||
|
||
## Existing pieces
|
||
|
||
当前项目已经具备大部分基础能力:
|
||
|
||
- [frontend/public/earth/js/country-boundaries.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/country-boundaries.js)
|
||
- `updateCountryBoundaryHover(coords)`:根据 `{ lat, lon }` 命中国家并更新高亮线。
|
||
- `clearCountryBoundaryHover()`:清除当前 hover 高亮。
|
||
- `getShowCountryBoundaries()`:判断国界线图层是否可见。
|
||
- [frontend/public/earth/js/utils.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/utils.js)
|
||
- `screenToEarthCoords(clientX, clientY, camera, earth, domElement)`:屏幕坐标 raycast 到地球表面。
|
||
- `vector3ToLatLon(vector)`:地球本地坐标转经纬度。
|
||
- [frontend/public/earth/js/constants.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/constants.js)
|
||
- `COUNTRY_BOUNDARY_CONFIG` 已定义普通国界线和 hover 国界线样式。
|
||
- 移动端布局状态已经通过 `layout-mode-mobile` body class 区分。
|
||
|
||
因此本需求的核心不是新增图层,而是补一个移动端中心取点控制器。
|
||
|
||
## Non-goals
|
||
|
||
- 不改变桌面端 hover 交互。
|
||
- 不替换 `countries-admin0.min.geojson` 数据源。
|
||
- 不新增后端 API。
|
||
- 不把国家面填充做成新的 selected country 面状 shader。
|
||
- 不为移动端增加永久准星 UI,除非后续产品明确需要视觉准星。
|
||
|
||
## Implementation plan
|
||
|
||
### 1. Add a small mobile center hover controller
|
||
|
||
新增一个轻量函数,建议放在现有主循环附近或单独模块,例如:
|
||
|
||
```text
|
||
frontend/public/earth/js/mobile-center-country-highlight.js
|
||
```
|
||
|
||
建议导出:
|
||
|
||
```js
|
||
updateMobileCenterCountryHighlight({
|
||
camera,
|
||
earth,
|
||
renderer,
|
||
now,
|
||
isBlocked,
|
||
});
|
||
|
||
clearMobileCenterCountryHighlight();
|
||
```
|
||
|
||
职责:
|
||
|
||
1. 判断是否处于移动端。
|
||
2. 判断国界图层是否开启。
|
||
3. 判断当前是否被移动端前景 UI 阻塞。
|
||
4. 对 renderer canvas 中心点做 raycast。
|
||
5. 命中地球后转经纬度。
|
||
6. 调用 `updateCountryBoundaryHover({ lat, lon })`。
|
||
7. 无命中或禁用时调用 `clearCountryBoundaryHover()`。
|
||
|
||
### 2. Use canvas center, not window center
|
||
|
||
中心点应基于 renderer canvas rect 计算:
|
||
|
||
```js
|
||
const rect = renderer.domElement.getBoundingClientRect();
|
||
const clientX = rect.left + rect.width / 2;
|
||
const clientY = rect.top + rect.height / 2;
|
||
```
|
||
|
||
这样在移动端安全区、地址栏变化、viewport resize 或 canvas 非全屏时仍然准确。
|
||
|
||
### 3. Convert center point into country hover coords
|
||
|
||
复用已有工具:
|
||
|
||
```js
|
||
const point = screenToEarthCoords(clientX, clientY, camera, earth, renderer.domElement);
|
||
if (!point) {
|
||
clearCountryBoundaryHover();
|
||
return;
|
||
}
|
||
|
||
const coords = vector3ToLatLon(point);
|
||
updateCountryBoundaryHover(coords);
|
||
```
|
||
|
||
注意:`screenToEarthCoords` 返回的是 earth local point,符合 `vector3ToLatLon` 的输入语义。
|
||
|
||
### 4. Gate updates by mobile and foreground UI state
|
||
|
||
建议新增一个本地判断函数:
|
||
|
||
```js
|
||
function isMobileCenterCountryHighlightBlocked() {
|
||
return (
|
||
!document.body.classList.contains("layout-mode-mobile") ||
|
||
document.body.classList.contains("earth-search-open") ||
|
||
document.body.classList.contains("earth-settings-open") ||
|
||
document.body.classList.contains("earth-media-open") ||
|
||
document.body.classList.contains("earth-info-open")
|
||
);
|
||
}
|
||
```
|
||
|
||
如果移动端抽屉只是半收起、且没有覆盖中心视野,可以继续允许中心高亮。若实际体验里抽屉展开会遮挡中心点,再把 drawer open 状态纳入阻塞条件。
|
||
|
||
### 5. Throttle and cache center updates
|
||
|
||
GeoJSON polygon 命中不应该无条件每帧执行。
|
||
|
||
第一版建议:
|
||
|
||
- `throttleMs = 120`
|
||
- 缓存上次经纬度,中心点变化小于 `0.05` 度时跳过。
|
||
- 禁用、切回桌面、图层关闭、UI 阻塞时立即清除一次高亮。
|
||
|
||
伪代码:
|
||
|
||
```js
|
||
if (now - lastUpdateAt < 120) return;
|
||
if (Math.abs(coords.lat - lastLat) < 0.05 && Math.abs(coords.lon - lastLon) < 0.05) return;
|
||
```
|
||
|
||
### 6. Wire into the Earth animation loop
|
||
|
||
在 [frontend/public/earth/js/main.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/main.js) 的动画循环中调用:
|
||
|
||
```js
|
||
updateMobileCenterCountryHighlight({
|
||
camera,
|
||
earth,
|
||
renderer,
|
||
now: performance.now(),
|
||
isBlocked: isMobileCenterCountryHighlightBlocked(),
|
||
});
|
||
```
|
||
|
||
这样自动旋转、手势旋转、缩放和巡航都会自然更新。
|
||
|
||
### 7. Keep desktop hover unchanged
|
||
|
||
桌面 pointer hover 仍然走当前逻辑。
|
||
|
||
移动端中心高亮只在 `layout-mode-mobile` 下生效,不应该监听 pointer move,也不应该抢占 desktop hover 状态。
|
||
|
||
### 8. Optional visual tuning
|
||
|
||
第一版复用:
|
||
|
||
- `COUNTRY_BOUNDARY_CONFIG.hoverLineColor`
|
||
- `COUNTRY_BOUNDARY_CONFIG.hoverLineOpacity`
|
||
- `COUNTRY_BOUNDARY_CONFIG.hoverGlowOpacity`
|
||
|
||
如果移动端体验太强,可以后续加独立配置:
|
||
|
||
```js
|
||
mobileCenterHoverLineOpacity
|
||
mobileCenterHoverGlowOpacity
|
||
```
|
||
|
||
但第一版不建议过早分叉样式。
|
||
|
||
## Verification
|
||
|
||
### Static checks
|
||
|
||
1. `npm` 前端构建或现有 lint/typecheck 命令通过。
|
||
2. `rg` 确认新增函数只在移动端路径调用,不影响桌面 pointer hover。
|
||
3. `git diff --stat` 和目标文件 diff 确认改动范围集中。
|
||
|
||
### Manual mobile checks
|
||
|
||
使用移动端 viewport,例如 390x844:
|
||
|
||
1. 打开 Earth。
|
||
2. 开启国界图层。
|
||
3. 转动地球到中国、美国、澳大利亚等大块陆地区域,确认中心国家国界高亮。
|
||
4. 转动到太平洋或印度洋,确认高亮消失。
|
||
5. 缩放地球,确认高亮仍跟随中心点。
|
||
6. 打开移动端搜索、设置、媒体或详情面板,确认没有明显误高亮或抖动。
|
||
7. 切回桌面 viewport,确认 hover 仍由鼠标位置控制。
|
||
|
||
### Playwright smoke check
|
||
|
||
如果已有 Playwright 流程,建议补一个移动端 smoke:
|
||
|
||
1. 设置 viewport 为手机尺寸。
|
||
2. 打开 Earth 页面。
|
||
3. 开启国界图层。
|
||
4. 等待国界数据加载。
|
||
5. 截图确认中心附近国家边界有 hover 高亮线。
|
||
|
||
这个 smoke 不必断言具体国家名称,因为当前功能核心是视觉高亮;更稳定的自动化可以后续通过暴露 debug state 实现。
|
||
|
||
## Risks and mitigations
|
||
|
||
### Polygon hit cost too高
|
||
|
||
风险:移动端设备上频繁 `featureContains` 可能带来卡顿。
|
||
|
||
缓解:
|
||
|
||
- 使用 `120ms` 节流。
|
||
- 经纬度变化小于阈值时跳过。
|
||
- 后续如仍慢,再为 GeoJSON features 预计算 bbox,先 bbox 粗筛再 point-in-polygon。
|
||
|
||
### UI blocking state 不完整
|
||
|
||
风险:某些移动端前景 UI 没有对应 body class,中心点被遮挡但高亮仍更新。
|
||
|
||
缓解:
|
||
|
||
- 第一版覆盖现有主要 class。
|
||
- 验证时记录遗漏项,补充到 `isMobileCenterCountryHighlightBlocked()`。
|
||
|
||
### Desktop hover 被移动端状态污染
|
||
|
||
风险:移动端中心高亮和桌面 hover 共用 `_hoveredFeature` 状态。
|
||
|
||
缓解:
|
||
|
||
- 只在 `layout-mode-mobile` 下运行中心高亮。
|
||
- 切出 mobile 或图层关闭时调用一次 `clearCountryBoundaryHover()`。
|
||
- 不改 `updateCountryBoundaryHover()` 的语义。
|
||
|
||
## Milestones
|
||
|
||
1. 设计落地:完成本 plan,明确目标和验收标准。
|
||
2. 最小实现:新增移动端中心取点 controller,并接入 animation loop。
|
||
3. 性能保护:加入节流、经纬度阈值和禁用态清理。
|
||
4. 验证:本地构建通过,移动端 viewport 手动检查通过。
|
||
5. 调优:根据截图或真机体验微调阻塞条件和节流阈值。
|
||
|