它在做什么
ORBIT READER 不把空间导航当作传统列表的替代品。真实文章链接先以语义列表输出,增强层再把同一组节点投影到椭圆轨道。选择节点会更新中心摘要;按 Enter 仍然进入标准文章 URL。
关键实现
节点位置由文章顺序与轨道半径确定,当前项通过整体相位旋转到阅读焦点。左右方向键切换相邻文章,Home 和 End 跳到边界,Tab 顺序保持 DOM 顺序。视觉位置变化不会改变辅助技术理解到的链接关系。
性能与降级
动画只修改 transform 与少量 CSS 自定义属性,不持续创建 RAF。一次选择对应一次短过渡;reduced-motion 下直接切换状态。窄屏幕改成横向索引与纵向文章信息,避免把触摸目标压进狭小轨道。
浏览器兼容
SVG 和 CSS transform 覆盖当前主流浏览器。不支持增强能力或禁用 JavaScript时,页面保留完整文章列表、专题信息与阅读入口。
KEY SOURCE / LIVE IMPLEMENTATION
运行源码节选
这不是为文档复制的示例。构建过程直接读取当前实验使用的 TypeScript 文件,并提取下方标记区段。
const setActive = (index: number, moveFocus = false) => {
activeIndex = (index + links.length) % links.length;
const active = links[activeIndex];
if (!active) return;
links.forEach((link, linkIndex) => {
const selected = linkIndex === activeIndex;
link.dataset.active = String(selected);
link.tabIndex = selected ? 0 : -1;
});
title.textContent = active.dataset.title ?? active.textContent?.trim() ?? '';
topic.textContent = active.dataset.topic ?? 'ARTICLE';
date.textContent = active.dataset.date ?? '';
date.toggleAttribute('hidden', !active.dataset.date);
description.textContent = active.dataset.description ?? '打开文章继续阅读。';
open.href = active.href;
open.setAttribute('aria-label', `打开《${title.textContent}》`);
current.textContent = String(activeIndex + 1).padStart(2, '0');
if (!reducedMotion) {
const angle = -(360 / links.length) * activeIndex;
stage.style.setProperty('--orbit-rotation', `${angle}deg`);
stage.style.setProperty('--orbit-counter-rotation', `${-angle}deg`);
}
if (moveFocus) active.focus();
}; 活动节点、中心摘要、链接地址和轨道相位由同一个状态更新函数维护。