一、介紹
關於better-scroll的原文詳細介紹請參考,這里只做總結
黃老師的文章《當 better-scroll 遇見 Vue》的詳細介紹
better-scroll的api:點擊
better-scroll的滾動原理
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
</div>
綠色部分為 wrapper,也就是父容器,它會有固定的高度。黃色部分為 content,它是父容器的第一個子元素,它的高度會隨着內容的大小而撐高。那么,當 content 的高度不超過父容器的高度,是不能滾動的,而它一旦超過了父容器的高度,我們就可以滾動內容區了,這就是 better-scroll 的滾動原理。
better-scroll 的初始化時機很重要,因為它在初始化的時候,會計算父元素和子元素的高度和寬度,來決定是否可以縱向和橫向滾動。因此,我們在初始化它的時候,必須確保父元素和子元素的內容已經正確渲染了。如果子元素或者父元素 DOM 結構發生改變的時候,必須重新調用 scroll.refresh()
方法重新計算來確保滾動效果的正常。
二、better-scroll在vue中的使用
Vue.js 提供了我們一個獲取 DOM 對象的接口—— vm.$refs
。在這里,我們通過了 this.$refs.wrapper
訪問到了這個 DOM 對象,並且我們在 mounted 這個鈎子函數里,this.$nextTick
的回調函數中初始化 better-scroll 。因為這個時候,wrapper 的 DOM 已經渲染了,我們可以正確計算它以及它內層 content 的高度,以確保滾動正常。
這里的 this.$nextTick
是一個異步函數,為了確保 DOM 已經渲染,感興趣的同學可以了解一下它的內部實現細節,底層用到了 MutationObserver 或者是 setTimeout(fn, 0)
。其實我們在這里把 this.$nextTick
替換成 setTimeout(fn, 20)
也是可以的(20 ms 是一個經驗值,每一個 Tick 約為 17 ms),對用戶體驗而言都是無感知的。
這里從今日頭條截取獲取后台數據鏈接,用vue-jsonp來異步獲取數據。
安裝vue-jsonp
npm install vue-jsonp --save-dev
在組件單獨使用
import Vue from 'vue' import VueJsonp from 'vue-jsonp' Vue.use(VueJsonp)
詳細請參考 vue-jsonp的npm地址:點擊
<template> <div> <nav class="nav"> <ul> <li>推薦</li> </ul> </nav> <div class="wrapper" ref="wrapper"> <div class="content" > <section class="has_action" v-for="item in data"> <div class="item-detail"> <h3 class="dotdot">{{item.title}}</h3> <div class="item-info"> <div> <span class="stick_label space">{{item.label}}</span> <span class="src space">{{item.media_name}}</span> <span class="cmt space">評論{{item.comment_count}}</span> <span class="time space" title="2018-11-05 19:23">{{item.datetime}}</span> </div> </div> </div> </section> </div> </div> </div> </template>
<script> import Vue from 'vue' import VueJsonp from 'vue-jsonp' import BScroll from 'better-scroll' Vue.use(VueJsonp) export default { name: 'myscroll', data() { return { data: [] } }, created() { this._getData().then(json => { this.data = json.data this.$nextTick(() => { this.scroll = new BScroll(this.$refs.wrapper,{}) }) }) }, methods: { // 或者頁面數據,調用今日頭條數據接口 _getData() { return this.$jsonp('https://m.toutiao.com/list',{ tag: '__all__', ac: 'wap', count: 20, format: 'json_raw', as: 'A1B57B7E600F6A7', cp: '5BE0AFC5FDAEAE1', min_behot_time: 1541469897, _signature: 'A6d5hAAAWEyp4NHR.YRHRAOneZ', i: 1541469650 }) } } } </script>
注意:nav和wrapper這兩個class的css定位屬性

<style scoped lang="stylus">
.nav
z-index: 999
position: fixed
display: block
box-sizing: border-box
height: 74px
width: 100%
text-align: center
line-height: 74px
font-size: 20px
color: #f85959
background: #f4f5f6
.wrapper
position: fixed
top: 74px
left: 0
bottom: 0
right: 0
overflow: hidden
.content
list-style: none
.has_action
position: relative
margin: 0 30px
border-bottom: 1px solid rgba(221, 221, 221, 0.6)
.item-detail
padding: 32px 0px;
.dotdot
overflow: hidden
text-overflow: ellipsis
line-height: 42px
font-size: 34px
font-weight: normal
color: #222
.item-info
margin-top: 12px
overflow: hidden
font-size: 0
color: #999
.space
display: inline-block
margin-right: 10px
vertical-align: middle
line-height: 30px
font-size: 28px
.stick_label
border-radius: 4px
border: 1PX solid rgba(248,89,89,.5)
width: 60px
text-align: center
color: #f85959
</style>
結果如下:
三、scroll 組件的抽象和封裝
在components下面新建一個scroll文件夾->scroll.vue

1 <template> 2 <div ref="wrapper"> 3 <slot></slot> 4 </div> 5 </template> 6 <script type="text/ecmascript-6"> 7 import BScroll from 'better-scroll' 8 export default { 9 props: { 10 /** 11 * 1 滾動的時候會派發scroll事件,會截流。 12 * 2 滾動的時候實時派發scroll事件,不會截流。 13 * 3 除了實時派發scroll事件,在swipe的情況下仍然能實時派發scroll事件 14 */ 15 probeType: { 16 type: Number, 17 default: 1 18 }, 19 /** 20 * 點擊列表是否派發click事件 21 */ 22 click: { 23 type: Boolean, 24 default: true 25 }, 26 /** 27 * 是否開啟橫向滾動 28 */ 29 scrollX: { 30 type: Boolean, 31 default: false 32 }, 33 /** 34 * 是否派發滾動事件 35 */ 36 listenScroll: { 37 type: Boolean, 38 default: false 39 }, 40 /** 41 * 列表的數據 42 */ 43 data: { 44 type: Array, 45 default: null 46 }, 47 /** 48 * 是否派發滾動到底部的事件,用於上拉加載 49 */ 50 pullup: { 51 type: Boolean, 52 default: false 53 }, 54 /** 55 * 是否派發頂部下拉的事件,用於下拉刷新 56 */ 57 pulldown: { 58 type: Boolean, 59 default: false 60 }, 61 /** 62 * 是否派發列表滾動開始的事件 63 */ 64 beforeScroll: { 65 type: Boolean, 66 default: false 67 }, 68 /** 69 * 當數據更新后,刷新scroll的延時。 70 */ 71 refreshDelay: { 72 type: Number, 73 default: 20 74 } 75 }, 76 mounted() { 77 // 保證在DOM渲染完畢后初始化better-scroll 78 setTimeout(() => { 79 this._initScroll() 80 }, 20) 81 }, 82 methods: { 83 _initScroll() { 84 if (!this.$refs.wrapper) { 85 return 86 } 87 // better-scroll的初始化 88 this.scroll = new BScroll(this.$refs.wrapper, { 89 probeType: this.probeType, 90 click: this.click, 91 scrollX: this.scrollX 92 }) 93 94 // 是否派發滾動事件 95 if (this.listenScroll) { 96 let me = this 97 this.scroll.on('scroll', (pos) => { 98 me.$emit('scroll', pos) 99 }) 100 } 101 102 // 是否派發滾動到底部事件,用於上拉加載 103 if (this.pullup) { 104 this.scroll.on('scrollEnd', () => { 105 // 滾動到底部 106 if (this.scroll.y <= (this.scroll.maxScrollY + 50)) { 107 this.$emit('scrollToEnd') 108 } 109 }) 110 } 111 112 // 是否派發頂部下拉事件,用於下拉刷新 113 if (this.pulldown) { 114 this.scroll.on('touchend', (pos) => { 115 // 下拉動作 116 if (pos.y > 50) { 117 this.$emit('pulldown') 118 } 119 }) 120 } 121 122 // 是否派發列表滾動開始的事件 123 if (this.beforeScroll) { 124 this.scroll.on('beforeScrollStart', () => { 125 this.$emit('beforeScroll') 126 }) 127 } 128 }, 129 disable() { 130 // 代理better-scroll的disable方法 131 this.scroll && this.scroll.disable() 132 }, 133 enable() { 134 // 代理better-scroll的enable方法 135 this.scroll && this.scroll.enable() 136 }, 137 refresh() { 138 // 代理better-scroll的refresh方法 139 this.scroll && this.scroll.refresh() 140 }, 141 scrollTo() { 142 // 代理better-scroll的scrollTo方法 143 this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments) 144 }, 145 scrollToElement() { 146 // 代理better-scroll的scrollToElement方法 147 this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments) 148 } 149 }, 150 watch: { 151 // 監聽數據的變化,延時refreshDelay時間后調用refresh方法重新計算,保證滾動效果正常 152 data() { 153 setTimeout(() => { 154 this.refresh() 155 }, this.refreshDelay) 156 } 157 } 158 } 159 </script>
在my-scroll中使用scroll組件

<template> <div> <nav class="nav"> <ul> <li>推薦</li> </ul> </nav> <scroll class="wrapper" :data="data"> <div class="content" > <section class="has_action" v-for="item in data"> <div class="item-detail"> <h3 class="dotdot">{{item.title}}</h3> <div class="item-info"> <div> <span class="stick_label space">{{item.label}}</span> <span class="src space">{{item.media_name}}</span> <span class="cmt space">評論{{item.comment_count}}</span> <span class="time space" title="2018-11-05 19:23">{{item.datetime}}</span> </div> </div> </div> </section> </div> </scroll> </div> </template>

<script> import Vue from 'vue' import VueJsonp from 'vue-jsonp' import BScroll from 'better-scroll' import scroll from 'components/scroll/scroll' Vue.use(VueJsonp) export default { name: 'myscroll', components: { scroll }, data() { return { data: [] } }, created() { this._getData().then(json => { this.data = json.data this.data = this.data.concat(this.data) }) }, methods: { // 或者頁面數據,跳用今日頭條數據接口 _getData() { return this.$jsonp('https://m.toutiao.com/list',{ tag: '__all__', ac: 'wap', count: 20, format: 'json_raw', as: 'A1D5EB0E82E44A1', cp: '5BE224C46AC11E1', min_behot_time: 1541555213, _signature: '.P8dTQAApydWuLUYyYBGu.z.HV', i: 1541555213 }) } } } </script>