了解页面渲染过程:
渲染过程如下:
- 解析 HTML 生成 DOM Tree;解析 CSS 生成 Style Rules;
- 两者合并生成 Render Tree;
- 根据几何信息(位置、大小等),生成页面布局 (Layout);
- 根据特征信息(颜色、透明度等),绘制页面 (Painting);
- 显示 (Display) ,将像素发送给GPU,展示在页面上。
注意,渲染(render)时,会遍历 DOM 节点,会忽略非可见节点(如 script 标签,meta 标签等 及 "display:none" 的元素)。
还有,"visibility:hidden" 与 "display:none" 不同:
- "visibility:hidden" 将元素设置为不可见,但是同样在布局上占有一定空间,仍在 render tree 上;
- 但 "display:none" 的元素是将节点从 render tree 中移除,所以不是布局中的一部分。
回流/重绘 的定义:
- 回流(reflow): 发生在上图的 Layout 位置,当修改属性引发页面布局变化,则触发回流。
- 重绘(repaint): 发生在上图的 Painting 位置,当修改属性不引发页面布局变化,则只触发重绘。
所以,可能只发生重绘;但 回流 必定包含 重绘 过程。
引发回流(reflow)的常见情况:
- 页面首次渲染(初始化)
- DOM 树变化(如:增/删/移动节点)
- Render 树变化(如:padding改变)
- 浏览器窗口大小发生变化
- 获取元素的某些属性
注意,获取元素某些属性也会引发回流,因为浏览器为了获取正确值而提前触发回流。这些属性包括:
- offsetLeft/Left/Width/Height、
- scrollTop/Left/Width/Height、
- clientTop/Left/Width/Height、
- getComputedStyle() 或者 IE的currentStyle
优化(减少 回流/重绘 的次数):
1. 尽量一次性修改节点样式,避免逐个
let el = document.getElementById('test');
// el.style.padding = '5px';
// el.style.borderLeft = '1px';
// el.style.borderRight = '2px';
// 使用 cssText 避免多次修改
el.style.cssText += 'border-left:1px; border-right:2px; padding:5px'
2. 批量修改 DOM 时,使用 DocumentFragment 缓存起来,再一次性插入到指定节点
const data = ['one', 'two', 'three', 'four', ...]
function appendDataToElement(appendToElement, data) {
let
li,
i,
// 存放 li
fragment = document.createDocumentFragment();
for (i = 0; i < data.length; i++) {
li = document.createElement('li');
li.textContent = data[i];
fragment.appendChild(li);
}
// 一次性插入所有元素
appendToElement.appendChild(fragment)
}
const ul = document.getElementById('list');
appendDataToElement(ul, data);
3. 将需要多次修改的元素设置 "display:none" ,操作完再显示(上面提及"display:none"不在render树内,不触发回流重绘)
// 但不建议使用本方法,插入元素时间过长会影响体验
function appendDataToElement(appendToElement, data) {
let li, i;
for (i = 0; i < data.length; i++) {
li = document.createElement('li');
li.textContent = 'text';
appendToElement.appendChild(li);
}
}
const ul = document.getElementById('list');
// 先隐藏该元素
ul.style.display = 'none';
appendDataToElement(ul, data);
// 插入完成后再显示
ul.style.display = 'block';
4. 避免多次读取某些属性
5. 将复杂节点元素脱离文档流 (如 "position:absolute" ),降低回流成本
多说几句:
- CSS 放在头部,原因是渲染(render)需要 DOM(HTML) 和 CSSOM(CSS) ,放在头部有助于缩短首次渲染时间
- JS 放在尾部,原因是JS会阻塞浏览器解析,当发现一个外链脚本需要下载完后在继续解析后续的 HTML
- DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片...
- load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成
其他博主的文章: