6.8 浏览器工作原理
6.8.1 渲染render
(1)前面介绍了浏览器工作原理的前4步,盒子树的每个节点是盒子Box,实现盒子模型。苹果公司Safari浏览器的内核是webkit,盒子Box对应其源码类RenderBox,完整实现是子类RenderBoxModelObject,这也是盒子树在经常被叫做渲染树的原因。Google公司Chrome浏览器的内核是blink,blink早期基于webkit开发,盒子Box对应其源码类LayoutBox,这是本教程称为盒子树的原因。
(2)第5步layout布局:计算所有盒子的坐标位置与大小。因为很多盒子采用流式布局等自动技术没有设置坐标位置、大小等属性,浏览器在该步骤完成所有盒子的所有属性的完整计算。术语说明。这里的“layout布局”算作专用术语,表示渲染流程的一个步骤。
(3)第6步paint绘制:画画是在白纸上一笔笔画出目标内容,同理,浏览器根据盒子位置与大小、以及其它CSS属性设置,把页面所有盒子一个个画在显示设备,最终完成页面显示。这个“画draw”的动作称为绘制(paint)。
渲染阶段包括其中3个步骤:生成盒子树(渲染树)、layout布局计算、paint绘制,广义渲染包括图示所有6个步骤。
实际渲染过程更为复杂,毕竟浏览器源码大小是G级别。不同浏览器实现也存在细节差异,而且各厂商浏览器本身也在不断升级,所以很多细节无法统一介绍,理解这些大致步骤能够帮助实现高效合理的设计、开发与调试。
6.8.2 理解paint与layout
1、一切皆是画
绘制paint就是“画draw”,更加专业的术语。页面经常包含文本、图片,文本与图片一样也是画出来的,白纸上写纸与画图并没有本质区别。白纸相当于一块画布,字也是在画布作出的一种画。字体文件相当于字的图库,通过工具FontForge查看Arial字体的字符“x”的设计图。
程序在显示设备最终显示文本、边框、图片等所有元素,都是在画布画出各种元素。这个过程称之绘制。
2、Java绘制示例
浏览器是C++编写的程序,包含绘制模块。为方便理解,这里通过Java语言模拟一个玩具浏览器程序界面,说明绘制过程。
运行效果
源码包含两个文件。BrowserFrame类:模拟一个浏览器窗口,含有标题与地址题,该类不重要;PaintPanel类:模拟页面内容区,对应画板,画出文本、边框,关注重点该类。
BrowserFrame.java代码
import javax.swing.*;
import java.awt.*;
public class BrowserFrame extends JFrame {
public static void main(String[] args) {
BrowserFrame browserFrame = new BrowserFrame();
}
public BrowserFrame() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = this.getContentPane();
contentPane.setLayout(null);
JTextField textFieldURL = new JTextField();
textFieldURL.setText("https://www.xiaobuteach.com/index.html");
textFieldURL.setBounds(5, 5, 400, 30);
contentPane.add(textFieldURL);
JButton buttonGO = new JButton();
buttonGO.setText("跳转");
buttonGO.setBounds(410, 5, 60, 30);
contentPane.add(buttonGO);
JPanel paintPanel = new PaintPanel();
paintPanel.setBounds(0, 40, 500, 160);
contentPane.add(paintPanel);
this.setTitle("小步浏览器");
this.setLocationRelativeTo(null);
this.setSize(500, 200);
this.setVisible(true);
}
}
JFrame表示窗口,内部包含组件:地址输入框textFieldURL、跳转按钮buttonGO、画板paintPanel。
PaintPanel.java源代码
import javax.swing.*;
import java.awt.*;
public class PaintPanel extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("微软雅黑", Font.BOLD, 12);
g.setFont(font);
g.setColor(Color.white);
g.fillRect(0 , 0 ,this.getWidth() , this.getHeight());
g.setColor(new Color(10, 170, 118));
g.drawString("小步教程" , 15 , 25);
g.drawRect(5 ,5 , 100 , 30);
g.setColor(new Color(74, 166, 252));
g.fillRect(5 ,50 , 300 , 50);
g.setColor(Color.white);
g.drawString("https://www.xiaobuteach.com" , 15,80);
}
}
对象Graphics g可简单理解成画笔,设置画笔的字体以及颜色,然后通过 drawString画字符串,通过drawRect画边框,fillRect画区域。前面所说“画”即对应draw、fill等系列方法。页面显示每项内容是浏览器一笔笔画出来的,这就是绘制paint。
3、理解第5步layout布局
绘制每个元素需要确定它的绝对位置与大小,述代码才画两个元素就出现大量的坐标值与宽高值。开发HTML页面时,通常只设置少量元素的位置与大小,大部分由浏览器自动计算,这个自动计算的过程即对应浏览器工作原理图的第5步layout布局。
4、开发者工具查看绘制
示例运行效果
示例代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小步教程</title>
<style>
.div1{
border: 1px solid #0aaa76;
margin-bottom: 20px;
}
.div2{
background-color: #4aa6fc;
}
</style>
</head>
<body>
<div class="div1">小步教程</div>
<div class="div2">https://xiaobuteach.com</div>
</body>
</html>
开发者工具点击“更多工具”,打开“图层Layer”,再次刷新页面,如下图如示。
点击下方“绘制性能剖析器”。
可以看出浏览器最终如何绘制图形。drawTextBlob绘制文本,drawRect绘制矩形区域。Chrome浏览器当前考虑弃用该面板功能,正在进行投票,无论以后是否存在该工具面板,不妨碍这里通过它理解绘制。
6.8.3 CSS调试整体思路
按照页面渲染过程的6个步骤整理对应5项调试内容。
调试1:网络面板查看请求响应。对应访问页面、页面或CSS等文件引用其它文件。
调试2:元素面板查看元素结构。对应浏览器解析HTML代码。
调试3:样式面板查看元素匹配的选择器以及选择器的样式。对应生成盒子树。
调试4:计算样式面板查看最终实际值,包括盒子模型。对应CSS属性值计算。
调试5:页面查看盒子的显示区域,并结合周围盒子的显示区域分析它的占据区域。对应整个渲染结果。
当页面出现bug无法直接定位时,可以按倒序一步步进行排查。
调试面板与层叠处理步骤的大致对照关系。“样式面板”大致对应声明值declared value,“计算样式”面板大致对应最终实际值actual value。