做者:滴滴webapp架構組-付楠javascript
BetterScroll 是一款重點解決移動端各類滾動場景需求的開源插件(GitHub地址),適用於滾動列表、選擇器、輪播圖、索引列表、開屏引導等應用場景。html
爲了知足這些場景,它不只支持慣性滾動、邊界回彈、滾動條淡入淡出等效果的靈活配置,讓滾動更加流暢,同時還提供了不少 API 方法和事件,以便咱們更快地實現滾動場景下的需求,以下拉刷新、上拉加載。前端
因爲它基於原生 JavaScript 實現,不依賴任何框架,因此既能夠原生 JavaScript 引用,也能夠與目前前端 MVVM 框架結合使用,好比,其官網上的示例就是與 Vue 的結合。java
首先,讓咱們來看一下它是怎樣讓滾動更流暢的吧。git
在移動端,若是你使用過 overflow: scroll
生成一個滾動容器,會發現它的滾動是比較卡頓,呆滯的。爲何會出現這種狀況呢?github
由於咱們早已習慣了目前的主流操做系統和瀏覽器視窗的滾動體驗,好比滾動到邊緣會有回彈,手指中止滑動之後還會按慣性繼續滾動一會,手指快速滑動時頁面也會快速滾動。而這種原生滾動容器卻沒有,就會讓人感到卡頓。web
試一試 BetterScroll 的滾動體驗吧。體驗地址api
能夠發現,在增長慣性滾動,邊緣回彈等效果以後,明顯流暢、舒服了不少。那麼,這些效果是怎麼實現的呢?瀏覽器
BetterScroll 在用戶滑動操做結束時,還會繼續慣性滾動一段。首先看一下源碼中的 BScroll.prototype._end
函數,這是 touchend、mouseup、touchcancel、mousecancel 事件的處理函數,也就是用戶滾動操做結束時的邏輯。架構
BScroll.prototype._end = function (e) {
...
if (this.options.momentum && duration < this.options.momentumLimitTime && (absDistY > this.options.momentumLimitDistance || absDistX > this.options.momentumLimitDistance)) {
let momentumX = this.hasHorizontalScroll ? momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options)
: {destination: newX, duration: 0}
let momentumY = this.hasVerticalScroll ? momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options)
: {destination: newY, duration: 0}
newX = momentumX.destination
newY = momentumY.destination
time = Math.max(momentumX.duration, momentumY.duration)
this.isInTransition = 1
}
...
}複製代碼
以上代碼的做用是,在用戶滑動操做結束時,若是須要開啓了慣性滾動,用 momentum 函數計算慣性滾動距離和時間。該函數,根據用戶滑動操做的速度和 deceleration選項 ——慣性減速來計算滾動距離,至於滾動時間,也是一個可配置的選項。
function momentum(current, start, time, lowerMargin, wrapperSize, options) {
...
let distance = current - start
let speed = Math.abs(distance) / time
...
let duration = swipeTime
let destination = current + speed / deceleration * (distance < 0 ? -1 : 1)
...
}複製代碼
超過邊緣時的回彈,有兩個處理步驟,第一步是滾動到超過邊界時速度要變慢,第二步是回彈到邊界。其中,第一步是在源碼的 BScroll.prototype._move
函數,這是 touchmove 和 mousemove 事件的處理函數,也就是在用戶滑動操做過程當中的邏輯。
// Slow down or stop if outside of the boundaries
if (newY > 0 || newY < this.maxScrollY) {
if (this.options.bounce) {
newY = this.y + deltaY / 3
} else {
newY = newY > 0 ? 0 : this.maxScrollY
}
}複製代碼
第二步是調用BScroll.prototype.resetPosition
函數,回彈到邊界。
BScroll.prototype.resetPosition = function (time = 0, easeing = ease.bounce) {
...
let y = this.y
if (!this.hasVerticalScroll || y > 0) {
y = 0
} else if (y < this.maxScrollY) {
y = this.maxScrollY
}
...
this.scrollTo(x, y, time, easeing)
...
}複製代碼
流暢的滾動僅僅是基礎,BetterScoll 真正的能力在於:提供了大量通用 / 定製的 option 選項 、 API 方法和事件,讓各類滾動需求實現起來更高效。
下面,以結合 Vue 的使用爲例,說一下 BetterScroll 在各類場景下的姿式。
好比,有以下列表:
<div ref="wrapper" class="list-wrapper">
<ul class="list-content"> <li @click="clickItem($event,item)" class="list-item" v-for="item in data">{{item}}</li> </ul>
</div>複製代碼
咱們想要讓它垂直滾動,只須要對該容器進行簡單的初始化。
import BScroll from 'better-scroll'
const options = {
scrollY: true // 由於scrollY默認爲true,其實能夠省略
}
this.scroll = new BScroll(this.$refs.wrapper, options)複製代碼
對於 Vue 中使用 BetterScroll,有一個須要注意的點是,由於在 Vue 模板中列表渲染還沒完成時,是沒有生成列表 DOM 元素的,因此須要在確保列表渲染完成之後,才能建立 BScroll 實例,所以在 Vue 中,初始化 BScroll 的最佳時機是 mouted 的 nextTick。
// 在 Vue 中,保證列表渲染完成時,初始化 BScroll
mounted() {
setTimeout(() => {
this.scroll = new BScroll(this.$refs.wrapper, options)
}, 20)
},複製代碼
初始化以後,這個 wrapper 容器就可以優雅地滾動了,而且能夠經過 BScroll 實例this.scroll
使用其提供的 API 方法和事件。
下面介紹幾個經常使用的選項、方法和事件。
scrollbar
選項,用來配置滾動條,默認爲 false。當設置爲 true 或者是一個 Object,開啓滾動條。還能夠經過 fade 屬性,配置滾動條是隨着滾動操做淡入淡出,仍是一直顯示。
// fade 默認爲 true,滾動條淡入淡出
options.scrollbar = true
// 滾動條一直顯示
options.scrollbar = {
fade: false
}
this.scroll = new BScroll(this.$refs.wrapper, options)複製代碼
具體效果可見普通滾動列表-示例。
pullDownRefresh
選項,用來配置下拉刷新功能。當設置爲 true 或者是一個 Object 的時候,開啓下拉刷新,能夠配置頂部下拉的距離(threshold)來決定刷新時機,以及回彈停留的距離(stop)
options.pullDownRefresh = {
threshold: 50, // 當下拉到超過頂部 50px 時,觸發 pullingDown 事件
stop: 20 // 刷新數據的過程當中,回彈停留在距離頂部還有 20px 的位置
}
this.scroll = new BScroll(this.$refs.wrapper, options)複製代碼
監聽 pullingDown 事件,刷新數據。並在刷新數據完成以後,調用 finishPullDown() 方法,回彈到頂部邊界
this.scroll.on('pullingDown', () => {
// 刷新數據的過程當中,回彈停留在距離頂部還有20px的位置
RefreshData()
.then((newData) => {
this.data = newData
// 在刷新數據完成以後,調用 finishPullDown 方法,回彈到頂部
this.scroll.finishPullDown()
})
})複製代碼
具體效果可見普通滾動列表-示例。
pullUpLoad
選項,用來配置上拉加載功能。當設置爲 true 或者是一個 Object 的時候,能夠開啓上拉加載,能夠配置離底部距離閾值(threshold)來決定開始加載的時機
options.pullUpLoad = {
threshold: -20 // 在上拉到超過底部 20px 時,觸發 pullingUp 事件
}
this.scroll = new BScroll(this.$refs.wrapper, options)複製代碼
監聽 pullingUp 事件,加載新數據。
this.scroll.on('pullingUp', () => {
loadData()
.then((newData) => {
this.data.push(newData)
})
})複製代碼
具體效果可見普通滾動列表-示例。
wheel
選項,用於開啓並配置選擇器。可配置選擇器當前選擇的索引(selectedIndex),列表的彎曲弧度(rotate),以及切換選擇項的調整時間(adjustTime)。
options.wheel = {
selectedIndex: 0,
rotate: 25,
adjustTime: 400
}
// 初始化選擇器的每一列
this.wheels[i] = new BScroll(wheelWrapper.children[i], options)複製代碼
具體效果可見選擇器 - 示例。
其中聯動選擇器,須要監聽每一個選擇列表的選擇,來改變其餘選擇列表。
data() {
return {
tempIndex: [0, 0, 0]
}
},
...
// 監聽每一個選擇列表的選擇
this.wheels[i].on('scrollEnd', () => {
this.tempIndex.splice(i, 1, this.wheels[i].getSelectedIndex())
})
...
// 根據當前選擇項,肯定其餘選擇列表的內容
computed: {
linkageData() {
const provinces = provinceList
const cities = cityList[provinces[this.tempIndex[0]].value]
const areas = areaList[cities[this.tempIndex[1]].value]
return [provinces, cities, areas]
}
},複製代碼
具體效果可見選擇器 - 示例中的聯動選擇器。
snap
選項,用於開啓並配置輪播圖。可配置輪播圖是否循環播放(loop),每頁的寬度(stepX)和高度(stepY),切換閾值(threshold),以及切換速度(speed)。
options = {
scrollX: true,
snap: {
loop: true, // 開啓循環播放
stepX: 200, // 每頁寬度爲 200px
stepY: 100, // 每頁高度爲 100px
threshold: 0.3, // 滾動距離超過寬度/高度的 30% 時切換圖片
speed: 400 // 切換動畫時長 400ms
}
}
this.slide = BScroll(this.$refs.slide, options)複製代碼
具體效果可見輪播圖 - 示例。
除了普通滾動列表、選擇器、輪播圖等基礎滾動場景,還能夠利用 BetterScroll 提供的能力,作一些特殊場景。
索引列表,首先須要在滾動過程當中實時監聽滾動到哪一個索引的區域了,來更新當前索引。在這種場景下,咱們可使用probeType
選項,當此選項設置爲 3 時,會在整個滾動過程當中實時派發 scroll 事件。從而獲取滾動過程當中的位置。
options.probeType = 3
this.scroll = new BScroll(this.$refs.wrapper, options)
this.scroll.on('scroll', (pos) => {
const y = pos.y
for (let i = 0; i < listHeight.length - 1; i++) {
let height1 = listHeight[i]
let height2 = listHeight[i + 1]
if (-y >= height1 && -y < height2) {
this.currentIndex = i
}
}
})複製代碼
當點擊索引時,使用scrollToElement()方法滾動到該索引區域。
scrollTo(index) {
this.$refs.indexList.scrollToElement(this.$refs.listGroup[index], 0)
}複製代碼
具體效果可見索引列表 - 示例。
開屏引導,其實就是一種不自動循環播放的橫向滾動輪播圖而已。
options = {
scrollX: true,
snap: {
loop: false
}
}
this.slide = BScroll(this.$refs.slide, options)複製代碼
具體效果可見開屏引導 - 示例。由於此需求場景通常只有移動端纔有,因此最好在手機模式下看效果。
freeScroll
選項,用於開啓自由滾動,容許橫向和縱向同時滾動,而不限制在某個方向。
options.freeScroll = true複製代碼
另外須要注意的是,此選項在 eventPassthrough 設置了保持原生滾動時無效。
具體效果可見自由滾動-示例。
BetterScroll 能夠用於幾乎全部滾動場景,本文僅介紹了在一些典型場景下的使用姿式。
做爲一款旨在解決移動端滾動需求的插件,BetterScroll 開放的衆多選項、方法和事件,其實,就是提供了一種讓咱們更加快捷、靈活、精準時機地處理滾動的能力。
歡迎你們關注滴滴FE博客: github.com/DDFE/DDFE-b…