用原生 JS 做了一个中国历史可视化站(7 朝 77 帝 + Leaflet 地图)
觉得不错希望能点个★支持一下。
技术栈
技术架构:
- 前端:原生 HTML + CSS + JavaScript,没框架,没构建
- 地图:Leaflet 1.9.4,vendor 到本地,不走 CDN
- 数据:全是 JSON,按朝代分目录
- 后端:没有后端,一个 30 行的 Node 静态服务器
- 部署:GitHub Pages / Gitee Pages,纯静态
- 头像:AI 生成工笔风,512×512
这个项目交互不复杂 —— 卡片切换、地图缩放、悬案展开、选择题作答。上 React 要多写一层构建和状态管理,不值当。原生 JS 一个 app.js 1900 行,能 hold 住。
项目结构
chinese-history-atlas/
├── data/ # 所有数据都是 JSON
│ ├── sui/ tang/ wudai/ song/ yuan/ ming/ qing/
│ │ ├── emperors/ # 皇帝卡片(生平、时间线、人物、冷知识)
│ │ ├── cases/ # 悬案档案
│ │ ├── places/ # 古地名(含今地名对照、经纬度)
│ │ ├── routes/ # 行军/航海路线
│ │ ├── quiz.json # 史论选择题
│ │ └── people.json # 关键人物小传
│ ├── boundaries/ # 各朝疆域 GeoJSON
│ └── portraits/ # 头像
├── web/
│ ├── index.html
│ ├── app.js
│ ├── style.css
│ ├── serve.mjs # 静态服务器
│ └── vendor/leaflet/ # Leaflet 本地化
└── scripts/
数据层和展示层完全分离。要加一个朝代,新建一个目录写 JSON,前端不用动。
我的技术决策
1. Leaflet 为什么本地化?
一开始走 CDN,后来发现两个问题:国内访问偶尔慢,CDN 挂了地图直接白屏。下载到 web/vendor/ 本地引用,零外部依赖,部署到哪都能跑。
2. 人物关系图用 SVG 画
皇帝在左侧,关键人物(皇后、大臣、对手)右侧竖排,虚线连接。关系标签(比如 "尚书左仆射・宰相")是动态宽度的圆角矩形。最开始写死 84px,长职位名直接溢出,改成根据文字长度自适应:
const rel = p.relation || p.role || '';
const w = Math.max(56, rel.length * 15 + 14);
return `<rect x="${mx - w/2}" y="${my-13}" width="${w}" height="24" rx="12" .../>`;
不复杂,但比写死尺寸省事。
3. 疆域是手绘 GeoJSON
各朝疆域轮廓不是扒的数据源,是对着谭其骧《中国历史地图集》手绘的简化多边形,标注了 "示意,非精确边界"。
这里踩过一个坑:隋唐元明清的疆域轮廓一开始画成一样的,被用户指出来才重新对着地图改。疆域这种东西,差一点就是错的。
郑和七下西洋、靖难之役朱棣南下、清军入关,路线都在 routes.json 里,用 Leaflet polyline 分段动画画出来。
4. 380 道选择题没用框架
史论工坊的数据在 quiz.json,前端渲染选项按钮、判断对错、展示解析。选项全用按钮胶囊,不用 <select>,状态全在内存,刷新重置。
题分两类:一类是论证判断("隋炀帝开大运河,功在千秋还是罪在当代?"),一类是因果链("安史之乱为什么爆发?")。约四分之一是开放题,可以接自己的 OpenAI 兼容接口做 AI 点评,配置存本地,不推仓库。
5. 静态服务器自己写
前端 fetch('../data/*.json') 读数据,双击 HTML 会被 CORS 拦。30 行 Node 解决:
import { createServer } from 'http';
import { readFile } from 'fs/promises';
import { extname, join } from 'path';
const types = { '.html':'text/html', '.js':'text/javascript', '.css':'text/css',
'.json':'application/json', '.png':'image/png', '.gif':'image/gif' };
createServer(async (req, res) => {
const p = join(process.cwd(), req.url === '/' ? '/web/index.html' : req.url);
try {
const buf = await readFile(p);
res.setHeader('Content-Type', types[extname(p)] || 'application/octet-stream');
res.end(buf);
} catch { res.statusCode = 404; res.end('not found'); }
}).listen(5173, () => console.log('http://127.0.0.1:5173/web/'));
Node 18+ 自带 fetch 和 createServer,不需要 npm install。
6. 五代十国怎么展示
这是个设计问题。五代(后梁→后唐→后晋→后汉→后周)是先后衔接的,可以按时间线推进;十国是同时并存的,最后用了横向甘特图 —— 每个政权一条横条,谁先建国谁后被灭,一目了然。
数据量
- 7 个朝代:隋、唐、五代十国、宋、元、明、清
- 77 位皇帝,每位有生平时间线、人物关系、冷知识、争议点
- 380 道史论题
- 545 条时间线
- 224 张 AI 生成头像
为什么三十岁了才对历史感兴趣
三十岁了才开始对历史感兴趣,正如唐太宗说的“以铜为镜可以正衣冠,以古为镜可以知兴替,以人为镜可以明得失”
我觉得是在学事情是怎么一步步走到这一步的。所以我就想让ai做一个前端网站帮我梳理,也能干中学。
杨广修了大运河,搞了科举,打了西域,建了洛阳 —— 每件事单拎出来都是好事。但他把所有事挤在同一个十年里干完,老百姓一年征发两百万,然后国家没了。你从这里学到的不是 "杨广是暴君",是好事做太急也会死人。
柴荣在位五年半,西败后蜀,三征南唐,北伐契丹,三十九岁病死。然后赵匡胤陈桥兵变,捡了个满配存档。你从这里学到的不是 "赵匡胤天纵神武",是你看到的结果,往往是别人铺好的路。
万历三十年不上朝,但万历三大征一场没落下,奏折批得比谁都勤。你从这里学到的不是 "万历摆烂",是人是复杂的,别用一个词概括任何人。
历史不会重复,但人会。一千年以前的人面对的问题 —— 权力怎么分配、功劳大了怎么收场、改革太急会不会翻车、信任的人会不会背叛 —— 和今天没有本质区别。你把这些故事看够了,再看身边的事,会有一种 "这个剧本我读过" 的感觉。
还有一点。历史看多了你会发现,很多我们以为必然发生的事,在当时的人眼里全是未知数。李建成那天早上不知道玄武门有埋伏,柴荣不知道自己三十九岁会死,崇祯到最后都觉得还有机会。结果是确定的,过程从来不是。 这会让你对自己正在做的事多一点耐心 —— 你也在过程里,你不知道结局。
所以学历史有什么用?
它不会让你升职加薪,不会让你少写 bug。但它会让你知道,你遇到的事,一千年前有人遇到过,一千年后还会有人遇到。你不是第一个,也不是最后一个。
这个认知本身,就是一种安慰。
在线体验
- GitHub Pages:zinging.github.io/chinese-his…
- 仓库(MIT):github.com/zinging/chi…
手机也能看,默认打开是隋朝,顶部切朝代。