移動端tab滑動和上下拉刷新加載

移動端tab滑動和上下拉刷新加載

查看demo(請在移動端模式下查看)

<a href="https://github.com/xiaosu95/tab-swiper" target="_blank">查看代碼</a>

開發該插件的初衷是,在作一個項目時發現如今實現移動端tab滑動的插件大多基於swiper,swiper的功能太強大而我只要一個小小的tab滑動功能,就要引入200+k的js這未免太過浪費。並且swiper是沒有下拉刷新功能的,要用swiper實現下拉刷新還得改造一番。在實現功能的同時產生了很多bug。要是在引入一個下拉刷新的插件又不免多了幾十kb的js。並且這些插件對dom結構又是有必定要求的,一不當心就有bug。修復bug的時間均可以在擼一個插件出來了。javascript

此次開發的這個插件只依賴手勢庫touch.js。使用原生實現功能。大小隻有6kb。兼容性也算不錯。html

其實對touch.js的依賴並不嚴重,只是用了其兩個手勢事件,花點時間徹底能夠本身實現的。java

<font color="red">插件我只是粗略的測試了一番,如有什麼bug請你們提出。有寫的不清楚的也請提出。以爲不錯的能夠給我一個星星</font>ios


  • 該插件基於百度手勢庫touch.js該改手勢庫的大小也只有13k不到。官方文檔連接是找不到了因此引用別人寫的吧:<a href="http://blog.csdn.net/wangjiaohome/article/details/49364177" target="_blank">API文檔</a>
總結一下此次開發插件的原理和所遇到的坑吧
實現的話主要分爲:
  1. 肯定好容器結構
  2. 捕獲滑動的事件(使用touch.js獲取滑動的方向、滑動的距離和滑動的速度)
  3. 實現滑動的效果(這裏使用的是transform來實現滑動, transtion來實現動畫)
  4. 肯定臨界點(根據滑動不一樣的距離判斷是否切換頁數,還有根據滑動的速度肯定是否切換頁數)
  5. 暴露監聽事件(產生不一樣狀態的回調)
坑:

主要體如今微信和ios瀏覽器對下拉時會有彈簧效果:git

tab滑動圖1

這個是瀏覽器的默認效果,是能夠經過「e.preventDefault()」取消默認效果的。不過這就會產生容器不能滾動了。 因此就不能直接e.preventDefault()取消默認效果了。只能在特定的條件下才能取消默認事件。那條件是什麼呢? 第一個條件就是滑動方向是向下&&是在容器頂部時候 第二個條件就是滑動方向向下&&在容器底部 在這裏touch.js能夠輕易的獲取滑動的方向,滾動條所在的位置也很容易算出。我已開始也覺得很簡單的,結果卻發現touch.js獲取滾動方向是有必定延時的,這就形成第一時間捕獲的位置是上一次的,因此出現偶爾能夠偶爾不可,有時乾脆滾動不了。因此使用touch.js獲取方向的方式是不可取的。 只能本身採集觸摸屏幕時的座標,在對比滑動時的座標取得方向。ok這個bug就這樣輕鬆解決了。這都是在微信上運行的結構,後來拉到uc的時候居然發現uc連左右滑動都有默認效果(喪盡天良)。 這就只能用老辦法解決了,增長兩組條件,左右滑動。根據採集的初始點,對比滑動過程的座標,判斷上下滾動仍是左右滑動。在取消默認效果。es6

API:

dom結構:github

<div id="box">						<!-- 主容器 -->
    <div class="pullDownHtml">		<!-- 下拉刷新的顯示內容 -->
      <div class="pullDownshow1">下拉刷新</div>
      <div class="pullDownshow2">正在刷新</div>
    </div>
    <div class="pullUpHtml">		<!-- 上拉加載的顯示內容 -->
      <div class="pullUpHtmlshow1">上拉加載</div>
      <div class="pullUpHtmlshow2">正在加載</div>
    </div>
    <div class="box">
      <div class="tab-container">
        <div class="s-pull">
			// 頁面一內容
        </div>
      </div>
      <div class="tab-container">
        <div class="s-pull">
			// 頁面二內容
        </div>
      </div>
      <div class="tab-container">
        <div class="s-pull">
			// 頁面三內容
        </div>
      </div>
    </div>
  </div>

一、初始化瀏覽器

var swiper = new TabSwiper(ele, options)
// ele:容器
// options: 參數(Object)

二、options參數微信

{
	speed: 300,						// 動畫速度
	threshold: 100,					// 上下拉觸發的閥值(px)
	xThreshold: 0.3,			  // 左右滑動觸發的閥值(0~1)默認爲:‘0.25’
  closeInertia: false,    // 是否關閉慣性滑動, 默認開啓
	isPullDown: true,				// 是否開啓下拉刷新
	isPullUp: true,					// 是否開啓上拉加載
	defaultPage: 0,					// 默認顯示的頁數
	initCb: function(){},			// 初始化回調
	onEnd: function(page){},				// 切換頁數時回調(返回當前頁數)
	onRefreshStart: function(page){},		// 觸發下拉刷新時回調(返回當前頁數)
	onLoadStart: function(page){},			// 觸發上拉加載時回調(返回當前頁數)
	onTouchmove: function(page, e){}			// 正在頁面上滑動回調(返回當前頁數和滑動信息。可經過滑動的信息獲得當前滑動的方向速度滑動的距離,進行功能擴展)
}

三、pullEnd(cb)方法:dom

swiper.pullEnd(function (page) {			// 返回當前頁數
	console.log(page)
})

四、changePage(page)方法:

swiper.changePage(page)						// 切換頁面page目標頁面從0開始

五、nowIndex屬性:

var nowIndex = swiper.nowIndex    // 獲取當前所在頁數(只讀)
下面是代碼(基於es6)

若要查看es5的版本請移步(<a href="https://github.com/xiaosu95/tab-swiper" target="_blank">查看代碼</a>)

;(function (window, document) {
  // 更改transform
  function changeTransform (ele, left, top) {
    ele.style.transform = `translate(${left}px, ${top}px)`
    ele.style.WebkitTransform = `translate(${left}px, ${top}px)`
  }
  class TabSwiper {
    get nowIndex () {
      return this._nowIndex
    }
    set nowIndex (val) {
      if (val === this._nowIndex) return
      this._nowIndex = val
      this.options.onEnd && this.options.onEnd(val)
    }
    constructor (ele, options) {
      this._nowIndex = 0
      this.ele = ele
      this.width = ele.clientWidth             // 容器寬度
      this.height = ele.clientHeight           // 容器高度
      this.totalWidth = 0                      // 總寬度
      this.box = ele.querySelector('.box')
      this.containers = ele.querySelectorAll('.tab-container')         // 容器
      this.direction = ''
      this.scrollTop = 0
      this.options = options                    // 配置參數
      this.prohibitPull = false                 // 禁止上下拉動操做標記
      this.startY = 0                           // 起始y座標
      this.startX = 0                           // 起始x座標
      this.isBottom = false                     // 是否在底部
      this.disX = 0                             // 滑動X差值
      this.disY = 0                             // 滑動Y差值
      this.pullDownHtml = ele.querySelector('.pullDownHtml')
      this.pullUpHtml = ele.querySelector('.pullUpHtml')
      this.pullDownHtmlHeight = 0               // 下拉的html高度
      this.pullUpHtmlHeight = 0                 // 上拉的html高度
      this.left = 0                             // 向左偏移量
      // 初始化
      this.init()
    }

    // 初始化
    init () {
      this.options.xThreshold = this.options.xThreshold || 0.25
      // 設置樣式
      this.ele.style.overflow = 'hidden'
      this.ele.style.position = 'relative'

      this.box.style.height = '100%'
      this.box.style.width = this.containers.length * 100 + 'vw'
      this.box.style.float = 'left'
      this.box.style.transition = 'all ' + this.options.speed / 1000 + 's'
      this.box.style.position = 'relative'
      this.box.style.zIndex = 2

      this.totalWidth = this.width * this.containers.length;;

      [].forEach.call(this.containers, (ele) => {
        ele.style.float = 'left'
        ele.style.width = '100vw'
        ele.style.height = '100%'
        ele.style.overflow = 'auto'
        ele.style.WebkitOverflowScrolling = 'touch'
        ele.addEventListener('touchstart', (e) => {
          this.startY = e.touches[0].clientY   // 設置起始y座標
          this.startX = e.touches[0].clientX   // 設置起始y座標
        }, false)

        ele.addEventListener('touchmove', (e) => {
          this.scrollTop = this.containers[this.nowIndex].scrollTop
          this.isBottom = this.containers[this.nowIndex].querySelector('.s-pull').clientHeight <= this.scrollTop + this.height
          // 判斷滑動方向是否爲上下
          const disY = e.touches[0].clientY - this.startY
          const disX = e.touches[0].clientX - this.startX
          // 設置事件(當爲頂部或底部是取消默認事件)
          if ((disY > 0 && ele.scrollTop == 0) || (disY < 0 && this.isBottom)) {
            e.preventDefault()
          }
          // 若爲左右滑動時取消默認事件
          if (Math.abs(disY) < Math.abs(disX)) e.preventDefault()
        }, false)
      })

      // 上下拉
      if (this.options.isPullDown) {
        this.pullDownHtml.style.position = 'absolute'
        this.pullDownHtml.style.width = '100%'
        this.pullDownHtmlHeight = this.pullDownHtml.clientHeight
      }
      if (this.options.isPullUp) {
        this.pullUpHtml.style.position = 'absolute'
        this.pullUpHtml.style.width = '100%'
        this.pullUpHtml.style.bottom = '0'
        this.pullUpHtmlHeight = this.pullUpHtml.clientHeight
      }


      // 添加事件
      // 拖拽
      touch.on(this.box, 'drag', (e) => {
        this.direction = e.direction
        this.touchmove(e)
        this.options.onTouchmove && this.options.onTouchmove(this.nowIndex, e) // 事件輸出
      })
      // 滑動
      !this.options.closeInertia && touch.on(this.box, 'swipe', (e) => {
        this.swipe(e)
      })
      // 手指離開屏幕
      touch.on(this.box, 'touchend', (e) => {
        this.touchend(e)
      })

      // 移動至默認頁面
      this.changePage(this.options.defaultPage || 0)
      this.options.initCb && this.options.initCb()
    }

    // 拖拽方法
    touchmove (e) {
      this.box.style.transition = 'none'              // 取消動畫
      if ((e.direction === 'left' || e.direction === 'right') && !this.disY) {
        // 左右滑動
        this.disX = e.distanceX
        changeTransform(this.box, (this.left + this.disX), this.disY)
      } else if (!this.disX && !this.prohibitPull) {
        // 上下滑動
        if (e.direction === 'down' && !this.options.isPullDown) return
        if (e.direction === 'up' && !this.options.isPullUp) return
        if ((this.scrollTop <= 0 && this.direction === 'down') || (this.isBottom && this.direction === 'up')) {
          // 上下拉動容器
          this.disY = e.distanceY
          changeTransform(this.box, (this.left + this.disX), this.disY)
        }
      }
    }
    // 手指離開屏幕
    touchend (e) {
      this.box.style.transition = 'all ' + this.options.speed / 1000 + 's'              // 開啓動畫
      if (!this.prohibitPull) {
        if (Math.abs(this.disY) < this.options.threshold) {                   // 上下拉小於閥值自動復原
          this.disY = 0
          changeTransform(this.box, (this.left + this.disX), this.disY)
        }

        // 下拉刷新觸發
        if (this.scrollTop <= 0 && this.direction === 'down' && this.disY >= this.options.threshold) {
          this.disY = this.pullDownHtmlHeight
          this.prohibitPull = true
          // 顯示加載中
          this.pullDownHtml.style.visibility = 'visible'
          this.options.onRefreshStart && this.options.onRefreshStart(this.nowIndex) // 輸出下拉刷新事件
        }
        // 上拉加載觸發
        else if (this.isBottom && this.direction === 'up' && Math.abs(this.disY) > this.options.threshold) {
          this.disY = -this.pullUpHtmlHeight
          this.prohibitPull = true
          // 顯示加載中
          this.pullUpHtml.style.visibility = 'visible'
          this.options.onLoadStart && this.options.onLoadStart(this.nowIndex)       // 輸出上拉事件
        }
      }
      // 左右滑動
      if (Math.abs(this.disX) < this.width * this.options.xThreshold) {
        changeTransform(this.box, this.left, this.disY)
        this.disX = 0
      } else {
        this.left += this.disX / Math.abs(this.disX) * this.width
        if (this.left > 0) this.left = 0
        if (this.left <= -this.totalWidth) this.left = -(this.totalWidth - this.width)
        changeTransform(this.box, this.left, this.disY)
      }
      this.direction = ''                                       // 重置方向
      this.nowIndex = Math.abs(this.left) / this.width          // 計算頁數
    }
    // 快速滑動
    swipe (e) {
      if (e.factor < 1 && !this.disX && !this.disY) {
        if (e.direction === 'left') {
          this.left -= this.width
        } else if (e.direction === 'right') {
          this.left += this.width
        }
        if (this.left > 0) this.left = 0
        if (this.left <= -this.totalWidth) this.left = -(this.totalWidth - this.width)
        changeTransform(this.box, this.left, this.disY)
      }
      this.disX = 0
      this.nowIndex = Math.abs(this.left) / this.width          // 計算頁數
    }

    // 關閉上下拉
    pullEnd (cb) {
      cb && cb(this.nowIndex)
      changeTransform(this.box, this.left, 0)
      this.disY = 0
      this.prohibitPull = false
    }

    // 切換頁數
    changePage (page) {
      if (this.prohibitPull) return
      this.left = -page * this.width
      changeTransform(this.box, this.left, this.disY)
      this.nowIndex = page
    }
  }
  window.TabSwiper = TabSwiper
})(window, document)
相關文章
相關標籤/搜索