React專案實戰(三)嘗試實現一個拉動重新整理元件

Ephemera發表於2019-05-03

總結下實現元件過程中遇到的問題;還有需要改進的地方

上一篇React專案實踐(二)一個登入頁面的狀態遷移

需求

分析:我們需要實現兩個方向(向下拉動,向上滑動)上的拉動重新整理,考慮完成 PullDownRefresh 和 PullUpRefresh 兩個元件的編寫。思考其中細節:
① 自適應滾動的內容是基於頁面還是基於內部容器;
② 元件是否可以組合使用;
③ 狀態提示loading如何設計;
④ loading開始與結束的時機,定時器規定時間?資料載入完結束?

目前實現
① 暫時未考慮頁面和內部容器同時滑動的情況;
② 通過 props.children 傳入需要拉動重新整理的內容,兩個元件可組合使用;
③ 通過一個 refreshing 狀態變數來決定當前是否處於重新整理狀態;
④ 通過在 componentWillReceiveProps 生命週期鉤子中接收新狀態/資料來控制 loading 的結束,即 refreshing=false

依舊是模仿掘金主頁的實現

React專案實戰(三)嘗試實現一個拉動重新整理元件

預備知識

弄清楚元素的幾個屬性值(翻一下MDN吧

  • clientHeight:這個屬性是隻讀屬性,對於沒有定義CSS或者內聯佈局盒子的元素為0,否則,它是元素內部的高度(單位畫素),包含內邊距,但不包括水平滾動條、邊框和外邊距

clientHeight

  • offsetHeight 是一個DOM屬性。它有時被稱為一個元素的物理/圖形的尺寸,或是一個元素的邊界框(border-box)的高度。

    offsetheight

  • scrollTop 屬性可以獲取或設定一個元素的內容垂直滾動的畫素數。部到視口可見內容(的頂部)的距離的度量。當一個元素的內容沒有產生垂直方向的滾動條,那麼它的 scrollTop 值為0

scrollTop

  • scrollHeight:這個只讀屬性是一個元素內容高度的度量,包括由於溢位導致的檢視中不可見內容。(屬性將會對值四捨五入取整)。包括元素的padding,但不包括元素的border和margin。scrollHeight也包括 ::before::after這樣的偽元素。
    scrollHeight
    判斷是否滾動到底部:

    element.scrollHeight - element.scrollTop === element.clientHeight

  • getBoundingClientRect()方法返回元素的大小及其相對於視口的位置。

React專案實戰(三)嘗試實現一個拉動重新整理元件

  • Window​.scrollY:返回文件在垂直方向已滾動的畫素值。跨瀏覽器相容:
    var supportPageOffset = window.pageXOffset !== undefined;
    var isCSS1Compat = ((document.compatMode || "") === "CSS1Compat");
    var y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;
    複製程式碼
  • Touch​.pageY:觸點相對於HTML文件上邊沿的的Y座標. 和 clientY 屬性不同, 這個值是相對於整個html文件的座標, 和使用者滾動位置無關. 因此當存在垂直滾動的偏移時, 這個值包含了垂直滾動的偏移

下拉重新整理元件

首先要明確的是下拉重新整理元件的觸發條件:① 內容垂直滾動的距離應該為0;② 觸屏拉動的距離應該大於某個給定的閾值;③ 明確究竟是哪個容器設定了內容自動滾動。下面以我寫的具體例子作為參考(沒有考慮複雜情況)

  • 在container頁面使用 PullDownRefresh 套了需要下拉重新整理的內容,這裡 scroll_content 設定了 overflow-y:auto
<div className="main scroll_content">
  <PullDownRefresh
    onRefresh={this._onRefreshDown}
    refreshing={this.state.refreshing}>
      {entryList.map((element, index) => {
        return <EntryItem item={element} key={index} />
      })}
  </PullDownRefresh>
複製程式碼
  • 在PullDownRefresh元件中
<>
  {this.state.refreshing ? <RefreshLoading orient="up" /> : null}
  <div
    ref={el => (this.scrollContent = el)}
    onTouchStart={this.handleTouchStart}
    onTouchMove={this.handleTouchMove}
    onTouchEnd={this.handleTouchEnd}
  >
    {this.props.children}
  </div>
</>
複製程式碼
  • 在 handleTouchStart 中我們記錄初始觸屏點位置
handleTouchStart = e => {
  this.setState({
    startPos: e.touches[0].pageY
  })
}
複製程式碼
  • 在 handleTouchMove 中,如果兩個觸屏點距離大於minHeight,且當前不處於正在重新整理狀態,且內容滾動高度為0,那麼可以開始載入(refreshing: true
handleTouchMove = e => {
  if (
    this.state.refreshing === false &&
    this.state.parentNode.scrollTop === 0
  ) {
    let _pullHeight = e.touches[0].pageY - this.state.startPos
    if (_pullHeight > this.state.minHeight) {
      this.setState({
        refreshing: true
      })
    }
  }
}
複製程式碼
  • 在 handleTouchEnd 中,我們進行資料更新請求。因為我們觀察重新整理操作是釋放之後才開始進行的,而且在move中進行的話可能多次觸發
handleTouchEnd = e => {
  if (this.state.refreshing) {
    this.props.onRefresh()
  }
}
複製程式碼
  • 如果我們直接在頁面進行請求,可以在promise的finally之後,設定更新結束(refreshing: false);如果我們是使用外部傳入的資料,需要在 componentWillReceiveProps 中設定更新結束(refreshing: false
componentWillReceiveProps(nextProps) {
  if (nextProps.refreshing !== this.state.refreshing) {
    this.setState({
      refreshing: nextProps.refreshing
    })
  }
}
複製程式碼
  • 獲取滾動容器的方法,可以在componentDidMount生命週期中獲取
var scrollContent = ReactDOM.findDOMNode(this.scrollContent).parentNode
複製程式碼
  • 基於頁面滾動
var isCSS1Compat = ((document.compatMode || "") === "CSS1Compat");
var scrollContent = isCSS1Compat ? document.documentElement : document.body;
複製程式碼

上滑載入元件

原理類似,不過多贅述,判斷到達底部的條件是:

parentNode.scrollHeight - parentNode.scrollTop - parentNode.clientHeight === 0
複製程式碼

瀏覽器相容

我們既想要滾動的功能,又不希望內部容器顯示滾動條,考慮為內部容器新增類 scroll_content 實現

::-webkit-scrollbar {
    /*隱藏滾輪*/
    display: none;
}
.scroll_content {
    -ms-scroll-chaining: chained;
    -ms-overflow-style: none;
    -ms-content-zooming: zoom;
    -ms-scroll-rails: none;
    -ms-content-zoom-limit-min: 100%;
    -ms-content-zoom-limit-max: 500%;
    -ms-scroll-snap-type: proximity;
    -ms-scroll-snap-points-x: snapList(100%, 200%, 300%, 400%, 500%);
    -ms-overflow-style: none;
    overflow: auto;
    -ms-overflow-style: none;
    overflow: -moz-scrollbars-none;
}
複製程式碼

--
總結:
① 一些功能仍待完善,有必要兩個元件可組合使用嗎?內部容器滾動時禁止外部頁面滾動;
② componentWillReceiveProps 是經常使用的一個生命週期鉤子,包括在引數路由改變時更新資料;

專案地址

相關文章