AO3 HTML 排版指南:哪些标签能用,为什么有些会失效
如果你在 AO3 发过文,可能遇到过这些情况:本地看起来好好的排版,贴到 AO3 后突然变样,颜色没生效;聊天体在手机上挤成一团;预览时 AO3 还自动给你补了一堆 <p>、<br>。
AO3 并不是一个“随便贴网页代码”的编辑器。它允许作者用一部分 HTML 和 CSS 做排版,但会先把内容送进 parser 和 sanitizer:parser 尽量补齐、整理不完整的代码,sanitizer 会移除不允许或有风险的部分。AO3 官方 HTML FAQ 已经直接说明了这一点。1
这篇不是开发教程,只是想解决一个作者经常会遇到的问题:为什么有些排版会失效;如果你希望作品可下载、能在不同设备上打开,排版时最好把力气花在哪里。正文会参考 AO3 官方 FAQ,也会提到 AO3 公开源码里能看到的一些实现细节,但重点还是作者写文时怎么判断。
AO3 为什么要清洗 HTML?
原因很简单:AO3 的作品页不是作者自己的私人网页,而是读者会直接打开的公共档案页面。
如果 AO3 允许任意 HTML、CSS 和 JavaScript,每一篇作品都可能影响整页:追踪读者、插入外部脚本、遮挡界面,甚至破坏站点结构。对一个长期保存用户投稿的档案馆来说,这个风险太高。
所以 AO3 用的是“允许名单”逻辑。不是“除了危险的都可以”,而是“只有名单里的才可以”。官方 HTML FAQ 列出了允许的 tags 和 attributes;源码里的 sanitizer_config.rb 也能看到类似配置:a、blockquote、br、div、em、img、ol、p、span、table、strong、ul 等元素可以保留,但属性和链接协议都有范围。12
很多排版失效并不是因为你“写错了”,而是 AO3 从一开始就不允许那段代码进入最终页面。
哪些 HTML 更稳?
从作者角度看,最稳的还是基础语义标签。
比如:
<p>:段落<br>:换行<strong>/<b>:加粗<em>/<i>:斜体<blockquote>:引用<hr>:分隔线<ul>、<ol>、<li>:列表<a href="...">:链接<img src="...">:图片<ruby>、<rt>、<rp>:注音 / ruby 标注<table>、<tr>、<td>、<th>:表格
这些标签在官方 FAQ 和源码允许名单里都能看到。它们不一定让页面看起来很“高级”,但胜在耐用:移动端能读,下载成 EPUB 后不太容易崩,读者换皮肤或关闭作者样式时,内容本身还在。
对中文作者来说,ruby 值得多看一眼。如果你要写汉字注音、日语假名、术语读音,AO3 允许 <ruby>、<rt>、<rp> 这一组标签,比把括号硬塞进正文里更干净。
为什么 script、iframe、style 会消失?
因为它们不在普通作品 HTML 的安全范围里。
在 AO3 源码的 sanitizer 配置里,script、iframe、style、svg、math、noscript 等会被列入 remove_contents。也就是说,它们不只是标签会被删,里面的内容也可能一起被移除。2
这会影响很多人常见的写法。
如果你把下面这种东西贴进作品正文:
| |
它不会像普通网页那样运行。AO3 会把这些东西清掉。想做样式,就不要把 <style> 写进正文,而应该用 Work Skin;想插入外部网页,也不能靠 iframe 嵌进去。
这是安全边界。读者打开作品页时,不应该执行陌生作者塞进来的脚本。
为什么 <3 有时也会出问题?
在 HTML 里,< 会被当成标签开始。比如 <em> 是斜体标签,<p> 是段落标签。那作者在正文里写 <3 呢?
AO3 源码的 HtmlCleaner 里有一行专门处理这个问题:它会把 <3 转成 <3。3
也就是说,AO3 知道很多作者会写这个小心心。它没有简单粗暴地把所有 < 都当成错误,而是在清洗前先保护了一些常见输入。
不过这也提醒我们:如果你要在正文里显示真正的小于号,最好写成 <;显示大于号,可以写成 >。官方 HTML FAQ 也建议这样做,因为 < 可能会让 parser 以为你开始写 HTML 了。1
Work Skin 能做什么?
如果基础 HTML 不够用,就轮到 Work Skin。
AO3 官方 Skins FAQ 说,skin 是用 CSS 改变 AO3 或单篇作品显示方式的 stylesheet;Work Skin 负责改变某一篇作品的呈现。创建 Work Skin 后,作者还要在作品里加对应的 HTML class,CSS 才知道该作用到哪里。4
这就是为什么很多聊天体、短信体、论坛体会长这样:
| |
然后在 Work Skin 里写:
| |
普通 HTML 负责“这是什么内容”,Work Skin 负责“它看起来像什么”。
为什么 Work Skin 也不能随便写 CSS?
Work Skin 不是无限制 CSS。
AO3 官方 Skins FAQ 说明,自定义 skin 只能使用有限的一组 CSS properties 和 values。源码里的 CssCleaner 也是这个思路:解析 CSS,检查 property 是否支持,再检查 value 是否符合允许的颜色、数字、单位、函数、URL 等格式。不符合要求的属性或值会被清掉,并给出错误。45
WorkSkin 模型里还有一个很关键的细节:保存 Work Skin 时,AO3 会给选择器加上 #workskin 前缀。6
这对作者很重要。你的 Work Skin 默认只能影响作品正文区域,而不是整个 AO3 页面。你写 .message,AO3 实际会让它变成类似 #workskin .message 的作用范围。这样 CSS 就不会跑出去改导航、评论区、kudos 按钮或整个站点界面。
源码里还限制了几种容易出事的 CSS:Work Skin 不允许 CSS custom properties,也就是 --name 这种变量定义;不允许 var();也禁止 position: fixed。6
AO3 没有给每个作者发一个完整网页沙盒,它只开放一小部分样式,用来改变作品正文的呈现。
为什么有些排版预览正常,发布后仍然不稳?
因为读者的阅读环境不一样。
Work Skin 默认会展示给读者,但读者可以选择隐藏作者样式。AO3 官方 Skins FAQ 说明,读者可以在单篇作品页面点击 “Hide Creator’s Style”,也可以在 Preferences 里勾选 “Hide work skins”,让所有作品隐藏自定义 Work Skin。4
所以有一个很实用的原则:不要把关键信息只放在样式里。
比如,下面这种写法就不太稳:
| |
读者一旦关闭 Work Skin,颜色没了,A/B 就分不出来了。
更稳的写法,是把身份写进文本结构里:
| |
聊天体、论坛体尤其需要注意这一点。好看的排版可以加分,但作品的基本信息最好不要只靠颜色、位置、背景图或浮动来传达。
为什么复杂排版下载后会变样?
AO3 的作品不只在网页里被阅读。很多读者会下载 EPUB、MOBI、PDF 或 HTML,也会用手机阅读器、电子墨水屏、浏览器阅读模式打开。
复杂 Work Skin 在网页上可能很好看,但下载文件未必会完整保留同样的 CSS 效果。就算保留了,阅读器也可能不支持某些样式。
如果你的目标是“作品长期可读”,排版可以分成两层:
第一层是内容结构。用段落、引用、列表、加粗、角色名,把文字本身写清楚。
第二层才是视觉皮肤。用 Work Skin 增加聊天气泡、论坛框、颜色和间距。
这样即使第二层失效,第一层仍然能读。
对作者来说,最容易踩的坑
第一,把富文本编辑器里的“脏” HTML 直接贴进 AO3。
从 Word、飞书、微信公众号后台复制出来的内容,常常带着一堆 AO3 不需要的样式和属性。AO3 会清洗它们,但清洗后的结果不一定是你想要的。更稳的做法是:先贴纯文本,或者在本地整理成简单 HTML,再放进 AO3 预览。
第二,把 CSS 写进作品正文。
正文里应该放 HTML;CSS 应该放 Work Skin。把 <style> 放进正文,通常会被清掉。
第三,用颜色区分所有信息。
聊天体很容易靠颜色、左右位置、头像来区分角色。但读者关闭 Work Skin、下载 EPUB,或者使用不同站点皮肤时,这些区别可能消失。最好在文本里保留角色名、时间、场景提示。
第四,不预览。
AO3 官方 FAQ 也提醒,发布前最好预览 HTML。parser 可能会帮你补闭合标签、调整结构;如果标签嵌套有问题,预览时通常能看出来。1
一个实际例子:更稳的聊天体
如果你想写聊天体,最容易踩的坑是:只用颜色、左右位置或气泡样式区分角色。这样的排版在网页上可能很好看,但读者一旦隐藏 Creator’s Style,或者下载成 EPUB,角色关系就可能变得很难读。
更稳的做法,是先让 HTML 本身能读,再用 Work Skin 美化。
作品正文里可以这样写。注意每条消息本身都带着角色名,left / right 只是给 Work Skin 用来控制显示方向:
| |
Work Skin 里可以这样写:
| |
这个例子不追求“最像手机截图”。它的重点是:关掉 Work Skin 后,每条消息仍然是 林:你到了吗?、周:还在路上。 这样的完整文本。气泡、左右位置和背景都只是第二层;冒号也保留在 HTML 里,只是在样式开启时隐藏。
notfun做了一个轻量预览页,可以用来练习这种基础 HTML + Work Skin 写法:AO3 HTML / Work Skin 预览器。聊天体模板和上面的结构是同一个思路:先保证关掉 Work Skin 也能读,再让样式负责气泡和位置。它不是 AO3 官方 sanitizer,也不能保证和 AO3 最终渲染完全一致;它只是帮你在发布前先看一眼:开关 Work Skin 以后,作品还能不能读。
这些规则对作者意味着什么?
从官方 FAQ 和 AO3 公开源码中的实现细节看,AO3 大概按这些原则处理作者输入。
比如:
- HTML 不是黑名单,而是允许名单。
- 普通字段不允许 inline CSS。
- Work Skin 里的 CSS 会被清洗。
- Work Skin 选择器会被限制在
#workskin之内。 - 不安全或难以清洗的标签会被移除。
- sanitizer 版本变化后,AO3 还有批量重新清洗旧字段的任务。7
但这些实现细节也不能告诉我们一切。对作者来说,最实用的结论不是背允许名单,而是理解 AO3 的取舍:
它允许你排版,但不允许你把作品页变成一整个自定义网页。
它允许你美化作品,但读者可以关闭作者样式。
它允许你做复杂格式,但长期保存最依赖的仍然是清楚的文本结构。
如果你想做聊天体、论坛体,可以用 Work Skin,但不要让关键信息只存在于颜色和位置里。
如果你并不追求复杂样式,只是想做普通的加粗、斜体、链接、列表或分隔线,AO3 自带的富文本编辑器可能是更好的选择。它少了很多手写 HTML 的维护成本,也不太容易因为一个标签没闭合就把后面的段落带歪。
如果你希望作品多年后还能被人顺利读到,最稳的排版往往不是最炫的,而是样式被清掉以后,文本仍然成立。
AO3 官方 FAQ “Formatting content on AO3 with HTML” 说明,AO3 会用 parser 和 sanitizer 处理作者输入,并列出允许的 HTML tags 和 attributes。https://archive.transformativeworks.org/faq/formatting-content-on-ao3-with-html?language_id=en ↩︎ ↩︎ ↩︎ ↩︎
AO3 开源仓库
sanitizer_config.rb中定义了 Archive 使用的 HTML 允许名单、属性、协议,以及会被移除内容的元素。https://github.com/otwcode/otwarchive/blob/master/config/initializers/gem-plugin_config/sanitizer_config.rb ↩︎ ↩︎AO3 开源仓库
html_cleaner.rb中的fix_bad_characters、sanitize_value和add_paragraphs_to_text展示了 AO3 如何在保存/显示前处理文本、HTML 字段和段落。https://github.com/otwcode/otwarchive/blob/master/lib/html_cleaner.rb ↩︎AO3 官方 “Skins and Archive Interface FAQ” 说明,skins 是用 CSS 改变 AO3 或单篇作品呈现的 stylesheet;Work Skin 需要和作品中的 HTML class 配合使用,读者也可以隐藏 creator’s style。https://archive.transformativeworks.org/faq/skins-and-archive-interface?language_id=en ↩︎ ↩︎ ↩︎
AO3 开源仓库
css_cleaner.rb展示了 Work Skin / Skin CSS 的解析、属性和值清洗逻辑,包括对支持属性、支持值、URL、字体和颜色的检查。https://github.com/otwcode/otwarchive/blob/master/lib/css_cleaner.rb ↩︎AO3 开源仓库
work_skin.rb中,Work Skin 保存时会调用clean_css_code并添加#workskin前缀,同时禁止 CSS custom properties、var()和position: fixed。https://github.com/otwcode/otwarchive/blob/master/app/models/work_skin.rb ↩︎ ↩︎AO3 开源仓库
resanitize_batch_job.rb展示了当 sanitizer 版本更新时,AO3 可以对允许 HTML 的字段进行批量重新清洗。https://github.com/otwcode/otwarchive/blob/master/app/jobs/resanitize_batch_job.rb ↩︎