关于 norigin-spatial-navigation-react
norigin-spatial-navigation-react 是使用 Norigin Spatial Navigation 管理 React 应用方向键和遥控器焦点的技能。它介绍可聚焦元素、嵌套容器、焦点边界及程序化焦点,适用于电视应用等界面。
主要功能
- 01React 空间焦点导航
- 02可聚焦叶节点与层级容器
- 03模态框和浮层的焦点边界
- 04程序化焦点与稳定焦点键
- 05自定义方向输入和事件回调
适用场景
- 01构建 Smart TV 界面的方向键导航
- 02管理菜单、网格和轮播中的焦点
- 03限制模态对话框内的焦点
- 04适配自定义遥控器按键
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ noriginmedia/ norigin-spatial-navigation --skill norigin-spatial-navigation-react安装后在 Claude Code 中输入/norigin-spatial-navigation-react确认已加载。
详细介绍
Norigin Spatial Navigation
用于在 React 应用(智能电视、机顶盒、浏览器)中管理方向键/遥控器焦点的库。它会根据空间位置计算下一个可聚焦元素,因此无需手动编写方向导航逻辑。
完整文档: https://github.com/NoriginMedia/Norigin-Spatial-Navigation/tree/main/docs
需要了解本技能未涵盖的详情时,请直接阅读相关页面:
- 概念与层级:
docs/guides/concepts.md、docs/guides/focus-hierarchy.md - 快速入门与安装:
docs/guides/quick-start.md、docs/guides/installation.md useFocusableAPI:docs/api-reference/useFocusable.mdSpatialNavigationAPI(init、setFocus、pause 等):docs/api-reference/SpatialNavigation.md- 边界/模态框:
docs/guides/focus-boundaries.md - 程序化设置焦点:
docs/guides/programmatic-focus.md - 距离计算调优:
docs/guides/distance-calculation.md - 按键映射(自定义遥控器):
docs/guides/key-mapping.md - 事件回调:
docs/guides/event-callbacks.md - 性能:
docs/guides/performance.md - 调试:
docs/guides/debugging.md - 示例(列表、网格、虚拟化):
docs/guides/recipes.md - 无障碍标签:
docs/guides/accessibility-labels.md - RTL:
docs/guides/rtl-support.md
心智模型
有两种组件形态:
- 叶节点 — 实际接收焦点的交互元素。通过
useFocusable读取focused。 - 容器 — 包含可聚焦子元素。读取
focusKey,并通过FocusContext.Provider提供该值,让子元素参与层级结构。也可以选择读取hasFocusedChild(需要设置trackChildren: true)。
每个可聚焦组件都必须将返回的 ref 附加到真实的 DOM 元素。容器必须将子元素包在 <FocusContext.Provider value={focusKey}> 中,否则子元素会成为根节点下的孤立元素,跨子元素导航将无法正常工作。
初始化
在应用启动时(例如在 App.tsx 中)调用一次 init:
import { init as initNavigation } from '@noriginmedia/norigin-spatial-navigation-core';
initNavigation({
debug: false,
visualDebug: false,
throttle: 0,
distanceCalculationMethod: 'corners' // 'center' | 'edges' | 'corners'
});
如果要发布自定义遥控器按键,也要配置按键映射(参见 docs/guides/key-mapping.md)。
模式
叶节点(按钮、菜单项)
import { useFocusable } from '@noriginmedia/norigin-spatial-navigation-react';
function Button({ label, onPress }: Props) {
const { ref, focused } = useFocusable({ onEnterPress: onPress });
return (
<div ref={ref} className={focused ? 'focused' : ''}>
{label}
</div>
);
}
容器
import {
useFocusable,
FocusContext
} from '@noriginmedia/norigin-spatial-navigation-react';
function Menu({ items }: Props) {
const { ref, focusKey, hasFocusedChild } = useFocusable({
trackChildren: true,
saveLastFocusedChild: true
});
return (
<FocusContext.Provider value={focusKey}>
<div ref={ref} className={hasFocusedChild ? 'active' : ''}>
{items.map((i) => (
<MenuItem key={i.id} {...i} />
))}
</div>
</FocusContext.Provider>
);
}
模态框/弹出窗口(限制焦点范围 + 挂载时获取焦点)
function Popup() {
const { ref, focusKey, focusSelf } = useFocusable({ isFocusBoundary: true });
useEffect(() => {
focusSelf();
}, [focusSelf]);
return (
<FocusContext.Provider value={focusKey}>
<div ref={ref}>{/* buttons */}</div>
</FocusContext.Provider>
);
}
如需部分限制焦点范围(例如只阻止水平方向),请使用 focusBoundaryDirections。参见 docs/guides/focus-boundaries.md。
从任意位置以编程方式设置焦点
import {
setFocus,
doesFocusableExist
} from '@noriginmedia/norigin-spatial-navigation-core';
const KEY = 'primary-cta';
useFocusable({ focusKey: KEY });
if (doesFocusableExist(KEY)) setFocus(KEY);
条件性设置是否可聚焦
const { ref } = useFocusable({ focusable: !disabled });
阻止/覆盖方向输入
onArrowPress(direction, props) — 返回 true 以允许默认导航,返回 false 以阻止导航。适用于分页行、自定义轮播,或在容器边缘阻止离开当前容器的情况。
useFocusable — 值得了解的参数
| 参数 | 用途 |
|---|---|
focusable | 切换元素是否参与导航。默认值为 true。 |
trackChildren | 启用 hasFocusedChild。会影响性能——仅在需要读取该值时启用。 |
saveLastFocusedChild | 容器重新进入时恢复上次聚焦的子元素。默认值为 true。 |
isFocusBoundary / focusBoundaryDirections | 将焦点限制在容器内。 |
focusKey | setFocus / doesFocusableExist 使用的稳定键。 |
preferredChildFocusKey | 进入容器时哪个子元素获得初始焦点。 |
onEnterPress、onArrowPress、onFocus、onBlur | 事件钩子。请参阅 docs/guides/event-callbacks.md。 |
extraProps | 转发给回调的任意负载(例如条目 ID)。 |
返回值:ref(必需)、focusKey、focused(叶节点)、hasFocusedChild(容器)、focusSelf()。
顶层 API 概览
import {
init,
setFocus,
getCurrentFocusKey,
doesFocusableExist,
navigateByDirection,
pause,
resume,
setKeyMap,
setThrottle,
destroy
} from '@noriginmedia/norigin-spatial-navigation-core';
完整列表和签名请参阅 docs/api-reference/SpatialNavigation.md。
最佳实践
应该
- 始终将
ref附加到 DOM 元素。 - 使用
<FocusContext.Provider value={focusKey}>包裹容器的子元素。 - 仅在实际使用
hasFocusedChild时启用trackChildren。 - 对需要以编程方式定位的元素使用稳定的
focusKey。 - 调用
setFocus前使用doesFocusableExist进行检查,尤其是在路由变更或异步数据场景中。 - 对模态框、抽屉和覆盖层使用
isFocusBoundary。 - 对禁用或隐藏的条目设置
focusable: false,而不是卸载它们(如果它们可能频繁切换)。
不应该
- 不要在容器上读取
focused——应改用hasFocusedChild。 - 不要在容器上省略
FocusContext.Provider;否则子元素无法从同级元素访问。 - 不要盲目地对可能尚未挂载的键调用
setFocus(这会导致焦点悄然丢失)。 - 不要生成每次渲染都会变化的
focusKey——焦点状态会丢失。 - 不要在生产构建中启用
debug/visualDebug。
调试
在 init 中启用诊断:
init({ debug: true, visualDebug: true });
常见问题:
- 元素无法获得焦点 — 未附加
ref,或设置了focusable: false,或祖先缺少FocusContext.Provider。 - 焦点跳到错误的相邻元素 — 尝试其他
distanceCalculationMethod(corners/center/edges);启用visualDebug查看命中框。 - 重新渲染时焦点丢失 —
focusKey不稳定,或之前聚焦的节点已卸载且没有备用方案。使用preferredChildFocusKey,或在新树挂载后调用setFocus。 - 模态框中焦点泄漏 — 模态框容器缺少
isFocusBoundary。
更多信息:docs/guides/debugging.md、docs/guides/performance.md。
快速参考
// Leaf
const { ref, focused } = useFocusable();
// Container
const { ref, focusKey } = useFocusable();
<FocusContext.Provider value={focusKey}>
<div ref={ref}>{children}</div>
</FocusContext.Provider>;
// Focus on mount
const { focusSelf } = useFocusable({ isFocusBoundary: true });
useEffect(() => {
focusSelf();
}, [focusSelf]);
// Manual focus
if (doesFocusableExist('my-key')) setFocus('my-key');