先看概念介绍:
重绘 Paint
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的 外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
咋看页面是不是在重绘? 重绘理解起来还不如重画,翻译成中文就是再画一遍.
在Chrome的开发者工具中点 竖着的三个点 . 然后点More tools,然后选Rendering ,
把这个checkbox 点上, 然后再看页面 中如果发生 重绘 的地方,就会用绿色闪过. 这个好观察 , 找一个timer 发现一秒变一次. 比如在淘宝上的这个 .
当然更直观一些的, 可以直接拖动滚动条. 因为很多资源没有加载出来或者 页面大小变更,触发重绘, 绿色的地方更明显.
回流 Layout
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow).Layout
当 页面布局 和 几何属性 改变时就需要回流.
页面布局:比如淘宝搜商品, 越往下滑,商品越多,这时候布局改变
几何属性:比如一个图片点击之后变大.
Note: 回流必将引起重绘,而重绘不一定会引起回流
回流重点在 位置, 重绘重点在 效果.
换了个位置, 需要在新的地方再画一个.
触发页面重布局的属性
- 盒子模型相关属性会触发重布局
- 定位属性及浮动也会触发重布局
- 改变节点内部文字结构也会触发重布局
只触发重绘的属性
新建DOM的过程
- 获取DOM后分割为多个图层
- 对每个图层的节点计算样式结果(Recalculate style--样式重计算)
- 为每个节点生成图形和位置(Layout--回流和重布局)
- 将每个节点绘制填充到图层位图中(Paint Setup和Paint--重绘)
- 图层作为纹理上传至GPU
- 符合多个图层到页面上生成最终屏幕图像(Composite Layers--图层重组)
通过Performance截取了一部分的淘宝页面中的轮播切换的位置 .就是一个图片切换到另外一个图片 ,浏览器做了些什么 ?
GPU? 啥玩意是GPU? 我就听说过CPU , 我刚开始还以为是写错了.....
GPU 简写来自 Graphic Processing Unit ,中文翻译为图形处理器. 简单点理解, 都知道CPU在电脑中的用处 , GPU在显卡中的作用 类似于CPU的作用. 专门的图形的核心处理器.
图层? 什么叫图层 . 中文一层一层的图 ? 英文叫layers .
还是Chrome 开发者工具, 点三个点, 然后More Tools ,然后选Layers.
能看见页面中的图层, 我试了一下 发现, 百度的首页里边一个图层都没有.
而淘宝的首页里边有N个图层 以至于 在点开每个图层的时候 非常卡.
那么有个问题 图层咋来的 ?
从Dom 元素变为 图层方式:
- CSS设置属性
transform:translateZ(0)
- CSS设置属性
will-change: transform
将频繁重绘回流的DOM元素单独作为一个独立图层,那么这个DOM元素的重绘和回流的影响只会在这个图层中
比如video ,早期电影不就是一帧一帧的图片么....每次都会重绘.这个给他放在一个独立的图层中,效果更好. 看一下腾讯视频的video
貌似行成图层的原因还挺多的 .
比如这个 .
这是因为加了css 样式 ,就是上边说的第一个
那么对于video 是为啥就成了图层了 ?
意思也就是只要是个video 元素 他就是一个图层了.
当然应该还有其他原因生成这些图层 就不举例子了 .还挺多的. 自己点点看吧
Chrome创建图层的条件
- 3D或透视变换(perspective transform)CSS属性
- 使用加速视频解码的<video>节点
- 拥有3D(WebGL)上下文或加速的2D上下文的<canvas>节点
- 混合插件(如Flash)
- 对自己的opacity做CSS动画或使用一个动画webkit变换的元素
- 拥有加速CSS过滤器的元素
- 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
那么知道了之后怎么用?
- 避免使用触发重绘.回流的css属性
- 将重绘回流的影响范围限制在单独的图层之内
实战优化点 这个更重要
- 用translate替代top改变 (优化测试: 详细例子在这里 )
- 用opacity替代visibility ,也就是用 opacity 0 或者1 来替代visibility
- 不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOM 的 className (常用)
- 把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
- 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量 ,比如offsetHeight offsetWidth ,涉及到什么回流的缓存机制的. 记住吧.
- 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局.之前面试过Oracle的时候问过, 为啥不用table 布局了? a:对于搜索引擎的收录更加友好。b:w3c规范. c:占用空间小 d:改样式费劲 .e: 再加上回流这个.
- 动画实现的速度的选择.(别设置个1ms就动一次)
- 对于动画新建图层,
- 启用 GPU 硬件加速
测试例子:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
#rect{
width:100px;
height:100px;
background-color: blue;
position:absolute;
}
</style>
</head>
<body>
<div id="rect"></div>
<script>
setTimeout(function(){
document.getElementById("rect").style.top = '100px';
},2000)
</script>
</body>
</html>
chrome中效果为
相比之下减少了Layout 也就是回流的过程
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
#rect{
transform: translateY(0);
width:100px;
height:100px;
background-color: blue;
}
</style>
</head>
<body>
<div id="rect"></div>
<script>
setTimeout(function(){
document.getElementById("rect").style.transform = 'translateY(100px)'
},2000)
</script>
</body>
</html>
测一下visibility 和 opacity
显示visibility ,会调用重绘.也就是Paint 就是第三个红柱子
document.getElementById("rect").style.visibility = 'hidden';
然后是opacity,然后发现, 我靠,不是说opacity效率更高么怎么 不光重绘 还有回流了?
document.getElementById("rect").style.opacity = '0';
关于这个 说是什么有阿尔法通道的还不是太理解. 先放着.
然后把opacity里边的元素放到图层里边
效果是这样
总体跟visibility差不多. 这一点先放着.暂时理解不是特别深.
测试第三点
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.rect{
width:100px;
height:100px;
background-color: blue;
}
.action{
top:100px;
width:150px;
height:150px;
background-color: red;
color:blue;
}
</style>
</head>
<body>
<div class="rect"></div>
<script>
setTimeout(function(){
document.getElementsByClassName("rect")[0].className = 'action'
},2000)
</script>
</body>
</html>
效果如下 .只有一次.
多次改变style的值
效果是几乎差不多的.也没有多次的Layout , 猜测是chrome 内部已经可以处理这样的问题.
测试第四点: 相当于做一个离线 . 看效果
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.rect{
width:100px;
height:100px;
background-color: blue;
display:none;
}
</style>
</head>
<body>
<div class="rect"></div>
<script>
setTimeout(function(){
console.log("change");
document.getElementsByClassName("rect")[0].style.opacity = "0";
document.getElementsByClassName("rect")[0].style.width = "150px";
document.getElementsByClassName("rect")[0].style.height = "150px";
document.getElementsByClassName("rect")[0].style.top = "100px";
document.getElementsByClassName("rect")[0].style.backgroundColor = "red";
document.getElementsByClassName("rect")[0].style.color = "blue";
document.getElementsByClassName("rect")[0].style.width = "150px";
document.getElementsByClassName("rect")[0].style.height = "150px";
document.getElementsByClassName("rect")[0].style.top = "100px";
document.getElementsByClassName("rect")[0].style.opacity = "1";
},2000)
</script>
</body>
</html>
看下浏览器做了些啥 ?
接着是2秒之后让他显示出来
也就是说 如果你不显示出来, 浏览器就不会回流重绘.
比较第五点的两种写法 :
第一种写法:
var doms = [];//通过选择器选择出一个dom元素的数组
var domTop = [];//当前的可视区域的高度来计算这些doms元素的top值 也就是他们的位置
for(var i = 0 ; i< doms.length; i++){
var clientHeight = document.body.clientHeight;
domTop.push(clientHeight + i * 100);
}
第二种写法
var doms = [];//通过选择器选择出一个dom元素的数组
var domTop = [];//当前的可视区域的高度来计算这些doms元素的top值 也就是他们的位置
var clientHeight = document.body.clientHeight;
for(var i = 0 ; i< doms.length; i++){
domTop.push(clientHeight + i * 100);
}
第一种写法,在循环中会一直回去当前的可视区域的高度,因为每次都需要取最新的, 但是浏览器本身有一个缓冲区, 也就是说每次都需要冲完缓冲区,然后取一个最新的. 这样会破坏浏览器的缓冲机制 .