泛览天下

阅读,看尽天下事

简单代码实现H5下拉刷新和触底加载更多

2022-06-22 22:21:23


接下来做一些处理,让其进入后只触发一次:export default { data(){ return { isReachBottom: false, reachBottomDistance: 100 }


首先说一下实现原理:
下拉刷新
实现下拉刷新主要分为三步:
监听原生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代码

<p class="refreshText"></p>
<ul id="refreshContainer">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
    <li>555</li>
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
    <li>555</li>
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
    <li>555</li>
</ul>

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会随着每次触底数据追加而变化,储存起来影响后续的比对加载,接下来进行改进。