輕鬆實現H5頁面下拉重新整理:滑動觸發、高度提示與資料重新整理全攻略

一颗冰淇淋發表於2024-06-16

前段時間在做小程式到H5的遷移,其中小程式中下拉重新整理的功能引起了產品的注意。他說到,哎,我們遷移後的H5頁面怎麼沒有下拉重新整理,於是乎,我就急忙將這部分的內容給填上。

本來是計劃使用成熟的元件庫來實現,嘗試之後發現這些元件和我們H5頁面的其他邏輯有衝突(H5還有吸頂、錨點、滑動高亮、橫向滾動),小小H5頁面上承載了太多的功能,相容起來非常麻煩,想著下拉重新整理功能也不復雜,乾脆我自己寫一個好了。

流程圖示

正常資料展示狀態 --> 手指觸控螢幕下拉 --> 手指鬆開 --> 資料獲取 --> 恢復正常資料展示狀態

功能梳理

要實現這個功能,主要分為兩部分。

監聽手指觸控事件

透過監聽事件,我們可以得知以下的資料

  • 手指滑動的時機(手指開始觸控,結束觸控時間)
  • 滑動方向(是橫向滑動還是縱向滑動)
  • 操作軌跡(手指操作從下往上還是從上往下滑動)
  • 是否首屏(如果非首屏進行滑動時是正常滑動操作)
    只有在向下滑動首屏非載入狀態縱向滾動並且有高度時,才能進行上述重新整理流程。

css 和 提示文案

  • 手指按住螢幕由上往下滑動未鬆開時,展示滑動的高度和提示【釋放重新整理】文案
  • 手指鬆開後高度回彈,顯示【資料更新中】文案
  • 資料請求介面成功後,顯示【更新成功】文案,loading 內容和圖示緩緩消失

具體實現

觸控的步驟可以分為: 手指按下(開始觸控)、手指移動不離開螢幕(觸控中)、手指離開螢幕(觸控結束),正好對應著三個 js 原生事件,touchstarttouchmovetouchend

觸控事件執行時機

touchstart 和 touchmove 在一次觸控流程只會執行一次,標誌著開始和結束,但是 touchmove 不一樣,只要你的手指還在螢幕上滑動沒有鬆開,就會一直執行。如下圖的輸出的執行次數一樣。

下拉元素繫結

首先需要給需要設定下拉重新整理的區域繫結上這些事件,對於我們業務場景來說,頭部區域無論你如何操作,都需要保留展示的,那麼我們只需要將事件繫結到下方開始顯示下拉重新整理的區域。

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

  {/* 載入時動畫 */}
  <div className={`loadingFlex ${loading ? "" : "loadingHidden"}`}>
    <div className="flexCenter">
      <div className="loadingRing" />
      <div className="loadingText">
        {loading ? "資料更新中..." : "更新成功"}
      </div>
    </div>
  </div>
  <div className="middleArea">重新整理區域下方內容區域</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 物件來記錄觸控的位置,包含 x 、y 軸。

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

觸控中

使用者觸控中需要給他一個反饋,隨著下拉的距離,螢幕上圈出的下拉區域會隨之變大(下拉展示的區域會設定一個最大高度,如果能無限擴大展示不好看)

endTouch 來儲存觸控中的座標值,因為觸控中的事件會執行多次,所以 endTouch 也會不斷的更新,用來更新下拉時滑動的高度。

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

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

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

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

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

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

觸控結束

觸控結束時,將 pulldownHeight 設定為0,非同步載入資料,載入資料時設定變數 loading 表示開始更新、結束更新,防止不停的下拉重新整理呼叫介面。

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

  // 首屏、非載入狀態、縱向滾動有高度時
  if (!loading && pullDownHeight) {
    this.setState({
      pullDownHeight: 0,
    });

    this.getData();

    // 重置觸控Y軸座標點
    this.startTouch = {};
    this.endTouch = {};
  }
};

平滑過渡動畫

當下拉高度發生變化時,直接修改高度效果會比較生硬,使用 css transition 屬性進行平滑過渡、animation 設定動畫緩慢進入/消失。

.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;
  }
}

完整程式碼

以上便是滑動觸發、高度提示、資料重新整理的下拉重新整理功能解析,完整程式碼我放在了 github 上,戳 drop-down-refresh 可檢視,歡迎大家點個 star~

相關文章