­

前端页面如何实现下拉刷新

  • 2019 年 10 月 12 日
  • 筆記

页面的滚动区域本来是用的插件vue-scroller,但是由于在一些低端安卓机中页面略有卡动,可能是GPU不给力造成的,这里尝试用原生的overflow: auto; 来测试一下滚动效果,发现效果不错,但是在ios设备中就发现了了滚动没有惯性很死板的体验,这里可以在滚动容器中写一行css属性-webkit-overflow-scrolling: touch;,这样就有原生的滚动体验啦,接下来就差一个下拉刷新的效果了,没有找到现成的轮子,这里就自己开撸

如何实现

  1. 当容器的scrollTop为0的时候,使用transform: translateY来模拟
  2. 检测下拉的高度当达到某一固定值的时候,释放手指,调用回调函数实现下拉刷新

着手实现

既然是下拉我们肯定需要监听touchstarttouchmovetouchend三个dom事件

touchstart

el.addEventListener('touchstart', e => {      if (el.scrollTop !== 0) {          return      }      beginPagY = e.touches[0].pageY      e.preventDefault()  })

用于记录手指点按屏幕时候的位置,为了后续translateY的值计算做准备

touchstartmove

el.addEventListener('touchmove', e => {      if (el.scrollTop !== 0) {          return      }      const pageY = e.touches[0].pageY      const distance = currentPos = pageY - beginPagY      if (distance < 0 || distance > maxTranslateY) {          // 上拉的和超过最大限定高度时候不做任何处理          return;      }      if (distance > 60) {          iconEl.classList.add('active')      } else {          iconEl.classList.remove('active')      }      e.preventDefault()      el.style.transform = `translateY(${distance}px)`  })

touchmove主要是根据手指下拉的距离不断的修改container的translate的值来达到效果, 同时记录当前下拉的distance,用于松手是判断是否触发下拉刷新的效果; 同时在模拟下拉效果的时候要阻止系统默认事件e.preventDefault()

touchend

let clear = () => {      this.isShowLoading = false      el.style.transform = `translateY(0)`      setTimeout(() => {          el.style.transition = ``      }, 200)  }  el.addEventListener('touchend', () => {      el.style.transition = `.2s`      if (currentPos >= 60) {          this.isShowLoading = true          el.style.transform = `translateY(30px)`          callback && callback(() => {              clear()          })          return      }      clear()  })

touchend 主要是用来松手时是否执行下拉刷新的效果, 当currentPos达到预先设定的值的时候就触发回调函数,这里设置transition来增加动画效果

最终效果

所有代码

<!doctype html>  <html lang="zh">  <head>      <meta charset="UTF-8">      <meta name="viewport"            content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">      <meta http-equiv="X-UA-Compatible" content="ie=edge">      <title>Document</title>      <style>          * {              margin: 0;              padding: 0;          }          #app {              height: 100vh;              overflow: hidden;              display: flex;              flex-direction: column;          }          header {              width: 100%;              height: 44px;              background: teal;              z-index: 10;          }          main {              height: calc(100% - 44px);              overflow: auto;              -webkit-overflow-scrolling: touch;              flex: 1;          }          .item {              font-size: 12px;              line-height: 30px;              color: #aaa;          }          .item + .item {              border-top: 1px solid;          }      </style>      <style>          .css-icon {              display: inline-block;              height: 1em; width: 1em;              font-size: 20px;              box-sizing: border-box;              text-indent: -9999px;              vertical-align: middle;              position: relative;          }          .css-icon::before,          .css-icon::after {              content: '';              box-sizing: inherit;              position: absolute;              left: 50%; top: 50%;              -ms-transform: translate(-50%, -50%);              transform: translate(-50%, -50%);          }              .icon-upward::before {              height: .65em; width: .65em;              border-style: solid;              border-width: 2px 0 0 2px;              -ms-transform: translate(-50%, -50%) rotate(45deg);              transform: translate(-50%, -50%) rotate(45deg);          }          .icon-upward::after {              height: .8em;              border-left: 2px solid;              top: 55%;          }            .icon-upward.active {              transform: rotate(180deg);              transition: transform .3s;          }              .pull-to-refresh-layer {              height: 60px;              margin-top: -60px;              font-size: 12px;              text-align: center;              color: #aaa;              line-height: 30px;          }      </style>  </head>  <body>  <div id="app">      <header></header>      <main>          <div ref="container">              <div class="pull-to-refresh-layer">                  <div v-show="!isShowLoading">                      <i ref="icon" class="css-icon icon-upward"></i>                      <p>下拉刷新</p>                  </div>                  <div v-show="isShowLoading" style="padding-top: 30px;">                      假装是个loading图标                  </div>              </div>              <div class="item" v-for="item in list" :key="item">{{item}}</div>          </div>      </main>  </div>  <script src="./vue.js"></script>  <script>      new Vue({          el: '#app',          data: {              isShowLoading: false          },          computed: {              list() {                  return new Array(100).fill(0).map((item, index) => index)              }          },          mounted() {              this.pullRefresh(this.$refs.container, (done) => {                  setTimeout(() => {                      done()                  }, 1000)              })          },          methods: {              pullRefresh(el, callback) {                  let beginPagY = 0                  let currentPos                  const maxTranslateY = 150                  const iconEl = this.$refs.icon                  el.addEventListener('touchstart', e => {                      if (el.scrollTop !== 0) {                          return                      }                      beginPagY = e.touches[0].pageY                      e.preventDefault()                  })                  el.addEventListener('touchmove', e => {                      if (el.scrollTop !== 0) {                          return                      }                      const pageY = e.touches[0].pageY                      const distance = currentPos = pageY - beginPagY                      if (distance < 0 || distance > maxTranslateY) {                          // 上拉的时候不做任何处理                          return;                      }                      if (distance > 60) {                          iconEl.classList.add('active')                          console.log(iconEl.classList);                      } else {                          iconEl.classList.remove('active')                      }                      e.preventDefault()                      el.style.transform = `translateY(${distance}px)`                  })                  let clear = () => {                      this.isShowLoading = false                      el.style.transform = `translateY(0)`                      setTimeout(() => {                          el.style.transition = ``                      }, 200)                  }                  el.addEventListener('touchend', () => {                      el.style.transition = `.2s`                      if (currentPos >= 60) {                          this.isShowLoading = true                          el.style.transform = `translateY(30px)`                          callback && callback(() => {                              clear()                          })                          return                      }                      clear()                  })              }          }      })  </script>  </body>  </html>