做公众号图文这几年,被问得最多的一类问题就是:在线排版器里排好的稿子,怎么变成能直接用的 HTML?以及那个"滑动图文"到底是怎么做出来的。标题里那个工具名我就不点了,下面统一叫它"在线排版器"——反正你打开浏览器搜图文排版,跳出来的那几家逻辑都差不多。这篇东西解决的就是两个具体问题:一是把可视化排版的成果稳定搬到公众号后台,不掉样式、不乱版、图片不裂;二是把"滑动图文"这件事讲透,哪些能直接在图文正文里做,哪些必须走 H5,哪些是看着炫但一上手就翻车。适合刚接手公众号运营的新人,也适合已经踩过几次坑、想把流程固化下来的老手。
1. 先搞清楚:微信图文到底吃什么样的 HTML
很多人第一次尝试"排版转 HTML"失败,不是因为操作不对,而是对目标环境的预期错了。公众号后台的编辑器不是浏览器,它本质上是一个富文本容器,你粘进去的东西会经过一轮解析、过滤、再序列化。你不了解这轮处理干了什么,就会一直在"我明明排好了"和"粘过去就散了"之间反复横跳。
1.1 三个硬约束,决定了后面所有操作
第一个约束是样式必须内联。后台不认<style>标签,也不认 class 选择器。你在排版器里看到的那些漂亮样式,靠的是一张外部样式表和一堆 class,粘进后台的一瞬间 class 被剥掉、样式表被丢弃,剩下的就是裸文本。所以转换的核心动作只有一个:把计算后的样式逐条写回元素的style属性里。
第二个约束是标签白名单。section、p、span、div、img、strong、em、table这些基本都能活下来;script、iframe、form、input、link和几乎所有on*事件属性会被清掉。伪元素::before/::after也会丢,因为它们在 DOM 里根本不存在,序列化的时候自然带不走。
第三个约束是图片要过转存。你粘进去的<img src="https://别人的域名/xxx.jpg">,后台会尝试抓取并转存到自己的图床。抓得到就正常显示,抓不到就给你一句"此图片来自微信公众平台未经允许不可引用",或者干脆一个裂图占位。这一步是所有"排版完美但发出去全裂"的元凶。
注意:这三个约束是层层叠加的,不是三选一。你只解决了内联样式,图片照样裂;你只换了图,class 丢了照样丑。排查问题的时候要按这个顺序一条条过。
把这三条记住,后面所有的操作都有了依据。我在带新人的时候,第一件事不是教他怎么点按钮,而是让他在后台随便粘一段带 class 的 HTML 进去,亲眼看看发生了什么。看过一次,比讲十遍都管用。
1.2 排版器导出的 HTML,结构长什么样
在线排版器的编辑区,本质就是一个contenteditable的容器,里面塞满了section和嵌套的section。它为了实现那些卡片、边框、圆角、渐变背景,会疯狂嵌套——一个看起来简单的引用卡片,拆开可能是五六层section套着走,每一层负责一个视觉细节:外层管圆角和阴影,中层管背景色和 padding,内层管文字颜色和行高。
这种结构在浏览器里没问题,粘进后台就有两个隐患。一是嵌套过深导致 margin 塌陷和间距叠加,你设了 20px 的段间距,五层嵌套各贡献一点,最后变成 60px 的空隙,手机上看着就是一堆莫名其妙的空白。二是部分样式写在 class 上,序列化的时候如果没做内联处理,这些层的视觉细节会成片消失,卡片变成一坨没有边框的方块。
我一般的处理原则是:转换之前先"减层"。能用一层section加 padding 解决的,就不要套三层。你在排版器里看着一样,粘出去之后的稳定性差很远。具体怎么减,下一节讲。
另外要留意背景图的写法。排版器经常把背景图写成background-image: url(...),这个属性能内联,但里面的相对路径在粘贴后会失效。要么在导出时把路径补成绝对地址,要么干脆把背景图换成<img>绝对定位铺底——虽然后者更笨重,但在后台的存活率高得多。
1.3 为什么"直接复制粘贴"有时能成、有时不能
这里有个很多人没想明白的点:为什么有时候在排版器里 Ctrl+A、Ctrl+C,粘到后台居然样式还在?
原因是剪贴板。浏览器复制富文本时,会往剪贴板里放两个版本:一个是text/html,一个是text/plain。放text/html的时候,浏览器通常会把当前元素的计算样式做一次简化内联。所以"复制粘贴"这条路,实际上浏览器已经帮你做了一部分内联工作。
但它做得不彻底也不稳定。表现为:简单元素(加粗、颜色、字号)能带过去,复杂元素(渐变、阴影、transform、伪元素做的装饰)经常丢;同一个稿子,Chrome 粘过去是好的,换台机器用另一个浏览器就散了;排版器版本一升级,粘贴结果又变了。
所以我的判断是:临时救急可以用复制粘贴,要稳定复用就必须走"导出 HTML + 内联化"这条路。前者靠运气,后者靠流程。一次性的活动推文,粘一粘也就算了;如果是每周固定更新的栏目,或者要给团队里几个人共用,那就老老实实把工具链搭起来。
2. 把排版稿转成能用的 HTML:完整链路拆解
这一节讲方案。市面上的做法大致三类,我按"稳定性"和"上手成本"两个维度都试过,结论写在下面,你可以直接对照自己的情况选。
2.1 三种做法的对比与选型建议
第一种是纯手工复制粘贴。零成本,适合一次性内容、结构简单的稿子(纯文字加图片、加粗、分段)。缺点是复杂样式必丢,且不可复现。
第二种是浏览器取 DOM + CSS 内联化。在排版器页面上按 F12,选中编辑区最外层节点,右键 Copy → Copy outerHTML,拿到完整结构,然后用一个内联化脚本把样式算进去。这条路可控性最高,能处理掉 90% 以上的样式丢失问题,代价是需要跑一次脚本。
第三种是从设计稿直接出 HTML。跳过排版器,用 Figma 之类的工具出稿,然后手写或半自动生成内联样式的 HTML。适合有设计资源、追求品牌一致性的团队,但对个人运营来说太重了。
| 方案 | 上手成本 | 样式保真度 | 可复用性 | 适用场景 |
|---|---|---|---|---|
| 直接复制粘贴 | 极低 | 中低 | 无 | 一次性推文、结构简单 |
| 取 DOM + 内联化 | 中 | 高 | 高 | 固定栏目、团队协作 |
| 设计稿直出 | 高 | 最高 | 高 | 有设计规范、品牌要求强 |
我自己的常态是第二种。理由很简单:排版器的可视化编辑效率确实高,我没必要为了"纯手工"而放弃它,只要把最后那一公里的转换做扎实就行。
2.2 CSS 内联化的具体做法与参数取舍
内联化的原理不复杂:遍历目标子树里的每个元素,用getComputedStyle读出它当前生效的所有样式,挑出需要保留的那些,拼成字符串写进style属性。核心难点在"挑出哪些"。
全量写进去会得到一个巨大无比的 style,每个元素几百个属性,粘进后台容易触发长度截断,而且会把继承来的、无关的属性一并固化,改都不好改。我的做法是维护一份白名单,只保留视觉相关的:
const KEEP = [ 'color', 'background-color', 'background-image', 'background-size', 'background-position', 'background-repeat', 'font-size', 'font-weight', 'font-style', 'font-family', 'line-height', 'letter-spacing', 'text-align', 'text-decoration', 'text-indent', 'word-break', 'margin-top', 'margin-bottom', 'margin-left', 'margin-right', 'padding-top', 'padding-bottom', 'padding-left', 'padding-right', 'border-top-width', 'border-top-style', 'border-top-color', 'border-radius', 'width', 'max-width', 'height', 'display', 'vertical-align', 'box-sizing' ];边框有四个方向,实际写的时候用一个循环拼,别手写四遍。border-radius的四个角同理。
有两个坑要提前说。一是getComputedStyle返回的长度全是像素值,line-height: 1.75会变成line-height: 28px(按 16px 字号算)。这在固定宽度下没问题,但如果你想让用户在深色模式或者不同字号下自适应,就丢掉了相对性。我的处理是:行高和字号这类,允许保留相对值,从元素的 inline style 或者原始 CSS 里读,读不到再用计算值兜底。
二是background-image里的 url 要处理。计算样式返回的是绝对地址,这点反而省事,直接用就行。但要注意跨域图片如果做了防盗链,你拿到地址也显示不出来,还得回到"图片转存"那条路上解决。
跑完内联化,你会得到一坨长得吓人的 HTML。别急着粘,先做两件事:把class和id属性整个删掉(留着没用还占体积),把空的style=""也删掉。这两个动作能让体积降三成左右。
2.3 图片、字体、间距的兜底处理
图片这块,我的标准流程是:先在排版器里把图画质压到位,导出 HTML 之后,把所有img的src换成已经上传到素材库的地址。素材库的图有稳定的 CDN,不会因为源站防盗链挂掉。
宽度上给img加一条max-width:100%; height:auto; display:block;。少这一条,宽图在手机上会溢出一截,用户左右滑动的时候页面跟着晃,体验极差。display:block是为了消掉行内元素底部那几像素的基线间隙——这个间隙单看无所谓,一排图叠起来就是明显的错位。
字体不用太纠结。公众号正文基本就是系统默认字体,你在排版器里设的宋体、楷体,粘过去多数会被统一。真想保留差异,只能用图片承载文字,但那会牺牲可搜索性和无障碍。我的取舍是:正文一律用默认字体,需要特殊字形的地方(比如大标题、引言)做成图片。字体族里写-apple-system, "PingFang SC", "Microsoft YaHei", sans-serif就够了,别堆一长串。
间距是最容易被忽略、也最影响观感的一项。我固定用这几个值:正文字号 15px,行高 1.75,字间距 0.5px,段间距 18px,左右内边距 15px。这套参数在主流机型上排出来的密度比较舒服,不会挤成一坨也不会松垮。字间距超过 1px 就开始有"注水"的感觉了,慎用。
提示:间距一定要用
margin而不是<br>堆。用<br>堆出来的空行,在后台编辑器里会被折叠,粘过去之后行距全乱,而且改起来极其痛苦。
3. 手把手实操:一份可复用的转换流程
前面讲的是原理和选型,这一节给一套我从头到尾跑过几十次的流程。你按顺序做,中间不要跳步,跳了大概率要返工。
3.1 第一步:在排版器里把结构做干净
转换的质量,七成取决于源头的结构。排版之前先想清楚三件事:这篇内容分几个视觉区块、每个区块用什么容器、区块之间的关系是并列还是嵌套。
我的习惯是,每一个独立视觉单元用一个section,里面只放内容,不加装饰层。需要背景色、圆角、边框,统一加在这一个section的 style 上,而不是外面再包一层专门做装饰。这样最终结构是扁平的,内联化之后体积小、粘过去稳。
段落内部不要用空section或者空p来撑间距。有些排版器默认会插这种空节点,你以为在控制留白,实际上在给后面的 margin 叠加埋雷。发现空节点就删掉,间距用margin-bottom控制。
还有一个细节:列表项。排版器里的有序列表经常被渲染成一堆带序号的section,而不是真正的<ol><li>。这在视觉上没差别,但粘到后台之后,如果后台做了列表样式重置,你的序号可能整片消失。稳妥做法是手动把序号写进文本里,或者用带背景色的序号标签图。
3.2 第二步:导出与清洗
打开排版器预览页,F12 打开开发者工具,用元素选择器点到正文最外层的那个容器,右键 Copy → Copy outerHTML。粘到一个.html文件里,然后跑内联化脚本。
脚本跑完,我会做一轮人工过目。检查项就三个:
- 有没有残留的
class/id/>.slide-wrap { height: 100vh; overflow-y: scroll; scroll-snap-type: y mandatory; -webkit-overflow-scrolling: touch; } .slide { height: 100vh; scroll-snap-align: start; scroll-snap-stop: always; }思路很直白:外层容器限高并开启滚动,声明纵向强制捕捉;每个页面高度等于容器高度,并声明自己是一个捕捉点。手指一划,页面自动吸附到下一屏。
这里有个必须处理的坑:移动端浏览器的
100vh不老实。地址栏收起和展开时,可视高度是变的,100vh会按最大高度算,导致底部内容被地址栏挡住。解法是用 JS 读一次真实高度,写到 CSS 变量上:function setVH() { document.documentElement.style.setProperty('--vh', window.innerHeight * 0.01 + 'px'); } setVH(); window.addEventListener('resize', setVH); window.addEventListener('orientationchange', setVH);然后 CSS 里把
100vh换成calc(var(--vh) * 100)。iOS 上这一步不做,实测会有明显的底部截断。第二种是引翻页库,比如常见的移动端轮播组件开竖向模式。好处是自带过渡动画、支持鼠标滚轮、生态成熟;坏处是体积上去了,而且它的滚动容器和页面滚动容易打架,在微信内置浏览器里偶尔会出现"滑到一半卡住"的现象。如果内容页数少(3 到 5 屏),我倾向用
scroll-snap,代码可控,出了问题好定位。不管用哪种,都有一个硬要求:每一屏的内容必须在一屏内放得下。我见过太多翻页 H5,第二屏的内容比屏幕高出一截,用户划到那里,页面卡在半截,下面的文字永远看不到。做法是设计阶段就把文案字数卡死,或者给内容区加
overflow-y:auto让它内部滚动——但那又违背了翻页的初衷。宁可多分一屏,也别让单屏内容溢出。4.3 SVG 交互图文:不跳转也能做"点击切换"
如果需求就是"在图文里点一下换张图",而且不想让用户跳出去,那唯一的路是 SVG。做法是把整个交互封装在一段
<svg>里,用图形元素承载内容,通过内嵌的动画元素控制属性的变化。举个最简化的结构示意(实际代码要按具体内容展开):
<svg viewBox="0 0 750 1000" xmlns="http://www.w3.org/2000/svg"> <image href="封面A.jpg" width="750" height="1000"> <animate attributeName="opacity" from="1" to="0" dur="0.3s" begin="click" fill="freeze" /> </image> <image href="封面B.jpg" width="750" height="1000" opacity="0"> <animate attributeName="opacity" from="0" to="1" dur="0.3s" begin="click" fill="freeze" /> </image> </svg>思路是两张图叠放,点击时第一张淡出、第二张淡入。
begin="click"是触发条件,fill="freeze"让动画结束后保持终态,否则会弹回初始状态。几个实操要点:图片必须用能稳定访问的绝对地址,且建议先压到合适尺寸,SVG 里放大图会明显拖慢加载;
viewBox的宽度按 750 设计,正好对应二倍图,显示清晰;文案不要用<text>直接写,字体在各端渲染差异大,把文字烧进图片里更稳。最关键的一点:这套东西必须在真机上测。不同系统、不同客户端版本对 SVG 动画的支持有差异,你在电脑浏览器里点得好好的,手机上可能纹丝不动。我吃过这个亏,一次活动推文在电脑上预览完美,发出去之后后台收到一堆"点了没反应"的反馈,只能删文重发。
5. 常见问题与排查速查表
前面讲的都是"怎么做得对",这一节讲"做错了怎么救"。下面这些问题我基本都遇到过,按出现频率排序。
5.1 样式丢失的六种典型表现
表现一:整篇变成纯文本,加粗和颜色全没了。这是最彻底的失败,说明内联化完全没生效。原因通常是取 DOM 时选错了节点——你可能选到了编辑器外层容器而不是内容区,或者复制的时候用的是 Copy text 而不是 Copy outerHTML。回去重新选,确认复制出来的内容里带
style属性。表现二:大部分样式在,但某个区块整个塌了。通常是那个区块用了伪元素做装饰,或者依赖了父级的
flex布局而父级样式没内联上。定位方法是把这段 HTML 单独拎出来,在本地浏览器打开,看它渲染成什么样,就知道缺什么了。表现三:边框缺一边。这是
border简写被拆开后某一方向的属性没写全。检查border-top-width有没有配套的border-top-style,只写宽度不写样式,边框不显示。表现四:图片底部有细缝。行内图片的基线间隙,加
display:block就好。如果加了还有,检查父容器是不是line-height撑高了。表现五:图片显示"未经允许不可引用"。源站做了防盗链,后台抓不到。解法是把图另存到本地再上传到素材库,然后把
src换掉。批量处理的话,可以先用脚本把src全部替换成占位地址,上传后按顺序回填。表现六:手机上和电脑上不一样。九成是宽度问题。电脑端浏览器窗口宽,某些元素靠固定宽度撑开了;到手机上宽度不够,就溢出或者挤压变形。统一给容器的宽度改成
width:100%; max-width:677px;,内部元素全部百分比或max-width。5.2 我的避坑清单
别在深色模式下用浅灰底深灰字。这条前面提过,但它翻车频率太高,值得再说一遍。测试方法很简单,手机开深色模式,看一眼自己的推文。
别用
position: fixed做悬浮元素。后台会过滤,粘过去之后元素直接掉到文档流里,位置全乱。别指望
transform做旋转和缩放。部分版本支持,部分不支持,表现不稳定。真需要倾斜效果,做成图片。别用外链字体。
@font-face引外部字体文件在正文里不生效,只会白白增加加载失败的风险。段落之间只用一种间距手段。要么统一
margin-bottom,要么统一用空行,别混着来。混用之后你永远不知道那个多出来的空白是谁贡献的。导出前先把草稿在窄屏下过一遍。把浏览器窗口拖到 375px 宽,横向滚动条一出现就说明有问题。这一步花十秒,能挡掉大部分手机端事故。
给团队留一份参数表。字号、行高、色值、间距固定成几个变量,所有人照着用。我见过最离谱的一次,同一个栏目三篇推文用了三套字号,读者一眼就能感觉到"这不是一个人做的"。
内容页数超过五屏就别用翻页 H5 了。用户耐心有限,划到第四屏还没结束就开始退出了。真需要长内容,老老实实做纵向滚动,反而留得住人。
SVG 交互务必灰度测试。先发给几个不同机型的朋友看一眼,确认没问题再群发。这一步多花十分钟,能避免发出去之后删文重发。
最后,把转换脚本存下来。这次的稿子转完了,脚本别删。下次排版器改版、后台改规则,你还能拿它快速验证到底是谁的问题。