工作中运营活动项目做的比较多,对页面的加载时间有很高的要求,我一般会对暂不显示的图片媒体资源做懒加载,但是遇到过以下困惑:
滚动到了图片的位置,但还是再转圈圈,用户体验并不好。我想要的效果是,页面正常滚动情况下,提前一屏到两屏进行加载。如果滑动的速度特比快,考虑到网页的惯性滚动,适当提前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…。