笔电还能外接折叠双屏,JSAUX FlipGo 真的很懂外出打工人的心
相信很多外出办公的朋友都遇到过这种情况,因为笔记本电脑屏幕不够大,所以有时候字体看不清、表格看不完、图片看不全,可能就会影响工作效率。
我此前去吉林出差就遇到过这样一个情况,当时忙着给甲方做一个汽车的视频,结果发现 DaVinci Resolve 里面的菜单模块变少了,怎么重置菜单布局都不行。我一度以为是软件故障了,回家后发现大屏又能显示,查了资料后才发现是因为 MacBook Pro 16 的缩放原因,如果选择默认的分辨率,放不下的模块就会消失。
只有改成最高分辨率,部分模块才会重新出现,但这样的情况下,画面图标、视频轨道都会变得非常小,操作交互也不方便,这个时候我就觉得出门要是也能有个大屏就好了,16 吋的屏幕都不能满足需求了。
后来当我测试了华硕灵耀 14 双屏的笔记本后,顿感这种折叠双屏是很有市场的产品。因为它在有限的空间内,实现了屏幕面积的翻倍。那时候我就在想要是有一个独立的折叠双屏,那简直太爽了。
就在我测试完华硕灵耀 14 双屏刚过一个月,就偶然了解到一家名叫 JSAUX(几硕)的出海品牌,它们主营海外市场的数码 3C 产品,目前已经拥有了 2000 多万用户。JSAUX 最近就联合少数派推出了一个便携折叠双屏显示器——【FlipGo】,算是当今市场上最轻的双屏便携式显示器。
它在 Kickstarter 上进行了众筹,已经从近 1900 个众筹支持者筹得了 835 万港币的资金,目前新品已经登陆国内市场,我也有幸提前拿到了它的工程样机,带大家上手感受一番。
目前 FlipGo 这款显示器是由两块 IPS 屏幕 + 180° 的铰链组成,屏幕背面设计有磁性支架系统。尺寸方面分成了 13.5 吋和 16 吋两种,前者是 2356×1504 分辨率 + 400nit 亮度 + 1.1 kg,后者是 2560×1600 分辨率 + 500nit 亮度 + 1.7kg。
在屏幕功能方面,根据是否支持联屏、分屏、触摸划分出了 3 个子系列共 5 个 SKU,我选择的就是其中的 16 吋专业版,因为它正好可以和我的 MacBook Pro 16、RedmiBook Pro 16 同尺寸,观感的一致性更强。
从【16 吋专业版】的产品参数可以看出,它是由两个 16:10 的 2.5K IPS 屏组成,组成联屏之后就是 3.2K 的 21.7 吋大屏了,这也是我用过最大的便携屏了,它的体验到底如何呢?本文就来带你一一揭秘。
- 「型号」:JSAUX FlipGo 双屏便携屏
- 「尺寸」:16 吋 × 2
- 「屏幕类型」:雾面 IPS 屏
- 「分辨率」:2560×1600 / 3200×2560(联屏)
- 「刷新率」:60Hz
- 「峰值亮度」:500nits
- 「对比度」:1200:1(静态)
- 「背光技术」:侧入式背光
- 「位深度」:原生 8bit / 抖动 10bit
- 「色域覆盖」:覆盖 100% sRGB
- 「I / O 端口」:USB-C(10Gbps)× 2、USB-C(480Mbps)×1、USB-A(480Mbps)×2、Mini HDMI 2.0
- 「支架」:Flex Folio(皮套支架) / Stand Holder(普通金属支架) / Snap Stand(金属支架) / Snap VESA Adapter(VESA 支架转换头)
- 「线缆」:USB-C to C / Mini HDMI to HDMI
- 「壁挂规格」:100mm × 100mm
- 「三围」:366.8×240.4×18mm(折叠) / 366.8×470.5×11mm(展开)
- 「重量」:1.7kg
- 「售价」:3149 元
❶ 16 吋双屏是如何组合在一起的?
首先我们来聊聊这块屏幕的外观设计,作为一款 16 吋双屏的便携显示器,18mm 的折叠厚度与 1.7kg 的重量就约等于一款 16 吋笔记本电脑。对比之下,我的 MacBook Pro 16 2021 款的厚度是 16.8mm,重量 2.14kg。
JSAUX FlipGo 两块屏幕面板来自于,分辨率为 2560×1600,比较时兴的 16:10 比例,组成联屏之后就是 3200×2560 的 21.7 吋大屏了,像素密度达到了 189ppi,要比传统 27 吋 4K 屏的 163ppi 更高。
在两块屏幕之间,靠中部的隐藏式转轴铰链实现连接。铰链有一定的阻尼感,可以多角度开合。屏幕边缘处还有辅助固定的合页,提升连接的刚度。
JSAUX FlipGo 采用了内翻折设计,支持 180° 的开合,也就是能够完全放平。单手操作也是比较轻松,合盖之后,还可以对内部的屏幕起到一定的保护作用。
显示器的背面是黑色磨砂质感的金属外壳,底部屏幕会凸起一部分,推测内部为主板电路,其上还有一张印有产品信息的贴纸。
在下方屏幕的左右两侧,分布着显示器的【I/O】端口,左侧是 2 个 USB-C(10Gbps) 和 Mini HDMI 2.0。右侧是 1 个 USB-C(480Mbps) 和 2 个 USB-A(480Mbps)、电源键、OSD 菜单键。
搭配 JSAUX FlipGo 的设备使用 USB-C to C 就能够一线连双屏,但此时的亮度会被锁死在 10%,使用体验很一般,所以最好搭配额外供电解锁它的全部实力。
官方为 JSAUX FlipGo 标配了一个 65W 的充电器,但实际上这个显示器在 PD 供电方案下 23-24W 就能喂饱它。
在有满血供电的方案下,OSD 菜单就会解锁亮度调节,50% 是 300nit 左右,100% 就是 500nit 了,调节一次显示器就会记住亮度,下次通电自动开启。
❷ 四种支架形态,总有一款适合你
因为 JSAUX FlipGo 双屏不能像笔记本那样独立使用,它的最佳形态就是伫立起来连接电脑,所以官方也为其设计了类似于苹果 Magsafe 的磁吸方案。
磁吸方案的优势在于,一方面是对位精准,只要靠近就可以稳定吸附,另一方面是吸力相当的猛,单手是没法卸下来的,这样即使是轻微的移动或者碰撞,都不会跌落,安全性还是很有保障。
通过显磁片的帮助,我们可以看到 SAUX FlipGo 的双屏各有两个磁吸模块,均靠近铰链位置,彼此之间的距离正好相等,这样便于屏幕吸附时既可以横屏也可以竖屏。而且每个磁吸模块中又是由 5 个圆形磁贴和 1 个磁环组成,不过左上角模块的磁贴只有 4 个。
围绕磁吸方案,JSAUX 也开发出了丰富的支架套件,下图就分别是磁吸皮套支架、便携金属支架、立式磁吸支架、磁吸 VESA 适配器共四种支架方案,需要注意的是 VESA 适配器不包含显示器支架,得另配。
「便携金属支架」像个手机支架,显示器靠在上面就行。「磁吸皮套支架」则通过折叠的仰角实现固定,支持两段式高度调节,「立式磁吸支架」和「磁吸 VSEA 适配器」还可以支持竖屏与多种角度调节。
从我实际的使用体验来说,我最喜欢的便携金属支架,因为它最小巧,也不占空间。相比之下,剩下三种就更加适合固定场景的使用,比如家里的书房、办公室的工位。
此外,官方还提供了一个橙色的保护套,便于显示器在包内时也可以避免硬质的摩擦损坏外壳,以此实现有效的保护。我也给官方提了建议,后续出一个带支架功能的保护套就更好了。
看过了外观之后,我们来聊聊 JSAUX FlipGo 双屏的内在,主要是两块屏幕的素质如何,一致性强不强,对于 Windows 和 macOS 生态的兼容性怎样?
❶ 两块屏幕的素质能否保持一致很重要
【注】:为了更好的对比两块屏幕,我将带 IO 端口的屏幕称之为下屏,另一块为上屏。
我们首先使用爱色丽 i1 Display Pro Plus 搭配 DisplayCAL 3 校后检测后,上屏覆盖了 96.9% sRGB 色域,下屏色域为 97.0% sRGB 色域,非常接近。
亮度方面,上屏实测亮度 512.7nits,下屏亮度 514.1nits,亮度接近。两块屏幕的伽马曲线均为 2.2 光度。上屏白点为 6440K,下屏白点为 6601K,所以上屏色温会偏暖一点,下屏偏冷一点。对比度方面,上屏达到了 1255.4:1,下屏为 1168.5:1。
色准表现方面,上屏平均 △E = 0.28,最大 △E 差值为 0.9,下屏平均 △E = 0.31,最大 △E 差值为 0.82,总体来说两块屏幕的一致性尚可。
从肉眼直观的感受来说,两块屏幕是看不出差异的。但在显示纯白的画面时,因为白点不同,所以冷暖有轻微的区别。
最后在屏幕漏光方面,JSAUX FlipGo 是两块 LCD 面板,采用的是侧入式背光方案。副屏的漏光控制较好,只有左侧较明显,主屏则是四角都有。
不过上图是在纯黑环境 + 显示纯黑画面 + 拉高感光度的情况下拍摄的。实际使用过程中,肉眼是很难感知到以上的效果,所以大家可以放心使用。
❷ JSAUX FlipGo 居然有三种模式和五种玩法
因为我测试的是 JSAUX FlipGo 16 吋专业版,官方对其划分的屏幕模式主要是「大屏模式(Ultra View)」和「双屏模式(Duo View)」、「镜像模式」三种,也就是联屏、分屏、同屏。
如果你选购的是标准版,就没有大屏模式了。单条视频线那就只有下图这样的镜像模式了,如果想要双屏双显就需要接入两组视频线。所以我觉得要买就买专业版,这样才不会辜负大屏的魅力。
虽然官方只划分出了三种模式,但由于在操作系统中屏幕方向可以旋转、屏幕的摆放也可以调整。所以在形态上,可以进一步增补出「竖拼横屏」、「对称镜像」等。
▼ 竖拼横屏
▼ 对称镜像:适合与对向他人分享内容
三种模式中我最喜欢的就是「大屏」模式,因为它可以将两块屏幕合二为一的显示,这样在显示一些图片和内容时就有更多的纵向空间。
❸ Mac / Windows 的兼容性,JSAUX FlipGo 都帮你想到了
前面我们提到 JSAUX FlipGo 有同屏、联屏、分屏三种模式,如果你有两块独立的显示器 + 两路独立的信号源是可以轻松实现的,但功能集成到了折叠屏身上,就发生了一些变化。因为 JSAUX FlipGo 希望实现【一根线输出双屏】,所以就需要在显示器内做做文章。
Windows 设备的接驳比较简单,通过 USB-C to C 或者 HDMI to MiniHDMI 都可以轻松的接驳,属于无驱动直连输出的多流模式(MST),两块屏幕可以独立设置扩展、镜像、方向等选项。
但到了 Mac 生态,特别是 M1、M2 芯片的版本对于双屏就极不友好,像 MacBook Air(M1 / M2)的入门笔记本就只能外接一个显示屏,不能像 Windows 那般支持多流模式(MST),所以 JSAUX FlipGo 进行曲线救国的方案就是【DisplayLink】。
通过在显示器主板上面专门配置一个芯片,捕获 Mac 设备端的屏幕内容,重新编码后再将 USB 视频流传给显示设备,有点类似于 AirPlay 投屏,这样一来就可以将视频信号输出到两个独立的屏幕上了,目前不少针对 Mac 生态的的拓展坞也是用的这一方案。
Mac 生态端的连接时,就需要先下载「DisplayLink Manager」,然后使用 USB-C to C 接驳到 JSAUX FlipGo 的 DisplayLink 端口即可,可以看到下图中就是三屏显示不同画面。但如果使用 HDMI 或者另一个 C 口,就只能同屏或者联屏了。
当使用大屏模式时,系统菜单中就只会有一个显示器选项,通过 DisplayLink 开启双屏后,则会出现两个屏幕,它们的排列、方向都可以独立设置。
需要注意的是大屏模式建议使用 USB-C 端口(DP),而非 DisplayLink 口,这样才可以开启 HiDPI 缩放,而双屏模式在 DisplayLink 口也支持 1280×800 的 HiDPI 缩放,无需更换端口。
总体来说,JSAUX FlipGo 专业版对于 Windows 和 Mac 生态的兼容性都是相当到位的,不过 Windows 的兼容性更好,苹果在双屏方向还需努力啊。好消息是今年 M3 芯片的 Macbook Air 可以接驳双屏了,但缺点就是电脑要合盖,所以要想三屏同框还得靠 DisplayLink 解围。
前面给大家铺垫了这么多 JSAUX FlipGo 的功能展示,最终还是要落脚到场景体验上,我们就以官方划分出的大屏模式与双屏模式来展示。
❶ 大屏模式:21.7 吋的身形,32 吋才有的体验
大屏模式一共有两种,最常见的就是这种竖屏,也被称作瀑布屏,大家平时都是见惯了横屏,再看看这个 JSAUX FlipGo,就有一种将手机进行超级放大后的视觉观感。
如果是传统横屏显示器想要达到这种浏览效果,尺寸起码要达到 32 吋,分辨率达到 5120×3200,目前市面上也就只有 4 万元的 Apple Pro Display XDR 能够有这样的规格了。
▼ 下图中的蓝色范围就是 JSAUX FlipGo 的显示范围,超出的红色范围就是想要达到同样竖屏大小显示效果的 32 吋的 5K 屏。
从实际的体验来说,21.7 吋屏幕相比于 7 吋不到的手机,显示器大了近 13 倍多,不论是编辑文档还是浏览网页、刷刷微博,这种单一页面的纵向浏览体验都是极爽的,简直是摸鱼神器。
当然我还发掘出了一个使用场景,就是刷抖音为代表的短视频,抖音视频以竖屏为主,16:10 的 JSAUX FlipGo 正好没有什么黑边,网页端只需要点击鼠标或者键盘就可以切了,相比于手机刷抖音就无需低头和手拿设备了。
因为屏幕可以旋转,所以也赋予了大屏模式另一种玩法——竖拼横屏,这就让 JSAUX FlipGo 变成一块屏幕比例为 5:4 的 21.7 吋显示器。
相比于传统 16:9 的屏幕,同样的长边下,5:4 比例屏幕的纵向面积会大上 42%,所以对于表格、文档一类的场景会更有优势,是不是感觉什么 16:10、3:2、4:3 都被打下去了。
▼ 下图中的绿色范围是一个 16:9 屏幕范围,超出的的蓝色部分就是 JSAUX FlipGo 可以额外显示的区域。
❷ 双屏模式:横叠竖拼都能办公学习娱乐
看完了大屏模式,我们再来聊聊双屏模式,根据屏幕的方向不同,使用场景同样有区别。当两块横屏叠加时,一个屏幕就可以拿来干活,另一个屏幕拿来查资料。
如果你的工作比较简单,无需双屏的参与,就可以考虑用另一个屏幕放着微信或者 QQ 等即时通讯工具,避免因为回复消息而干扰到工作屏幕。当然你也可以选择一边干活一边看视频,劳逸结合。这都是 JSAUX FlipGo 双屏能够带给你的魅力。
除了文字外,Davinci Resolve 的视频编辑工作流也可以通过双屏提升效率,可以看到虽然 16 吋的单屏显示内容有限,但是我可以将部分模块拖拽到下方的屏幕之中,之前 MacBook 上遇到的问题就迎刃而解了。
如果你像我一样偶尔还要玩个云顶之弈,还可以用双屏降维打击,上屏负责游戏操作下自走棋,下屏看攻略查玩法。最近暴雪也要回归国服了,《炉石传说》可以安排上这种高端操作了。
相比于双横屏的既能工作,也可以兼顾娱乐,双竖屏就是纯粹的为工作而生了。对于【学生群体】而言,双竖屏可以左边看文献,右边写论文。而且因为单边屏幕就有 16 吋,所以可以清晰完整的显示 A4 大小的一页内容,这是传统笔记本无法达到的效果。
对于【办公人士】而言,做表格也是经常打交道的场景,JSAUX FlipGo 双屏让你可以一边查资料,一边显示完整表格内容。再也不怕遇到双表格,反复切屏录入对比的打工人崩溃情景了。
对于我这样的【内容创作者】而言,写文章时候总少不了图片 + 文字的堆叠,竖屏的写文效率更高,而且左侧也方便查询资料加以佐证。我在写本文的时候,就用上了不少这样的操作。
总体来说,JSAUX FlipGo 的大屏模式与双屏模式拥有相当丰富的场景优势,对于商务办公、影音娱乐、内容创作都有着较好的适配度,就看大家能开发出哪些玩法了。
经过本文的测评之后,我们来总结一下 JSAUX FlipGo 这款便携双屏的使用体验。我曾经用过 4 款便携屏,它们基本上都是单屏方案,对于笔电场景也就是个辅助定位,但 JSAUX FlipGo 的出现实现了反客为主,500nit 的亮度 + 100%sRGB + △E<1 的屏幕素质,也足以傲视大部分笔电屏幕了。
而且 JSAUX FlipGo 借助大屏、双屏、镜像三种模式,以及屏幕旋转之后所衍生出的各种显示模式,能够充分契合我的移动办公、娱乐、创作场景。我未来可能也会考虑体验一下 13.5 吋的专业版,它的重量只有 1.1kg,厚度仅 16mm,更加适合轻量化的户外需求。
当然,JSAUX FlipGo 也还有如下【值得改进】之处:首先是屏幕铰链的阻尼可以考虑再提高一些,避免因为重力原因,啪的一声就自动合盖了,让我有点忧心屏幕的安全;其次是希望屏幕的保护壳未来能够集成支架功能,提高配件的集成度。最后是希望能够加入重力感应来自动切换屏幕显示方向,就不用手动设置显示器方向了。
分享到此结束,感谢您的耐心观看,我是 Geek 研究僧,我们下期再见!
10个酷炫图像悬停动画特效「值得收藏」
作者:semlinker
转发链接:https://mp.weixin.qq.com/s/p0U8sVLtWd78CLc8kM22FQ
本文小编将给大家介绍10个 图像悬停效果,希望小编精心录制的十个 Gif 动画能让大家眼前一亮,当然更希望这些特效能给大家设计图片悬停效果带来一些 「”灵感“」。
❝
示例说明:该示例会根据鼠标移入的方向展示不同的动画效果。
示例来源:Noel Delgado
在线地址:https://codepen.io/noeldelgado/pen/pGwFx
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:当鼠标悬停在图片上时,会产生 3D 的堆叠运动效果。
示例来源:https://tympanus.net/
在线地址:https://tympanus.net/Development/StackMotionHoverEffects/
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:当鼠标悬停在 3D 的网格图片上时,会产生 3D 的堆叠效果。
示例来源:https://tympanus.net
在线地址:https://tympanus.net/Development/IsometricGrids/
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:当鼠标悬停在图片上时,会产生 3D 的折叠效果。
示例来源:https://tympanus.net/
在线地址:https://tympanus.net/Tutorials/3DHoverEffects/
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:鼠标悬停图片时显示相应的文字介绍。
示例来源:LittleSnippets.net
在线地址:https://codepen.io/littlesnippets/pen/bpMmBO
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:鼠标悬停图片时,图片会有滑动效果。
示例来源:LittleSnippets.net
在线地址:https://codepen.io/littlesnippets/pen/dGVQvB
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:鼠标悬停在背景图片上,滑动会显示前景图。
示例来源:Hervé
在线地址:https://codepen.io/herve/pen/GoEna
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:使用 CSS3 和 jQuery 实现放大镜效果。
示例来源:Rohan Hapani
在线地址:https://codepen.io/desirecode/pen/vgwaoe
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:悬停引导按钮时,显示其他的功能菜单。
示例来源:Jouan Marcel
在线地址:https://codepen.io/jouanmarcel/pen/NLgVjm
❞
「静态效果图」
「Gif 动态效果图」
❝
示例说明:悬停引导按钮时,显示其他的功能菜单。
示例来源:Shaw
在线地址:https://codepen.io/shshaw/pen/RyOPzb
❞
「静态效果图」
「Gif 动态效果图」
在日常工作中,如果小伙伴们也想实现图片悬停特效,小编推荐一个 Github 上的一个可扩展的、轻量的 CSS 图片悬停动画库 ——「imagehover.css」 。该库是一个制作精良的 CSS 库,允许你轻松实现各种图片悬停效果,支持 「40」 个悬停效果,压缩后仅有 「19KB」。
作者:semlinker
转发链接:https://mp.weixin.qq.com/s/p0U8sVLtWd78CLc8kM22FQ
从零开始实现一个玩具版浏览器渲染引擎
浏览器渲染原理作为前端必须要了解的知识点之一,在面试中经常会被问到。在一些前端书籍或者培训课程里也会经常被提及,比如 MDN 文档中就有渲染原理[1]的相关描述。
作为一名工作多年的前端,我对于渲染原理自然也是了解的,但是对于它的理解只停留在理论知识层面。所以我决定自己动手实现一个玩具版的渲染引擎。
渲染引擎是浏览器的一部分,它负责将网页内容(HTML、CSS、JavaScript 等)转化为用户可阅读、观看、听到的形式。但是要独自实现一个完整的渲染引擎工作量实在太大了,而且也很困难。于是我决定退一步,打算实现一个玩具版的渲染引擎。刚好 Github 上有一个开源的用 Rust 写的玩具版渲染引擎 robinson[2],于是决定模仿其源码自己用 JavaScript 实现一遍,并且也在 Github 上开源了 从零开始实现一个玩具版浏览器渲染引擎[3]。
这个玩具版的渲染引擎一共分为五个阶段:
分别是:
1.解析 HTML,生成 DOM 树2.解析 CSS,生成 CSS 规则集合3.生成 Style 树4.生成布局树5.绘制
每个阶段的代码我在仓库上都用一个分支来表示。由于直接看整个渲染引擎的代码可能会比较困难,所以我建议大家从第一个分支开始进行学习,从易到难,这样学习效果更好。
现在我们先看一下如何编写一个 HTML 解析器。
HTML 解析器的作用就是将一连串的 HTML 文本解析为 DOM 树。比如将这样的 HTML 文本:
解析为一个 DOM 树:
写解析器需要懂一些编译原理的知识,比如词法分析、语法分析什么的。但是我们的玩具版解析器非常简单,即使不懂也没有关系,大家看源码就能明白了。
再回到上面的那段 HTML 文本,它的整个解析过程可以用下面的图来表示,每一段 HTML 文本都有对应的方法去解析。
为了让解析器实现起来简单一点,我们需要对 HTML 的功能进行约束:
1.标签必须要成对出现:<div>…</div>2.HTML 属性值必须要有引号包起来 <div class=\”test\”>…</div>3.不支持注释4.尽量不做错误处理5.只支持两种类型节点 Element 和 Text
对解析器的功能进行约束后,代码实现就变得简单多了,现在让我们继续吧。
首先,为这两种节点 Element 和 Text 定一个适当的数据结构:
然后为这两种节点各写一个生成函数:
这两个函数在解析到元素节点或者文本节点时调用,调用后会返回对应的 DOM 节点。
下面这张图就是整个 HTML 解析器的执行过程:
HTML 解析器的入口方法为 parse(),从这开始执行直到遍历完所有 HTML 文本为止:
1.判断当前字符是否为 <,如果是,则当作元素节点来解析,调用 parseElement(),否则调用 parseText()2.parseText() 比较简单,一直往前遍历字符串,直至遇到 < 字符为止。然后将之前遍历过的所有字符当作 Text 节点的值。3.parseElement() 则相对复杂一点,它首先要解析出当前的元素标签名称,这段文本用 parseTag() 来解析。4.然后再进入 parseAttrs() 方法,判断是否有属性节点,如果该节点有 class 或者其他 HTML 属性,则会调用 parseAttr() 把 HTML 属性或者 class 解析出来。5.至此,整个元素节点的前半段已经解析完了。接下来需要解析它的子节点。这时就会进入无限递归循环回到第一步,继续解析元素节点或文本节点。6.当所有子节点解析完后,需要调用 parseTag(),看看结束标签名和元素节点的开始标签名是否相同,如果相同,则 parseElement() 或者 parse() 结束,否则报错。
HTML 的入口方法是 parse(rawText)
入口方法需要遍历所有文本,在一开始它需要判断当前字符是否是 <,如果是,则将它当作元素节点来解析,调用 parseElement(),否则将当前字符作为文本来解析,调用 parseText()。
parseElement() 会依次调用 parseTag() parseAttrs() 解析标签和属性,然后再递归解析子节点,终止条件是遍历完所有的 HTML 文本。
解析文本相对简单一点,它会一直往前遍历,直至遇到 < 为止。比如这段文本 <div>test!</div>,经过 parseText() 解析后拿到的文本是 test!。
在进入 parseElement() 后,首先调用就是 parseTag(),它的作用是解析标签名:
比如这段文本 <div>test!</div>,经过 parseTag() 解析后拿到的标签名是 div。
解析完标签名后,接着再解析属性节点:
parseAttr() 可以将这样的文本 class=\”test\” 解析为一个对象 { class: \”test\” }。
有时不同的节点、属性之间有很多多余的空格,所以需要写一个方法将多余的空格清除掉。
同时为了方便调试,开发者经常需要打断点看当前正在遍历的字符是什么。如果以前遍历过的字符串还在,那么是比较难调试的,因为开发者需要根据 index 的值自己去找当前遍历的字符是什么。所以所有解析完的 HTML 文本,都需要截取掉,确保当前的 HTML 文本都是没有被遍历:
sliceText() 方法的作用就是截取已经遍历过的 HTML 文本。用下图来做例子,假设当前要解析 div 这个标签名:
那么解析后需要对 HTML 文本进行截取,就像下图这样:
至此,整个 HTML 解析器的逻辑已经讲完了,所有代码加起来 200 行左右,如果不算 TS 各种类型声明,代码只有 100 多行。
CSS 样式表是一系列的 CSS 规则集合,而 CSS 解析器的作用就是将 CSS 文本解析为 CSS 规则集合。
例如上面的 CSS 文本,经过解析器解析后,会生成下面的 CSS 规则集合:
每个规则都有一个 selector 和 declarations 属性,其中 selectors 表示 CSS 选择器,declarations 表示 CSS 的属性描述集合。
每一条 CSS 规则都可以包含多个选择器和多个 CSS 属性。
在 parseRule() 里,它分别调用了 parseSelectors() 去解析 CSS 选择器,然后再对剩余的 CSS 文本执行 parseDeclarations() 去解析 CSS 属性。
选择器我们只支持标签名称、前缀为 # 的 ID 、前缀为任意数量的类名 . 或上述的某种组合。如果标签名称为 *,则表示它是一个通用选择器,可以匹配任何标签。
标准的 CSS 解析器在遇到无法识别的部分时,会将它丢掉,然后继续解析其余部分。主要是为了兼容旧浏览器和防止发生错误导致程序中断。我们的 CSS 解析器为了实现简单,没有做这方面的做错误处理。
parseDeclaration() 会将 color: red; 解析为一个对象 { name: \”color\”, value: \”red\” }。
CSS 解析器相对来说简单多了,因为很多知识点在 HTML 解析器中已经讲到。整个 CSS 解析器的代码大概 100 多行,如果你阅读过 HTML 解析器的源码,相信看 CSS 解析器的源码会更轻松。
本阶段的目标是写一个样式构建器,输入 DOM 树和 CSS 规则集合,生成一棵样式树 Style tree。
样式树的每一个节点都包含了 CSS 属性值以及它对应的 DOM 节点引用:
先来看一个简单的示例:
上述的 HTML、CSS 文本在经过样式树构建器处理后生成的样式树如下:
现在我们需要遍历 DOM 树。对于 DOM 树中的每个节点,我们都要在样式树中查找是否有匹配的 CSS 规则。
匹配选择器实现起来非常容易,因为我们的CSS 解析器仅支持简单的选择器。 只需要查看元素本身即可判断选择器是否与元素匹配。
当查找到匹配的 DOM 节点后,再将 DOM 节点和它匹配的 CSS 属性组合在一起,生成样式树节点 styleNode:
在 CSS 选择器中,不同的选择器优先级是不同的,比如 id 选择器就比类选择器的优先级要高。但是我们这里没有实现选择器优先级,为了实现简单,所有的选择器优先级是一样的。
文本节点无法匹配选择器,那它的样式从哪来?答案就是继承,它可以继承父节点的样式。
在 CSS 中存在很多继承属性,即使子元素没有声明这些属性,也可以从父节点里继承。比如字体颜色、字体家族等属性,都是可以被继承的。为了实现简单,这里只支持继承父节点的 color、font-size 属性。
在 CSS 中,内联样式的优先级是除了 !important 之外最高的。
我们可以在调用 getStyleValues() 函数获得当前 DOM 节点的 CSS 属性值后,再去取当前节点的内联样式值。并对当前 DOM 节点的 CSS 样式值进行覆盖。
第四阶段讲的是如何将样式树转化为布局树,也是整个渲染引擎相对比较复杂的部分。
在 CSS 中,所有的 DOM 节点都可以当作一个盒子。这个盒子模型包含了内容、内边距、边框、外边距以及在页面中的位置信息。
我们可以用以下的数据结构来表示盒子模型:
CSS 的 display 属性决定了盒子在页面中的布局方式。display 的类型有很多种,例如 block、inline、flex 等等,但这里只支持 block 和 inline 两种布局方式,并且所有盒子的默认布局方式为 display: inline。
我会用伪 HTML 代码来描述它们之间的区别:
块布局会将盒子从上至下的垂直排列。
内联布局则会将盒子从左至右的水平排列。
如果容器内同时存在块布局和内联布局,则会用一个匿名布局将内联布局包裹起来。
这样就能将内联布局的盒子和其他块布局的盒子区别开来。
通常情况下内容是垂直增长的。也就是说,在容器中添加子节点通常会使容器更高,而不是更宽。另一种说法是,默认情况下,子节点的宽度取决于其容器的宽度,而容器的高度取决于其子节点的高度。
布局树是所有盒子节点的集合。
盒子节点的类型可以是 block、inilne 和 anonymous。
我们构建样式树时,需要根据每一个 DOM 节点的 display 属性来生成对应的盒子节点。
如果 DOM 节点 display 属性的值为 none,则在构建布局树的过程中,无需将这个 DOM 节点添加到布局树上,直接忽略它就可以了。
如果一个块节点包含一个内联子节点,则需要创建一个匿名块(实际上就是块节点)来包含它。如果一行中有多个子节点,则将它们全部放在同一个匿名容器中。
现在开始构建布局树,入口函数是 getLayoutTree():
它将遍历样式树,利用样式树节点提供的相关信息,生成一个 LayoutBox 对象,然后调用 layout() 方法。计算每个盒子节点的位置、尺寸信息。
在本节内容的开头有提到过,盒子的宽度取决于其父节点,而高度取决于子节点。这意味着,我们的代码在计算宽度时需要自上而下遍历树,这样它就可以在知道父节点的宽度后设置子节点的宽度。然后自下而上遍历以计算高度,这样父节点的高度就可以在计算子节点的相关信息后进行计算。
这个方法执行布局树的单次遍历,向下执行宽度计算,向上执行高度计算。一个真正的布局引擎可能会执行几次树遍历,有些是自上而下的,有些是自下而上的。
现在,我们先来计算盒子节点的宽度,这部分比较复杂,需要详细的讲解。
首先,我们要拿到当前节点的 width padding border margin 等信息:
如果这些属性没有设置,就使用 0 作为默认值。拿到当前节点的总宽度后,还需要和父节点对比一下是否相等。如果宽度或边距设置为 auto,则可以对这两个属性进行适当展开或收缩以适应可用空间。所以现在需要对当前节点的宽度进行检查。
详细的计算过程请看上述代码,重要的地方都已经标上注释了。
通过对比当前节点和父节点的宽度,我们可以拿到一个差值:
如果这个差值为正数,说明子节点宽度小于父节点;如果差值为负数,说明子节点大于父节。上面这段代码逻辑其实就是根据 underflow width padding margin 等值对子节点的宽度、边距进行调整,以适应父节点的宽度。
计算当前节点的位置相对来说简单一点。这个方法会根据当前节点的 margin border padding 样式以及父节点的位置信息对当前节点进行定位:
比如获取当前节点内容区域的 x 坐标,计算方式如下:
在计算高度之前,需要先遍历子节点,因为父节点的高度需要根据它下面子节点的高度进行适配。
每个节点的高度就是它上下两个外边距之间的差值,所以可以通过 marginBox() 获得高度:
遍历子节点并执行完相关计算方法后,再将各个子节点的高度进行相加,得到父节点的高度。
默认情况下,节点的高度等于其内容的高度。但如果手动设置了 height 属性,则需要将节点的高度设为指定的高度:
为了简单起见,我们不需要实现外边距折叠[4]。
布局树是渲染引擎最复杂的部分,这一阶段结束后,我们就了解了布局树中每个盒子节点在页面中的具体位置和尺寸信息。下一步,就是如何把布局树渲染到页面上了。
绘制阶段主要是根据布局树中各个节点的位置、尺寸信息将它们绘制到页面。目前大多数计算机使用光栅(raster,也称为位图)显示技术。将布局树各个节点绘制到页面的这个过程也被称为“光栅化”。
浏览器通常在图形API和库(如Skia、Cairo、Direct2D等)的帮助下实现光栅化。这些API提供绘制多边形、直线、曲线、渐变和文本的功能。
实际上绘制才是最难的部分,但是这一步我们有现成的 canvas[5] 库可以用,不用自己实现一个光栅器,所以相对来说就变得简单了。在真正开始绘制阶段之前,我们先来学习一些关于计算机如何绘制图像、文本的基础知识,有助于我们理解光栅化的具体实现过程。
在计算机底层进行像素绘制属于硬件操作,它依赖于屏幕和显卡接口的具体细节。为了简单起点,我们可以用一段内存区域来表示屏幕,内存的一个 bit 就代表了屏幕中的一个像素。比如在屏幕中的 (x,y) 坐标绘制一个像素,可以用 memory[x + y * rowSize] = 1 来表示。从屏幕左上角开始,列是从左至右开始计数,行是从上至下开始计数。因此屏幕最左上角的坐标是 (0,0)。
为了简单起见,我们用 1 bit 来表示屏幕的一个像素,0 代表白色,1 代表黑色。屏幕每一行的长度用变量 rowSzie 表示,每一列的高度用 colSize 表示。
如果我们要在计算机上绘制一条直线,那么只要知道计算机的起点坐标 (x1,y1) 和终点坐标 (x2,y2) 就可以了。
然后根据 memory[x + y * rowSize] = 1 公式,将 (x1,y1) 至 (x2,y2) 之间对应的内存区域置为 1,这样就画出来了一条直线。
为了在屏幕上显示文本,首先必须将物理上基于像素点的屏幕,在逻辑上以字符为单位划分成若干区域,每个区域能输出单个完整的字符。假设有一个 256 行 512 列的屏幕,如果为每个字符分配一个 11*8 像素的网格,那么屏幕上总共能显示 23 行,每行 64 个字符(还有 3 行像素没使用)。
有了这些前提条件后,我们现在打算在屏幕上画一个 A:
上图的 A 在内存区域中用 11*8 像素的网格表示。为了在内存区域中绘制它,我们可以用一个二维数组来表示它:
上面二维数组的第一项,代表了第一行内存区域每个 bit 的取值。一共 11 行,画出了一个字母 A。
如果我们为 26 个字母都建一个映射表,按 ascii 的编码来排序,那么 charsMap[65] 就代表字符 A,当用户在键盘上按下 A 键时,就把 charsMap[65] 对应的数据输出到内存区域上,这样屏幕上就显示了一个字符 A。
科普完关于绘制屏幕的基础知识后,我们现在正式开始绘制布局树(为了方便,我们使用 node-canvas[6] 库)。
首先要遍历整个布局树,然后逐个节点进行绘制:
这个函数对每个节点依次绘制背景色、边框、文本,然后再递归绘制所有子节点。
默认情况下,HTML 元素按照它们出现的顺序进行绘制。如果两个元素重叠,则后一个元素将绘制在前一个元素之上。这种排序反映在我们的布局树中,它将按照元素在 DOM 树中出现的顺序绘制元素。
首先拿到布局节点的位置、尺寸信息,以 x,y 作为起点,绘制矩形区域。并且以 CSS 属性 background 的值作为背景色进行填充。
绘制边框,其实我们绘制的是四个矩形,每一个矩形就是一条边框。
通过 canvas 的 fillText() 方法,我们可以很方便的绘制带有字体风格、大小、颜色的文本。
绘制完成后,我们可以借助 canvas 的 API 输出图片。下面用一个简单的示例来演示一下:
上面这段 HTML、CSS 代码经过渲染引擎程序解析后生成的图片如下:
至此,这个玩具版的渲染引擎就完成了。虽然这个玩具并没有什么用,但如果能通过实现它来了解真实的渲染引擎是如何运作的,从这个角度来看,它还是“有用”的。
•Let\’s build a browser engine![7]
•robinson[8]
•渲染页面:浏览器的工作原理[9]
•关键渲染路径[10]
•计算机系统要素[11]
[1] 渲染原理: https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work#%E6%B8%B2%E6%9F%93[2] robinson: https://github.com/mbrubeck/robinson[3] 从零开始实现一个玩具版浏览器渲染引擎: https://github.com/woai3c/tiny-rendering-engine[4] 外边距折叠: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_box_model/Mastering_margin_collapsing[5] canvas: https://github.com/Automattic/node-canvas[6] node-canvas: https://github.com/Automattic/node-canvas[7] Let\’s build a browser engine!: https://limpet.net/mbrubeck/2014/08/08/toy-layout-engine-1.html[8] robinson: https://github.com/mbrubeck/robinson[9] 渲染页面:浏览器的工作原理: https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work[10] 关键渲染路径: https://developer.mozilla.org/zh-CN/docs/Web/Performance/Critical_rendering_path[11] 计算机系统要素: https://book.douban.com/subject/1998341/
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。