最近在做一个 vue 商城的项目,项目中要求首先加载第一页商城的商品列表,当用户滚动查看商品时,在快到达列表底部的时候提前加载第一页商品列表,即诸如淘宝、天猫、京东商城的滚动懒加载。
分析需求:关键是如何判断在滚动的时候到达列表底部。我们可以在列表底部放一个 div ,判断该 div 出现在可视区域中的时候,即滚动到列表底部。那么如何判断一个元素是否在可视区域中出现呢?
判断元素是否出现在可视区域
首先我们来总结一下几个关键的概念
偏移量
偏移量(offset dimension),元素的可见大小由其高度、宽度决定,包括所有的内边距、滚动条和边框大小,不包含外边距。以下是获取元素偏移量的方法:
offsetHeight = content + padding + border + scrollX
元素在垂直方向上占用的空间大小,以像素计算。包含元素的高度、边框、内边距和元素的水平滚动条(如果存在且渲染的话),不包含:before或:after等伪类元素的高度。
如果元素被隐藏(例如 元素或者元素的祖先之一的元素的style.display被设置为none),则返回0
这个属性会被四舍五入为整数,如果需要一个浮点数值,请使用
element.getBoundingClientRect()
offsetWidth = content + padding + border + scrollY
元素在水平方向上占用的空间大小,以像素计算。包含元素的宽度、边框、内边距和元素的竖直滚动条(如果存在且渲染的话),不包含:before或:after等伪类元素的高度。
offsetLeft
元素的左外边框至包含元素的左内边框之间的像素值。
offsetTop
元素的上外边框至包含元素的上内边框之间的像素距离。
如下图所示:
客户区域大小
客户区域大小有以下两个属性:
clientWidth = content + padding
clientWidth是元素内容区域宽度加上左右内边距宽度
clientHeight = content + padding
clientWidth是元素内容区域高度加上左右内边距高度
可以通过如下方法来确定浏览器视口大小:
1 | let viewPort = { |
客户区大小不包括滚动条、边框、外边距。
滚动大小
- scrollHeight:在没有滚动条的情况下,元素内容的总高度,即 clientHeight
- scrollWidth
- scrollLeft:被隐藏在内容区域左侧的像素数。通过设置这个属性可以改变元素的滚动位置。
- scrollTop
scrollWidth 和 scrollHeight 主要用于确定元素内容的实际大小。
scrollLeft 和 scrollTop属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位 置。在元素尚未被滚动时,这两个属性的值都等于 0。如果元素被垂直滚动了,那么 scrollTop 的值 会大于 0,且表示元素上方不可见内容的像素高度。如果元素被水平滚动了,那么 scrollLeft 的值会 大于 0,且表示元素左侧不可见内容的像素宽度。这两个属性都是可以设置的,因此将元素的 scrollLeft 和 scrollTop 设置为 0,就可以重置元素的滚动位置。
确定元素大小
getBoundingClientRect:一般来 说,right 和 left 的差值与 offsetWidth 的值相等,而 bottom 和 top 的差值与 offsetHeight 相等。
实现判断元素在可视区内
方法一
1
2
3
4
5
6
7
8function isInViewPort(el) {
const viewPortHeight = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight
const elOffsetTop = el.offsetTop
const dScrollTop = document.documentElement.scrollTop
const top = elOffsetTop - dScrollTop
return top <= viewPortHeight
}
方法二
1
2
3
4
5
6function isInViewPort (el) {
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
console.log('top', top)
return top <= viewPortHeight
}
Vue 滚动加载的实现
设计一个 FooterLine 组件,判断该组件是否将要出现在视图中,如将要出现则进行加载。
1 | <template> |
接下来监听页面的滚动事件,当 isInViewPort 函数返回 true 时,表明组件即将进入页面,触发函数加载数据:
1 | mounted() { |
这样实现之后我们发现,当页面不断滚动的时候,控制台会不断打印出 top 值,并且当 top <= viewPortHeight 时会不断发送 http 请求数据,显然这对页面的性能影响很严重,并且也可能导致不断发送重复请求。经过思考,我们可以使用 vue 提供的 watch 来解决该问题。具体思路是:
- 首先定义一个数据 IsEmit= false,使用 watch 监听该数据的更改
- 监听页面的滚动事件,当滚动到接近底部的时候,将 IsEmit 修改为 true
- watch 监听到 IsEmit 的修改,并且 IsEmit 修改为 true 时,触发函数加载数据。这样即使继续滚动也不会不断发送重复请求
具体代码实现如下:
1 | export default { |
上述方法利用 vue 的特性结局了不会重复发送 http 请求的问题,但是依然没有解决控制台不断打印 top 值的问题,即在页面滚动过程中,滚动事件执行的过于频繁,但是我们并不希望这么频繁的执行。
基于以上问题,我们可以利用防抖函数和节流函数来解决。