vue + better-scroll 實現移動端歌手列表字母索引導航。算是一個學習筆記吧,寫個筆記讓本身瞭解的更加深刻一點。css
Demo: list-view,使用 chrome 手機模式查看。換成手機模式以後,不能滑動的話,刷新一下就 OK 了。html
Github:移動端字母索引導航(厚着臉皮求個 star,嘿嘿)前端
由於用到的是 vue-cli 和 better-scroll,因此首先要安裝 vue-cli,而後再 npm 安裝 better-scroll。vue
簡單介紹一下 better-scroll:webpack
better-scroll 是一款重點解決移動端(已支持 PC)各類滾動場景需求的插件。它的核心是借鑑的 iscroll 的實現,它的 API 設計基本兼容 iscroll,在 iscroll 的基礎上又擴展了一些 feature 以及作了一些性能優化。git
better-scroll 是基於原生 JS 實現的,不依賴任何框架。它編譯後的代碼大小是 63kb,壓縮後是 35kb,gzip 後僅有 9kb,是一款很是輕量的 JS lib。github
除了這兩,還使用 scss、vue-lazyload。scss 預處理器,你們都懂,用別的也同樣。lazyload 實現懶加載,不用也能夠,主要是優化一下體驗。web
數據直接使用了網易雲的歌手榜單, 偷懶就直接放在 data 裏面了。chrome
CSS 樣式我就不貼了,直接看源碼就能夠了。vue-cli
直接使用 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 實現滾動。對了,使用的時候別忘了用 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 插件也順便說一下哈,增長一下用戶體驗。
使用方法
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload, {
loading: require('./common/image/default.jpg')
})
複製代碼
添加一張 loading 圖片,使用 webpack 的 require 獲取圖片。
:src=""
換成 v-lazy=""
就實現了圖片懶加載的功能。移動端字母索引導航就這麼實現啦,感受仍是頗有難度的哈(對我來講)。
主要就是使用了 better-scroll 的 on 獲取移動偏移值(實現高亮)、scrollToElement 跳轉到相應的位置(實現跳轉)。以及使用 touch 事件監聽觸摸,來獲取開始的位置,以及滑動距離(計算最後的位置)。
本文最初發表於 Blog of Jin,歡迎在 GitHub 上關注我,一塊兒入門和學習前端。