WEB 端 Markdown 阅读器:纯原生 JS+HTML+CSS,三态视图 + 代码高亮 + 文档大纲 + 五类图形离线渲染,与官网、鸿蒙、Android 共用同一份渲染内核
| Files | Last commit | Last update |
|---|---|---|
| 1 month ago | ||
| 10 days ago | ||
| 10 days ago | ||
| 10 days ago | ||
| 1 month ago | ||
| 1 month ago | ||
| 2 months ago | ||
| 10 days ago | ||
| 10 days ago | ||
| 1 month ago | ||
| 10 days ago | ||
| 10 days ago |
md-reader-web
WEB 端 Markdown 阅读器 — 纯原生 JS + HTML + CSS,零框架、零打包、零构建,浏览器直接跑。
打开 .md 文件即可阅读:预览 / 源码 / 分屏三态切换、表格列宽自适应、代码高亮、文档大纲,
以及五类图形的离线渲染(流程图与时序图、公式、数据图表、关系图、思维导图)。
✨ 功能
- 三态视图切换 — 预览(只读渲染)/ 源码(可编辑)/ 分屏(左编辑右预览,实时同步)
- 本地文件打开 — 点工具栏文件夹图标选
.md/.markdown文件,亦可直接拖拽到窗口 - 编码自动识别 — UTF-8 优先,出现替换字符(U+FFFD)时自动回退 GB18030
- 完整 Markdown 语法 — 标题 / 段落 / 列表 / 引用 / 代码块 / 分割线 / 图片 / 链接 / 表格
- 表格列宽自适应 — 列宽按内容计算、同行等高、超宽横向滚动
(列宽常量与鸿蒙端
MarkdownParser.ts、Android 端MarkdownParser.kt共享同一套) - 代码高亮 — 内置 highlight.js,按需懒加载,不联网
- 文档大纲 — 从已渲染正文提取标题,三档响应式:宽屏右侧常驻 / 中屏折成边缘标签 / 窄屏抽屉;
标题少于 6 条不显示;点击按
#文档&h=标题深链跳转 - 流程图与时序图 —
�KE_PH_0�katex/�KE_PH_1�tex/�KE_PH_2�echarts/�KE_PH_3�graphviz围栏块,Graphviz 离线布局 - 思维导图 —
```markmap围栏块,markmap + d3 离线渲染 - 图形放大 — 点击图形进入查看器,支持缩放 / 拖拽 / 双指缩放
- 顶栏菜单 — 低频操作收进「⋯」,含正文宽度、字号与主题设置(偏好存本地)
- 键盘友好 — 编辑器支持 undo/redo、全选、复制粘贴;编辑实时同步预览(200ms 防抖)
- 离线优先 — 解析、渲染、高亮、出图全部在浏览器本地完成,阅读链路不发起任何远端请求
🚀 启动方式
方式 A:起静态服务器(推荐)
python -m http.server 8123 --bind 127.0.0.1
然后浏览器打开 http://127.0.0.1:8123/。
index.html使用 ES 模块(<script type="module">),浏览器对file://默认禁用跨源 import, 用http://起服务即可解决。
方式 B:Firefox 直接打开
Firefox 对 file:// 下的 ES 模块更宽松,直接双击 index.html 也能运行。
📁 目录结构
md-reader-web/
├── index.html # 单页入口:工具栏 + 三态内容框
├── styles.css # 全部样式(标题字号 / 表色 / 三态布局)
├── icon.ico # 站点图标
├── js/
│ ├── app.js # 状态机 + 事件绑定(渲染交给下面的共享内核)
│ ├── outline.js # 文档大纲(独立模块,只读已渲染正文,不碰内核)
│ ├── md-menu.js # 顶栏「⋯」菜单:收纳低频操作 + 显示偏好
│ ├── parser.js # 薄转发层(指向 shared/md/md-core.js)
│ └── graph.js # 薄转发层(指向 shared/md/md-enhance.js)
├── shared/md/ # ★ 共享 Markdown 渲染内核(与志睿源官网 Node 侧同一份源码)
│ ├── md-core.js # 解析 + HTML 生成(浏览器 / Node 双侧可用)
│ ├── md-enhance.js # 浏览器增强层:图形水合、SVG 清洗、放大查看器、代码高亮
│ ├── md-enhance.css # 增强层样式
│ ├── render/ # 五类图形的离线渲染页(iframe 内嵌 + 手势缩放)
│ │ ├── formula.html # 公式(KaTeX)
│ │ ├── chart.html # 数据图表(ECharts)
│ │ ├── graphviz.html # 关系图(Graphviz)
│ │ ├── markmap.html # 思维导图(markmap + d3)
│ │ └── gesture.js # 缩放 / 拖拽手势
│ ├── PARSING-SPEC.md # 解析规范(各端行为对齐的依据)
│ └── test/ # 跨端一致性用例与校验器
├── vendor/mermaid/
│ ├── mermaid.min.js # Mermaid 引擎(10.9.8,MIT)
│ └── mermaid.esm.js # ESM 包装层:按经典脚本加载引擎,默认导出实例
├── md/vendor/highlight/ # 代码高亮(路径与线上同构,见下「高亮引擎的路径约束」)
├── doc/ # 设计与功能说明
├── LICENSE / NOTICE
└── README.md
🧭 设计要点
渲染内核:单一真源
2026-09-18 起,页面不再有本地 DOM 逐块构造的渲染层:
md 原文 ──renderHtml()──▶ HTML ──enhance()──▶ 图形水合 + 语法高亮 → 可放大
▲ ▲
shared/md/md-core.js shared/md/md-enhance.js
早先版本在浏览器里用 createElement 逐块搭 DOM,等于把渲染实现写了两遍;
现在与志睿源官网共用同一份 md-core.js(官网在 Node 侧 await import() 同一文件),
改一处两端同时生效。shared/md/PARSING-SPEC.md 是各方共同遵守的解析规范,
shared/md/test/ 是跨端一致性用例与校验器。
本仓库的目录布局与线上部署略有差异:线上
md/与shared/是同级的两个 nginx location, 本仓库把md/的内容放在根目录。因此js/app.js里对共享内核的引用比线上少一层../, 且 mermaid 引擎地址由app.js显式传入(不依赖md-enhance.js的线上默认定址)。
缓存与版本号
shared/ 与 md/ 在线上分属不同 nginx location,.js / .css 曾命中 30 天强缓存 ——
老访客浏览器里的缓存条目在过期前不会发请求,表现为「改了内核却看不到效果,非得清缓存」。
因此 app.js / graph.js / 入口页对内核与样式的引用带内容哈希版本号(取文件 md5 前 8 位):
内核一改 URL 即变,强制重新拉取。本仓库的版本号由同步脚本按内容哈希自动写入,不手改。
⚠️
md-core.js刻意不带版本号:md-enhance.js内部以裸路径./md-core.js引用它。 此处若带?v=会形成两个不同的模块 URL → 加载两份、两个实例、状态不共享。
Mermaid 引擎为什么用 <script> 加载
vendor/mermaid/mermaid.min.js 是经典脚本(IIFE 打包产物):它把引擎赋给顶层
var __esbuild_esm_mermaid,并在文件末尾执行 globalThis.mermaid = __esbuild_esm_mermaid.default。
经典脚本里顶层 var 落在全局作用域,末尾那句能取到值;但一旦被当作模块 import,
顶层 var 就只存在于模块作用域,globalThis.__esbuild_esm_mermaid 取不到,
末尾那句对 undefined 取 .default 直接抛 TypeError → 模块求值失败 →
动态 import() 被拒 → 所有 mermaid 块静默降级成源码。
这个故障很隐蔽:
enhance()依然返回{graphs:4, ok:4}(水合流程本身没抛错), 但 DOM 里.mermaid-block内没有<svg>,页面看不出任何报错。因此
vendor/mermaid/mermaid.esm.js不能用import './mermaid.min.js', 而是动态插一个<script src>按经典脚本加载,等onload后读globalThis.mermaid再默认导出。
高亮引擎的路径约束
md-enhance.js 里的高亮加载函数没有可传参覆盖的地址入口,只能按
../../md/vendor/highlight/11.9.0/highlight.min.js(相对自身定位)推导。
本仓库因此保留 md/vendor/highlight/ 这一层目录,与线上同构 —— 否则高亮会静默失效:
代码块仍有 hljs 类名,但没有着色,控制台也不报错。
解析器容错规则
- 剔除 BOM、CRLF → LF 归一、全角井号 / 全角空格归一
- 容忍行首 ≤ 3 空格、井号后无空格、行尾闭合符
- 表格识别后,未识别的孤立
|行不并入段落(防止超长字符串) - 表格列宽常量(
CELL_CHAR_WIDTH / CELL_PADDING / CELL_WIDE_FACTOR / CELL_MIN_WIDTH / CELL_MAX_WIDTH)三端一致
图形安全
图形产出会经过两层清洗:
md-core.js侧的源码级清洗(去<script>、on*事件属性、外部引用);md-enhance.js侧的 DOM 白名单裁剪(引擎自己产出的<style>与foreignObject文字保留, 用户手写 SVG 一律净化)。
🚫 未包含:站点专属模块
以下文件在线上站点存在,但不属于本仓库,因为它们依赖后端的 /markdown 接口与四页体系
(阅读器 / 问一问 / 文章管理 / 配置),单独拿出来跑不起来:
| 模块 | 依赖 |
|---|---|
tts.js、tts-text.js、tts-playlist.js(语音朗读) |
/markdown/kb/api/prefs、读者标识 |
read-book.js、read-book.css、read.html(合订阅读) |
/markdown/share/:uuid |
md-nav.js(四页统一入口) |
/md/ask.html、/md/articles.html、/md/config.html |
本仓库定位是离线阅读器 + 渲染内核,上述业务模块不在此范围。
js/md-menu.js 在仓库版中已移除指向这三页的跳转项。
🧩 与其它端的关系
| 端 | 仓库 | 形态 |
|---|---|---|
| HarmonyOS | harmonyos-md-reader | ArkTS / ArkUI |
| HarmonyOS(彻底本地化分支) | harmonyos-md-reader-lite | ArkTS / ArkUI |
| Android | android-md-reader | Kotlin + Jetpack Compose |
| WEB | 本仓库 | 原生 JS + HTML + CSS |
| 官网渲染内核 | zry-cms 的 shared/md/ |
与本站 shared/md/ 同源 |
📜 许可证 / License
本项目基于 Apache License 2.0 开源 — 详见 LICENSE 与 NOTICE。 第三方组件(全部离线内置):Mermaid(MIT)、KaTeX(MIT)、ECharts(Apache-2.0)、 Graphviz 的 viz.js(MIT)、markmap 与 d3(MIT / ISC)、highlight.js(BSD-3-Clause)。
Copyright 2026 白志明 (Bai Zhiming)
Introduction
WEB 端 Markdown 阅读器:纯原生 JS+HTML+CSS,三态视图 + 代码高亮 + 文档大纲 + 五类图形离线渲染,与官网、鸿蒙、Android 共用同一份渲染内核
Customize your domain