Vue + better-scroll 實現移動端字母索引導航

KSIG發表於2018-05-03

vue + better-scroll 實現移動端歌手列表字母索引導航。算是一個學習筆記吧,寫個筆記讓自己瞭解的更加深入一點。

Demo: list-view,使用 chrome 手機模式檢視。換成手機模式之後,不能滑動的話,重新整理一下就 OK 了。

Github:移動端字母索引導航(厚著臉皮求個 star,嘿嘿)

效果圖

Vue + better-scroll 實現移動端字母索引導航

配置環境

因為用到的是 vue-cli 和 better-scroll,所以首先要安裝 vue-cli,然後再 npm 安裝 better-scroll

簡單介紹一下 better-scroll:

better-scroll 是一款重點解決移動端(已支援 PC)各種滾動場景需求的外掛。它的核心是借鑑的 iscroll 的實現,它的 API 設計基本相容 iscroll,在 iscroll 的基礎上又擴充套件了一些 feature 以及做了一些效能優化。

better-scroll 是基於原生 JS 實現的,不依賴任何框架。它編譯後的程式碼大小是 63kb,壓縮後是 35kb,gzip 後僅有 9kb,是一款非常輕量的 JS lib。

除了這兩,還使用 scss、vue-lazyload。scss 前處理器,大家都懂,用別的也一樣。lazyload 實現懶載入,不用也可以,主要是優化一下體驗。

資料直接使用了網易雲的歌手榜單, 偷懶就直接放在 data 裡面了。

CSS 樣式我就不貼了,直接看原始碼就可以了。

實現基本樣式

直接使用 v-for 和 雙側巢狀實現歌手列表、以及右側索引欄。

HTML 結構:

<ul>
  <li v-for="group in singers" 
  class="list-group" 
  :key="group.id" 
  ref="listGroup">
    <h2 class="list-group-title">{{ group.title }}</h2>
    <ul>
      <li v-for="item in group.items" 
      class="list-group-item" :key="item.id">
        <img v-lazy="item.avatar" class="avatar">
        <span class="name">{{ item.name }}</span>
      </li>
    </ul>
  </li>
</ul>
<div class="list-shortcut">
  <ul>
    <li v-for="(item, index) in shortcutList"
    class="item"
    :data-index="index"
    :key="item.id"
    >
      {{ item }}
    </li>
  </ul>
</div>
複製程式碼

shortcutList 是通過計算屬性得到的,取 title 的第一個字元即可。

shortcutList () {
  return this.singers.map((group) => {
    return group.title.substr(0, 1)
  })
}
複製程式碼

使用 better-scroll

使用 better-scroll 實現滾動。對了,使用的時候別忘了用 import 引入。

created () {
  // 初始化 better-scroll 必須要等 dom 載入完畢
  setTimeout(() => {
    this._initSrcoll()
  }, 20)
},
methods: {
  _initSrcoll () {
    console.log('didi')
    this.scroll = new BScroll(this.$refs.listView, {
      // 獲取 scroll 事件,用來監聽。
      probeType: 3
    })
  }
}
複製程式碼

使用 created 方法進行 better-scroll 初始化,使用 setTimeout 是因為需要等到 DOM 載入完畢。不然 better-scroll 獲取不到 dom 就會初始化失敗。

這裡把方法寫在兩 methods 裡面,這樣就不會看起來很亂,直接呼叫就可以了。

初始化的時候傳入兩 probeType: 3,解釋一下:當 probeType 為 3 的時候,不僅在螢幕滑動的過程中,而且在 momentum 滾動動畫執行過程中實時派發 scroll 事件。如果沒有設定該值,其預設值為 0,即不派發 scroll 事件。

給索引新增點選事件和移動事件實現跳轉

首先需要給索引繫結一個 touchstart 事件(當在螢幕上按下手指時觸發),直接使用 v-on 就可以了。然後還需要給索引新增一個 data-index 這樣就可以獲取到索引的值,使用 :data-index="index"

<div class="list-shortcut">
  <ul>
    <li v-for="(item, index) in shortcutList"
    class="item"
    :data-index="index"
    :key="item.id"
    @touchstart="onShortcutStart"
    @touchmove.stop.prevent="onShortcutMove"
    >
      {{ item }}
    </li>
  </ul>
</div>
複製程式碼

繫結一個 onShortcutStart 方法。實現點選索引跳轉的功能。再繫結一個 onShortcutMove 方法,實現滑動跳轉。

created () {
  // 新增一個 touch 用於記錄移動的屬性
  this.touch = {}
  // 初始化 better-scroll 必須要等 dom 載入完畢
  setTimeout(() => {
    this._initSrcoll()
  }, 20)
},
methods: {
  _initSrcoll () {
    this.scroll = new BScroll(this.$refs.listView, {
      probeType: 3,
      click: true
    })
  },
  onShortcutStart (e) {
    // 獲取到繫結的 index
    let index = e.target.getAttribute('data-index')
    // 使用 better-scroll 的 scrollToElement 方法實現跳轉
    this.scroll.scrollToElement(this.$refs.listGroup[index])

    // 記錄一下點選時候的 Y座標 和 index
    let firstTouch = e.touches[0].pageY
    this.touch.y1 = firstTouch
    this.touch.anchorIndex = index
  },
  onShortcutMove (e) {
    // 再記錄一下移動時候的 Y座標,然後計算出移動了幾個索引
    let touchMove = e.touches[0].pageY
    this.touch.y2 = touchMove
    
    // 這裡的 16.7 是索引元素的高度
    let delta = Math.floor((this.touch.y2 - this.touch.y1) / 18)

    // 計算最後的位置
    // * 1 是因為 this.touch.anchorIndex 是字串,用 * 1 偷懶的轉化一下
    let index = this.touch.anchorIndex * 1 + delta
    this.scroll.scrollToElement(this.$refs.listGroup[index])
  }
}
複製程式碼

這樣就可以實現索引的功能了。

當然這樣是不會滿足我們的對不對,我們要加入炫酷的特效呀。比如索引高亮什麼的~~

移動內容索引高亮

emmm,這個時候就有點複雜啦。但是有耐心就可以看懂滴。

我們需要 better-scroll 的 on 方法,返回內容滾動時候的 Y軸偏移值。所以在初始化 better-scroll 的時候需要新增一下程式碼。對了,別忘了在 data 中新增一個 scrollY,和 currentIndex (用來記錄高亮索引的位置)因為我們需要監聽,所以在 data 中新增。

_initSrcoll () {
  this.scroll = new BScroll(this.$refs.listView, {
    probeType: 3,
    click: true
  })

  // 監聽Y軸偏移的值
  this.scroll.on('scroll', (pos) => {
    this.scrollY = pos.y
  })
}
複製程式碼

然後需要計算一下內容的高度,新增一個 calculateHeight() 方法,用來計算索引內容的高度。

_calculateHeight () {
  this.listHeight = []
  const list = this.$refs.listGroup
  let height = 0
  this.listHeight.push(height)
  for (let i = 0; i < list.length; i++) {
    let item = list[i]
    height += item.clientHeight
    this.listHeight.push(height)
  }
}

// [0, 760, 1380, 1720, 2340, 2680, 2880, 3220, 3420, 3620, 3960, 4090, 4920, 5190, 5320, 5590, 5790, 5990, 6470, 7090, 7500, 7910, 8110, 8870]
// 得到這樣的值
複製程式碼

然後在 watch 中監聽 scrollY,看程式碼:

watch: {
  scrollY (newVal) {
    // 向下滑動的時候 newVal 是一個負數,所以當 newVal > 0 時,currentIndex 直接為 0
    if (newVal > 0) {
      this.currentIndex = 0
      return
    }
    
    // 計算 currentIndex 的值
    for (let i = 0; i < this.listHeight.length - 1; i++) {
      let height1 = this.listHeight[i]
      let height2 = this.listHeight[i + 1]

      if (-newVal >= height1 && -newVal < height2) {
        this.currentIndex = i
        return
      }
    }
    
    // 當超 -newVal > 最後一個高度的時候
    // 因為 this.listHeight 有頭尾,所以需要 - 2
    this.currentIndex = this.listHeight.length - 2
  }
}
複製程式碼

得到 currentIndex 的之後,在 html 中使用。

給索引繫結 class -->  :class="{'current': currentIndex === index}"
複製程式碼

最後再處理一下滑動索引的時候改變 currentIndex。

因為程式碼可以重複利用,且需要處理邊界情況,所以就把

this.scroll.scrollToElement(this.$refs.listGroup[index])
複製程式碼

重新寫了個函式,來減少程式碼量。

// 在 scrollToElement 的時候,改變 scrollY,因為有 watch 所以就會計算出 currentIndex
scrollToElement (index) {
  // 處理邊界情況
  // 因為 index 通過滑動距離計算出來的
  // 所以向上滑超過索引框框的時候就會 < 0,向上就會超過最大值
  if (index < 0) {
    return
  } else if (index > this.listHeight.length - 2) {
    index = this.listHeight.length - 2
  }
  // listHeight 是正的, 所以加個 -
  this.scrollY = -this.listHeight[index]
  this.scroll.scrollToElement(this.$refs.listGroup[index])
}
複製程式碼

lazyload

lazyload 外掛也順便說一下哈,增加一下使用者體驗。

使用方法

  1. 先 npm 安裝
  2. 在 main.js 中 import,然後 Vue.use
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  loading: require('./common/image/default.jpg')
})
複製程式碼

新增一張 loading 圖片,使用 webpack 的 require 獲取圖片。

  1. 然後在需要使用的時候,把 :src="" 換成 v-lazy="" 就實現了圖片懶載入的功能。

總結

移動端字母索引導航就這麼實現啦,感覺還是很有難度的哈(對我來說)。

主要就是使用了 better-scroll 的 on 獲取移動偏移值(實現高亮)、scrollToElement 跳轉到相應的位置(實現跳轉)。以及使用 touch 事件監聽觸控,來獲取開始的位置,以及滑動距離(計算最後的位置)。

收穫

  • 對 touch 事件有了瞭解
  • 對 better-scroll 的使用熟練了一點
  • vue 也熟練也一點啦,emmm
  • 以後再寫這樣的東西就有經驗啦

Vue + better-scroll 實現移動端字母索引導航

本文最初發表於 Blog of Jin,歡迎在 GitHub 上關注我,一起入門和學習前端。

相關文章