跳到正文

阅读体验

主题在“读”这件事上做了四件事:一条进度条、一个带编号的页内目录、加工过的代码块,以及 文章卡片上的阅读时间与字数。这一页说明每一项的行为与开关。

阅读进度条

页面顶部有一条 2px 的进度条,随滚动前进。

theme: githubTheme({
  progressBar: false,   // 关掉
})
  • 它由滚动位置驱动,宽度写在一个 CSS 变量上,滚动期间不会触发 Vue 重渲染
  • 滚动与尺寸变化都合并进 requestAnimationFrame,一帧最多测量一次
  • 纯装饰:aria-hidden,不进入无障碍树,也不会读给屏幕阅读器
  • 换页时归零,新页面从顶部开始
只在页面够长时才看得见
文档比视口短的时候,它始终是空的 —— 因为没有可滚动的内容,进度本来就是 100% 或没有意义。

页内目录

右侧目录由页面实际渲染出来的标题生成,所以它不会列出不存在的章节。

  • 深度由 sidebarDepth 决定(2–6),总是从 h2 开始 —— h1 是页面标题
  • 更深的标题嵌套成树,折叠时仍保留在 DOM 中,每个条目都保留自己的锚点
  • 嵌套标题默认带大纲编号:1.、1.1.、2.1.。已经以数字开头的标题不动,页面第一个 标题也不动(GitHub 的行为)
  • 激活项按滚动位置计算,并把激活的深层条目滚进可视区
  • 指针悬停在目录上时暂停跟随 —— 这样点击深层条目时高亮不会在脚下跳走
  • 键盘聚焦同样会暂停跟随,Tab 浏览目录时不会跳
---
toc: false        # 本页隐藏
numbered: false   # 本页目录不编号
sidebarDepth: 3   # 本页目录深度
---
theme: githubTheme({ numbered: false })   // 全站关掉编号

代码块

每个代码块会在客户端被加上两样东西:

  • 右上角的语言标签 —— fence 里声明的那个(```js → js)
  • 右下角的复制按钮 —— 悬停或键盘聚焦时出现,触屏常显,复制后短暂变成“已复制”
theme: githubTheme({
  codeBlockLanguage: false,        // 关掉语言标签与复制按钮
  codeBlockLabel: '复制代码',
  codeBlockCopied: '已复制',
})

实现细节值得知道一点,因为它解释了行为:

  • 锚点是 pre > code,所以有没有代码高亮器都能用 —— 关闭 shiki 与 prismjs 时渲染器 会输出 <pre><code class="language-js">(没有外层 div),按钮照样在
  • 没写语言的 fence 只有按钮、没有标签。渲染器会给它 language-plain 或 language-text, 主题不会把这两个当成真实语言显示出来
  • 复制用事件委托:整页共用一个监听器,一页 200 个片段也不会多 200 个监听器
复制按钮需要安全上下文
浏览器只在 https:// 或 localhost 下提供 navigator.clipboard。主题为 http:// 站点准备了 document.execCommand 兜底,所以本地用 IP 访问也能复制。

阅读时间与字数

文章卡片上,日期后面跟着预估阅读时间和字数:

2026 年 3 月 1 日 · 预计阅读 3 分钟 · 812 字

两个数字都在构建期算好,因为客户端拿不到 markdown 正文(payload 只带聚合后的列表)。 它们来自同一趟统计,所以永远不会互相矛盾。

估算规则:

  • 英文 200 词/分钟;中日韩文字按字计,400 字/分钟
  • 代码块算进字数(一篇全是代码的页面也要读)
  • HTML 标签、标签属性、script / style 内容不算
  • HTML 实体算一个字符(&amp; 不是 amp 三个字母)

文案全部可本地化:

theme: githubTheme({
  blog: {
    readingTime: '预计阅读 :minutes 分钟',
    words: ':words 字',   // 不设就不显示字数
  },
})

无障碍

阅读相关的一切都照顾了键盘与屏幕阅读器:

  • 页内目录的每个条目的锚点始终在 DOM 里,不会被 display: none 藏掉
  • 代码复制按钮有可读名称,Tab 到即显示
  • 进度条不进入无障碍树
  • 所有动画与平滑滚动都尊重 prefers-reduced-motion

细节与自查清单见 无障碍。