一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
1.前言
图片懒加载在网上应该已经烂大街的东西。每次需要使用都直接百度谷歌一下问题就不大。 但是作为一个程序员还是在理解这玩意儿的基础上,有一个自己的知识数据库才行。所以决定整理一下代码。
图片懒加载的作用自不用说,提高页面性能,避免浏览器一次性访问服务器太多次导致的阻塞。在APP中还能提高长列表的稳定性,这里的稳定性指的是安卓手机在爱啪啪使用内存过多的时候,切换到后台之后会直接给爱啪啪杀死的情况。
2. 简单使用
思路:当image在未出现在可视窗的时候,不加载该image的网络图片,只使用本地图片进行展位(一般是公司logo加载图)
由上而知,我们需要:
-
image真实数据和占位符
-
image在页面中距离顶部的距离
-
当前页面滚动条距离容器顶部的距离
window.addEventListener('scroll', () => {})
2.1 准备获取数据
const TEMP_IMG = [
'http://www.baidu.com/xxx1.png',
'http://www.baidu.com/xxx2.png',
'http://www.baidu.com/xxx3.png',
'http://www.baidu.com/xxx4.png',
'http://www.baidu.com/xxx5.png',
]
import placeHold from './placeHold.png'
2.2 div到容器顶部的距离
.lazy-img {
background: url(placeHold)
}
<>
{
TEMP_IMG.map((item, index) => {
return <img key={index} data-src={item} class='lazy-img'/>
})
}
</>
const getLazyImgNodeToArray = ({nodeClass}) => {
let nodeArray = Array.from(document.querySelectorAll(nodeClass))
return nodeArray
}
const showRealImg = () => {
let nodes = getLazyImgNodeToArray({nodeClass: '.lazy-img'})
let screenHeight = document.documentElement.clientHeight
let len = nodes.length
for (let i = 0; i < len; i++) {
let nodeItem = nodes[i]
const rect = nodeItem.getBoundingClientRect()
if (rect.top < screenHeight) {
nodeItem.src = nodeItem.dataset.src
nodes.splice(i, 1)
len--
i--
}
}
}
2.3 监听当前位置做相应操作
不管是vue还是react还是RN还是小程序,都需要等待dom渲染之后
showRealImg()
window.addEventListener('scroll', () => {
showRealImg()
})
3 加入简单的函数防抖(debounce)
不给无聊的人滑来滑去的。 监听scroll事件会有性能问题,节流函数可以极大的提升性能,也基本可以用。但是使用Intersection Observer当块到可视窗的时候进行就进行相应的操作。
showRealImg()
let timer = false
window.addEventListenner('scroll', () => {
timer && clearTimeout(timer)
timer = setTimeout(() => {
showRealImg()
}, timeout)
})
动态控制timeout,可以通过判断当前网速情况,网速越快timeout可以设置越小
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
// 如果元素可见
if (entry.isIntersecting) {
let lazyImage = entry.target
lazyImage.src = lazyImage.dataset.src
lazyImage.classList.remove("lazy-img")
lazyImageObserver.unobserve(lazyImage)
}
})
})
this.lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage)
})
4. 长列表结合
5. 最简单的办法
<img loading='lazy' />
该方法是很简单,性能理论也会是最好的。而且省却了太多的时间了。 不过可惜的是现在还是处于测试阶段,很多浏览器是不支持的
6. 整理思路流程
-
初始化数据
-
加载真实图片逻辑
-
防抖节流函数接入
-
函数入口
-
兼容配置
-
导出
function LazyImg(node) { this.nodes = Array.from(document.querySelectorAll(node)) this.init = () => { if (typeof (window['IntersectionObserver'] !== 'undefined')) { let lazyImageObserver = new IntersectionObserver((entries, observer) => { entries.forEach((entry, index) => { // 如果元素可见 if (entry.isIntersecting) { let lazyImage = entry.target lazyImage.src = lazyImage.dataset.src lazyImage.classList.remove("lazy-image") lazyImageObserver.unobserve(lazyImage) } }) }) this.lazyImages.forEach(function (lazyImage) { lazyImageObserver.observe(lazyImage); }) } else { _showRealImg() this._throttleFn = this.throttle(_showRealImg) document.addEventListener('scroll', this._throttleFn.bind(this)) } } _showRealImg = () => { let len = this.nodes.length for (let i = 0; i < len; i++) { let imageElement = this.nodes[i] const rect = imageElement.getBoundingClientRect() if (rect.top < document.documentElement.clientHeight) { imageElement.src = imageElement.dataset.src this.nodes.splice(i, 1) len-- i-- _clearListenner() } } } _clearListenner = () => { if (this.nodes.length === 0) { window.removeEventListener('scroll', this._throttleFn) } } throttle(fn, delay = 15, mustRun = 30) { let t_start = null let timer = null let context = this return function () { let t_current = +(new Date()) let args = Array.prototype.slice.call(arguments) clearTimeout(timer) if (!t_start) { t_start = t_current } if (t_current - t_start > mustRun) { fn.apply(context, args) t_start = t_current } else { timer = setTimeout(() => { fn.apply(context, args) }, delay) } } } }