把全国大学画进山河:拆解高考志愿填报模拟器
- 使用 MapLibre GL 和分层设色地形构建可交互中国地图,支持球面与平面投影切换
- 国风视觉系统以宣纸色、朱砂红、楷体为核心,旗子系统按 985/211 等分层着色
- 冲稳保推荐基于概率区间,聚焦模式降低非推荐校透明度以减轻信息过载
- 志愿表支持拖拽排序、多校对比、风险提示,并集成 RIASEC 兴趣测评辅助选专业
- 工程细节包括内联 SVG 宣纸噪点、prefers-reduced-motion 兜底、移动端底部抽屉适配
在线体验:高考志愿填报模拟器
大家好,我是若风。
最近刷到一个小而美的工具——高考志愿填报模拟器。它做的事并不新:填个分数或位次,告诉你能上哪些大学。但打开它的那一瞬间,我有点意外:它没有做成一个表格、也没有做成一个排行榜,而是把全国 1500+ 所大学落在了一幅可以漫游的山河画卷上,背景是水墨宣纸,选中的大学会立起朱砂红的旗子。
填报志愿本来是一件非常焦虑的事,但这个网站硬是把它做得很安静、很顺滑。
我花了点时间把它从 HTML 到 JS 全翻了一遍,想搞清楚一个问题:它到底是怎么把地图、数据和志愿填报这三件事揉到一起的? 结论比想象的清晰——它的核心是 MapLibre 地图 + 分层设色地形 + 一套状态驱动的 UI,外加几个非常克制的小创新。
它到底做了什么
先交代背景。这个模拟器核心场景是:
- 在一幅可缩放、可平移的中国地形图上,标注全国 1500+ 所本科院校的位置。
- 用户填入省份、科类(文/理或新高考选科)、分数或位次。
- 系统按「冲、稳、保」三档点亮地图上的学校,并给出录取概率。
- 点开任一学校,看历年各专业的录取位次曲线。
- 把心仪的学校收进「志愿表」,可拖拽排序、多校对比。
围绕这条主线,它还加了几个增强体验的东西:RIASEC 兴趣测评帮你找专业方向、一分一段曲线可视化、分区天气动画、地球/平面两种投影切换,以及一个每天一抽的「今日签」彩蛋。
下面拆它的技术实现。
一、地图引擎:MapLibre GL + 分层设色
整个网站的底座是一张地图。但它没有用百度/高德这些国内商业地图,而是用了开源的 MapLibre GL JS——这是 Mapbox GL 的开源 fork,矢量瓦片渲染、支持 3D 地形和地球投影。
<script src="vendor/maplibre-gl.js"></script>
<script src="gk-engine.js?v=10"></script>
引擎脚本 maplibre-gl.js 约 1MB,被刻意放到了 <body> 末尾,而不是 <head>。注释写得很直白:
引擎(~1MB)挪到 body 末尾:原本在 <head> 阻塞渲染,
整页(含加载页)要等它下完才出;
现在加载页先秒出山水底,引擎在其后台下载
这是一个很实在的首屏优化:先让用户看到一张静态的精致地形底图(一张 156K 的 relief-cn2.webp,在 <head> 里 preload),引擎在后台悄悄下,下完再无缝替换成可交互地图。用户感知到的不是「白屏等加载」,而是「画卷正在铺开」。
分层设色:让地形看起来像水墨画
地图的视觉风格是这个网站最抓人的地方。它默认是「山形符号」画风,可以切换到「水彩浮雕」。核心实现是 MapLibre 的颜色分层(ramp),按海拔用一组色标做线性插值:
const STYLES = {
sym: {
ramp: ["interpolate", ["linear"], ["elevation"],
-11000, "#efe7d2", -1, "#efe7d2", 0, "#9fb87e",
250, "#b3bc7e", 700, "#d2bd72", 1200, "#e3b95c",
2000, "#dfa64e", 3000, "#bc8a52", 4000, "#a87f54",
5000, "#cbb78f", 5600, "#f6f0e1"],
hsShadow: "#6e4f2c", hsHighlight: "#fff6dd",
hsAccent: "#8a6a3f", hsEx: 0.18,
terrainEx: 3.2, symbols: true, label: "画风:山形符号"
},
wc: {
ramp: ["interpolate", ["linear"], ["elevation"],
-11000, "#eee7d3", 0, "#a9bf8e", 250, "#bcc88e",
700, "#d6c98f", 1300, "#ddbe7e", 2100, "#d3a368",
3000, "#b98e63", 4700, "#b3a7a8", 5800, "#fbf9f4"],
label: "画风:水彩浮雕"
// ...
}
};
这套色标不是随便选的,它对应的是经典的分层设色法(hypsometric tint):平原绿、丘陵黄、山地棕、雪线白。这是地理学教材里用了几十年的地形表达法,所以一眼看上去「很地理」,但整体色温被压暖、饱和度被压低,于是又像一幅旧式山水画。
除了颜色分层,它还叠加了晕渲(hillshade)——hsShadow(阴影色)、hsHighlight(高光色)、hsAccent(强调色)三个参数控制光照。两种画风的区别主要在光照强度:山形符号画风 hsEx: 0.18,对比弱、像木刻;水彩浮雕 hsEx: 0.55,对比强、像立体浮雕。
投影切换:地球与画卷
它支持两种投影模式:球面地球(globe)和平面 Mercator 画卷。切换时不只是换投影,连天空、雾色、背景色都一起换:
function applyProjection(on) {
globeOn = on;
map.setProjection({ type: on ? "globe" : "mercator" });
map.setPaintProperty("bg", "background-color",
on ? "#c3d3cf" : "#ece2c8"); // 球面淡青海,平面宣纸色
try { map.setSky(on ? SKY_GLOBE : SKY_FLAT); } catch(e) {}
flyHome(700);
}
一个细节值得注意:默认是球面,但放大到一定级别(z≈5)会自动摊平成画卷——既有「俯瞰九州」的仪式感,又保证放大后查校时是正读的平面图。
二、国风视觉系统:朱砂、宣纸与楷体
除了地图本身,整个 UI 的视觉语言是一套高度统一的中式设计。CSS 变量定义在 :root:
:root {
--paper-a: rgba(251,244,228,.95);
--paper-b: rgba(243,234,213,.95);
--ink: #3f3220;
--ink-soft: #6b5630;
--zhu: #9c2f1b; /* 朱砂红,主操作色 */
--gold: #bd9a55;
--gold-soft: #d8c08a;
--elev: 0 14px 34px -8px rgba(74,52,20,.30),
0 0 0 3px rgba(189,154,85,.12),
0 1px 0 rgba(255,250,238,.6) inset;
}
这套配色的逻辑很清楚:
- 宣纸色(
--paper-a/b)做面板背景,半透明 + 毛玻璃(backdrop-filter: blur(10px)),像一张半透明的宣纸浮在地图上。 - 朱砂红(
--zhu)只用在「主操作」和「选中态」——推荐按钮、选中校的旗子描边、概率徽章。它出现得越克制,出现时越有份量。 - 金色(
--gold)做边框和点缀,是宣纸和朱砂之间的过渡色。 - 字体全场用楷体栈
"Kaiti SC","STKaiti","KaiTi",serif,连<select>和<input>都不放过。
更巧妙的是旗子系统。地图上每所大学是一面旗子,按层次用不同颜色:
.t985 .banner, .t985 .pole { background:#8c2f1b; color:#f7e8d6; } /* 985:朱砂红 */
.t211 .banner, .t211 .pole { background:#96660f; color:#f7e8d6; } /* 211:赭金 */
.tdfc .banner, .tdfc .pole { background:#564a85; color:#ece9f7; } /* 双一流:靛紫 */
.tben .banner, .tben .pole { background:#6b6357; color:#f2ece0; } /* 其他:素灰 */
重叠时按层次叠放(985 最高,z-index:14),悬停时按光标距离动态排序,选中的校会描朱砂边 + 脉冲光圈。这不是一个简单的「打点标注」,而是一套有视觉层级的空间信息系统。
三、冲稳保推荐:从分数到点亮
推荐是这个工具的核心。它的输入很简单——省份、科类、分数或位次——输出是地图上的「冲稳保」三档高亮。
从源码能看到推荐的判定逻辑是概率区间。每个推荐结果带一个录取概率,映射到三档:
const REC_MAP = {
"复旦大学": ["冲", "35%"],
"南京大学": ["冲", "42%"],
"中国科学技术大学": ["冲", "30%"],
"厦门大学": ["稳", "68%"],
"武汉大学": ["稳", "72%"],
"华中科技大学": ["稳", "70%"],
"中南大学": ["保", "91%"],
"兰州大学": ["保", "93%"]
};
映射到 UI 上,三档有各自的强调色:
.umk.rec-冲 { --rc: #d85a30; } /* 冲:橙红,有风险 */
.umk.rec-稳 { --rc: #e0a32e; } /* 稳:金黄,较稳妥 */
.umk.rec-保 { --rc: #1d9e75; } /* 保:墨绿,稳进 */
推荐结果在地图上有两种呈现模式:
- 正常模式:所有学校旗子照常显示,推荐校加描边和脉冲。
- 聚焦模式(
body.rec-mode):非推荐校的旗子降到 12% 透明度且不可点击,只让推荐的学校亮着——这是很聪明的信息减负,1500+ 所学校里只突出十几所。
body.rec-mode .umk .dot { opacity: .12; }
body.rec-mode .umk.rec .dot { opacity: 1; }
body.rec-mode .umk.rec .banner { outline: 2px solid var(--rc); animation: pulse 1.6s infinite; }
除了分数,推荐面板还支持高级筛选:限定地区、限定专业类(支持多选 + 搜索)、选科匹配。表单控件全部重绘成国风——连原生 <select> 的下拉箭头都被替换成了一个手绘的小折角 SVG:
.recform select {
appearance: none;
background-image: url("data:image/svg+xml;utf8,<svg ...><path d='M1 1l4 4 4-4' .../></svg>"), linear-gradient(...);
}
这是细节控才会做的事:原生 select 在不同操作系统长得不一样、且无法主题化,所以干脆自己画一个箭头。
四、院校详情与一分一段
点开任一所学校,会展开一张详情卡片。在宽屏(>1200px)下,卡片右侧会并排出现一个「招录数据看板」,展示近三年各专业的录取位次,甚至有位次升降趋势:
.dtrend.up { color: #d85a30; } /* 位次上升=变难 */
.dtrend.dn { color: #1d9e75; } /* 位次下降=变易 */
这里有一个特别用心的设计:历年多专业组用「最难组」展示,悬停看完整区间:
.dtab .rg { border-bottom: 1px dotted #c4b79a; cursor: help; }
这些数据呈现是「让人做决定」的关键,而不是单纯堆数据。它区分了「变难/变易」,让趋势一眼可见。
更进阶的是独立的「数说高考」可视化面板,有三个 tab:
- 位次曲线:一分一段的历年变化。
- 学科实力榜:各校学科评估。
- 各省考生:考生分布。
这是一个独立的模态层(.vizpanel),居中弹出,用 SVG 自己画图表(#viz-chart svg),不依赖 Chart.js 这类库。
五、志愿表:可拖拽、可对比、可导出
志愿表是这个工具的「出口」——你在这里把选中的学校组织成一份正式的志愿表。
它的交互做得相当完整:
- 收藏:从地图、推荐面板、详情卡片多处都能加入收藏,收藏数实时显示在右下角徽章。
- 省份格式表头:志愿表顶部按省份的志愿表格式显示(
fav-prov),还显示已填数和上限(超额时变红)。 - 院校专业组聚合:同一所学校的不同专业组嵌套显示,支持组级拖拽排序。
.fav-g-h { cursor: grab; }
.fav-row[draggable] { cursor: grab; }
.fav-row.dragging { opacity: .4; }
- 风险提示:志愿表下方有风险卡片,绿色(
ok)表示志愿结构合理,红色(warn)表示冲稳保失衡。
.fav-risk.ok { background: rgba(29,158,117,.12); color: #15795a; }
.fav-risk.warn { background: rgba(216,90,48,.14); color: #9c2f1b; }
- 多校对比:勾选 2-4 所学校,进入对比模态。对比表支持表头吸顶、按方向(地区/科类)分组、展开专业详情。
- 导出:一键复制清单,还能同步到某个外部服务(
fav-sync区域)。
这是一个完整的、可用的志愿草表工具,而不是一个「收藏夹」。
六、RIASEC 兴趣测评:用心理学工具找方向
这是我觉得最有「产品思考」的一个功能。志愿填报最难的不是算分,是不知道学什么。它内置了一个基于霍兰德职业兴趣理论(RIASEC)的测评——24 道题,测出 6 个维度的兴趣画像,再匹配适合的专业方向。
从引导文案看它的设计意图:
花 1 分钟,测出适合你的专业方向
RIASEC 兴趣测评 · 十几道题即测即得 · 不存隐私
它甚至把测评放在了首访引导(onboarding)的第一选项——先测方向,再填分数。这个漏斗顺序很关键:先帮用户解决「学什么」,再解决「去哪里」,比一上来就让人填分更人性化。
测评结果还会反向影响推荐,形成「兴趣 → 专业 → 学校 → 录取概率」的完整闭环。
七、几个值得抄的工程细节
翻源码时,有几个细节让我觉得「这人真的在乎」。
1. 宣纸噪点用 inline SVG,不依赖图片
纸质感不是一张贴图,而是一段内联的 SVG 滤镜:
.paper {
mix-blend-mode: multiply;
background-image: url("data:image/svg+xml,%3Csvg ...%3E
%3Cfilter id='n'%3E
%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E
%3CfeColorMatrix type='saturate' values='0'/%3E
%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.07'/%3E%3C/feComponentTransfer%3E
%3C/filter%3E
%3Crect width='300' height='300' filter='url(%23n)'/%3E
%3C/svg%3E");
}
零额外请求,且 mix-blend-mode: multiply 让它融进底图而不是盖在上面。
2. 落花动画带 prefers-reduced-motion 兜底
地图上有飘落的花瓣动画,但尊重无障碍设置:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .001ms !important;
animation-iteration-count: 1 !important;
transition-duration: .001ms !important;
}
.petals { display: none !important; }
}
整个站对 prefers-reduced-motion 都有完整降级——骆驼、帆船、风筝、大雁、灯塔光这些点缀动画全部停止。这是很多炫酷网站会漏掉的一环。
3. 移动端面板转底部抽屉
桌面端的面板在移动端不是简单缩小,而是全部转为「底部抽屉」:
@media (max-width: 640px) {
.card, .recpanel, .quizpanel, .favpanel {
position: fixed; left: 0; right: 0; bottom: 0; top: auto;
border-radius: 16px 16px 0 0;
max-height: 80dvh;
animation: sheetUp .35s cubic-bezier(.22,1,.36,1);
}
}
注意它用 80dvh(动态视口高度)而非 80vh,规避了移动端浏览器地址栏伸缩的坑。抽屉顶部还加了抓手视觉提示(一条小横条)。这是真在手机上测过的设计。
4. 即时浮标替代 tooltip
工具栏按钮的说明没有用原生 title 的延迟提示,而是自绘了一个 .tb-flyout,鼠标移上去立刻显示,且根据按钮位置自动选择从右侧(dir="r")或上方(dir="u")弹出:
const FLY = {
stylebtn: ['画风 · 山水 / 水彩', 'r'],
'act-rec': ['填分数 / 位次 → 冲稳保院校', 'u'],
// ...
};
原生 tooltip 在不同浏览器、不同设备表现差异巨大,自绘是更可控的选择。
5. 全站无障碍语义
从搜索框到下拉菜单,都带了完整的 ARIA 属性:
<input id="search-input" type="text" role="combobox"
aria-expanded="false" aria-controls="search-results"
aria-autocomplete="list" aria-label="搜索学校、城市或省份">
省份下拉是自绘的 listbox,带 aria-haspopup、aria-expanded、aria-selected。键盘焦点有可见的 :focus-visible 样式。这是一个认真对待可达性的项目。
八、如果我要做一个类似的产品
拆完它的实现,我总结一个可复用的技术清单:
| 模块 | 技术选型 | 关键点 |
|---|---|---|
| 地图底座 | MapLibre GL JS | 开源、矢量瓦片、支持 3D 地形和 globe |
| 地形可视化 | 分层设色 + hillshade | 按海拔 ramp 插值 + 光照参数 |
| 视觉风格 | CSS 变量 + 楷体字体栈 | 2-3 个主色,朱砂只用于主操作 |
| 数据呈现 | 自绘 SVG 图表 | 不引重型图表库 |
| 推荐 | 概率区间映射冲稳保 | 聚焦模式降低信息密度 |
| 表单 | 重绘原生控件 | 自绘 select 箭头、国风下拉 |
| 动效 | CSS animation + prefers-reduced-motion | 所有动画都有降级 |
| 移动端 | 面板转底部抽屉 + dvh | 真机测试过的适配 |
| 无障碍 | 完整 ARIA + focus-visible | 自绘组件也带语义 |
它的核心启示是:地图不只是一个底图,它可以是一个信息组织方式。 1500 所大学放进一个表格是灾难,放进一个排行榜是焦虑,但放进一幅山河里,它变成了「你可以去哪里看看」。
写在最后
这个模拟器让我印象最深的,不是它用了什么高深的技术——MapLibre、CSS 动画、SVG 都是常规武器。而是它把一件焦虑的事,做成了一件安静的事。
朱砂、宣纸、楷体、山河、落花——这些元素不是装饰,它在服务于一个体验目标:让你在做一个重要决定时,感觉是在「展开一幅画卷」,而不是「填一份表格」。
冲稳保的橙红、金黄、墨绿,不是随便选的颜色,它们对应着你内心对风险的直觉感受。志愿表的风险提示,不是冷冰冰的「建议」,是「你这盘棋稳不稳」的判断。RIASEC 测评放在第一位,是因为它知道——比「能上哪」更难的,是「想学啥」。
技术选型是手段。一个产品最终好不好用,取决于你有没有把用户的真实处境想透。这个模拟器想透了。
参考资料:
评论互动