使用方法
- 把 HTML 片段或整页源码粘进上面的输入框。页面打开时输入框里已经放了一段 28 行的示例,结果区同时给出它的格式化结果,可以立刻对照着看。
- 设置「每级缩进的空格数」,允许 1~8 的整数,默认 2;勾选「用 Tab 缩进」后这个数字被忽略,每级改成一个制表符(ASCII 码 0x09)。在空格数输入框里直接按 Enter 就会立刻重排。
- 在源码输入框里按 Ctrl + Enter(macOS 用 Cmd + Enter)触发格式化。单独按 Enter 仍然是换行——这个框是用来写代码的,把回车抢去当快捷键会让多行代码根本没法手动调整。
- 看统计条:示例的六个数字是元素数 14、最大嵌套深度 4、空元素 2、原样区块 5、输出行数 30、输出字节 751。「最大嵌套深度」指整段里最深的那一层,示例是 body → div → p 这条链,第 4 层。
- 点「复制结果」带走格式化后的源码,或点「下载 .html」存盘。下面那张 47 行的标签对照表默认只显示前 12 行,点「加载更多」展开其余;也可以「导出 CSV」(带 BOM,Excel 打开不乱码)或「复制对照表」(制表符分隔,可直接粘进表格软件)。
这个 HTML格式化 能解决什么问题
一是把挤成一行的模板拆开。模板引擎吐出来的 HTML 常常整段压在一行里,标签套标签,出问题时光看结构都费劲。这类文本里没有任何有意义的空白,重排缩进是纯收益。
二是把「不能动的空白」保护起来。这正是在线 HTML格式化 最容易翻车的地方:拿到 <pre>、<textarea>、<script>、<style> 就当普通元素处理,给里面的每一行统一加缩进。可 <pre> 里的行首空格要显示给用户看,<textarea> 的内容会被原样提交给服务端,<script> 里可能藏着多行字符串——一旦被重新缩进,页面上就凭空多出空格,代码语义也可能被悄悄改掉。本工具把 <script>、<style>、<pre>、<textarea>、<title> 这五个区块的正文整体原样输出,开标签、正文、结束标签三者紧贴,中间连一个换行都不插。
三是看清单行的标签有没有漏闭合。压成一行的源码里少一个 </div> 根本看不出来;重排之后缩进层级会立刻显出异常,统计里的「最大嵌套深度」也会比预期深。工具还会在提示区报出未闭合标签的数量。
四是让 diff 变得可读。两个版本的模板如果都没格式化,代码评审时几乎所有行都被标成改动;先各自格式化再对比,改动的往往只有真正被改的那几行。
五是属性值里的尖括号不再把解析带偏。<a title="a > b"> 这种写法在粗心的实现里会被当成「标签在 > 处结束」,于是 b"> 变成游离文本。本工具按引号成对扫描标签,引号里的 > 不当分隔符用。
本页不做这些事:不压缩体积(去空白请用 HTML 压缩)、不校验标签是否符合规范(请用 HTML 校验)、不把富文本转成纯文本或 Markdown、不格式化 CSS 与 JavaScript 内部代码。它只做一件事:在不改变文档内容的前提下重排缩进。
原理和变量:词法切分、缩进栈与五个原样区块
① 调用的算法。本页没有引入任何第三方 beautifier,逻辑是一个两段式的手写词法器 + 缩进栈。第一段把源码切成四类节点:普通文本、注释与声明(<!-- -->、<!DOCTYPE>)、标签、原样区块;标签再分开始标签、结束标签与自闭合标签。第二段用一个整数 d 记录当前层级:遇到块级开始标签就 d++,遇到块级结束标签就 d--,每行输出前用 d 乘以一个缩进单元做前缀。
② 标签是怎么切出来的。扫描到 < 之后按字符往前走,同时维护一个「当前引号」状态:遇到 " 或 ' 进入引号态,只有再遇到同一个引号才退出;只有不在引号里的 > 才被当作标签结束。这就是 <a title="a > b"> 能被正确切成一个标签的原因。很多实现图省事用正则 /<[^>]*>/ 抓标签,碰到属性里带 > 必然切错,这是本页与它们最直接的差别。
③ 五个正文原样区块,凭什么是这五个。依据是 W3C HTML 5.1 与 WHATWG HTML Living Standard 第 13.1.2 节对元素的分类:script 与 style 是 raw text elements,解析器不把它们的内容当标签看;textarea 与 title 是 escapable raw text elements,内容里的字符引用会解码,但同样不产生子元素;pre 本身是普通元素,它保留空白靠的是标准渲染章节给的默认样式 white-space: pre。这五个的正文都属于「内容」而不是「排版」,所以工具一个字都不动。
④ 为什么不给原样区块的正文加缩进。因为缩进就是在正文前面插空白。给 <pre> 的每一行补上缩进空格,页面上显示的文字就真的会多出这些空格;给 <textarea> 补缩进,提交给服务端的表单值也跟着变了。所以工具输出的是「缩进前缀 + 开始标签 + 原样正文 + 结束标签」这一整段,只在整段的第一行前面加缩进,正文里的换行原封不动。示例里 <style>、<pre>、<textarea>、<script> 四块的正文看起来比周围浅一层,就是这个原因——不是漏缩进,是不能缩。
⑤ 空白只动两处。一是标签内部、引号之外的连续空白压成一个半角空格,所以示例里 <div class="card" data-id="7"> 变成 <div class="card" data-id="7">,而引号内的空白原样保留;二是文本节点里的连续空白压成一个空格,行首行尾的空格去掉。注意 <style> 与 <script> 的正文属于原样区块,不参与这两条——所以示例里 .card { color : #333 ; } 那三段空白一字未改。这两条合起来就是「只改排版、不改内容」的边界。
⑥ 空元素清单为什么是 13 个,工具里却装了 18 个。规范第 13.1.2 节列出的 void elements 是 area、base、br、col、embed、hr、img、input、link、meta、source、track、wbr 共 13 个,它们没有结束标签、不产生子节点,所以不增加层级。但 MDN 的 Void element 词条至今还把 param 一起列成 14 个,那是旧版 HTML5 的清单。本工具的处理是:13 个按规范算,另外把 basefont、bgsound、frame、keygen、param 这 5 个已废弃的空元素也按空元素处理,一共 18 个。理由是它们同样没有子节点,若按普通元素处理,会凭空多出一层缩进,而且每次都会报「未闭合」——那是纯粹的噪声。
⑦ 缩进栈对不上时会怎样。遇到没有配对的结束标签,d 到 0 就被夹住,不会变成负数。收尾时如果 d 还大于 0,说明有开始标签没闭合,工具照样输出结果,只在提示区报出数量——格式化器不该替你做「补标签」这种有副作用的决定。
⑧ 行内元素的处理。缩进单元只有两个来源:数字输入框(1~8 个空格)或勾选 Tab(0x09)。行内元素(a、span、strong、em、code 等 24 个,其中 4 个同时是空元素)不会换行,而是和前后文本拼在同一行里,这样 <p> 里的加粗和链接才不会各占一行。
完整示例(数值仅作演示)
示例条件与页面打开时完全一致:28 行源码、每级 2 个空格。实测结果:
- 输入 28 行 / 529 字符 / 677 字节,输出 30 行 / 603 字符 / 751 字节;
- 解析出 14 个元素,其中空元素 2 个(
<meta>、<img>)、原样区块 5 个; - 最大嵌套深度 4(html → body → div → p);行内元素与文本同行,没有各自占行。
同一段输入换缩进单元,行数不变、体积会变,这就是缩进宽度这个变量唯一的影响面:
| 缩进方式 | 输出行数 | 输出字节 | 相对 2 空格 |
|---|---|---|---|
| 2 个空格(默认) | 30 | 751 | 基准 |
| 4 个空格 | 30 | 825 | +74 字节(+9.9%) |
| 1 个 Tab(0x09,按 1 字节算) | 30 | 714 | −37 字节(−4.9%) |
最能说明「原样保留」的是 <pre> 那两行。输入里它们是「两个空格 + 第一行」「四个空格 + 第二行」,输出里依旧是这两个和四个空格,工具既没有把它们补齐到当前第 3 层(6 个空格),也没有把第二行相对第一行的两级缩进压平。同样地,<style> 里的 .card { color : #333 ; } 保持三段空白原封不动——CSS 的内容不归本工具管,它只负责不破坏。另两处对照:
- 属性折叠:输入
<div class="card" data-id="7">→ 输出<div class="card" data-id="7">(引号外的三个空格压成一个); - 引号保护:输入
<a title="a > b">→ 输出<a title="a > b">,整个标签被完整识别,没有在>处断开。
输入范围与标签对照表
下面把输入条件和结果放在一起,方便预判。表里的数字都来自上面那段 28 行示例。
| 输入 / 选项 | 能否处理 | 结果 |
|---|---|---|
| 空输入或只有空白 | 拒绝 | 报「输入框是空的」,结果区清空,不会给出空白的成功结果 |
| 超过 20 万字符 | 拒绝 | 报出实际字符数并说明上限,避免长文本把页面卡住 |
没有任何 < 标签的纯文本 | 拒绝 | 报「没有找到以 < 开头的标签」,提醒这页是给 HTML 用的 |
标签里的引号没闭合(title="a > b) | 拒绝 | 报出出错的行第几列,指出从那里的 < 起找不到配对的 > |
有开始标签没闭合(<div><p>a</p>) | 处理 | 照常输出,提示区报「1 个开始标签未闭合」,最大深度按解析结果记 |
| 缩进空格数填 0 或 9 | 拒绝 | 报「只能填 1~8 的整数」,页面上限与常见编辑器一致 |
| 缩进空格数填 2.5 或空 | 拒绝 | 同上,小数与空值都不接受,不做四舍五入 |
| 勾选「用 Tab 缩进」 | 处理 | 忽略空格数,每级 1 个 0x09;示例总体积从 751 降到 714 字节 |
| SVG / MathML 片段 | 处理 | 按同名元素的通用规则缩进;<circle /> 这类自闭合标签不增加层级 |
结果边界、失败路径与精度说明
- 输入上限 20 万字符(按 UTF-16 码元计,就是输入框的
maxlength)。超出会被拒绝并给出实际长度。之所以不设更高,是因为词法器逐字符扫描,20 万字符在这个实现下耗时仍在几十毫秒量级,再往上首屏就会卡。 - 每级缩进 1~8 个空格,或 1 个 Tab。下限 1 是为了让层级差看得出来,上限 8 是因为再宽就会出现大量横向滚动;示例用 2,这也是 HTML 社区最常用的取值。
- 属性里的
>不会误判,但属性里的引号必须成对。写成title="a > b(少一个引号)时,扫描会一路吃到文件末尾都找不到配对的>,工具就把这个<的行、列报出来。这是唯一会报行:列的失败路径,也是剪贴板截断最常见的形态。 - 未闭合标签只报警、不修数据。格式化器擅自补一个
</div>会改变文档结构,所以工具选择原样输出并告诉你数量。示例是 0 个。 - 大小写原样保留。工具不改写标签名和属性名的大小写,
<DIV CLASS="x">输出还是<DIV CLASS="x">。HTML 对这两者大小写不敏感,改与不改都能跑,但不改更尊重源码。 - 不处理实体与字符集。
<、{这类字符引用被当作普通文本搬运,不会被解码成<。这一点很关键:如果先解码再切标签,正文里本来想显示给人看的一段 HTML 就会被当成真标签参与解析。 - 文本内的连续空白会被压缩。这是重排的必然代价。如果某段文本的空格有意义,把它放进
<pre>或<textarea>里,工具就会原样保留。 - 输出字节数通常比原文件大。缩进是新插进去的空白,每个空格都要按字节算,因此示例从 677 字节涨到 751 字节,多出的 74 字节几乎全是新加的缩进。格式化是用来读的,不是用来省体积的——要压体积请用 HTML 压缩。
- 输出是「一次快照」,不是持续同步。改了输入或选项要重新触发,防止你编辑到一半时结果区跟着抖动。
常见问题
为什么 pre 和 textarea 里的内容看起来比周围浅一层?
因为没有给它们加缩进,这是刻意的。<pre> 的行首空格会真实渲染给用户看,给它补缩进等于改了显示内容;<textarea> 的正文会被当作表单值原样提交,补缩进等于改了数据。工具只在整段的第一行前面加缩进,正文里的换行和空格一律不动,所以看起来像少了一层。
我写 <div/> 会被当成空元素吗?
不会。HTML 解析器会直接忽略非空元素开始标签里那个斜杠,<div/> 等价于 <div>,后面的内容都是它的子节点。工具按同样的口径处理:非空元素带 /> 仍然增加一层缩进,而且因为等不到结束标签,会计入「未闭合」。这条规则对 <script/> 尤其危险——浏览器会把后面整段 HTML 都吃成脚本文本。
空元素到底是 13 个还是 14 个?
规范第 13.1.2 节列的是 13 个:area、base、br、col、embed、hr、img、input、link、meta、source、track、wbr。MDN 的 Void element 词条还多列了一个 param,那是旧版 HTML5 的清单,param 后来被废弃了。本页的 HTML格式化 按 13 个处理规范清单,另外把 basefont、bgsound、frame、keygen、param 这 5 个已废弃的空元素也一起当空元素,总共 18 个。
属性值里带 >,会被切断吗?
不会。切标签时工具维护了一个引号状态机,只有不在引号里的 > 才结束标签,所以 <a title="a > b"> 会被当成一个完整标签。真正会切错的是用 /<[^>]*>/ 这类正则抓标签的实现,它碰到属性里的 > 就断了。
为什么不顺手把 CSS 和 JavaScript 也格式化?
因为它们不是 HTML。CSS 的花括号与行内注释、JavaScript 的正则字面量与模板字符串,各自都要一套独立的状态机才能安全重排;用 HTML 的规则去套,很容易把 // 注释里的内容或正则里的空白改坏。本页只保证 HTML 层面的缩进正确,代码内部的排版请交给各自的格式化器。
代码里出现 </pre> 这种文字会不会提前结束区块?
会,而且这是 HTML 本身的规则,不是工具的缺陷。按解析规范,<pre> 的正文里出现 </pre> 就代表这个元素结束了,剩下的文字落到外面。要在页面上显示这段文字,必须写成 </pre>。工具同样如此——它不猜你的意图,只按规范切。
格式化之后前端样式跑偏了,可能是什么原因?
绝大多数情况不是本工具改了内容,而是原文件本来就靠空白占位:比如把一段文字直接写在 <div> 里,用连续空格或换行做间距。这类写法依赖的是空白折叠之外的排版技巧,重排后自然就散了。正确做法是把间距交给 CSS 的 margin / padding,或者把需要原样显示的部分放进 <pre>。
能处理 XML、SVG 或 Vue / React 的组件文件吗?
能分段处理但不能当成专用格式化器。SVG 与 MathML 片段是按标签嵌套缩进的,<circle /> 这类自闭合标签不增加层级,这部分没问题。但 .vue 与 .jsx 里混着 JavaScript 表达式和插值语法,表达式里的比较运算符会被当成标签字符,所以整份组件源码不适合直接扔进来;只把其中的模板片段单独格式化是安全的。
相关工具
- HTML压缩:反过来的方向,去掉注释和多余空白换更小的体积,同样要小心 pre 与 textarea。
- HTML校验:标签拼写、属性合法性、嵌套规则这类本页不查的问题交给它。
- HTML转纯文本转换器:剥掉全部标签只留文字,适合做摘要或提取正文。
- HTML转Markdown转换器:把结构化的 HTML 转成 Markdown,写文档时省手工。
- CSS格式化美化:本页故意不动的样式代码,交给它单独排版。
- XML格式化:同为标记语言,但自闭合标签的规则比 HTML 严格得多。
- 开发者工具:查看全部开发者工具。