浏览器被打脸:JS竟然比原生CSS排版更快
浏览器这个老古董干了一件特别欠揍的事——必须“先渲染再测量”。就好比你妈让你先打扫房间再告诉你零花钱多少,结果你扫完了她说“哎呀我刚才说的是下周”。而Pretext这个新来的狠人直接掀桌子:“不等你渲染,我先给你算得明明白白!”
Pretext这个库干的事情,简单到离谱,但又强到变态,就像你考试前偷偷拿到了标准答案,而且还是开卷考的那种。
正常浏览器的流程是什么样呢?你先把文字辛辛苦苦画出来,画得漂漂亮亮的,然后你卑微地问一句:“老师老师,我这段话到底有多高啊?”老师推了推眼镜,慢悠悠地说:“你都写完了才来问?那你重写一遍吧,这次我帮你量。”
这就是传说中的reflow,也叫重排,而且是同步进行的,慢得就像你周一早上被闹钟吵醒后,在床上做的那套“再睡五分钟”的极限运动。
你想想,一个聊天窗口五千条消息,每条高度都不一样,有的短得像“哦”,有的长得像你前任半夜发的小作文。浏览器居然说:“没事,你先全给我画出来,画完了我再告诉你每条多高。”你这时候是不是想顺着网线爬过去揍他?
Pretext怎么说?它冷笑一声:“别折腾了兄弟们,我提前帮你算好。每一行多长、会折成几行、总高度是多少,全都提前算出来。你连DOM长啥样都不用碰,直接开挂起飞。”
换句话说,浏览器的脑回路是:先傻乎乎地干活,干完了再动脑子想想刚才干了啥。
Pretext的脑回路是:先把脑子用足,想清楚每一笔怎么走,然后再动手。
(先计划再动手 vs. 先动手后修补 两种思维方式)
这不是什么性能优化小技巧,这TM是一场思想革命,是排版界的“日心说”。你想想,过去三十年,所有前端都被浏览器按在地上摩擦,乖乖地说“好的我先渲染”,现在突然有人说“老子不干了,我自己算”,这感觉就像班里一直乖乖交作业的学霸,突然有一天站起来对老师说:“你的答案错了,看我的。”
浏览器这套老逻辑到底有多坑
来,咱们沉浸式体验一下浏览器的沙雕操作。
你在做一个聊天软件,不是那种只有你好友列表的小程序,而是像你们班群那种动不动就999+消息的超级应用。你现在要实现一个看起来人畜无害的功能:滚动列表。手指一划,消息哗啦啦往上走,多丝滑啊。
但问题来了,每条消息高度不一样。有的消息就一个字“哈”,高度小得像蚂蚁。有的消息是一篇小作文,还是那种分段落的,高度堪比你们学校的围墙。你必须提前知道每一条消息有多高,否则滚动条就会像你们班那个坐不住的皮猴子,上蹿下跳,用户划一下,滚动条蹦三蹦,体验感直接从iPhone掉落到小灵通。
结果浏览器怎么回答你?
它理直气壮地说:“你先把这五万条消息全部渲染到页面上,渲染得完完整整,我再挨个告诉你每条的高度。”
你听到这话,血压是不是直接拉满?
这就像你去食堂打饭,阿姨说:“你先把这个月的饭全打走,我再告诉你每顿饭多少钱。”
你是来吃饭的还是来进货的?
更离谱的还在后面。
你每调用一次getBoundingClientRect,就是那个问浏览器“这个元素现在位置尺寸多少”的方法,或者offsetHeight这个直接读高度的属性,浏览器就要立刻马上暂停一切正在做的事情——动画暂停、滚动暂停、用户点击没反应——然后重新计算整个页面的布局。就像你在玩王者荣耀打到团战最关键的时候,你妈突然把网线拔了说“我先问问你作业写完没”。
实测数据告诉你有多恐怖:五百条文本,就五百条,你循环调用一下高度,至少三十毫秒起步。
三十毫秒听起来很短对吧?但是同学们,六十帧的动画每帧只有十六毫秒的时间。
三十毫秒意味着你直接掉到三十帧以下,PPT级别的流畅度,你划一下屏幕,感觉就像在翻阅一本被胶水粘住的漫画书。这不是性能问题,这是谋杀帧率,是前端开发者的噩梦,是产品经理提需求时你脸上笑嘻嘻心里妈卖批的根源。
Pretext到底是怎么开挂的
好了,骂完浏览器这个猪队友,咱们来看看Pretext这个神队友到底是怎么开挂的。
它的思路简单粗暴但极其狠辣,就像一个武功高手,不出招则已,一出招就直击要害。
它分两步走,第一步叫prepare,翻译过来就是“准备开干”。这一步会干一次重活,注意,只有一次。它会做哪些脏活累活呢?首先把你的文本拆分成一个一个的单元,不是简单按字拆,而是像你们语文老师分析句子那样,考虑词边界、考虑空格、考虑换行规则。然后它偷偷摸摸地创建一个canvas,就是画图用的那个画布,在画布上用和你浏览器一模一样的字体、字号、字重,把每一段文字的宽度精确测量出来。测完之后,把所有结果缓存起来,存到内存里。
这一步有点慢,比如十九毫秒。十九毫秒什么概念?你眨一下眼睛大概需要一百到一百五十毫秒。也就是说,你还没眨完眼呢,它已经把脏活干完了。而且只干这一次,一劳永逸。就像你期末考试前花了一整个周末把所有知识点整理成思维导图,虽然累,但后面复习起来就快如闪电。
第二步叫layout,翻译过来就是“排版计算”。这一步就离谱他妈给离谱开门——离谱到家了。因为它完全不碰DOM,就是完全不碰你页面上那些真实的HTML元素,纯数学计算。它直接用之前缓存的数据,套一个公式:“当前容器宽度是三百像素,这段话里每个字符宽度我都知道,空格宽度我也知道,那请问它会折成几行?每行多长?总高度多少?”答案在零点零九毫秒内就算出来了。
零点零九毫秒!你对比一下刚才浏览器那三十毫秒。三十除以零点零九,约等于三百三十三倍。这不是性能提升,这是维度打击。就像你骑自行车和开火箭比速度,不是一个次元的东西。
这就是冷启动和热路径的区别:
冷启动就是你周一早上刚被闹钟吵醒,浑身僵硬,找袜子都要五分钟。
热路径就是你喝完三杯美式,打完一套军体拳,脑子里能同时算五道数学题。
Pretext把最慢的部分只做一次,然后后面的所有计算都是闪电速度。你滚动一万次,它也不怕,因为数据早就准备好了,就像你考试时把公式都背熟了,题目怎么变你都能秒答。
真正的创新点藏在哪
肯定有同学要举手了:“老师老师,用canvas测量文字宽度,这不是早就有的技术吗?我上个月看那个谁谁的博客就写过。”对,你说得没错,单独拿出canvas测量这个技能,确实不是什么新鲜事。但Pretext的牛逼之处在于,别人只是捡了个工具,它却造了一整套兵器库,而且每一件兵器都打磨得锃亮。它干了哪些脏活累活?我给你数数,保证你听完肃然起敬。
第一,它支持中文、日文、韩文这些CJK文字。你知道这些文字有多坑吗?
中文没有空格,词和词之间是连在一起的,你让计算机判断“南京市长江大桥”到底是在说南京市长/江大桥还是南京市/长江大桥?这比你们做的那些语文阅读理解还难。第二,它支持阿拉伯语,就是从右往左写的那个。你想象一下,你习惯从左往右读,突然让你从右往左排版,还要保证数字和英文单词还是从左往右,这酸爽就像让你同时用左手画圆右手画方。第三,它修复了不同浏览器下emoji宽度不一致的千古难题。同一个笑哭了的表情,在Chrome上宽度是二十像素,在Safari上就变成二十二像素,在Firefox上又是二十点五像素。以前前端开发者为这个bug熬掉了多少头发你知道吗?Pretext说:别怕,我来统一。
更变态的是它的验证系统。作者为了确保测量结果绝对准确,直接拿整本《了不起的盖茨比》去测。还不够?再加泰文、中文、韩文、日文、阿拉伯文,每种语言一本书,一顿狂测,测到每一个字符、每一个空格、每一个换行都跟浏览器原生渲染完全一致。这已经不是写代码了,这是在炼丹,是在进行科学实验,是把排版这件事当成造原子弹来搞。你想想,一个开源库的作者,为了让你滚动列表时不卡顿,居然把世界各大语言的文学名著都搬出来了。这是什么精神?这是前端界的白求恩啊!
有个功能,CSS根本做不到
接下来要讲的功能,可能会颠覆你对CSS的认知。CSS里有个属性叫fit-content,翻译过来就是“适应内容”。它的意思是:容器的宽度自动调整为最长那一行的宽度。听起来很合理对吧?就像你买裤子,按你腿最粗的那个位置来选尺码。但是有个致命问题。假设你有三行文字:第一行很长,比如“我妈给我转了五千块生活费但是要月底才到账”。第二行中等,比如“今天中午吃啥”。第三行很短,比如“好”。按照fit-content的逻辑,容器宽度会按第一行的长度来定。结果就是,第二行右边会空出一大片空白,第三行右边更是空得像太平洋。你用过的所有聊天软件,肯定见过这种气泡:左边是头像,右边是气泡,但气泡右边空了一大截,就像没吃饱饭一样,瘦骨嶙峋的。
Pretext说:“不行,这太丑了,我要最紧凑的。”它提供一个变态能力:找到一个最小宽度,刚好能把这三行文字完整放下,不折行也不留大片空白。怎么做?用二分查找。对,你没听错,就是你们数学课上学过的那个二分查找算法。你先猜一个宽度,比如一百像素,看看能不能放下。放不下,就猜两百像素。放下了,就试试一百五十像素。来回几次,找到那个临界点。这个功能叫walkLineRanges,翻译过来就是“走一遍行区间”。于是你终于可以做出真正贴脸的聊天气泡,不是那种右边空一大片像没吃饱的,而是像定制西装一样,每一寸都贴合你的身体。这功能CSS这辈子都做不到,因为CSS没有二分查找,CSS没有算法,CSS只会傻乎乎地按照最长行来定宽度。
为什么开发者会兴奋到原地转圈
这个东西不是玩具,不是那种你玩两天就丢到GitHub星标里吃灰的项目。它直接解决了前端界几个老大难问题,就像给生锈的齿轮上了润滑油。第一个老大难是虚拟列表。过去十年,所有做虚拟列表的开发者都在干同一件事:猜高度。不是算,是猜。你说你列表里每条高度可能不一样,但又没法提前知道,怎么办?猜一个平均高度,然后祈祷用户的运气好,滚动条别抽风。但用户一滚动,列表项突然变高了,滚动条就会像触电一样乱跳。Pretext让你在渲染前就能精确计算每条的高度,不再靠玄学,不再靠烧香拜佛。
第二个老大难是聊天UI。像微信、QQ、Telegram这种,消息气泡成千上万条,每条高度都不一样,有的带图片,有的带表情,有的带链接。以前的做法是等用户滚动到附近了再去渲染,但滚动条的高度又需要提前知道所有消息的总高度。这是个死循环。Pretext直接让你在零渲染的情况下算出总高度,精确到像素。第三个是设计工具,像Figma、Canva这种画布类的应用。它们用canvas渲染文字,但以前要么估算,要么偷偷在后台创建一个隐藏的DOM去测量,慢得要死。现在可以纯JS计算,干净利落,像手术刀一样精准。
第四个是服务端渲染。未来甚至可以在服务器上就把布局算好,页面还没到浏览器呢,就已经排版完成。用户打开网页的瞬间,看到的不是白屏,不是乱跳,而是整整齐齐排好版的文字。这就很离谱了,相当于你去餐厅还没坐下,菜已经做好了端到你面前。第五个是AI开发。现在AI写UI代码时有个大问题:它不知道文字会不会换行。AI说“这个按钮宽度一百像素”,结果文字是“提交并继续浏览更多内容”,一放进去就折成两行,丑哭了。Pretext可以让AI在没有浏览器的情况下验证UI布局,直接告诉AI“你这行文字放不下,会换行”。这等于给AI的眼睛装上了显微镜,补上了最后一个闭环。
但别上头,这玩意也有坑
好了,吹了这么久,我得泼点冷水,不然你们真要上头了。那个“五百倍性能提升”的标题党数据,听听就好,别当真。作者自己在文档里都说了:这个对比不公平。为什么?因为它对比的是“没有任何缓存的冷启动DOM测量”和“已经缓存好所有数据的Pretext热计算”。这就像你拿电动车起步和火箭起步比谁快,电动车起步确实快,但你不能说电动车比火箭厉害。真实世界里,你应该对比的是“优化后的DOM测量,比如批量读取、防抖节流”和“Pretext的纯计算”。这个数据目前作者没给,所以我劝你别看到五百倍就开始在朋友圈吹爆,容易被打脸。
还有更幽默的。Pretext刚发布的时候,demo直接翻车了。桌面端文字被裁剪,最后几个字不见了。移动端更惨,直接炸裂,页面都打不开。一个做排版的库,自己的排版出了问题,这画面就像理发师自己的头发被狗啃了一样,充满了喜剧效果。另外,它还不支持服务端渲染,README里写的是“soon”。翻译一下就是“未来某天,也许是下个版本,也许是下辈子”。还有个更隐蔽的坑:system-ui字体在macOS上测量不准。system-ui是很多项目的默认字体,就是你啥都不设置,浏览器就会用这个。结果你高高兴兴用了Pretext,测量出来的宽度和实际渲染差了那么几个像素,你还不知道为什么,排查三天三夜,最后发现是字体在搞鬼。这就属于那种“悄悄坑你,坑完还对你微笑”的bug。
这个东西真正可怕的地方
说了这么多坑,但我要告诉你,Pretext真正可怕的地方不是它本身,不是它的性能,不是它的功能。而是它代表了一个趋势:浏览器不再是唯一的真理。过去三十年,前端开发者对浏览器几乎是跪着的。你想测量文字?找DOM。你想布局?找DOM。你想知道任何和界面有关的信息?乖乖去问浏览器,浏览器给你什么你就用什么。就像中世纪的人只能听教皇的话,教皇说地球是平的,那就是平的。
但现在有人说:我用JS重写一套布局系统,不依赖浏览器,而且比你快。这就有点像你一直用计算器算数学题,突然有人说我心算比你快,你还不信,结果他算出来不仅快而且准。更可怕的是,Pretext只是第一枪。以后会有更多开发者说:浏览器的渲染管线太慢了,我自己写一个;浏览器的布局算法太傻了,我自己写一个;浏览器的测量机制太蠢了,我自己写一个。浏览器不再是那个高高在上的神,而变成了一个可以被替换、被优化、被超越的底层基础设施。就像当年Windows垄断了操作系统,后来Linux、macOS、Chrome OS一个个站起来说:我也行。
AI在背后的作用
还有一个点特别有意思,就是作者在开发Pretext的时候大量用了AI来做验证。流程大概是这样的:他先写一段代码实现某个排版功能,然后用浏览器原生的渲染结果作为“标准答案”,也就是真值。接着让AI不断生成各种奇怪的测试用例,比如混入emoji、零宽字符、组合字符、不同语言的混合文本,跑一遍Pretext的结果,再跑一遍浏览器的结果,看两者是否一致。如果不一致,AI会帮忙分析是哪一行代码出了问题,甚至直接给出修复建议。
尤其是多语言支持这一块,正常人写会写到发疯。你想想,你要支持中文、日文、韩文、阿拉伯文、泰文,还要支持从右往左、双向文本、连字规则……这就像让你同时学十门外语,每门都要达到母语水平。AI帮他把所有边角案例全部磨平,就像一台不知疲倦的打磨机。这就是未来的开发方式:人类负责定目标和标准,比如“我要一个能精确测量所有语言文本宽度的库”,AI负责逼近这个标准,不断地测试、验证、修复。人类是将军,AI是士兵。将军负责指明方向,士兵负责冲锋陷阵。以前一个人单枪匹马写这样的库,可能要写一年。现在有了AI,可能只需要一个月。
最后说点实在的
好了,最后咱们说点实在的,不吹不黑。
Pretext不是万能药,它有明确的边界。它只覆盖了white-space: normal(正常空白处理)、word-break: normal(正常换行规则)、overflow-wrap: break-word(在单词内部换行)这三种最常见但也是最基础的场景。超出这个范围,比如你要搞什么text-wrap: balance(平衡文本行宽),或者你想支持多列布局,它不管。它也没打算成为一个完整的排版引擎,不想跟浏览器抢饭碗。但它在自己擅长的领域——也就是绝大多数普通文本布局、聊天消息、列表项、卡片内容——它是目前最完整、最认真、最狠的方案,没有之一。
核心结论一句话给你总结:浏览器逼你“先画再算”,让你画完了再告诉你高度,画不对还得重画。
Pretext让你“先算再画”,算得明明白白,画一次就到位。
这顺序一换,世界就不一样了。
就像你做数学卷子,以前是边做边想,做到最后一题发现前面全错。
现在是先想清楚所有步骤再下笔,一遍过。
哪个更爽?不用我说了吧。