type
status
date
slug
summary
tags
category
icon
password
什么是 RenderObject?
样式匹配结束后,我们就得到了一颗带有完整 CSS 样式内容的 Dom 树,但此时 Dom 树上的内容,并不能作为数据依据直接在屏幕上绘制出东西,因此下一步,我们就需要利用 Dom 树作为基本,生成一颗新的树 —— RenderObject 树。
Dom 与 RenderObject 的对应关系
与 Dom 树相比,RenderObject 树上的每个节点,会将 Dom 的 Style 中的属性计算成可以直接绘制的到屏幕上的内容,和每个 Dom 都有自己的 Element 类不同的是,RenderObject 的类型并不会有很多,大部分情况下我们只会看到:
- RenderInline
- RenderBlockFlow
- RenderFlexibleBox
- RenderText
- RenderImage
从 Dom 生成到这些 RenderObject 的规则如下:
Dom 类型 | RenderObject 类型 |
Dom 中的纯文本 | RenderText |
display 为 flex 的元素 | RenderFlexibleBox |
display 为 block、inline-block 的元素 | RenderBlockFlow |
display 为 inline 的元素 | RenderInline |
<img> 元素 | RenderImage |
匿名 Box 的产生规则
如果没有意外情况,一个 Dom 会和 RenderObject 一一对应,但是在实际实现中,由于 Web 规范中定义的一些特殊行为,导致这个过程不能这样简单。
在如下情况下,RenderObject 和 Dom 不会是一一对应的关系:
- Block、Flex 容器元素直接子元素为 Text
- Block、Inline 元素中既包含 inline-level 又包含 block-level 的元素
- 特殊的,Inline 元素中含有 block-level 的元素时,inline 元素会对应生成两个甚至更多的 RenderObject
简单的总结一下:所有会产生匿名 Box 的场景下,RenderObject 和 Dom 都不会一一对应。
接下来我们展开描述一下这些会产生匿名 Box 的情况
Block、Flex 容器元素直接子元素为 Text
这种情况下匿名 Box 的策略比较简单,直接给 Text 外部包裹一个 Block Box 即可:
不过相对 Flex 容器元素和 Block 容器还有所区别,Flex 容器元素不会有直接的 Text 子元素,而 Block 可以:
Block、Inline 元素中既包含 inline-level 又包含 block-level 的元素
对于 Block 元素来说,事情比较简单,如果自己的直接子元素中既有 inline-level 又有 block-level 的,那么直接给所有的 inline-level 的子元素包裹一个匿名 Block Box 即可:
而对于 Inline 元素,事情就比较复杂了。
首先 Inline 元素会找到自己的父元素中的 Block Box(也就是 Block Container),将自己分成多个 “分身”,然后再将多个 “分身” 与 inilne-level 子元素的外部包裹一个匿名 Block Box 中;
同时子元素中的 block-level 的外部也会包裹一个匿名 block box,然后作为 Block Container 的直接子元素存在(也就是 “上位”):
如果有 n 个 block-level 的子元素,Inline 元素就会有 n+1 个 “分身”:
生成 RenderObject 这个模块的职责
- 根据 Dom 生成对应的 RenderObject
- 创建匿名 RenderObject
匿名 Box 在浏览器中是怎么生成的?
以上步骤在浏览器中,发生在将新生成的 RenderObject 挂载到其他 RenderObject 的过程中。
WebCore 中,这一部分的代码逻辑在 RenderTreeBuilder::attachInternal() ;Blink 中则是在 LayoutTreeBuilderForElement::CreateLayoutObject() 中(更准确的说,是在 LayoutBlockFlow / LayoutInline / LayoutBlock 的 AddChild 方法中);
这部分的代码逻辑可以简单描述为:
- 当前元素为 inline
- 当 “子元素均为 block-level”
- 添加了 inline-level 子元素:将该子元素添加到最后一个 “分身” 下
- 添加了 block-level 子元素:过程同 1.b.II
- 当 “子元素均为 inline-level” 或 “没有子元素”
- 添加了 inline-level 子元素:直接添加
- 添加了 block-level 子元素:找到该 block 所在的 block container(注意,可能不是该 block 的直接父元素,可能会跨好几层),将该 block 之前的所有的 inline-level box(包括父元素的,同时中间不能再有 block box)包裹在一个匿名 block box(这里称其为匿名 block box 1)中,插入到 block container 的上一个 block 后面;同时当前 block box 包裹在一个匿名 block box (这里称其为匿名 block box 2)中,将匿名 block box 2 插入到匿名 block box 1 的后面,同属于 block container 的子元素
- 当前元素为 block
- 当 “子元素均为 inline-level”:
- 添加了 inline-level 子元素:直接添加
- 添加了 block-level 子元素:将之前的 inline box 都包裹在一个 block box 中 (Blink 代码见 MakeChildrenNonInline)
- 当 “子元素均为 block” 或 “没有子元素”:
- 添加了 inline-level 子元素:在 inline 的外部包裹一个匿名 block box;如果之前已经有一个用于包裹 inline 的匿名 block,则添加到这个匿名 block 中
- 如果该 inline 之前的元素是纯文本,将文本包裹在一个匿名的 inline block 中
- 添加了 block-level 子元素:直接添加
- 当前元素为 flex
- 添加了非纯文本子元素:直接添加(flex 容器的子元素的 display 如果是 inline、inline-block,都会被改成 block)
- 添加了纯文本元素:文本外部包裹一个 block box
如果想验证一个 Dom 会生成什么样的 RenderObject 树,可以使用 WebKit 中的 DumpRenderTree 这个工具来验证:
例如,上面的 Dom 结构:
会打印出如下结果:
- 作者:Onee
- 链接:https://onee.me/article/how-browser-create-new-render-object
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。