
字节笔记本
2026年10月7日 · 约 7 分钟读完
一个发布页的五套样式:从裸 HTML 到设计语言的取舍
AI 结对写前端的常见开局,是给一个具体需求:做一个信息发布页,能填标题、传封面、写 Markdown 正文,点一下就发布。几分钟后你会得到一个能在浏览器里直接打开的单文件 HTML。这类页面撑不起一个产品,但对产品概念演示、团队内部小工具、教学示例这三类场景绰绰有余——反馈快、零依赖、发给别人只是发一个文件。而它真正的价值往往不在第一版,而在之后的每一轮迭代:一次只改一个关注点,样式与功能交替推进,一个空表单就这么长成了一件像样的东西。本文复盘这个完整过程,重点讲清两件事:五套样式方案各自的适用边界,以及功能应该按什么顺序往上堆。
五套样式,各有各的活法
同一个表单,前后穿过的五套样式,恰好覆盖了前端样式方案的完整光谱。
第一套是 CDN 工具类库:head 里引一行 Tailwind 的运行时脚本,类名直接堆在标签上,几分钟出成品。它的原理是在浏览器里跑一个即时编译器,扫描类名现生成样式,所以定位很明确——原型专用,官方明确不建议生产环境使用,正式项目应走构建流程把真正用到的样式提取出来。
第二套反向操作:把样式全部剥掉,只剩语义结构。别小看裸 HTML,它是排查「样式怎么都压不上去」时最有效的手段——先确认结构本身没问题,再逐层把样式加回去。浏览器默认样式还自带可访问性基线,表单控件天生可聚焦、可回车提交。
第三套是内联样式:所有规则写进 style 属性。它的独特价值是自包含——整页拷进邮件模板、第三方沙箱、富文本编辑器都不会丢样式,因为不存在外部依赖。代价同样明显:无法复用,写不了伪类和媒体查询,hover 这类效果只能靠 JS 模拟。
第四套回归正轨:样式集中到 style 标签(真实项目里挪进独立 css 文件,结构不用动),HTML 只留类名。伪类、过渡动画、媒体查询全部回归,一套 form-group 类覆盖全站表单,全局改一处颜色只需动一行。
四套走完,选型标准其实只剩一条:按页面的预期寿命定样式方案的重量。活一天的演示用 CDN 类库,活一周的内部工具用 style 标签,要嵌进别处的内容用内联,长期演进的项目走独立样式文件加设计约定。样式方案没有高下,只有合身与否。

换设计语言:分离的回报
样式与结构分离之后,迭代顺势做了一次压力测试:整套换成苹果官网风格。字体换成以 -apple-system 打头的系统字体栈,背景 #fbfbfd,主色 #0071e3,按钮圆角直接拉到 980px,容器限宽 980px——这组取值正是仿苹果风格时的经典配置。结果是 HTML 一行未动,只改样式层就完成了整个换装。这组字体、色板、圆角、限宽的取值组合,本质就是一套最小化的设计令牌:先把它们集中写在样式层顶部,后续微调就是改几个变量的事。可以想见,如果当初样式散落在内联属性里,这次换肤就得逐个标签翻修。分离的价值不在教科书里,而在第二次改版时兑现。
功能堆叠:先本地,后网络
样式定调之后,功能按一个朴素的原则往上叠:先做不依赖后端的,每加一层都立刻能用。
有个细节先说:整页按钮全部显式声明 type 为 button。表单里的按钮默认行为是提交并整页刷新,原型阶段最常见的「一点发布页面闪一下全没了」就是它干的。显式声明之后,行为完全由自己的函数接管。
先是封面上传:file 输入框配 FileReader 的 readAsDataURL,选图后立刻在本地渲染预览,后端还没影就能看到效果。装饰文件选择框有个经典技巧——把 file input 透明度设为零,叠在漂亮的按钮上,点击区域不变。
然后是 Markdown:引入 marked.js,在输入框的 oninput 里调用 marked() 把原文转成 HTML 塞进预览区,左右双栏就是最简的分屏编辑器,两侧各自滚动,输入即见渲染。接着上 Cropper.js,指定 16:9 的裁剪比例,保证封面规格统一。
交互层再补齐三件小事:实时校验,标题不足 5 个字符、正文不足 20 个字符时即时提示,提交时再复验一遍兜底——页面上的校验可以被绕过,前端校验永远要在提交时再跑一次;草稿自动保存,每次输入写入 localStorage,页面加载时读回,发布成功后清除,十几行代码治好「手滑关页面」的老毛病;再加上字数统计与响应式,窄屏下编辑器和预览改为上下排列。
最后才是配置弹窗:右上角齿轮按钮弹出模态框,填用户名、密码和发布地址,发布前检查配置是否齐全。到这里,页面才第一次真正面向网络。注意这个顺序的含义:在接入网络之前,编辑体验的所有环节——预览、裁剪、校验、草稿——都已经可以单独验收,出问题时很容易定位是哪一层引入的。

上线前必修的三处
这套代码能跑,但离生产还差三处关键修正,也正是 AI 生成代码最典型的三处欠账。
其一,密码明文进 localStorage 是高危做法:localStorage 对同源下任何 JavaScript 完全开放,一段被注入的脚本就能读走全部凭据,且用户难以自行清除。前端不应落盘凭据,正确路径是登录换取 httpOnly Cookie,或令牌只存内存。
其二,marked() 的输出直接塞 innerHTML 会引入 XSS——Markdown 允许内嵌原生 HTML 标签,恶意脚本会原样进入 DOM。渲染结果必须过一层 DOMPurify 消毒;旧版 marked 自带的 sanitize 选项早已废弃,别指望它兜底。
其三,readAsDataURL 走 base64 编码,体积膨胀约三分之一,大图预览也吃内存。本地预览无妨,正式上传应走对象存储;Cropper 的 getCroppedCanvas 输出的画布正好是现成的裁剪与压缩结果,可直接导出上传。
三处修完,这个页面才从「能跑的演示」变成「敢给同事日常用的工具」。原型到产品的距离,很多时候就差这几行。
什么时候该拆
九轮迭代跑完,一个 HTML 文件装下了五套样式和一整套编辑器功能。它留下两条经验:原型阶段最大的敌人不是技术不够,而是改动面失控——每轮只动一个关注点,上一轮成果全部保留,这恰好也是与 AI 结对最稳的节奏,指令边界越窄,产出越可控。而单文件的天花板同样清晰:当你需要路由、跨页状态共享或第二个页面时,就该迁去框架工程了;在那之前,一个文件就是全部,保存即所见。



