页面外观

每页 HTML = 内容 HTML + 模板函数拼装的页面外观。模板为纯 TypeScript 函数(renderLayout(data: TemplateData): string),零模板引擎依赖,类型安全,用户可控字符串统一转义。

布局结构

页面自上而下分三层:

  • Header(通栏):站点品牌、搜索触发器、语言切换、主题切换;sticky 悬浮于整窗滚动内容之上,恒为悬浮盒(毛玻璃底 + 细边框);圆角随滚动量连续插值,页面顶部时为卡片级 14px,随向下滚动在前 120px 内按 easeOutCubic 扩张为胶囊(半高 28px)
  • 子文档 Tab 栏(通栏):docdoc.json 声明多个子文档时渲染;普通流透明一行,不常驻,滚动时随内容向上滚出视口、钻入 Header 外层的不透明画布之下
  • 主体(左右两栏):侧栏与文档区。侧栏趴在暖色画布上(宽度由 --sidebar-width 变量驱动,可拖拽调宽并记忆);文档区是一张浮于画布上的白色卡片(圆角、细边框、微阴影,高度跟随内容),卡片内是文档正文、Minimap 与上一页/下一页导航;站点页脚(版权、构建时间)独立浮于卡片下方的画布上,视觉与正文脱离

整窗滚动模型:Header position: sticky 悬浮(外层铺不透明画布,遮蔽滚动经过的 Tab 栏与正文);Tab 栏不悬浮,随内容向上滚走;侧栏 sticky——滚动时与正文一起向上,触碰 Header 下沿(--header-height,72px)后固定,正文继续向上。视口高度一律采用 svh 动态视口单位(带 vh / -webkit-fill-available 回退),规避移动端浏览器大视口(lvh)大于可视区造成的底部多余滚动空间。html 的 scroll-padding-top 按 Header(--header-height)+ 16px 统一为锚点跳转与程序化滚动让位。

移动端(≤900px)响应式布局:侧栏不再是常驻列,改为抽屉,交互全部基于原生 Popover API,不依赖 JS——侧栏声明 popover,汉堡开关(仅移动端显示)以 popovertarget 声明式接线,浏览器原生提供开启/收起、::backdrop 半透明遮罩(点击即收起)、Escape 与 light-dismiss;打开期间 body:has(#sidebar:popover-open) 锁定页面滚动,滑入动画由 @starting-style + display/overlay allow-discrete 过渡实现(prefers-reduced-motion 时退化)。桌面端以作者样式覆写 UA 的 popover 隐藏(@media (min-width: 901px) { #sidebar[popover] { display: block } }),闭合态恢复 sticky 常驻列;JS 仅在 toggle 事件里同步开关的 aria-expanded。搜索弹窗保持 fixed 覆盖层 + z-index 实现:居中对话框与侧位抽屉的定位模型不同,不套 popover(UA 对 popover 的 fit-content + margin: auto 默认样式会破坏面板以自身 margin 定位);侧栏抽屉在 top layer,任何 z-index 都压不过,因此打开搜索时 JS 先收起抽屉再展示弹窗。宽表格由 rehypeTableScroll 在构建期包裹 .table-scroll 横向滚动容器,接口 schema 块自带 overflow-x,避免窄屏撑破内容卡片。Popover API 需 2023–2024 起的主流浏览器,不支持的旧浏览器移动端退化为无侧栏(正文完整可读)。桌面端(>900px)样式零变化。

图标体系

界面图标全部使用 Phosphor Icons(MIT License)bold 字重,构建期内联为 SVG(currentColor 继承文字颜色),零运行时依赖、无字体文件。图标清单由 scripts/gen-icons.mjs 从 @phosphor-icons/core 生成到 src/chrome/icons.ts。

标题图标复用同一图标清单:任意级别的标题中可书写 <Icon name="rocket-launch" />,无论书写位置,图标永远呈现在标题最前;构建期标题净化把它从侧边栏条目、搜索索引与标题锚点中剥离,rehypeHeadingIcons 在 slug 计算前移除图标节点保证锚点干净。

组件与拼装时机

组件 拼装时机 内容
Header 构建期 站点图标与名称、搜索触发器(⌘ K pill)、语言切换器(多语言时)、主题三态切换(太阳 / 月亮 / 显示器图标按钮)
子文档 Tab 栏 构建期 docdoc.json 声明的子文档列表,多于一个时渲染为 Header 与主体之间的通栏一行,当前子文档高亮
Sidebar 构建期烘焙 三种渲染器逐节点生效(形态是目录节点的属性:生效形态 = 目录自身 metadata.docdoc.json 的 renderer ?? 继承外层 ?? doc.docdoc.json 的文档级缺省 ?? tree)。侧栏自文档根起按各节点生效形态递归渲染:tree 可展开折叠树(折叠箭头 + 目录行 + 条目,当前页高亮、祖先目录展开);submenu 逐级进入(面包屑链 + 当前目录条目,点击目录行进入;目录首页以同标题条目呈现在条目列表首位;面包屑仅剩文档标题时(文档根页)不渲染该行,避免与子文档 Tab 重复);group 分组恒展(目录题作小号纯文本分组标签,目录首页以同标题条目呈现在组内首位,全部条目始终展开、无折叠,嵌套目录作缩进子分组)。跨形态嵌套:group 子树内的 tree 子目录为可展开块、submenu 子目录为点击进入条目(页面进入该子目录后其槽位切换为面包屑 + 当前目录条目);tree 中的 group 子目录烘焙为平铺分组块;submenu 条目列表中的直属子目录一律为点击进入条目(含声明为 group 或 tree 形态的子目录,不因其自身形态在列表中铺开或展开),进入该子目录后其侧栏页面按该目录的生效形态渲染(group 铺开为分组、tree 展开为树、submenu 继续逐级进入)。选中态三形态统一为白卡片:tree 的目录行或页面行、submenu 与 group 的页面条目(含重复呈现的目录首页条目)。均支持拖拽调宽
Minimap 构建期烘焙 当前页标题大纲(带列表图标),滚动联动高亮
上一页/下一页 构建期烘焙 按目录序渲染相邻两页链接(左右箭头图标);无 index.md 的目录无实体页面,不参与相邻页序列
Try it 面板 构建期烘焙容器,客户端水合 接口文档专用,数据与语义见站内搜索 ?接口标记
Footer 构建期 独立于内容卡片、浮于画布上的站点页脚:版权信息(docdoc.json 顶层 copyright,缺省为 © 年份 + 站点名)、构建时间(构建机本地时区格式)

构建期烘焙保证页面无闪烁、无客户端渲染依赖;模板函数的输入契约见站内搜索 ?TemplateData。

亮暗主题

首帧前内联脚本按 localStorage 记忆的选择或系统偏好设置 data-theme,避免错误主题闪现;CSS 变量驱动两套配色,代码块在两种主题下均保持深色外观。构建期不烘焙主题选择,切换即时生效。

rootPrefix 机制

每页按自身深度计算指回站点根的前缀(../..、./ 或 /my-docs/),所有资源引用、页间链接与内容接口请求都拼接该前缀。同一份产物因此无须改动即可在三种环境下正确工作:

环境 站点根
域名路由 /
路径前缀路由 /:name/
preview 命令行指定的根

客户端脚本

assets/docdoc.js 为单文件 IIFE,零运行时依赖,只做交互增强:

行为 说明
搜索弹窗 ⌘ K / Ctrl-K 唤起(fixed 覆盖层实现,打开时自动收起移动端抽屉),防抖调用站点的全文搜索接口渲染命中列表,键盘上下选择、回车跳转
主题切换 Light / Dark / System 三态,localStorage 记忆,跟随系统偏好变化
Sidebar 折叠 目录节点的展开与收起、当前页高亮、拖拽调宽并记忆
Header 滚动态 窗口滚动时切换 page-scrolled 类(阴影),并按滚动量逐帧插值 Header 悬浮盒圆角
Minimap 联动 滚动时同步高亮标题大纲
代码块复制 代码块右上角复制按钮,成功后短暂勾选反馈
Try it 面板水合 读取烘焙的 data-docdoc-try 数据构建表单;HTTP 面板实发请求,NATS 面板构建并复制请求载荷

纯静态打开(无服务端)时优雅降级:导航照常可用,搜索提示服务不可用。