简介:代码高亮是网页展示源代码时提升可读性的常用手段,这份压缩包专为需要在个人站点、技术博客或在线文档中嵌入代码块的开发者准备。内含三个文件,包括两份JavaScript脚本与一份CSS样式表,压缩包整体仅14KB,轻量易部署。CSS样式负责定义关键字、注释、字符串、变量等语法元素的配色与字体,让代码层次分明;两份脚本则承担识别多语言代码、遍历页面中的预格式化或代码标签并应用高亮的逻辑,其中压缩优化版能减少请求时间、加快页面加载。资源覆盖HTML、CSS、JavaScript、Python、Java、C++等常见开发语言,为代码块添加对应类名即可自动高亮,同时支持行号显示,还能按需扩展内联错误提示。目前已有842人学习下载,对追求轻量、美观且易集成的代码展示效果的站主与博主而言,是一套上手简单、几乎零配置的实用工具资源。
1. 三个文件就能完成代码高亮:prettify 的用法比你想的更简单
很多人拿到 prettify 的代码包,看到里面只有 prettify.css、prettify.js、run_prettify.min.js 三个文件,第一反应是“这么点东西能干什么”。实际上,这个组合正是它的价值所在:不依赖重型框架,不引入一大串 node_modules,只要在页面里放一个<pre class="prettyprint">,加载完脚本后,HTML、CSS、JS 等常见语言就会被自动拆成几十个带语义 class 的 span,样式全交给 prettify.css 控制。它适合博客文章、文档站、后台管理系统里那种“只需要展示一小段代码,不想为一个功能装一整套工程化方案”的场景。很多从业者把这三个文件一股脑丢进项目后高亮没出来,就怪库不好用,其实是没搞清楚三个文件的角色分工。这篇文章就把文件关系、接入步骤和典型翻车现场一次讲透。
2. 拆开 prettify 的三个文件:样式、逻辑与自动启动器各自在做什么
2.1 文件清单与分工逻辑
先把三个文件的职责边界理清。表格里是它们在页面里的实际角色:
| 文件 | 类型 | 角色 |
|---|---|---|
| prettify.css | 样式表 | 定义高亮后 token 的配色、行号样式、背景色 |
| prettify.js | 核心解析器 | 提供全局对象 PR,负责把代码文本解析成带 class 的 span |
| run_prettify.min.js | 自动启动器 | 加载后自动扫描页面里的 prettyprint 节点,并触发解析 |
这份东西的核心设计是“样式与逻辑分离”。prettify.js 只负责解析不出颜色,默认情况下没有 prettify.css,高亮后的代码就是一堆没有任何视觉区别的裸 span。CSS 才是真正让你看到“keyword 是蓝色、字符串是绿色”的那一层。很多人在引入时只记得加 script 忘了加 link,最后页面上代码结构都拆好了,但看起来跟纯文本没两样,问题就出在这。
2.2 手动挡与自动挡:为什么需要一个启动器
prettify.js 很“被动”,它只提供 PR 对象,不扫描页面。如果你只引入 prettify.js,页面里的 pre 不会有任何变化,必须手动调用一次PR.prettyPrint()。而 run_prettify.min.js 则把这一步包进了自动逻辑里,内部会在 DOM ready 之后自己扫描并调用解析。两者不是叠加关系,而是替代关系:
<!-- 自动挡:加载后无需额外写 JS --> <script src="assets/prettify/run_prettify.min.js"></script><!-- 手动挡:自己控制触发时机 --> <script src="assets/prettify/prettify.js"></script> <script> // 最近使用这种写法时,注意要等页面上的代码块都挂载完成 window.addEventListener('load', function () { PR.prettyPrint(); }); </script>逻辑说明:第二种写法给了你控制权,适合代码块是异步渲染的场景,但必须在所有内容插入完成后再调用。自动挡的好处是省事,代价是它只扫一次,后插入的节点它管不着。参数说明:window.addEventListener('load')比DOMContentLoaded更稳妥,因为有些懒加载的代码块要等静态资源都就绪了才会出现,load 事件能覆盖到这部分。
2.3 高亮器认的是 class:pre 标签的命名约定
prettify 的触发条件很简单——class 里带着prettyprint的<pre>标签。它不认<code>,也不会主动分析页面里所有代码块。这是这套机制最大的认知差:以为引入脚本就能高亮全局的pre,结果页面纹丝不动。
<pre class="prettyprint"> const hello = 'world'; console.log(hello); </pre>这里有个容易被忽略的细节:pre 里的代码如果有<、>,必须转义成<、>,否则浏览器会先把它们当成真实标签解析,代码直接消失一部分。我一般建议在服务端渲染时就把代码原文的 HTML 实体转义好,或者用文本节点写入,别在模板里手写示例代码,很容易翻车。
3. 落地接入:本地文件组织与参数配置
3.1 项目目录结构与引入顺序
拿到这份代码包后,先把三个文件放进项目的统一静态目录,不要散落在不同文件夹里。常见做法是在项目里建一个 vendor 目录,跟其他第三方资源放在一起:
# 推荐目录结构 assets/ └── vendor/ └── prettify/ ├── prettify.css ├── prettify.js └── run_prettify.min.js页面里的引入顺序也有讲究,CSS 放在 head,脚本放在 body 末尾:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>代码高亮页面</title> <link rel="stylesheet" href="assets/vendor/prettify/prettify.css"> </head> <body> <pre class="prettyprint lang-js"> const a = 1; </pre> <!-- 自动挡,放 body 最后 --> <script src="assets/vendor/prettify/run_prettify.min.js"></script> </body> </html>参数说明:样式文件必须先行,否则脚本解析完没有对应样式,页面会闪一下“裸代码 → 上色完成”的过程。脚本放 body 末尾是为了确保 DOM 树已存在,run_prettify 内部虽然也监听 DOMContentLoaded,但位置放对可以少等一个事件循环,视觉上体验更顺。避免在 head 里加defer也行,但没必要,直接在底部引入就是最稳的。
3.2 语言标注、行号与样式入口:class 里的三个实用参数
prettify 通过 class 名来接收参数,不需要任何配置对象。最常用的三个组合如下:
<!-- 指定语言 + 显示行号 --> <pre class="prettyprint lang-js linenums"> function add(a, b) { return a + b; } </pre>lang-js告诉解析器按 JavaScript 语法规则切 token,linenums打开行号列。这里的核心机制是:有了明确的lang-*前缀,解析器就不再依赖开头几个字符去猜语言,出错率大幅降低;linenums则会让代码块内部生成一个列表结构,行号由 CSS 控制展示方式。
要说明的是,lang-*的取值不是随意写的,只有解析器内置的语言识别模块或额外加载的语言子集文件认识这些前缀,才能生效。比如lang-html、lang-css、lang-js属于默认带的基础能力,而部分冷门语言需要额外加载对应模块,这个在第五章细说。
3.3 多语言混合页面:一次高亮搞定所有代码块
一个页面里同时展示 HTML、CSS、JS 的示例是常态,prettify 的处理方式就是把语言标注写在各自的 pre 上,互不影响:
<pre class="prettyprint lang-html"> <div class="box">内容</div> </pre> <pre class="prettyprint lang-css"> .box { color: red; } </pre> <pre class="prettyprint lang-js"> const box = document.querySelector('.box'); </pre>这里最容易踩的坑是最前面那个 pre。HTML 代码里的<div>如果没转义,浏览器会真的创建一个 div 元素,pre 里剩下的只有文本,高亮出来的结果缺字少标。所以凡是展示 HTML 标签,必须用<和>写。实际项目里,我会用正则把<替换成<,不是手动写出这些代码。文本转义和处理逻辑分离,才不会在几十段代码里改到手抽筋。
4. 高亮失效排查:五个真实的翻车现场
4.1 现象:页面刷新后才高亮,首次进来全是裸代码
现象:打开页面,代码块先是纯文本状态,过一两秒才“啪”地染色。或者手动刷新一次就正常,分享给别人打开又是裸代码。
原因:许多入门项目把 prettify 脚本放在 head 里,run_prettify 监听到 DOMContentLoaded 时,代码块也许还没被插入页面,或者脚本执行时机早于渲染进程。自动挡脚本只扫一次,错过就再也触发不了。
解决:把 run_prettify.min.js 的 script 标签移到 body 末尾,确保它执行时代码块已经在 DOM 里。如果代码块是异步接口返回后再注入的,就不要用自动挡,改用手动挡,在数据渲染完成后调用PR.prettyPrint()。
4.2 现象:行号不显示或者和代码对不齐
现象:加了linenums类名,行号没出来;或者行号出来了,但第一行和第二行之间有空白,行号数字跟代码行对不上。
原因:prettify 的行号依赖一组列表结构,而默认主题里这块样式依赖对ol.linenums的预设。如果你的项目全局样式里重置了列表的padding、margin或list-style,行号列就会被压缩或隐藏。另一个常见问题是代码块里第一行是空行,导致行号整体偏移。
解决:给 prettify 设置专属样式,别让全局 reset 波及:
pre.prettyprint ol.linenums { padding-left: 40px; margin: 0; list-style: decimal; }参数说明:padding-left控制行号的水平占位,list-style: decimal强制出现数字序号。加上前缀pre.prettyprint是为了把作用域限定在当前代码块,避免污染页面其他有序列表。写完这个后,行号列和代码列的间距就稳定了。
4.3 现象:语言识别失败,代码永远显示成纯文本
现象:给 pre 加了lang-c,想着高亮 C 语言,结果页面上一行颜色变化都没有。
原因:prettify 的语言模块命名有自己的一套规则,C/C++ 家族的文件前缀是lang-cc或lang-cpp,不是lang-c。你写了一个解析器不认识的语言前缀时,它会退化成“猜模式”,靠代码内容开头几个字符去猜,猜不中就显示纯文本。这套命名在几个冷门语言上尤其不直观。
解决:在使用前先确认当前这份资源里有哪些语言模块。常见做法是先看看目录里是否存在lang-*.js文件,或者直接看 prettify.js 源码头部的语言注册列表。写 class 时用确认过的前缀,例如lang-html、lang-css、lang-js、lang-java、lang-cc。如果背后有语言模块但不确定文件叫什么,可以全名试,比如lang-cpp、lang-java,不行就换成lang-cc,这类玄学问题靠名称清单能一次规避。
4.4 现象:Markdown 渲染出的代码块高亮失效,页面出现多重嵌套的 pre
现象:用 Markdown 写了 fenced code block,渲染出来的页面里代码缩进越来越深,或者 pre 套 pre,高亮完全没生效。
原因:很多 Markdown 渲染引擎会把 ``` 代码围栏渲染成<pre><code>...</code></pre>结构。如果你在渲染模版里又手动包了一层<pre class="prettyprint">,就形成了嵌套。prettify 扫描到外层 prettyprint 后,内部锁定的还是另一个 pre,解析器按 pre 内容抽取时把 pre 标签本身当成了代码文本,结果越高亮越糟。
解决:路由选择二选一。要么放弃 Markdown 渲染,直接手写高亮节点;要么在渲染完成后再用脚本修正结构:
// 找出所有 pre > code,给父级 pre 补上 prettyprint 类 document.querySelectorAll('pre > code').forEach(function (codeEl) { var pre = codeEl.parentNode; if (pre && !pre.classList.contains('prettyprint')) { pre.classList.add('prettyprint'); } }); // 修正后手动触发一次高亮 PR.prettyPrint();逻辑说明:这个脚本先把 class 补上,再用PR.prettyPrint()统一处理,避免你在模版层改一堆东西。注意要在 Markdown 渲染完成之后执行。要是页面里有多个 Vue 或 React 组件反复渲染,最好改成在组件生命周期的渲染钩子里调用,而不是全局跑一次。
4.5 现象:动态插入的代码永远是裸的,切换路由后高亮消失
现象:单页应用里点击按钮后拉取一段代码插入页面,前几秒没有颜色,重新刷新又正常;或者路由切换后新页面里的代码块全是纯文本。
原因:run_prettify.min.js 只在页面初始化时扫描一轮,之后新插入的 DOM 节点不在它的监听范围内。这类问题是单页应用的经典盲区,换成哪个高亮库都一样。
解决:每次动态内容插入完成后,手动调用PR.prettyPrint():
// 拉取代码片段并插入后,手动扫描 fetch('/api/code-sample') .then(function (response) { return response.text(); }) .then(function (raw) { var box = document.getElementById('codeBox'); box.innerHTML = '<pre class="prettyprint lang-js">' + escapeHtml(raw) + '</pre>'; // 关键:手动触发 PR.prettyPrint(); });参数说明:PR.prettyPrint()不传参会扫全页,传参可以只扫一个容器,比如PR.prettyPrint(document.getElementById('codeBox'))。我一般只传容器,能少折腾页面里其他无关节点。escapeHtml不能省,接口返回的代码可能夹带<、>、&,不转义的话高亮前就被浏览器解析了。
5. 进阶定制:把 prettify 调成顺手的代码展示工具
5.1 换主题:token class 才是真正的皮肤
prettify 本身不绑定一套死的皮肤,它解析出的每个 span 都有语义 class,比如kwd是关键字、str是字符串、com是注释、typ是类型、lit是字面量、pun是标点、tag是 HTML 标签名、atn是属性名、atv是属性值。换主题本质上是给这些 class 重新定义颜色。
pre.prettyprint { background: #282c34; padding: 16px; border-radius: 6px; } pre.prettyprint .kwd { color: #c678dd; } pre.prettyprint .str { color: #98c379; } pre.prettyprint .com { color: #5c6370; font-style: italic; } pre.prettyprint .typ { color: #e6c07b; } pre.prettyprint .lit { color: #d19a66; } pre.prettyprint .tag { color: #e06c75; }逻辑说明:这些 class 名是 prettify 源码里固定的,覆盖它们就等于换了主题,不需要改 prettify.js。参数说明:.tag、.atn、.atv三个 class 要组合调整。只改.tag不改.atn,HTML 标签会变成一半有颜色一半没颜色,看起来非常割裂。直接改 prettify.css 里对应段落也是一种方式,但独立写一份覆盖样式更利于日后维护。
5.2 按需加载语言模块:语言子集不要全塞进页面
prettify 的默认包只包含一部分常用语言识别能力。遇到冷门语言时,需要额外加载语言子集文件。常见配置方式有两种:
<!-- 方式一:带参数引入,run_prettify 按需加载 --> <script src="assets/vendor/prettify/run_prettify.min.js?lang=css&lang=java&lang=py"></script><!-- 方式二:手动逐个引入语言脚本 --> <script src="assets/vendor/prettify/prettify.js"></script> <script src="assets/vendor/prettify/lang-css.js"></script> <script src="assets/vendor/prettify/lang-java.js"></script> <script src="assets/vendor/prettify/lang-py.js"></script>参数说明:方式一的?lang=参数会在初始化时拉取对应语言模块,简洁但依赖网络请求时序。方式二更可控,语言脚本在解析器加载完成后按顺序注入,适合本地静态站点。我不知道这个资源包里到底有哪几个语言模块,你自己动手时先列个清单:把目录里的lang-*.js文件名过一遍,然后只引入页面真正会出现的语言,不要全塞。多加载十个语言模块,首屏就多付出十个 HTTP 请求的代价。
5.3 手动触发高亮:PR.prettyPrint 的正确打开方式
前面几章反复提到PR.prettyPrint(),这里的PR就是 prettify.js 暴露出来的全局对象。手动触发时有两个关键点:一是调用时机必须晚于代码块插入 DOM,二是能传参就传参。
// 在某个异步流程中,代码块已经被插入页面 var container = document.getElementById('articleContent'); // 只扫描容器内的 prettyprint 节点 PR.prettyPrint(container);不传参会重新扫描整个文档,页面大、代码块多时会有可感知的卡顿。传容器之后,扫描范围被压缩,性能更可控。实践中,如果代码块是列表页多个区域动态刷新,每个区域刷新后各调一次PR.prettyPrint(domNode)就够用了。
6. 验证高亮是否真生效:看 DOM 而不是看屏幕
经常有人“看起来没颜色”就认为高亮失败,但眼睛会被主题色骗。最可靠的验证方式是打开开发者工具,找到任意一个prettyprint节点,展开它的内部结构。如果里面出现了形如<span class="kwd">if</span>的结构,说明解析已经完成;如果只有文本节点没有任何 span,那就是解析本身没触发,跟样式无关。另一个快速验证入口是控制台输入window.PR,如果返回 undefined,说明 prettify.js 根本没加载成功,网络路径写错了。
// 验证解析层是否工作 document.querySelectorAll('.prettyprint span.pln, .prettyprint span.kwd, .prettyprint span.str').length;返回的数字大于 0,就证明解析器把代码切成了语义节点,接下来要调的是配色而不是脚本。还有一个经常被忽略的细节:长代码块在整页滚动时会出现明显卡顿。给代码块加一个高度上限,能显著改善页面性能:
pre.prettyprint { max-height: 480px; overflow: auto; }这段样式让超过 480px 的代码块内部滚动,而不是把整个页面撑得老长。我印象很深的是,有次把一个很长的事务日志塞进代码块里,页面直接卡了两秒,从那以后每次集成 prettify,我都会强制过一遍三件事:class 拼写正确、语言模块已加载、动态插入后重新调用过PR.prettyPrint()。这几个环节都正常,高亮基本不会再出玄学问题。希望帮到你。
本文还有配套的精品资源,点击获取