图片懒加载与预加载是前端优化中比较常见的方法,也是前端面试中会被问到的问题。懒加载就是当你打开一个页面时,没有出现在屏幕中的图片先不加载,等滑到该图片位置时再加载,而预加载就是一次性把图片加载完。
如果不做懒加载和预加载,浏览器的回流重绘很快,而图片的加载是需要发送网络请求的,当图片一百甚至一千张时,一次性发很多请求就会导致网络的堵塞,影响用户体验,接下来就让我们来实现一下懒加载以及预加载的效果。
懒加载
懒加载原理就是监听屏幕滚动事件,然后判断图片是否出现在屏幕内,等图片要出现在屏幕内再加载。可以使用js中的getBoundingClientRect()
方法,获取元素的集合属性,打印如下。
js
<body>
<img src="" data-src="https://t7.baidu.com/it/u=1732966997,2981886582&fm=193&f=GIF" alt="">
<script>
let img = document.querySelector('img')
let rect = img.getBoundingClientRect() // 获取元素几何属性
console.log(rect);
</script>
</body>
left:左边框到窗口左边的距离,right:右边框到窗口左边的距离,bottom:下边框到窗口上边的距离,top:上边框到窗口上边的距离。
然后判断图片是否在容器内,图片的top小于窗口的高度,图片的bottom大于0。
为了让图片先不展示,就不能把图片的url放在src属性上,于是我们可以在img标签中自己定义一个属性data-src
,将图片地址放进去,然后当图片要展示时,令图片的src等于data-src属性,展示过后的图片再移除掉data-src
属性。
js
<img src="" data-src="https://t7.baidu.com/it/u=17329669972981886582&fm=193&f=GIF" alt="">
js
<script>
let height = window.innerHeight
function lazyLoad() {
const imgs = document.querySelectorAll('[data-src]')
// console.log(imgs);
for (let i = 0; i < imgs.length; i++) {
let rect = imgs[i].getBoundingClientRect() // 获取元素的集合属性
if (rect.bottom > 0 && rect.top < height) {
imgs[i].src = imgs[i].getAttribute('data-src')
imgs[i].onload = function () { // 图片被浏览器加载完毕
imgs[i].src = imgs[i].getAttribute('src')
}
imgs[i].removeAttribute('data-src')
}
}
}
lazyLoad()
window.addEventListener('scroll', lazyLoad)
</script>
当然里面的imgs[i].src = imgs[i].getAttribute('data-src')
可以换成如下:
js
// imgs[i].src = imgs[i].getAttribute('data-src')
// imgs[i].onload = function () { // 图片被浏览器加载完毕
// imgs[i].src = imgs[i].getAttribute('src')
// }
// 换成
let newImg = new Image()
newImg.src = imgs[i].getAttribute('data-src') // 读取 url
newImg.onload = function () { // 图片被浏览器加载完毕
imgs[i].src = newImg.getAttribute('src')
}
目的是为了将图片的加载过程异步化,以此来减轻主线程的压力。这是通过创建一个新的Image
对象,然后设置其src
属性来预加载图片,一旦图片加载完成(onload
事件触发),再将图片的src
属性设置到原来的img
元素上。
效果如下:
预加载
用户一打开页面,全部图片先加载,然后后用户滑动就会非常地丝滑,但是就会导致同一时间加载图片过多,对服务器压力过大。
原理:在加载页面的同时,创建第二个线程去加载图片,当图片加载完毕后,将图片资源交给第一个线程去展示,从而实现图片的预加载。
代码如下;
js
<div id="pic"></div>
<script>
let pic = document.getElementById('pic')
let arr = [xxxxx] // 图片资源
// 创建一个新的线程
const worker = new Worker('worker.js')
// 将数据发送给子线程
worker.postMessage(arr)
</script>
worker函数是js自带的一个函数,作用就是创建一个新线程,但是这个新线程不能操作dom结构。这两个线程可以通信,通过postMessage进行通讯。
然后worker.js里面去加载图片,直接通过http请求去加载资源,回来的是blob类型的文件。
js
// worker.js self就是worker
self.onmessage = function (e) {
// console.log(e.data); // 传过来的图片资源
// 将数组中的地址资源加载出来
let arr = e.data;
for (let i = 0; i < arr.length; i++) {
let xhr = new XMLHttpRequest();
xhr.open("get", arr[i], true);
xhr.responseType = 'blob' // 文件类型
xhr.send();
xhr.onload = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// console.log(xhr.response);
self.postMessage(xhr.response); //将图片发送给主线程
}
}
}
}
然后主线程去接收资源再进行渲染;
js
<body>
<div id="pic"></div>
<script>
let pic = document.getElementById('pic')
let arr = [xxxxx] // 图片资源
// 创建一个新的线程
const worker = new Worker('worker.js')
// 将数据发送给子线程
worker.postMessage(arr)
// 接收子线程发送的数据
worker.onmessage = function (e) {
console.log(e.data);
const img = new Image()
// console.log(window.URL.createObjectURL(e.data));
img.src = window.URL.createObjectURL(e.data)
pic.appendChild(img)
}
</script>
</body>
window.URL.createObjectURL(e.data)
是window自带的方法,作用就是将blob资源转换成url。
小结
- 懒加载原理:当页面加载完毕后,判断在可视区域内的图片先加载,当用户滚动时,判断图片是否进入可视区域,如果进入可视区,则将图片的src替换为真实图片路径,从而实现懒加载的原理。
- 预加载原理:在加载页面的同时,开一个新线程去加载图片,当图片加载完毕后,将图片资源交给主线程去展示,从而实现图片的预加载。