Files
hesu-JT/docs/superpowers/specs/2026-08-21-global-presence-map-design.md
T
2026-08-27 11:54:26 +08:00

8.6 KiB
Raw Blame History

首页 GLOBAL PRESENCE 地图动画替换 — 设计文档

日期:2026-08-21 状态:已与用户确认(1:1 还原 demo)

轮播模式迭代:累积滑窗(2026-08-21 四次补充)

应用户要求,首页地图与 auto-export 地图的轮播从"独占切换"(同一时刻一个)改为累积滑窗:

  • 每步激活下一个据点,已激活的保持显示
  • 窗口满后(n-1=4 个),每次新激活时最旧的一个淡出(0.45s opacity 过渡)
  • 稳态 = 同时显示 4 个:最新的播完整动画序列,最旧的渐隐离场
  • 生命周期不变:可见才播 / 完全离开归零 / 每次进入从第一个完整累积

实现:main.js 两个地图块的 step()(tick % n 激活,tick ≥ n-1 时 (tick-(n-1)) % n 淡出);CSS 给 .location/.marker/.line 加 opacity 过渡。版本 bump ?v=20260821-6。

验证:两页 t0→t4 逐步累积至 4 个、t4 时首个淡出、t5 滑窗循环、离场归零、回来重播,全过。

丝滑包升级(2026-08-22)

应用户要求做动效升级,四项全部落地(两张地图统一):

  1. 弧线引线:首页 5 条折线 d 改为三次贝塞尔曲线(水平出发→自然弯到点位),绘制缓动 cubic-bezier(.3,.7,.2,1)
  2. 退场反擦除:入场=animation(.active),退场=反向 animation(.out 类,main.js 的 exit() 添加、650ms 后移除):线倒收回(lineOut dashoffset 0→1)、圆点缩小(dotOut)、标签下滑淡出(labelOut)、光环衰减(haloOut)。⚠️ 不能用 transition 实现退场 — CSS 规范规定 animation 移除时的值跳变不触发 transition
  3. 重叠时序:label .28s@.02s / line .55s@.12s / dot .32s@.5s — 各元素交叉进场
  4. 呼吸 + 双涟漪:dot 落定后 CSS r 属性呼吸(breatheR 7.5→8.3px / ae-breatheS 5.5→6.3 / ae-breatheL 6→6.9,r 与 transform 不冲突可同元素并存);每个点加第二圈光环/涟漪(halo2 / pulse.p2,延迟错开)

结构变化:子元素自管隐藏基态(group 不再切 opacity);HTML 每点 +1 光晕圆。版本 ?v=20260821-9。

验证注意:Playwright 窗口不可见时 Chrome 冻结动画时钟(currentTime 恒 0)— 动画类验证必须先 Page.bringToFront。带前台后实测退场淡出中间态(175ms 时 opacity 0.71)精确符合曲线。

目标

把 index.html 首页 GLOBAL PRESENCE 板块的静态占位地图(image/home/global-map.png)替换为 global-map-animation-demo.html 中的据点轮播动画效果,1:1 还原。

效果(以 demo 为准)

  • 背景图:image/home/Frame 119.png(1219 × 661)
  • SVG 覆盖层与背景图共享 viewBox="0 0 1219 661" 坐标系,所有点/线/文字用原图坐标
  • 5 个据点:DUBAI、ABU DHABI、ETHIOPIA、GHANA、RUSSIA
  • 每个据点 = 白色标签 + 橙色(--orange-700 = #ff6a00)折线引线 + 圆点 + 脉冲光环
  • 同一时刻只激活一个据点,每 3 秒切换下一个(循环)
  • 动画序列:标签滑入(.35s)→ 引线绘制(.85s 延迟 .18s)→ 圆点弹出(.4s 延迟 1s)→ 光环脉冲(1.4s 无限循环)
  • 含大屏首帧防闪烁修复:非 active 线 stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0

改动文件

文件 改动
index.html 替换 .map-wrap 内部为 img.map-bg + svg.map-overlay(保留外层 reveal、aria-hidden、loading="lazy")
css/style.css GLOBAL PRESENCE 段合入 demo 样式:.map-wrap 改 aspect-ratio: 1219/661,新增 .map-bg/.map-overlay/.location 系列 + 4 个 keyframes(颜色用 var(--orange-700));删除废弃 .map-img;删除 ≤560px 媒体查询里 .map-wrap { height: 260px }(与 aspect-ratio 冲突,比例自适应后手机端高度 ≈200px 与原设计一致)
css/mobile.css .map-wrap 规则去掉写死 height: 200px,保留 margin
js/main.js 新增独立 IIFE:查 .map-overlay,.location 每 3s 轮播 active(选择器限定,不影响其他页面)

刻意不做的(YAGNI)

  • 不做全部常显模式(用户已确认单点轮播)
  • 不移植 demo 里 vestigial 的 --path-length JS:CSS 从未读取该变量,pathLength="1" 属性已完成路径长度归一化
  • 不删旧图 global-map.png(可能被 WP 主题或其他地方引用)
  • 不加 demo 工具栏按钮(那是调试用的)

降级行为

  • prefers-reduced-motion:站点全局 animation: none,当前据点显示标签+圆点(静止),引线保持未绘制 — 可接受
  • 无 JS:DUBAI 常显(HTML 里初始 active)— 可接受

防闪加固(2026-08-21 补充)

用户报告症状:大屏上到达地图时先闪一帧完整线路 → 重置 → 才正常播放。

根因(Playwright 取证确认):非 CSS 首帧竞态(demo 的 base-state 修复已移植且有效),而是状态机时序——轮播从页面加载就开始空转,加载 6s 后(地图仍在视口外 2380px 处)ABU DHABI 的引线已处于 fill: both 保持的完结状态(opacity 1 / dashoffset 0)。用户滚到时第一眼撞见这条"陈旧完结帧"(= 闪一下),3s 后下个 tick 归零重置(= 消失),下一个据点才从头播放(= 正常出现)。屏幕越大板块离首屏越近,越容易触发。

修复(js/main.js):

  • init 时先 show(-1) 清掉 HTML 里 DUBAI 的 active(main.js 是 body 末尾同步脚本,先于首帧执行;无 JS 时保留 DUBAI 静态回退)
  • 首次激活用 IntersectionObserver 门控(观察 .map-wrap,threshold 0.12,与站点 reveal/count-up 同模式),触发后双 rAF 再 show(0)——先让不可见 base 状态绘制一帧,动画永远从已沉淀的样式树起步
  • 进入视口才开始 3s 轮播;无 IO 的老浏览器直接 begin()

防闪加固第二层:re-entry 重启 + 缓存击穿(2026-08-21 二次补充)

用户反馈"还是会出现"。系统排查后确认两条残余路径:

  1. re-entry 陈旧态:第一层修复只管"首次到达"。轮播一旦启动永不停 → 用户滚走再滚回时,当前据点又处于完结状态 → 闪 → 重置 → 下一个正常播放(症状与首次完全相同)。
  2. 旧版 main.js 缓存:python http.server 等无缓存头环境下,普通 F5 会命中启发式缓存执行旧 JS(Playwright 验证时实测踩中)。

修复:

  • 轮播改为可见才播放的生命周期:IO 持续观察(threshold 0.12),isIntersecting → start()(每次进入都从 DUBAI 从头播,双 rAF 起步);完全离开 → stop()(清定时器 + 归零到不可见 base 态)。首次到达、回访、F5 原位刷新(Chrome 恢复滚动位置)三种场景统一为"从 DUBAI 完整播放"
  • start() 内 rAF 等待期若已离开视口则放弃启动(守卫 if (!timer) return)
  • index.html 脚本标签加版本号 main.js?v=20260821 击穿缓存(今后 main.js 有实质改动时手动 bump)

验证(Playwright):

  • 静置顶部不滚动:零激活、所有线 opacity 0
  • 滚入 → DUBAI dashoffset 0.93(从 0 画起);3.3s 后 ABU DHABI
  • 滚到页脚离开 7s:active: null(轮播已停、已归零)
  • 滚回:DUBAI dashoffset 重现从 0 画起;3.3s 后 ABU DHABI —— re-entry 不再撞见完结帧
  • 原位 reload(Chrome 恢复 scrollY=2309 地图在视口):t=135ms DUBAI 已干净启动,无闪帧;t=10.2s 轮到 GHANA,节奏精准
  • 控制台 0 错误 0 警告

A/B 验证:

  • 修复前:加载 6s 未滚入 → ABU DHABI 激活、线完整(陈旧完结帧)
  • 修复后:加载 8s 未滚入 → 零激活、所有线 opacity 0
  • 滚入 0.6s → DUBAI 从 dashoffset 0.83 开始绘制;3.1s → ABU DHABI;6.3s → ETHIOPIA,循环正常
  • 控制台 0 错误 0 警告

相关:bento 业务格渐显重播(2026-08-21 三次补充)

用户要求 FOUR CORE BUSINESSES(STRONG RESOURCES, RELIABLE DELIVERY)板块的图片逐个渐显在每次进入视口时重播(原 reveal 一次即 unobserve)。

  • index.html:5 个 .cell.reveal 追加 reveal-repeat 标记类(仅此板块)
  • main.js reveal 观察器:带 reveal-repeat 的元素不 unobserve,按 isIntersecting 切换 .in — ≥12% 可见整组阶梯重播,完全离开归零;其余元素保持一次性 reveal
  • 脚本版本 bump 至 ?v=20260821-2

验证:滚入 5/5 in → 滚走 0/5 → 滚回 5/5 重播;标题等一次性 reveal 不受影响;控制台 0 错误。

验证

  • Playwright 打开 index.html:控制台无报错、3s 轮播切换、桌面/移动端尺寸比例正常