移动规则
按下方向键时,vuEPG 根据元素在屏幕上的实际位置选出下一个焦点。整个过程可以概括为两句话:
- 先在同一层级里挑:同一个分组内,按“方向 → 重叠 → 最近”三步挑出目标;
- 挑不到就往外走一层:把整个分组当作一个元素,到外层继续挑。
本页的示意图都由 vuEPG 真实的导航算法实时计算,点击图下方的步骤按钮,可以逐步查看每一步保留和排除了哪些元素。
同一层级:三步挑选
① 只看这个方向上的元素
按 ↓ 时只考虑位于 O 下方的元素,按 → 时只考虑位于 O 右方的元素,其余全部排除。
D 距离最近,成为目标。
试一试
点击右下角的方向按钮切换方向。注意斜向的 E:按 ↓ 或 → 时它虽然在这个方向上,但最终没有被选中,原因见下一步。
② 优先同一行 / 同一列
在该方向的元素中,与 O 有重叠的优先:上下移动时看左右方向是否重叠,左右移动时看上下方向是否重叠。图中的阴影带就是 O 的覆盖范围。
Y 距离最近,成为目标。
X 离 O 更近,但它在斜下方,与 O 没有重叠;Y 虽然更远,但与 O 在同一列,所以选 Y。这符合遥控器的直觉:按 ↓ 应该落在正下方,而不是斜下方。
没有重叠时,只看完全越过的元素
如果该方向上没有任何元素与 O 重叠,就只考虑完全位于 O 那一侧的元素(按 ↓ 时,上边缘要低于 O 的下边缘)。
T 距离最近,成为目标。
U 与 O 在同一高度,不在下方,第①步就被排除;S 与 O 上下交叠,并不“完全在下方”,第②步被排除;只剩 T。
③ 最后选最近的
在剩下的元素中,选距离最近的:按 ↓ 时比较上边缘,按 ↑ 时比较下边缘,按 ← → 时同理比较左右边缘。
距离相同时,选与 O 更对齐的:上下移动时比较左边缘,左右移动时比较上边缘。逐层向外查找时,如果距离并列,先比较候选与当前焦点元素在交叉轴上的间距,再比较与整组的对齐程度。仍然相同时,选文档中靠前的。
L、R 距离相同,取与 O 更对齐的 R。
L 和 R 的上边缘在同一高度,距离相同;R 几乎正对着 O,更对齐,所以选 R。
边缘相接不算重叠
判断重叠时,O 两侧各向内收缩 5%,避免只是边缘碰到一点的元素被当作“同一列”。
Q 距离最近,成为目标。
P 与 O 只重叠了 3 像素,没有超过收缩范围,不算重叠;Q 在阴影带内,所以选 Q。
跨层级:逐层向外
在分组内挑不到目标时,vuEPG 会先在这个分组上派发可取消的方向事件,再把整个分组当作一个元素,在外一层继续按上面的三步挑选。挑中的如果是一个分组,就进入这个分组。
- 事件日志
试试下面几种操作,对照右侧的事件日志:
| 操作 | 过程 | 结果 |
|---|---|---|
| 焦点在 A1,按 → | 分组 A 内右侧没有元素 → 以整个分组 A 为起点 → 右侧找到分组 B → 进入分组 B | B2 |
| 焦点在 B1,按 ← | 分组 B 内左侧没有元素 → 以整个分组 B 为起点 → 左侧找到分组 A → 进入分组 A | A1 |
| 焦点在 A1,按 ↑ | 分组 A 内上方没有元素 → 外层也没有 → 保持不动 | A1 |
| 焦点在 B2,按 ↓ | 分组 B 内下方最近的是 B4 | B4 |
注意第一行:从 A1 按 → 落在 B2 而不是几何上更近的 B1,因为 B2 是分组 B 的默认焦点(★)。
进入分组时落在哪里
按以下顺序查找第一个可以获得焦点的元素:
- 带
default的子节点; - 其余子节点,按文档顺序;
- 子节点是分组时,递归进入该分组,规则相同。
哪些元素会被跳过
| 节点 | 可以成为目标的条件 |
|---|---|
| EPGItem | 未禁用,且已渲染(自身和祖先都不是 display: none,且在文档中) |
| EPGGroup | 未禁用、已渲染,且组内至少有一个可以获得焦点的元素 |
visibility: hidden 与 position: fixed 的元素视为已渲染。
没有焦点或焦点失效时
页面还没有焦点时,第一次方向操作会落在页面入口,也就是对整个页面套用“进入分组”的规则。
焦点元素被卸载或隐藏(例如切换了 KeepAlive 页面)后,下一次方向操作优先进入原焦点路径中最内层仍可进入的分组;这些分组都不可进入时才回到页面入口。已获得焦点的元素后来被禁用,只影响它能否成为下一次移动的目标;它仍可作为移动的起点。
源码
规则与源码逐条对应,并由单元测试覆盖:
- 同一层级的挑选:
src/core/navigation.ts - 逐层向外与进入分组:
src/core/navigate.ts、src/core/tree.ts