告别 DOM 的旧时代:从零重塑 Web 渲染的未来
引言
浏览器这玩意儿现在真够诡异的。WebAssembly 在服务器端混得风生水起,但客户端还是那副老样子,跟十年前没啥区别。
WASM 粉会跟你吹,通过点 JS 胶水代码就能调原生 Web API。但核心问题是:为啥非得用 DOM?这东西就是个默认选项罢了。本文直击 DOM 和相关 API 的痛点,为什么该让它们退场了,顺便脑洞下怎么改进。
作者不是浏览器全栈专家——没人能全懂了,这正是症结所在:东西太杂太乱。
DOM 的“文档”模型:臃肿得像个大胖子
DOM 烂到什么程度?Chrome 里document.body有 350+个键值,大致分类:
- 节点操作:appendChild、removeChild之类的。
- 样式相关:style对象塞了 660 个 CSS 属性。
- 事件处理:那些过气的onevent属性,比如onclick,基本没人用了。
- 杂七杂八:innerHTML、className等。
属性和方法界限模糊,很多 getter 会偷偷触发重排,setter 藏在暗处。还有一堆历史遗毒。
DOM 不瘦身,还在发福。你是否感受到这痛苦,取决于你是搞静态页还是 Web App。作为开发者,我们大多避开直接操 DOM,转而用框架。但偶尔有纯 DOM 党吹它牛逼——纯属自欺欺人。DOM 的声明式功能,比如innerHTML,跟现代 UI 模式八竿子打不着。同一件事 DOM 有 N 种方式,全都不优雅。
Web Components 的尴尬处境
Web Components 是浏览器原生组件方案,但来得太晚,人气不高。API 设计笨重,Shadow DOM 加了层嵌套和作用域,调试起来头大。粉丝的辩护听着像在找借口。以下是一个简单的示例:
class HelloWorld extends HTMLElement {connectedCallback() {const shadow = this.attachShadow({ mode: 'closed' });const template = document.getElementById('hello-world').content.cloneNode(true);const hwMsg = `Hello ${this.getAttribute('name')}`;Array.from(template.querySelectorAll('.hw-text')).forEach(n => n.textContent = hwMsg);shadow.append(template);}
}
customElements.define('hello-world', HelloWorld);
看起来还行?但实际开发中,Shadow DOM 的复杂性和 DOM 的字符串化特性(stringly typed)让开发者头疼。相比之下,React、Vue 等框架的虚拟 DOM 完全避开了这些问题,因为它们的语法只是“长得像 XML”,而不是真的依赖 DOM。
HTML 的停滞不前
HTML10-15 年没大动静。ARIA 是亮点,但只是补语义 HTML 的漏。语义 HTML 从 2011 年就开始推,但到现在都没<thread>或<comment>标签。嵌套<article>来模拟评论?指导原则也奇葩。
HTML 总像在嫉妒纸媒,没能真正拥抱超文本本质,也不信开发者能守规矩。
WHATWG(浏览器厂商)接管后,没啥愿景,就在边边角角加补丁。CSS 甚至长出了表达式——每个模板语言都想变编程语言。
编辑 HTML?contentEditable理论上行,但实际搞成可用编辑器是黑魔法。Google Docs 和 Notion 的工程师肯定有吐不完的槽。
渐进增强、 markup/style 分离?做 App 的开发者早不信这套了。
现在 App 大多用 HTML/CSS/SVG 拼凑 UI,但开销巨大,越来越不像正经 UI 工具箱。
比如 Slack 的输入框:用一堆 div 模拟富文本。剪贴板 hack 用隐藏元素。列表/表格得手动虚拟化,自管布局、重绘、拖拽。聊天窗滚动条粘底每次都得重写。虚拟化越深,越得重造页面搜索、右键菜单等。
Web 混淆了 UI 和流式内容,当年新鲜,现在过时。UI 陈旧,内容同质化。
CSS 的“内外倒挂”:别用错心智模型
CSS 口碑一般,但问题在哪?很多人误以为它是约束求解器。看这例子:
<div><div style="height: 50%">...</div><div style="height: 50%">...</div>
</div>
<div><div style="height: 100%">...</div><div style="height: 100%">...</div>
</div>
第一个想分一半高?第二个自相矛盾?实际 CSS 忽略height,父元素收缩包裹内容。
CSS 是两趟约束:先外到内传尺寸,再内到外集内容大小。App 布局外到内:分空间,内容不影响面板大小。文档内到外:段落撑开父级。
CSS 默认内到外,文档导向。要外到内,得手动传约束,从body { height: 100%; }开始。这就是垂直对齐难的原因。
Flexbox 给显式控制:
用flex-grow/shrink做无溢出自适应布局,加 gap 间距。
但 Flex 混淆了简单模型:需先“猜测”子自然尺寸,布局两次——一次假设浮空,一次调整。递归深了可能爆栈,虽少见,但大内容一丢,一切变形。
避坑:用contain: size隔离,或手动设flex-basis。
CSS 有contain、will-change这类直击布局的,暴露底层层级本质。代替position: absolute包裹。
本质上,这些切断 DOM 全局约束流——默认太宽泛,太文档化。
CSS 的好地方?
Flexbox 懂了这些坑,还挺靠谱。嵌套行列+gap,直观适配尺寸。CSS 好部分在这,但得用心打磨。Grid 类似,但语法太 CSS 味儿,啰嗦。
从零设计布局,不会这样:不会用减法 API 加屏障提示。会拆成组件,用外/内容器+放置模型,按需组合。
inline-block/inline-flex示意:内部 block/flex,外部 inline。盒模型两正交面。
文本/字体样式是特例:继承如font-size,为<b>工作。但 660 属性大多不继承——边框不递归子级,那会傻。
CSS 至少两东西混搭:继承的富文本样式系统 + 非继承的嵌套布局系统。用同语法/API 是错。
em相对缩放过时,现在逻辑/设备像素更 sane。
SVG 无缝入 DOM,动态形状/图标调色。但 SVG 非 CSS 子/超集,重叠处微差,如transform。坐标字符串化,烦。
CSS 加圆角/渐变/剪裁,有 SVG 嫉妒,但远不及。SVG 做多边 hit-testing,CSS 不行。SVG 有自己图形效果。
选 HTML/CSS 还是 SVG?基于具体 trade-off,全是向量后端。
注意一下的坑:
- text-ellipsis只截单行文本,非段落。检测/测量文本 API 烂,大家数字母凑合。
- position: sticky零抖动滚动固定,但有 bug。无条件 sticky 需荒谬嵌套,本该简单。
- z-index绝对层级战,z-index-war.css 里 +1/-1 比拼。无相对 Z 概念。
API 设计难,得迭代建真东西,找漏。
SVG 与 CSS 的权衡
SVG 在 Web 中用于动态生成图形或调整图标样式,但它与 CSS 并非完全兼容。例如,SVG 的 transform 与 CSS 的变换属性有微妙差异,且 SVG 的坐标全是字符串序列化,增加了开发复杂性。
国内场景:假设你在开发一个数据可视化仪表盘,类似 ECharts 的柱状图。你可以选择用 SVG 绘制图形,或者用 CSS 实现类似效果。SVG 支持多边形点击检测(hit-testing),而 CSS 不行;但 CSS 的圆角、渐变等功能又让 SVG 显得多余。最终,你可能需要在两者间做痛苦的权衡。
Canvas 上的油画:HTML in Canvas 的坑
DOM 坏,CSS 几成好,SVG 丑但必备……没人修?
诊断:中间层不合用。HTML6 先砍东西起步。
但关键解放现有功能。理想:用户空间 API 同逃生口,狗食自家。
HTML in Canvas 提案:画 HTML 到<canvas>,控视觉。不是好法。
API 形因塞 DOM:元素须 canvas 后代参与布局/样式/无障碍。离屏用有“技术关切”。
例子:旋转立方体交互用 hit 矩形+paint 事件。新 hit API,但只 2D——3D 纯装饰?问题多。
从零设计,不会这样!尤其浏览器有 CSS 3D transform,何须为自定义渲染全接交互?
未覆盖用例如曲面投影,需复杂 hit。想过下拉菜单吗?
像没法统 CSS/SVG 滤镜,或加 CSS shader。经 canvas 是剩选项。“至少可编程”?截 DOM 一好用,但非卖点。
Canvas 上复杂 UI 是为绕 DOM:虚拟化、JIT 布局/样式、效果、手势/hit 等。全低级。预备 DOM 内容反生产。
反应性上,路由回同树设循环。渲染 DOM 的 canvas 非文档元素了。
Canvas 真痛:无系统字体/文本布局/UI 工具。从零实现 Unicode 分词,就为包裹文本。
提案“DOM 黑箱内容”,但知套路:还得 CSS/SVG 拼凑。text-ellipsis仍破,得从 90 年代 UI 重造。
全或无,要中道。下层需开。
未来的方向:重新设计 DOM
DOM 和 CSS 的问题根源在于它们背负了太多的历史包袱。以下是一些可能的改进方向:
- 精简的数据模型:未来的 DOM 需要大幅减少属性数量(从 350+精简到几十个),专注于核心功能。类似 React 的虚拟 DOM,但直接内置于浏览器中。在开发类似头条的信息流应用时,开发者需要快速渲染大量卡片。精简的 DOM 模型可以减少不必要的 API 调用,提高性能。
- 统一的布局系统:将 CSS 的内外布局模式明确分开,支持更直观的“外部约束”和“内部自适应”。例如,垂直居中应该像 align-items: center 一样简单。在电商平台的商品详情页中,开发者希望轻松实现复杂的布局(例如商品图片和描述的动态对齐),而不是依赖一堆 CSS hack。
- WebGPU 的潜力:WebGPU 提供了更底层的渲染能力,可以完全抛弃 DOM 的复杂性。例如,Use.GPU 项目展示了一个基于 WebGPU 的简洁布局系统,代码量仅为传统 DOM/CSS 的几分之一。在开发类似 B 站的弹幕播放器时,WebGPU 可以用来高效渲染动态弹幕,省去 DOM 的性能开销。
- 多线程与隔离:现代浏览器已经是多进程架构,但 DOM 的设计没有跟上。未来的 DOM 需要支持更好的多线程和跨源隔离,适应复杂的 Web 应用需求。在企业级应用(如钉钉的协作平台)中,开发者需要集成第三方服务(如 OAuth 登录)。一个支持多线程的 DOM 可以显著提高安全性和性能。
结论
HTML、CSS 和 DOM 的现状就像一辆老旧的马车,虽然还能跑,但早已不适合现代 Web 应用的复杂需求。国内开发者在开发小程序、电商平台或社交应用时,常常需要用框架和 hack 来弥补 DOM 的不足。未来的 Web 需要一个更精简、更灵活的渲染模型,可能基于 WebGPU 或全新的 API 设计。
与其修补 DOM 的漏洞,不如从第一性原理出发,重新设计一个适合现代应用的 Web 渲染层。就像当年的 Netscape 开启了 Web 时代,今天的我们也有机会重新定义浏览器的未来。