前端頁面如何實現下拉刷新

  • 2019 年 10 月 12 日
  • 筆記

頁面的滾動區域本來是用的插件vue-scroller,但是由於在一些低端Android機中頁面略有卡動,可能是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>