懒加载 即延迟加载,在电商或是页面很长的业务场景中,我们通常会使用懒加载的方式对图片进行请求,只有在图片进入可视区域之后才请求图片资源,而在之前都通过一张占位图进行占位,将真正的图片路径存储在元素的 data-url
中,这样做的好处在于减少无效资源的加载,并不是所有的用户都会浏览完网站的所有图片,而且浏览器是存在并发上限的,并发加载的资源过多会阻塞 JS 的加载,影响网站的正常使用
懒加载具体效果可自行通过下面代码实现,也可以使用 zepto.lazyload 插件或 vue-lazyload 插件
<img src='' class='image-item' lazyload='true' data-original='http://upload-images.jianshu.io/upload_images/1662958-e1f38db94deaddd1.jpg'>
<img src='' class='image-item' lazyload='true' data-original='http://upload-images.jianshu.io/upload_images/1662958-f3dd943e438d31f6.jpg'>
<img src='' class='image-item' lazyload='true' data-original='http://upload-images.jianshu.io/upload_images/1662958-5684e095da8b8a2b.jpg'>
var viewHeight = document.documentElement.clientHeight // 可视区域的高度
function lazyload() {
var eles = document.querySelectorAll('img[data-original][lazyload]')
Array.prototype.forEach.call(eles, function(item, index) {
var rect
if(item.dataset.original === '')
return
rect = item.getBoundingClientRect()
if(rect.bottom >= 0 && rect.top < viewHeight) {
!function() {
var img = new Image()
img.src = item.dataset.original
img.onload = function() {
item.src = img.src
}
item.removeAttrbute('data-original')
item.removeAttrbute('lazyload')
}()
}
})
}
lazyload() // 首屏尚未触发 scroll 事件,需要手动去触发该事件进行图片加载
document.addEventListener('scroll', lazyload)
预加载 即在图片等静态资源在使用之前提前请求,当资源使用时直接从本地缓存中加载,提升用户体验,适用于页面需要资源相互依赖的场景,如 H5 动画
预加载主要有 3 种方式,① 使用 display:none;
将图片请求下来但并不显示,通过脚本进行控制显示/隐藏;② 使用 Image 对象,通过 new Image()
的方式创建一个图片对象,通过 JS 给图片 src
属性进行赋值;③ 使用 XMLHttpRequest 对象,其优点在于能更加精细的控制预加载过程,但缺点在于,可能会出现跨域问题
若是想对跨域可能性进行兼容,推荐大家使用 PreloadJS 模块
var queue = new createjs.LoadQueue(false); // 使用 html 方式进行预加载
queue.on("complete", handleComplete, this);
queue.loadManifest([
{id: "myImage", src:"http://upload-images.jianshu.io/upload_images/1662958-5684e095da8b8a2b.jpg"},
{id: "myImage", src:"http://upload-images.jianshu.io/upload_images/1662958-f3dd943e438d31f6.jpg"}
]);
function handleComplete() {
var image = queue.getResult("myImage");
document.body.appendChild(image);
}
在浏览器中,JS 引擎和 UI 是在单独线程中工作的,有一个线程负责进行 JS 的解析,还有一个线程负责 UI 渲染,JS 在某些场景下会获取渲染的结果,若 JS 线程和 UI 线程是在并行执行的,那有可能获取不到我们预期的结果,所以这两个线程是互斥的,当一个线程在解析或渲染时,另一个线程则被冻结,所以我们就能够知道 CSS 的性能会让 JS 变慢, 而频繁的触发重绘与回流,会导致 UI 频繁渲染,最终导致 JS 变慢
当 Render Tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为 回流 Reflow,当 Render Tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,就称为 重绘 Repaint,在回流的时候,浏览器会使 Render Tree 中受到影响的部分失效,并重新构造这部分 Render Tree,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,所以回流必将引起重绘,而重绘不一定会引起回流
width
, height
, padding
, margin
, display
, border-width
, border
, min-height
top
, bottom
, left
, right
, position
, float
, clear
text-align
, overflow-y
, font-weight
, overflow
, font-family
, line-height
, vertival-align
, white-space
, font-size
触发重绘的相关属性有 color
, border-style
, border-radius
, visibility
, text-decoration
, background
, background-image
, background-position
, background-repeat
, background-size outline-color
, outline
, outline-style
, outline-width
, box-shadow
我们通过 Chrome 的 Performance 工具,记录手淘 tab 图切换时,页面的重绘回流过程
新建 DOM 的过程:① 获取 DOM 后分割为多个图层;② 对每个图层的节点计算样式结果 Recalculate style 样式重计算;③ 为每个节点生成图形和位置 Layout 回流和重布局;④ 将每个节点绘制填充到图层位图中 Paint Setup 和 Paint 重绘;⑤ 图层作为纹理上传至 GPU;⑥ 符合多个图层到页面上生成最终屏幕图像 Composite Layers 图层重组
在图像层面,我们可以局限重绘回流的范围,将不断重绘或消耗大量运算量的 DOM 元素独立为一个图层,在 Chrome 的 Rendering 工具中勾选 Paint flashing 选项,拖动窗口大小,可以看到重绘的元素被标志为绿色,而 <video>
元素不断的在重绘
Chrome 中的 Layer 工具可查看图层数量,将全局 DOM 元素设置 transform:translateZ(0);
或 will-change: transform;
属性,将其变成新的独立图层,而每一个图层会消耗大量的时间和运算量,直接导致了页面崩溃
translate
替代 top
改变,top
会触发 Layout 过程,translate
不会// top
#rect {
position: relative;
top: 0;
width: 100px;
height: 100px;
background: lightcyan;
}
<div id="rect"></div>
<script>
setTimeout(() => {
document.getElementById('rect').style.top = '100px'
}, 2000)
</script>
使用 top
共计耗时 56+55(Layout)+92+23+110=336us
// translate
#rect {
transform: translateY(0);
width: 100px;
height: 100px;
background: lightcyan;
}
<div id="rect"></div>
<script>
setTimeout(() => {
document.getElementById('rect').style.transform = 'translateY(100px)'
}, 2000)
</script>
使用 translate
共计耗时 62+58+57=177us,之后的例子同学们可自行查看运行耗时,就不再逐个展示
opacity
替代 visibility
,visibility
会不断触发重绘过程// visibility
#rect {
width: 100px;
height: 100px;
background: lightcyan;
}
<div id="rect"></div>
<script>
setTimeout(() => {
document.getElementById('rect').style.visibility = 'hidden'
}, 2000)
</script>
rect
元素是位于 document
图层中的,当我们改变 rect
元素的阿尔法值时,是会影响到 rect
元素的兄弟元素的,虽然在当前例子中只有一个 rect
元素,但浏览器无法判断 document
图层是不是只有 rect
元素,所以我们需要将 rect
元素独立为一个新的图层
// opacity
#rect {
width: 100px;
height: 100px;
background: lightcyan;
opacity: 1;
transform: translateZ(0);
}
<div id="rect"></div>
<script>
setTimeout(() => {
document.getElementById('rect').style.opacity = '0'
}, 2000)
</script>
#rect {
position: relative;
width: 100px;
height: 100px;
background: lightcyan;
opacity: 1;
}
<div id="rect"></div>
<script>
setTimeout(() => {
document.getElementById('rect').style.width = '200px'
document.getElementById('rect').style.height = '300px'
document.getElementById('rect').style.left = '30px'
document.getElementById('rect').style.top = '20px'
}, 2000)
</script>
#rect {
position: relative;
width: 100px;
height: 100px;
background: lightcyan;
opacity: 1;
}
#rect.active {
width: 200px;
height: 300px;
left: 30px;
top: 20px;
}
<div id="rect"></div>
<script>
setTimeout(() => {
document.getElementById('rect').className = 'active'
}, 2000)
</script>
display:none
,此时会触发一次 Reflow
,之后进行的样式修改都不会触发重绘回流,修改完毕后再把它显示出来#rect {
position: relative;
width: 100px;
height: 100px;
background: lightcyan;
opacity: 1;
display: none;
}
<div id="rect"></div>
<script>
setTimeout(() => {
document.getElementById('rect').style.opacity = '0'
document.getElementById('rect').width = '200px'
document.getElementById('rect').height = '300px'
document.getElementById('rect').left = '30px'
document.getElementById('rect').top = '20px'
document.getElementById('rect').opacity = '1'
document.getElementById('rect').display = 'block'
}, 2000)
</script>
offsetHeight
, offsetWidth
var doms = [] // 通过选择器选择出一个dom元素的数组
var domsTop = []
// 根据当前页面的可视区域的高度,去计算这个dom元素的位置
for (var i = 0; i < doms.length; i++) {
domsTop.push(document.body.clientHeight + i * 100)
}
var doms = [] // 通过选择器选择出一个dom元素的数组
var domsTop = []
// 根据当前页面的可视区域的高度,去计算这个dom元素的位置
var clientHeight = document.body.clientHeight
for (var i = 0; i < doms.length; i++) {
domsTop.push(clientHeight + i * 100)
}
<video>
, <canvas>
及设置了 transform:translateZ(0);
或 will-change: transform;
属性的元素transform: translateZ(0);
和 transform: translate3d(0, 0, 0);
,当检测到这些 CSS 属性时,浏览器就会启用硬件加速