首先说一下实现原理:
下拉刷新
实现下拉刷新主要分为三步:
监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY;
监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
监听原生touchend事件,若此时元素滑动达到最大值,则触发对应的callback,同时将translateY重设为0,元素回到初始位置。
触底加载更多
当网页向上卷曲出去的高度+浏览器高度=网页正文高度的时候,判定为网页已经触底。
下面直接上代码
CSS代码
#refreshContainer li {
background-color: #eee;
margin-bottom: 1px;
padding: 20px 10px;
}
.refreshText {
position: absolute;
width: 100%;
line-height: 50px;
text-align: center;
left: 0;
top: 0;
}
HTML代码
- 111
- 222
- 333
- 444
- 555
- 111
- 222
- 333
- 444
- 555
- 111
- 222
- 333
- 444
- 555
JS代码
(function (window, document, undefined) {
var upDownRefresh = function (box, text) {
var _element = document.getElementById(box),
_refreshText = document.querySelector(text),
_startPos = 0,
_transitionHeight = 0;
_element.addEventListener('touchstart', function (e) {
console.log('初始位置:', e.touches[0].pageY);
_startPos = e.touches[0].pageY;
_element.style.position = 'relative';
_element.style.transition = 'transform 0s';
}, false);
_element.addEventListener('touchmove', function (e) {
// console.log('当前位置:', e.touches[0].pageY);
_transitionHeight = e.touches[0].pageY - _startPos;
console.log(_transitionHeight)
if (_transitionHeight > 0 && _transitionHeight < 60) {
_refreshText.innerText = '下拉刷新';
_element.style.transform = 'translateY(' + _transitionHeight + 'px)';
}
}, false);
_element.addEventListener('touchend', function (e) {
if (_transitionHeight > 55) {
_refreshText.innerText = '更新中...';
console.log("触发更新")
}
_element.style.transition = 'transform 0.5s ease 1s';
_element.style.transform = 'translateY(0px)';
}, false);
}
window.upDownRefresh = upDownRefresh;
})(window, document);
new upDownRefresh("refreshContainer", ".refreshText")
如果我们要监听的是整个页面的触底,则通过以下代码就可以
window.onscroll = function () {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
let windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
if (scrollTop + windowHeight == scrollHeight) {
console.log('触底加载更多')
}
}
一定要完全触底才触发加载更多的事件会显得不那么友好,我们可以设置一个距离范围
window.onscroll = function () {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
let windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
if (scrollTop + windowHeight + 40 >= scrollHeight) {
console.log('触底加载更多')
}
}
但是这样写有一个很大的缺点就是只要进入到了这个距离范围,触底事件就会一直触发,即使此时页面是向上滑动的,只要还是在40这个距离范围内滑动,都会触发触底事件,这不是我们想要的,接下来对代码进行优化:
let flag = ''
window.onscroll = function () {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
let windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
if (flag === '' || flag === 'open') {
if (scrollTop + windowHeight + 100 >= scrollHeight) {
console.log('触底加载更多')
// 触发了一次后阀门关闭
flag = 'close'
}
}
if (scrollTop + windowHeight + 100 < scrollHeight && (flag === '' || flag === 'close')) {
console.log('开启阀门')
flag = 'open'
}
}
如果我们需要实现的是某个元素的触底加载更多,则需要另外的实现方式:
样式方面不多赘述,滚动区域是给固定高度,设置 overflow-y: auto 来实现。
接下来看看js方面的实现,其实也很简单,触发的条件是:可视高度 + 滚动距离 >= 实际高度 。例子我会使用vue来实现,和原生实现是一样的。
- 可视高度(offsetHeight):通过 dom 的 offsetHeight 获得,表示区域固定的高度。这里我推荐通过 getBoundingClientRect() 来获取高度,因为使用前者会引起浏览器回流,造成一些性能问题。
- 滚动高度(scrollTop):滚动事件中通过 e.target.scrollTop 获取,表示滚动条距离顶部的px
- 实际高度(scrollHeight):通过 dom 的 scrollHeight 获得,表示区域内所有内容的高度(包括滚动距离),也就是实际高度
基础实现
onScroll(e) {
let scrollTop = e.target.scrollTop
let scrollHeight = e.target.scrollHeight
let offsetHeight = Math.ceil(e.target.getBoundingClientRect().height)
let currentHeight = scrollTop + offsetHeight
if (currentHeight >= scrollHeight) {
console.log('触底')
}
}
加点细节:
加点细节,现在我们希望是离底部一定距离就触发事件,而不是等到完全触底。如果你做过小程序,这和onReachBottom差不多的意思。
声明一个离底部的距离变量reachBottomDistance
这时候触发条件:可视高度 + 滚动距离 + reachBottomDistance >= 实际高度
export default {
data(){
return {
reachBottomDistance: 100
}
},
methods: {
onScroll(e) {
let scrollTop = e.target.scrollTop
let scrollHeight = e.target.scrollHeight
let offsetHeight = Math.ceil(e.target.getBoundingClientRect().height)
let currentHeight = scrollTop + offsetHeight + this.reachBottomDistance
if (currentHeight >= scrollHeight) {
console.log('触底')
}
}
}
}
在距离底部100px时成功触发事件,但由于100px往下的区域是符合条件的,会导致一直触发,这不是我们想要的。
接下来做一些处理,让其进入后只触发一次:
export default {
data(){
return {
isReachBottom: false,
reachBottomDistance: 100
}
},
methods: {
onScroll(e) {
let scrollTop = e.target.scrollTop
let scrollHeight = e.target.scrollHeight
let offsetHeight = Math.ceil(e.target.getBoundingClientRect().height)
let currentHeight = scrollTop + offsetHeight + this.reachBottomDistance
if(currentHeight < scrollHeight && this.isReachBottom){
this.isReachBottom = false
}
if(this.isReachBottom){
return
}
if (currentHeight >= scrollHeight) {
this.isReachBottom = true
console.log('触底')
}
}
}
}
优化:
实时去获取位置信息稍微会损耗性能,我们应该把不变的缓存起来,只实时获取可变的部分
export default {
data(){
return {
isReachBottom: false,
reachBottomDistance: 100
scrollHeight: 0,
offsetHeight: 0,
}
},
mounted(){
// 页面加载完成后 将高度存储起来
let dom = document.querySelector('.comment-area .comment-list')
this.scrollHeight = dom.scrollHeight
this.offsetHeight = Math.ceil(dom.getBoundingClientRect().height)
},
methods: {
onScroll(e) {
let scrollTop = e.target.scrollTop
let currentHeight = scrollTop + this.offsetHeight + this.reachBottomDistance
if(currentHeight < this.scrollHeight && this.isReachBottom){
this.isReachBottom = false
}
if(this.isReachBottom){
return
}
if (currentHeight >= this.scrollHeight) {
this.isReachBottom = true
console.log('触底')
}
}
}
}
上面代码有个坑:scrollHeight会随着每次触底数据追加而变化,储存起来影响后续的比对加载,接下来进行改进。