移动端下拉刷新和上拉加载实现

2019/08/08 移动端

H5 下拉刷新和上拉加载实现

最近在做移动端开发,移动端的性能不如 PC 端,屏幕页没有 PC 大,需要我们优化的东西很多;在工作中我所做的移动端小页面,无一例外的都是将网页嵌入到安卓或者 IOS 里面去。

问题:如果数据太多前端一次性渲染或者请求所有数据,就不能做到用户体验和用户效果最佳

解决方案: 移动端分页,滚动到页面底部重新请求接口,然后把上次请求的数据和这一次请求的数据拼接到一个数组里面

关于原生的滚动 scroll 事件会失效这个问题坑大了,有兴趣可以看看 解决工作 bug 或者需求系列文章

因为用的是 vue 所以会好一点,没有那么坑,但是很多 css 样式都是缺一不可的 实现代码:

<template>
  <div class='wrapper'>
    <p class="refreshText"></p>
    <ul class='refreshContainer' @touchstart='getStart' @touchmove='getMove' @touchend='getEnd' @scroll="onScroll($event)">
      <li class='freshContainer' v-for='item in listData' :key='item.value'></li>
    </ul>
  </div>
</template>
<script>
export default {
//   当前手势滑动位置与初始位置差值大于零时,提示正在进行下拉刷新操作;
// 下拉到一定值时,显示松手释放后的操作提示;
// 下拉到达设定最大值松手时,执行回调,提示正在进行更新操作。
  data(){
    return {
      text:"",
      listData:[{
        label:'测试数据001',
        value:'hjh'
      },{
        label:'测试数据002',
        value:2
      },{
        label:'测试数据003',
        value:3
      },{
        label:'测试数据004',
        value:4
      },{
        label:'测试数据005',
        value:5
      },{
        label:'测试数据006',
        value:6
      },{
        label:'测试数据007',
        value:7
      },{
        label:'测试数据008',
        value:8
      },{
        label:'测试数据009',
        value:9
      },{
        label:'测试数据010',
        value:10
      },{
        label:'测试数据0011',
        value:11
      }]
    }
  },
  methods:{
    onScroll (event) {
         const target = event.target
        // 滚动条的总高度
        const scrollHeight = target.scrollHeight
        // 可视区的高度
        const clientHeight = target.clientHeight
        // 距离顶部的距离
        const scrollTop = target.scrollTop
        console.log('滚动条的总高度',scrollHeight);
        console.log('可视区的高度',clientHeight);
        console.log('距离顶部的距离',scrollTop);

        // 滚动到底部
        if (scrollTop + clientHeight >= scrollHeight - 80) {
          // 这里可以进行数据分页请求了this.getListMore
          this.listData.push({
        label:'测试数据0012',
        value:12
      },{
        label:'测试数据0013',
        value:13
      },{
        label:'测试数据004',
        value:14
      },{
        label:'测试数据0015',
        value:15
      },{
        label:'测试数据0016',
        value:1
      },{
        label:'测试数据0016',
        value:16
      },{
        label:'测试数据0017',
        value:17
      },{
        label:'测试数据0018',
        value:18
      },{
        label:'测试数据0019',
        value:19
      },{
        label:'测试数据00100',
        value:100
      },{
        label:'测试数据00101',
        value:101
      },{
        label:'测试数据00102',
        value:102
      },{
        label:'测试数据00103',
        value:103
      })
          console.log('滚动事件',event);
        }
    }
  }
}
</script>

<style>
html,
body,
.body-container {
  height: 100%;//必须有
}
.wrapper {
  width: 100%;
  height: 100%;//必须有
}
ul {
  height: 100%;//必须有
  border: 1px red solid;
  overflow: auto;//必须有
}
ul li {
  width: 100%;
  line-height: 100px;
  /* border: 1px red solid; */
  text-align: center;
}
.refreshText {
  display: flex;
  justify-content: center;
  align-items: center;
  /* height: 50px; */
  border: 1px red solid;
}
</style>

问题: 下拉实现数据更新

解决方案: 监听原生的 touchstart,touchmove,touchend 事件,这个还好,没啥问题

实现代码:

    <p class="refreshText"></p>
    <ul class='refreshContainer' @touchstart='getStart' @touchmove='getMove' @touchend='getEnd'>
      <li v-for='item in listData' :key='item.value'></li>
    </ul>
    <script>
export default {
//   当前手势滑动位置与初始位置差值大于零时,提示正在进行下拉刷新操作;
// 下拉到一定值时,显示松手释放后的操作提示;
// 下拉到达设定最大值松手时,执行回调,提示正在进行更新操作。
  data(){
    return {
      text:"",
      listData:[{
        label:'测试数据001',
        value:'hjh'
      },{
        label:'测试数据002',
        value:2
      },{
        label:'测试数据003',
        value:3
      },{
        label:'测试数据004',
        value:4
      },{
        label:'测试数据005',
        value:5
      },{
        label:'测试数据006',
        value:6
      },{
        label:'测试数据007',
        value:7
      },{
        label:'测试数据008',
        value:8
      },{
        label:'测试数据009',
        value:9
      },{
        label:'测试数据010',
        value:10
      },{
        label:'测试数据0011',
        value:11
      },{
        label:'测试数据0012',
        value:12
      },{
        label:'测试数据0013',
        value:13
      },{
        label:'测试数据004',
        value:14
      },{
        label:'测试数据0015',
        value:15
      },{
        label:'测试数据0016',
        value:1
      },{
        label:'测试数据0016',
        value:16
      },{
        label:'测试数据0017',
        value:17
      },{
        label:'测试数据0018',
        value:18
      },{
        label:'测试数据0019',
        value:19
      },{
        label:'测试数据00100',
        value:100
      },{
        label:'测试数据00101',
        value:101
      },{
        label:'测试数据00102',
        value:102
      },{
        label:'测试数据00103',
        value:103
      }],
      startPosition:0,
      movePostion:0,
      refreshContainer:"",
      refreshText:""
    }
  },
  methods:{
    getStart(e){
      this.refreshContainer = document.querySelector('.refreshContainer')
      this.startPosition = e.touches[0].pageY
      this.refreshContainer.style.position = 'relative';
      this.refreshContainer.style.transition = 'transform 0s';
    },
    getMove(e){
      this.movePostion = e.touches[0].pageY
      let transitionHeight = this.movePostion - this.startPosition
      this.refreshText = document.querySelector('.refreshText')
      if(transitionHeight > 0 &&transitionHeight < 50){
      this.refreshText.style.height = transitionHeight+'px';
      }else{
      this.refreshText .style.height = '50px';
      }
      if (transitionHeight > 0 && transitionHeight < 100) {
            this.text = '下拉刷新';
            this.refreshContainer.style.transform = 'translateY('+transitionHeight+'px)';
            if (transitionHeight > 50) {
              this.text = '释放更新';
            }
        }
    },
    getEnd(){
      this.refreshContainer.style.transition = 'transform 0.5s ease 1s';
        this.refreshContainer.style.transform = 'translateY(0px)';
        this.text = '更新中...';
        this.refreshText.style.height = '0px';
        this.text = '';
        // 接口请求什么的事情

    },
  }
}
</script>

详细介绍请参考H5 下拉刷新和上拉加载实现原理浅析

大概原理是这样的,样式什么的瞎写的,有兴趣可以自己好好写一下样式

问题: 下拉实现数据加载数据问题

上面说的通过 scrollTop + clientHeight >= scrollHeight - 80 去实现业务中滑到距离底部 80px 就加载数据,请求接口,在这个里面是有一个小小的问题的。

举个场景:如果后端默认给了 10 条数据,第一次加载距离底部还有 80px 像素的时候就请求一次接口,然后继续滑动,每一次滑动 1px,就会去请求一次接口,因为都满足这个条件,scrollTop + clientHeight >= scrollHeight - 80 ,那就是说会有很多大量重复的数据,最新的数据,我们总是没有办法看到。(每一次请求接口我们都会把上一次请求的数据保留,向上滑动时可以看到之前的数据,不用请求接口)

理论上可行,可是结合到实际业务代码总会有各种情况考虑,所以就是结合业务开发才是王道

改进方案 Math.ceil(scrollTop + clientHeight) >= scrollHeight 替换那个scrollTop + clientHeight >= scrollHeight - 80 只有到了底部,我才去加载,这个时候把分页信息带上去请求接口。为了避免手机设备滚动有小数点,我们使用向上取整,因为我就遇到了(scrollTop + clientHeight)scrollHeight 小 0.4,导致不能滚动,实际上是要可以滚动的。坑都是踩出来的

问题: scroll 事件会失效

没有任何问题,我在用 vue开发去监听 Scroll 事件(网上有很好的 BetterScroll ,开发的时候会用它,但是想自己可以知道实现原理),监听了半天没有任何用,网上给出很多的解决方案 window.addEventListener(),元素的 overflow 属性要设置为 scroll 都可以解决,但是不明白为啥,继续深究原来是看到了 MDNscroll 事件的描述: elementscroll 事件不冒泡, 但是 documentdefaultViewscroll 事件冒泡;有一篇写的很不错的文章推荐你所不知道的 scroll 事件:为什么 scroll 事件会失效? 我觉得里面写的蛮好的。借用里面的代码分析

<body>
  <div id="root">
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
    <p>Hello world</p>
  </div>
  <script src="scroll-in-window.js"></script>
</body>
// scroll-in-window.js

window.addEventListener('scroll', e => console.log('scroll handler is triggered in window during capture phase.'), true)
window.addEventListener('scroll', e => console.log('scroll handler is triggered in window during bubble phase.'))

const root = document.getElementById('root')
root.addEventListener('scroll', e => console.log('scroll handler is triggered in root during capture phase.'), true)
root.addEventListener('scroll', e => console.log('scroll handler is triggered in root during bubble phase.'))

请自行运行看效果

<!DOCTYPE html>
<html>

<head>
  <title>Scroll In Element</title>
  <style>
    body {
      margin: 0;
    }

    #wrapper {
      width: 100%;
      height: 100vh;
      display: flex;
      overflow: hidden;
    }

    #left-col {
      overflow: hidden;
      flex: 0 0 300px;
      background-color: aqua;
    }

    #right-col {
      flex: 1;
      overflow: auto;
    }
  </style>
</head>

<body>
  <div id="wrapper">
    <div id="left-col">
      <nav>
        barabara
      </nav>
    </div>
    <div id="right-col">
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
      <p>Hello world</p>
    </div>
  </div>
  <script src="scroll-in-element.js"></script>
</body>

</html>

const log = (elem, phase) => {
  console.log(`scroll handler is trigged in ${elem} during ${phase}`)
}

const bindEvent = (elem, elemName)  => {
  elem.addEventListener('scroll', log.bind(null, elemName, 'capture'), true)
  elem.addEventListener('scroll', log.bind(null, elemName, 'bubble'))
}

bindEvent(window, 'window')
const wrapper = document.querySelector('#wrapper')
bindEvent(wrapper, 'wrapper')
const rightCol = document.querySelector('#right-col')
bindEvent(rightCol, 'rightCol')

还有一篇有问题的vue 无法监听 scroll 事件


一名伪程序猿——sunseekers,曾被bug虐的体无完肤,却依旧待他如初恋。

如果我改过的某一个bug,吐槽过的某一个需求,写过的某一行代码

曾在你的心里荡起涟漪,那至少说明在逝去的岁月里,我们在某一刻,共同经历着一样的情愫。

有时候,虽然素未谋面。却已相识很久,很微妙也很知足。


如果你喜欢我写过的某一个文字,请支持我,鼓励我,你的鼓励是我最大的动力来源

当然恰好你也喜欢我的话,我们可以互相关注,相互学习的哟!

sunseekers

Search

    Table of Contents