CSS、JS对页面渲染的影响

1,422 阅读5分钟

前言

根据 W3C 的规范,我们平常会将 HTML、CSS、JS 进行分层书写;并且一般 CSS 在上方,HTML 随后书写,JS 在最后才会书写。相信这是读者书写前端的默认习惯了,但是你是否有想过为啥要如此进行排列呢?

请记住下面的结论,接下来我们慢慢分析:

  • CSS:CSS 不会阻止 DOM 树解析,但是会阻止 DOM 树渲染。
  • JS:JS 会阻止 DOM 树解析以及渲染。

CSS 为何会在最上方显示?

CSS 加载不会阻塞 DOM树 的解析,但是 CSS 加载会阻塞 DOM树 的渲染,CSS 加载会阻塞后面 JS 语句的执行。

个人猜测:CSS是有可能会修改 DOM 的(别忘了 context),所以 CSS 加载会阻塞 DOM树 的渲染。CSS 放在最上方,也是防止用户可能会看见默认的 HTML 样式,造成用户体验不佳。

JS 为何会在最下方显示?

JS 会阻止 DOM 树解析以及渲染(因为 JS 可以操作 DOM)。一般来说,JS 都远比 CSS 和 HTML 要来的庞大。若是等 JS 完全加载完毕后,才显示 HTML 那么用户白屏的时间会稍长(参考 SPA 开发所出现的白屏时间)。

但是 script 加载分为几种情况:

  • 默认只会执行完了当前脚本,才会继续渲染页面或者往后执行其他脚本。
  • 使用 defer 属性可以让脚本先下载,等页面加载完毕之后再执行,一般是按照顺序来执行。
  • 使用 async 属性可以让脚本一边下载,一边让页面渲染,等脚本下载好了,就先停止页面渲染,再执行脚本,可能不会按照顺序来执行。

关于重排以及重绘

重排

  • 重排(回流、reflow):JS 动态的修改 DOM 即更改了DOM树了,更改 DOM 树之后,renderTree也就变了(可以理解为 DOM 树处理之后,会进行 renderTree 处理),renderTree变了也就是要重新建立一个renderTree了 ,这个过程叫做重排。故:重排必定引发重绘。

    重排也叫做回流,网上看到一个解释比较有趣:回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流。 只要满足其中一个条件便会触发:

    1. 页面首次渲染
    2. 浏览器窗口大小发生改变
    3. 元素尺寸或位置发生改变(位置、边距、边框、宽度和高度等发生改变)
    4. 元素内容变化(文字或图片内容等发生改变)
    5. 元素字体大小变化(因为会使布局改变)
    6. 添加或者删除可见的DOM元素或者 display 触发
    7. 设置 offsetWidth 和 offsetHeight 等属性
    8. 获取 offsetWidth 和 offsetHeight 等属性(渲染队列不为空时,这一块后续进行说明)

      设置宽高属性,触发重排很好理解,这是修改了元素的大小。获取这些属性会触发重排,是因为浏览器的渲染队列机制。当我们修改了元素的几何属性,导致浏览器触发重排或重绘时,它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作,当我们去获取这些数据时,浏览器为保证我们获取的数据是最新的,就会将渲染队列清空。

注意:如果把一个 DOM 的宽高信息确定,然后在 DOM 内部触发重排,就只会触发局部重排,否则会进行全局重排。

重绘

  • 重绘(repaint):DOM 节点的 CSS 样式颜色、字体等不改变布局的变化所引发的重新渲染过程叫做重绘。改变的是cssTree 一部分变化,对randerTree影响相对较小,所以相对与重排而言对浏览器性能影响较小。

    但是 table 及其内部元素比较特殊,它可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,所以尽量避免使用table布局。 只要满足下面条件便会触发:

    1. 当页面中元素样式的改变且并不影响它在文档流中的位置时(例如:color、background-color、visibility等)

      visibility只会触发重绘是因为 visibility 为 hidden 的元素虽然不可见,但是位置不会消失。

浏览器的渲染队列

当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。当我们去获取这些数据时,浏览器为保证我们获取的数据是最新的,就会将渲染队列清空。

优化建议

  • 避免频繁的样式操作,最好一次性重写style(cssText),或者一次性更改class,避免频繁操作DOM。
  • 需要多次重排的元素(比如复杂动画的元素)使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁重排。
  • 动画尽量使用 transition、transform (这样使用的 GPU 加速)
  • 能使用 CSS 完成的事情,请不要交给 JavaScript

    由于 DOM 和 JavaScript 是被分开独立实现的,因此,每一次在通过 JS 操作 DOM 的时候,就需要先去连接 JS 和 DOM。