手动操刀写一个自己的图片懒加载插件

1,166 阅读4分钟

工作中运营活动项目做的比较多,对页面的加载时间有很高的要求,我一般会对暂不显示的图片媒体资源做懒加载,但是遇到过以下困惑:

滚动到了图片的位置,但还是再转圈圈,用户体验并不好。我想要的效果是,页面正常滚动情况下,提前一屏到两屏进行加载。如果滑动的速度特比快,考虑到网页的惯性滚动,适当提前2屏以上进行加载,力求DOM元素在窗口出现时,图片已经准备完毕。

这里是我自己常用的一段常用的懒加载Javascript脚本

1、了解我们的网页是如何滚动的

       今天心血来潮,仔细观察了电脑浏览器和手机浏览器的滚动方式,观察结果如下:

  • PC使用鼠标滚动滚动,网页是一段一段的过渡的,网页的惯性滚动效果相对移动端并没有那么明显。PC使用触摸板,页面滚动很丝滑动,惯性滚动效果和我们手机端很相似。无论鼠标还是触摸板滚动页面时触发的事件都是mousewheel。
  • 移动端因为都是触摸屏幕,触发页面滚动的事件是touchmove 

2、笔者想实现一个页面滚动速度越快,懒加载的提前量越多的懒加载插件。

下面开始操刀代码:

计算滚轮事件触发后的瞬时速度

class IntsanceSpeed {
    constructor() {
        this.prev = {}
        this.speed = 0
    }
    static create() {
        if (!intsanceSpeed.instance) {
            intsanceSpeed.instance = new IntsanceSpeed()
        }
        return intsanceSpeed.instance;
    }
    static listenScroll() {
        let instance = this.create() ;
        let eventName = !isMobile ? 'mousewheel': 'touchmove';
        //移动端监听touchmove,pc端监听mousewheel事件记录此刻的时间戳和页面滚动的位置
        window.addEventListener(eventName, (event) = >{
            let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
            //PC端的会立刻计算当前滚动触发后的瞬时速度
            if (instance.prev.scrollTop && !isMobile) {
                instance.speed = ((scrollTop - instance.prev.scrollTop) / (event.timeStamp - instance.prev.timeStamp)).toFixed(2)
            }
            instance.prev = {
                scrollTop,
                timeStamp: event.timeStamp
            }
        });
        //移动端手指离开时再计算当前的瞬时速度
        isMobile && window.addEventListener('touchend', (event) = >{
            let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
            if (instance.prev.scrollTop) {
                instance.speed = ((scrollTop - instance.prev.scrollTop) / (event.timeStamp - instance.prev.timeStamp)).toFixed(2) instance.prev = {}
            }
        })
    }
}

有了计算瞬时速度的方法,接下来,手写一个图片、视频、音频等常见懒加载的一个方法

代码在这里:

class LazyLoad {
        //start,开启我们的图片懒加载功能
	start(selector = '.lazyload') {
		IntsanceSpeed.listenScroll()
		const elements = LazyLoad.getElements(selector)
		const windowHeight = window.innerHeight || document.documentElement.clientWidth || document.body.clientHeigh
		window.addEventListener('scroll', throttle(function() {
			let speed = intsanceSpeed.create().speed;
			if (speed < 0) {
				return
			}
			speed = Math.max(speed, 3)
			for (let i = 0; i < elements.length; i++) {
				if (LazyLoad.isLoaded(elements[i])) {
					continue
				}
				if (elements[i].getBoundingClientRect().top < windowHeight * (speed + 1)) {
					LazyLoad.load(elements[i])
					LazyLoad.markAsLoaded(elements[i])
				}
			}
		}))
	}
        //通过类名获取dom元素
	static getElements(selector, root = document) {
		if (selector instanceof Element) {
			return [selector]
		}
	
		if (selector instanceof NodeList) {
			return selector
		}
	
		return root.querySelectorAll(selector)
	}
        //加载html标签上面记录的需要异步加载的资源文件
	static load(element) {
		if (element.nodeName.toLowerCase() === 'picture') {
			const img = document.createElement('img')
			if (isIE && element.getAttribute('data-iesrc')) {
				img.src = element.getAttribute('data-iesrc')
			}
	
			if (element.getAttribute('data-alt')) {
				img.alt = element.getAttribute('data-alt')
			}
	
			element.append(img)
		}
	
		if (element.nodeName.toLowerCase() === 'video' && !element.getAttribute('data-src')) {
			if (element.children) {
				const childs = element.children
				let childSrc
				for (let i = 0; i <= childs.length - 1; i++) {
					childSrc = childs[i].getAttribute('data-src')
					if (childSrc) {
						childs[i].src = childSrc
					}
				}
	
				element.load()
			}
		}
	
		if (element.getAttribute('data-poster')) {
			element.poster = element.getAttribute('data-poster')
		}
	
		if (element.getAttribute('data-src')) {
			element.src = element.getAttribute('data-src')
		}
	
		if (element.getAttribute('data-srcset')) {
			element.setAttribute('srcset', element.getAttribute('data-srcset'))
		}
	
		if (element.getAttribute('data-background-image')) {
			element.style.backgroundImage =
				`url('${element.getAttribute('data-background-image').split(',').join('\'),url(\'')}')`
		} else if (element.getAttribute('data-background-image-set')) {
			const imageSetLinks = element.getAttribute('data-background-image-set').split(',')
			let firstUrlLink = (imageSetLinks[0].substr(0, imageSetLinks[0].indexOf(' ')) || imageSetLinks[0]) // Substring before ... 1x
			firstUrlLink = firstUrlLink.indexOf('url(') === -1 ? `url(${firstUrlLink})` : firstUrlLink
			if (imageSetLinks.length === 1) {
				element.style.backgroundImage = firstUrlLink
			} else {
				element.setAttribute('style', (element.getAttribute('style') || '') +
					`background-image: ${firstUrlLink}; background-image: -webkit-image-set(${imageSetLinks}); background-image: image-set(${imageSetLinks})`
				)
			}
		}
	
		if (element.getAttribute('data-toggle-class')) {
			element.classList.toggle(element.getAttribute('data-toggle-class'))
		}
	}
        //加载完成后,标记完成
	static markAsLoaded(element) {
		element.setAttribute('data-loaded', true)
	}
        //判断是否已完成加载
	static isLoaded(element) {
		element.getAttribute('data-loaded') === 'true'
	}
}

两个方法结合起来使用如下

const isIE = typeof document !== 'undefined' && document.documentMode
const isMobile = !!navigator.userAgent.match(/AppleWebKit.*Mobile.*/)
//笔者封装的一个立即执行的防抖函数
const throttle = (fn, wait = 10, immediate = true) => {
	let timer = null
	let callNow = immediate

	return function() {
		let context = this
		let args = arguments

		if (callNow) {
			fn.apply(context, args)
			callNow = false
		}

		if (!timer) {
			timer = setTimeout(() => {
				fn.apply(context, args)
				timer = null
			}, wait)
		}
	}
}


class IntsanceSpeed {
......
}
class LazyLoad {
......
}//在html结构渲染完成之后启用

new LazyLoad().start()

用法

在HTML中,向元素添加一个标识符

< img  class ="lazyload"  data-src = "image.png" />

用于响应式图片

< img  class ="lazyload"   data-src = "image.png"  data-srcset = "image.png 1000w,image-2x.png 2000w" />

用于背景图片

< div class="lazyload" data-background-image="image.png"></ div>

用于多个背景图像

< div class="lazyload" data-background-image="path / to / first / image,path / to / second / image,path / to / third / image"></ div>

  用于响应式背景图像(图像集) 

 < div class="lazyload" data-background-image-set="url('photo.jpg')1x,url('photo@2x.jpg')2x"></ div>

picture标签示例

<picture>
	<source class="lazyload" data-srcset="image-768.png"
	 media="(max-width: 768px)">
	<source class="lazyload" data-srcset="image-769.png"
	 media="(min-width: 769px)">
	<img class="lazyload" data-src="image-768.png">
</picture>

Video视频标签

< video  data-poster = "images /video-poster.jpeg" >
    < source src = "video /mov.mp4"  type = "video / mp4" >
    < source src = "video /mov.ogg"  type = "video / ogg" >
</ video >

这样就实现了滚动越快速,加载越会提前进行,亲测完美。目前最多允许提前4屏加载,可根据需求调整土中speed的值


参考资料:github.com/ApoorvSaxen…