html裡列表滑動刪除的實現如此簡單

juejin周輝發表於2019-01-04

預覽

html裡列表滑動刪除的實現如此簡單

前言

Demo gist地址 ?

做web開發經常會遇到列表操作, 如果不涉及移動端, 那麼在列表上放幾個按鈕, 使用者點選就完事了, 如果是移動端, 受限於螢幕寬度, 操作按鈕太多會影響佈局, 所以在移動端列表的滑動操作比較常見.

做原生開發, 系統可能給列表提供了基本的刪除等功能, 那麼網頁應該如何實現呢?

本文以地址管理為demo, 用react實現, 其實不管是什麼框架, 涉及到的大部分都是 web 的介面.

demo用到了coroutine, 使用協程方便管理一系列事件 (event flow).

原理

2件事要處理: 滑動佈局

滑動

滑動事件需要被監聽, 應該在列表的每一個item上設定監聽, 每個item處理滑動事件.

如果是 mobile 監聽這三個事件:

  • touchstart 滑動開始, 記錄初始位置
  • touchmove 滑動過程中會有一系列的位置產生
  • touchend 滑動結束, 記錄結束位置

否則監聽這幾個:

  • mousedown
  • mousemove
  • mouseup
  • mouseleave (交給 mouseup 處理)

在生命週期開始時候監聽這幾個事件:

startupTouchEvent() {
  const current = ReactDOM.findDOMNode(this);
  current.addEventListener('touchstart', this.moveLoop);
  current.addEventListener('touchend', this.moveLoop);
  current.addEventListener('touchmove', this.moveLoop);
}
複製程式碼

其中 this.moveLoop 是:

this.moveLoop = coroutine(function*() {
      let e = {};
      while (e = yield) {
        if (e.type === 'touchstart') {
          // trace position
          const startX = e.touches[0].clientX;
          while (e = yield) {
            if (e.type === 'touchmove') {
              // trace position
              // console.log('touchmove', e);
              const movedX = e.changedTouches[0].clientX;
              const deltaX = movedX - startX;
              // console.log('moved', deltaX);
              if (deltaX <= 0) {
                that.moveMask(deltaX);
              }
            }
            if (e.type === 'touchend') {
              const endX = e.changedTouches[0].clientX;
              const deltaX = endX - startX;
              // console.log('end', deltaX);
              if (deltaX >= -40) {
                that.closeMaskIfNeeded();
              } else {
                that.openMask();
              }
              break;
            }
          }
        }
      }
    })
複製程式碼

這裡用到了 corutine.

首先當手指放到 item 上時, 記錄位置 startX = e.touches[0].clientX;.

當手指滑動時獲取此時的位置 e.changedTouches[0].clientX, 減去初始位置 deltaX = movedX - startX;, 如果 deltaX 小於0, 那麼此時是左滑, 進行 UI 上的操作, 將上層 div 左移 deltaX.

當手指離開螢幕時候, 記錄此時位置並獲取與初始位置的差值 deltaX = endX - startX, 判斷 deltaX, 如果滑動距離太小(40px)或者向右滑, 那麼就關掉展開的 div, 如果滑動距離夠長, 那麼就完全展開 div.

佈局

layout

      <div className="address-swipe-wrapper">
        <div className="swiper-operation-btns">
          <button style={{
            backgroundColor: '#7EA1D6'
          }} onClick={onEdit}>
            編輯
          </button>
          <button style={{
            backgroundColor: 'red'
          }} onClick={onDelete}>
            刪除
          </button>
        </div>
        <div className="address-item" onClick={onClick} style={{
          left,
          position: 'relative',
          transition: 'all 250ms',
        }}>
          {selected &&
            <img className="address-item-selected-icon" src={require('../img/check.png')} alt="選中" />
          }
          <div className="address-content">
            <div>{`${name}  ${mobile}`}</div>
            <div>{provinceName+cityName+districtName+detailedAddress}</div>
          </div>
        </div>
      </div>
複製程式碼

幾個操作按鈕是絕對佈局被蓋在 address-item 內容的下面, 當滑動或者展開時候 address-item 會左移 left 距離, 它是 relative 佈局.

為了讓滑動有動效, 可以新增 transition: 'all 250ms'.

其他幾個方法

  openMask() {
    this.setState({
      left: -160
    });
  }
  moveMask(deltaX) {
    this.setState({
      left: deltaX
    });
  }
  closeMaskIfNeeded() {
    this.setState({
      left: 0
    });
  }
複製程式碼

小結

會不會手勢滑動與點選衝突?

不會, 經過pc和手機(ios/android)嘗試, 滑動時候不會觸發 address-item 的選中, 並沒發現會衝突, 除非你寫 evt.preventDefault().

如何實現點選空白關掉?

window 新增監聽事件:

window.addEventListener('touchstart', this.closeMaskIfNeeded);
複製程式碼

在 PC 上表現良好, 但是在 mobile 上表現異常. 所以移到 TODO 裡待解決.

TODO

  1. 封裝到HOC
  2. 橫向滑動時候禁止縱向滑動
  3. 點選空白區域關掉展開的item
  4. 展開時新增操作按鈕的bounce動效 (TelegramX-iOS的右滑效果)

參考

相關文章