浏览器底层渲染机制

浏览器渲染机制

页面之所以能渲染

  1. 从服务器获取需要渲染的内容(URL解析/DNS/TCP/HTTP…)
  2. 浏览器基于自己的渲染引擎(例如:webkit/gecko/trident/blink…)开始自上而下加载渲染代码

服务器获取的是文件流

  1. 浏览器首先把16进制的字节流信息编译为“代码字符串”
  2. 按照w3c规则进行字符解析,生成对应的Tokens,最后转换为浏览器内核可以识别渲染的DOM节点
  3. 按照节点最后解析为对应的树DOM树,CSSOM树,Render树

浏览器

  1. 在css资源还没有请求回来之前,先生成DOM树
  2. 当所有的css请求回来之后,浏览器按照css的导入顺序,依次进行渲染,最后生成cssom树
  3. 把DOM树和CSSOM树结合在一起,生成有样式,有结构的render树
  4. 浏览器按照渲染树,在页面中进行渲染和解析
  5. 计算元素在设备饰扣的大小和位置布局(layout)或重拍/回流(reflow)
  6. 根据渲染树以及回流得到的几何信息,得到节点的绝对像素=>绘制/重绘(painting)

link和@import都是导入外部样式(从服务器获取样式文件)

  1. 遇到link,浏览器会派出一个新的线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码。不论css是否请求回来,代码都会继续渲染
  2. 遇到@import,GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前,是不会继续渲染的@import阻碍浏览器的渲染,项目中尽量少用
  3. 如果是style,GUI直接渲染

正常情况下js也会阻碍GUI的渲染

  1. js在一般在页面的底部,就是为了确保DOM树生成完才去加载js
  2. 可能会与defer和async异步管控js的请求

性能优化

  1. 减少DOM树渲染的时间(HTML层级不要太深,标签语义化…)
  2. 减少CSSOM树渲染时间(选择器是从右向左解析,所以尽可能减少选择器的层级【less/sass中的层级嵌套虽然好用,但是是一个大坑】)
  3. 减少HTTP的请求次数和请求大小
  4. 一般会把CSS放在页面的开始位置(提前请求资源 用link别用@import,对于移动端来讲,如果CSS比较少,尽可能采用内嵌式即可…)
  5. 为了避免白屏,可以进来第一件事,快速生成一套 loding 的渲染树(前端骨架屏);服务器的SSR骨架屏所提高的渲染是避免了客户端再次单独请求数据,而不是样式和结构上的(首屏处理)
  6. 把JS放在页面底部以及尽可能使用defer或者async
  7. CRP性能节点优化

DOM的重绘和回流Repaint和Reflow

  1. 重绘:元素样式的改变(但宽高、大小、位置不变)如:outline、visibility、color、background-color等
  2. 回流:元素的大小或者位置发生变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重薪计算布局和渲染
  3. 如添加或删除可见的DOM元素;元素的位置发生变化;元素的尺寸发生变化(比如文本变化或图片被另一个不同尺寸的图片替代);页面一开始渲染的时候(这个无法避免);因为回流是根据视口大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流
  4. 回流一定会触发重绘,但是重绘不一定会回流

性能优化:避免DOM的回流

1、缓存布局信息
div.style.left = div.offsetLeft + 1 + ‘px’;
div.style.top = div.offsetTop + 1 + ‘px’;
修改为=>
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + ‘px’;
div.style.top = curTop + 1 + ‘px’;

2、元素批量修改
文档碎片:createDocumentFragment
模板字符串拼接

3、动画效果应用position属性为absolute或fixed的元素上(脱离文档流)

4、CSS3硬件加速(GPU加速)
比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘;transform/opacity/filters/这些属性会触发硬件加速,不会引发回流和重绘
可能会引发的坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等

5、牺牲平滑度换取速度
每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看起来是跳动的,因为浏览器正在与更新回流做斗争,每次移动3像素可能看起来平滑度低了,但它不会导致cpu在比较慢的机器中抖动

6、避免table布局和使用css的JavaScript表达式

示例代码

	/* 点击盒子,让其立即回到0的位置,然后再运动到200的位置(动画1S) */
	<style>
	  .box {
	    position: absolute;
	    top: 100px;
	    left: 100px;
	    width: 100px;
	    height: 100px;
	    background: red;
	    transition: left 1s linear 0s;
	  }
	</style>

	// 现代版浏览器,两行修改操作,只引发一次回流
    // 浏览器的渲染队列机制:遇到修改样式的代码,浏览器没有立即渲染,而是先把他放到渲染队列中,继续看下面是否还是修改样式的,
    // 是的话继续放进去....(直到遇到获取元素样式的代码或者没有修改样式的代码了,则立即把队列中的样式统一进行渲染,最后只引发一次回流重绘)
	box.onclick = function () {
	  // 立即回到零
	  box.style.transitionDuration = '0s';
	  box.style.left = '0px';
	  // 运动到200的位置
	  box.style.transitionDuration = '1s';
	  box.style.left = '200px';
	};

	// 由于渲染队列机制原因,这样会触发两次,一旦遇到获取样式的代码,需要先把之前队列中的样式进行渲染
	box.onclick = function () {
	  box.style.transitionDuration = '0s';
	  box.style.left = '0px';
	  // 获取样式,会立即刷新渲染队列
	  box.offsetLeft;
	  box.style.transitionDuration = '1s';
	  box.style.left = '200px';
	};

	// 如果把2个操作放到宏任务中,也可以实现效果,但是两个宏任务的时间必须不一样,如果一样,那么还是会一起渲染的
	box.onclick = function () {
	  setTimeout(() => {
	    box.style.transitionDuration = '0s';
	    box.style.left = '0px';
	  }, 1)
	  setTimeout(() => {
	    box.style.transitionDuration = '1s';
	    box.style.left = '200px';
	  }, 2)
	};

	// 会同时渲染
	box.onclick = function () {
	  setTimeout(() => {
	    box.style.transitionDuration = '0s';
	    box.style.left = '0px';
	  })
	  setTimeout(() => {
	    box.style.transitionDuration = '1s';
	    box.style.left = '200px';
	  })
	};

	// 使用文档碎片
	let frg = document.createDocumentFragment();
	for (let i = 0; i < 10; i++) {
	  let span = document.createElement('span');
	  span.innerHTML = i;
	  frg.appendChild(span);
	}
	document.body.appendChild(frg);

你可能感兴趣的:(个人分享,高级前端编程)