輕松實(shí)現(xiàn)H5頁面下拉刷新:滑動(dòng)觸發(fā)、高度提示與數(shù)據(jù)刷新全攻略

前段時(shí)間在做小程序到H5的遷移,其中小程序中下拉刷新的功能引起了產(chǎn)品的注意。他說到,哎,我們遷移后的H5頁面怎么沒有下拉刷新,于是乎,我就急忙將這部分的內(nèi)容給填上。

本來是計(jì)劃使用成熟的組件庫來實(shí)現(xiàn),嘗試之后發(fā)現(xiàn)這些組件和我們H5頁面的其他邏輯有沖突(H5還有吸頂、錨點(diǎn)、滑動(dòng)高亮、橫向滾動(dòng)),小小H5頁面上承載了太多的功能,兼容起來非常麻煩,想著下拉刷新功能也不復(fù)雜,干脆我自己寫一個(gè)好了。

流程圖示

正常數(shù)據(jù)展示狀態(tài) --> 手指觸摸屏幕下拉 --> 手指松開 --> 數(shù)據(jù)獲取 --> 恢復(fù)正常數(shù)據(jù)展示狀態(tài)

1_下拉刷新流程.png

功能梳理

要實(shí)現(xiàn)這個(gè)功能,主要分為兩部分。

監(jiān)聽手指觸摸事件

通過監(jiān)聽事件,我們可以得知以下的數(shù)據(jù)

  • 手指滑動(dòng)的時(shí)機(jī)(手指開始觸摸,結(jié)束觸摸時(shí)間)
  • 滑動(dòng)方向(是橫向滑動(dòng)還是縱向滑動(dòng))
  • 操作軌跡(手指操作從下往上還是從上往下滑動(dòng))
  • 是否首屏(如果非首屏進(jìn)行滑動(dòng)時(shí)是正?;瑒?dòng)操作)
    只有在向下滑動(dòng)首屏、非加載狀態(tài)、縱向滾動(dòng)并且有高度時(shí),才能進(jìn)行上述刷新流程。

css 和 提示文案

  • 手指按住屏幕由上往下滑動(dòng)未松開時(shí),展示滑動(dòng)的高度和提示【釋放刷新】文案
  • 手指松開后高度回彈,顯示【數(shù)據(jù)更新中】文案
  • 數(shù)據(jù)請求接口成功后,顯示【更新成功】文案,loading 內(nèi)容和圖標(biāo)緩緩消失

具體實(shí)現(xiàn)

觸摸的步驟可以分為: 手指按下(開始觸摸)、手指移動(dòng)不離開屏幕(觸摸中)、手指離開屏幕(觸摸結(jié)束),正好對(duì)應(yīng)著三個(gè) js 原生事件,touchstart、touchmovetouchend。

觸摸事件執(zhí)行時(shí)機(jī)

touchstart 和 touchmove 在一次觸摸流程只會(huì)執(zhí)行一次,標(biāo)志著開始和結(jié)束,但是 touchmove 不一樣,只要你的手指還在屏幕上滑動(dòng)沒有松開,就會(huì)一直執(zhí)行。如下圖的輸出的執(zhí)行次數(shù)一樣。

2.png

下拉元素綁定

首先需要給需要設(shè)置下拉刷新的區(qū)域綁定上這些事件,對(duì)于我們業(yè)務(wù)場景來說,頭部區(qū)域無論你如何操作,都需要保留展示的,那么我們只需要將事件綁定到下方開始顯示下拉刷新的區(qū)域。

// html元素
<div className="refreshWrap">
  {/* 下拉時(shí)文字提示 */}
  <div className={`pullDownContent`} style={{ height: pullDownHeight }}>
    {loading ? "" : "釋放刷新"}
  </div>

  {/* 加載時(shí)動(dòng)畫 */}
  <div className={`loadingFlex ${loading ? "" : "loadingHidden"}`}>
    <div className="flexCenter">
      <div className="loadingRing" />
      <div className="loadingText">
        {loading ? "數(shù)據(jù)更新中..." : "更新成功"}
      </div>
    </div>
  </div>
  <div className="middleArea">刷新區(qū)域下方內(nèi)容區(qū)域</div>
</div>


// js 綁定
const pullDownClassName = ".refreshWrap";
 bindPullDown() {
  const pulldownElement = document.querySelector(pullDownClassName);
  pulldownElement.addEventListener("touchstart", this.bindTouchstart);
  pulldownElement.addEventListener("touchmove", this.bindTouchMove);
  pulldownElement.addEventListener("touchend", this.bindTouched);
}

觸摸開始

手指觸摸到屏幕的邏輯非常簡單,使用 startTouch 對(duì)象來記錄觸摸的位置,包含 x 、y 軸。

bindTouchstart = (event) => {
    this.startTouch = event.touches[0];
  };

觸摸中

用戶觸摸中需要給他一個(gè)反饋,隨著下拉的距離,屏幕上圈出的下拉區(qū)域會(huì)隨之變大(下拉展示的區(qū)域會(huì)設(shè)置一個(gè)最大高度,如果能無限擴(kuò)大展示不好看)

5.png

endTouch 來保存觸摸中的坐標(biāo)值,因?yàn)橛|摸中的事件會(huì)執(zhí)行多次,所以 endTouch 也會(huì)不斷的更新,用來更新下拉時(shí)滑動(dòng)的高度。

 bindTouchMove = (event) => {
    const { loading } = this.state;
    this.endTouch = event.touches[0];
    if (!loading && this.isInOneScreenPull() && this.isVerticalSliding()) {
      const pullDownHeight = this.getPullDownHeight();
      this.setState({
        pullDownHeight,
      });
    }
  };

根據(jù) endTouch 的值可以判斷出滑動(dòng)距離、橫向還是縱向滑動(dòng),滑動(dòng)的高度、再獲取滑動(dòng)元素是否在首屏。

// 判斷滑動(dòng)的距離
calcDeltaY = () => Math.abs(this.endTouch.pageY - this.startTouch.pageY);

// 判斷是否縱向滾動(dòng)
isVerticalSliding = () => {
  const deltaY = this.calcDeltaY();
  const deltaX = Math.abs(this.endTouch.pageX - this.startTouch.pageX);
  if (deltaY > deltaX && deltaY > 50) return true;
};

// 下拉展示高度最多展示為100,不能讓加載區(qū)域無限制的擴(kuò)大
getPullDownHeight = () => {
  const deltaY = this.calcDeltaY();
  return Math.min(deltaY, 100);
};

// 是否在首屏
isInOneScreenPull() {
  const pulldownElement = document.querySelector(pullDownClassName);
  return pulldownElement.scrollTop <= 0;
}

觸摸結(jié)束

觸摸結(jié)束時(shí),將 pulldownHeight 設(shè)置為0,異步加載數(shù)據(jù),加載數(shù)據(jù)時(shí)設(shè)置變量 loading 表示開始更新、結(jié)束更新,防止不停的下拉刷新調(diào)用接口。

bindTouched = (e) => {
  const { loading, pullDownHeight } = this.state;

  // 首屏、非加載狀態(tài)、縱向滾動(dòng)有高度時(shí)
  if (!loading && pullDownHeight) {
    this.setState({
      pullDownHeight: 0,
    });

    this.getData();

    // 重置觸摸Y軸坐標(biāo)點(diǎn)
    this.startTouch = {};
    this.endTouch = {};
  }
};

平滑過渡動(dòng)畫

當(dāng)下拉高度發(fā)生變化時(shí),直接修改高度效果會(huì)比較生硬,使用 css transition 屬性進(jìn)行平滑過渡、animation 設(shè)置動(dòng)畫緩慢進(jìn)入/消失。

.pullDownContent {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  font-size: 12px;
  color: rgba(0, 0, 0, 0.25);
  margin: auto;
  transition: height 0.3s ease-out; /* 平滑過渡效果 */
  overflow: hidden;
}

.loadingHidden {
  animation: shrinkHeight 1s forwards;
}

@keyframes shrinkHeight {
  100% {
    height: 0;
    opacity: 0;
    overflow: hidden;
  }
}

完整代碼

以上便是滑動(dòng)觸發(fā)、高度提示、數(shù)據(jù)刷新的下拉刷新功能解析,完整代碼我放在了 github 上,戳 drop-down-refresh 可查看,歡迎大家點(diǎn)個(gè) star~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容