它在做什么

ORBIT READER 不把空间导航当作传统列表的替代品。真实文章链接先以语义列表输出,增强层再把同一组节点投影到椭圆轨道。选择节点会更新中心摘要;按 Enter 仍然进入标准文章 URL。

关键实现

节点位置由文章顺序与轨道半径确定,当前项通过整体相位旋转到阅读焦点。左右方向键切换相邻文章,Home 和 End 跳到边界,Tab 顺序保持 DOM 顺序。视觉位置变化不会改变辅助技术理解到的链接关系。

性能与降级

动画只修改 transform 与少量 CSS 自定义属性,不持续创建 RAF。一次选择对应一次短过渡;reduced-motion 下直接切换状态。窄屏幕改成横向索引与纵向文章信息,避免把触摸目标压进狭小轨道。

浏览器兼容

SVG 和 CSS transform 覆盖当前主流浏览器。不支持增强能力或禁用 JavaScript时,页面保留完整文章列表、专题信息与阅读入口。

KEY SOURCE / LIVE IMPLEMENTATION

运行源码节选

这不是为文档复制的示例。构建过程直接读取当前实验使用的 TypeScript 文件,并提取下方标记区段。

src/scripts/labs/orbit-reader.ts ACTIVE ORBIT · 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();
};

活动节点、中心摘要、链接地址和轨道相位由同一个状态更新函数维护。