錢包—>滴滴出行”體驗效果。什么是 bette..."/>
您好,登錄后才能下訂單哦!
在我們?nèi)粘5囊苿佣隧椖块_發(fā)中處理滾動列表是再常見不過的需求了以滴滴為例可以是這樣豎向滾動的列表如圖所示
微信 —> 錢包—>滴滴出行”體驗效果。
什么是 better-scroll better-scroll 是一個移動端滾動的解決方案它是基于 iscroll 的重寫它和 iscroll 的主要區(qū)別在 這里 。better-scroll 也很強大不僅可以做普通的滾動列表還可以做輪播圖、picker 等等。
不能滾動是現(xiàn)象我們得搞清楚這其中的根本原因。在這之前我們先來看一下瀏覽器的滾動原理
那么對于 better-scroll 也是一樣的道理我們先來看一下 better-scroll 常見的 html 結(jié)構(gòu)
<div class="wrapper"> <ul class="content"> <li>...</li> <li>...</li> ... </ul> </div>
為了更加直觀我們再來看一張圖
固定的高度 。×××部分為 content它是父容器的第一個子元素它的高度會隨著內(nèi)容的大小而撐高。那么當(dāng) content 的高度不超過父容器的高度是不能滾動的而它一旦超過了父容器的高度我們就可以滾動內(nèi)容區(qū)了這就是 better-scroll 的滾動原理。
import BScroll from 'better-scroll' let wrapper = document.querySelector('.wrapper') let scroll = new BScroll(wrapper, {})
better-scroll 的文檔 。
scroll.refresh() 方法重新計算來確保滾動效果的正常。所以同學(xué)們反饋的 better-scroll 不能滾動的原因 多半是初始化 better-scroll 的時機不對或者是當(dāng) DOM 結(jié)構(gòu)發(fā)送變化的時候并沒有重新計算 better-scroll 。
Vue.js 都不陌生當(dāng) better-scroll 遇見 Vue會擦出怎樣的火花呢
<template> <div class="wrapper" ref="wrapper"> <ul class="content"> <li>...</li> <li>...</li> ... </ul> </div></template><script> import BScroll from 'better-scroll' export default { mounted() { this.$nextTick(() => { this.scroll = new Bscroll(this.$refs.wrapper, {}) }) } }</script>
js 提供了我們一個獲取 DOM 對象的接口—— vm.$refs 。在這里我們通過了 this.$refs.wrapper 訪問到了這個 DOM 對象并且我們在 mounted 這個鉤子函數(shù)里 this.$nextTick 的回調(diào)函數(shù)中初始化 better-scroll 。因為這個時候wrapper 的 DOM 已經(jīng)渲染了我們可以正確計算它以及它內(nèi)層 content 的高度以確保滾動正常。
this.$nextTick 是一個異步函數(shù)為了確保 DOM 已經(jīng)渲染感興趣的同學(xué)可以了解一下它的 內(nèi)部實現(xiàn)細節(jié) 底層用到了 MutationObserver 或者是 setTimeout(fn, 0) 。其實我們在這里把 this.$nextTick 替換成 setTimeout(fn, 20) 也是可以的20 ms 是一個經(jīng)驗值每一個 Tick 約為 17 ms對用戶體驗而言都是無感知的。
<template> <div class="wrapper" ref="wrapper"> <ul class="content"> <li v-for="item in data">`item`</li> </ul> </div></template><script> import BScroll from 'better-scroll' export default { data() { return { data: [] } }, created() { requestData().then((res) => { this.data = res.data this.$nextTick(() => { this.scroll = new Bscroll(this.$refs.wrapper, {}) }) }) } }</script>
axios 或者 vue-resource 。我們獲取到數(shù)據(jù)的后需要通過異步的方式再去初始化 better-scroll因為 Vue 是數(shù)據(jù)驅(qū)動的 Vue 數(shù)據(jù)發(fā)生變化 this.data = res.data 到頁面重新渲染是一個異步的過程我們的初始化時機是要在 DOM 重新渲染后所以這里用到了 this.$nextTick 當(dāng)然替換成 setTimeout(fn, 20) 也是可以的。
數(shù)據(jù)改變 —> DOM 重新渲染仍然是一個異步過程 所以即使在我們拿到數(shù)據(jù)后也要異步初始化 better-scroll。
<template> <div class="wrapper" ref="wrapper"> <ul class="content"> <li v-for="item in data">`item`</li> </ul> <div class="loading-wrapper"></div> </div></template><script> import BScroll from 'better-scroll' export default { data() { return { data: [] } }, created() { this.loadData() }, methods: { loadData() { requestData().then((res) => { this.data = res.data.concat(this.data) this.$nextTick(() => { if (!this.scroll) { this.scroll = new Bscroll(this.$refs.wrapper, {}) this.scroll.on('touchend', (pos) => { // 下拉動作 if (pos.y > 50) { this.loadData() } }) } else { this.scroll.refresh() } }) }) } } }</script>
這段代碼比之前稍微復(fù)雜一些, 當(dāng)我們在滑動列表松開手指時候 better-scroll 會對外派發(fā)一個 touchend 事件我們監(jiān)聽了這個事件并且判斷了 pos.y > 50我們把這個行為定義成一次下拉的動作。如果是下拉的話我們會重新請求數(shù)據(jù)并且把新的數(shù)據(jù)和之前的 data 做一次 concat也就更新了列表的數(shù)據(jù)那么數(shù)據(jù)的改變就會映射到 DOM 的變化。需要注意的一點這里我們對 this.scroll 做了判斷如果沒有初始化過我們會通過 new BScroll 初始化并且綁定一些事件否則我們會調(diào)用 this.scroll.refresh 方法重新計算來確保滾動效果的正常。
scroll 組件的抽象和封裝 因此我們有強烈的需求抽象出來一個 scroll 組件類似小程序的 scroll-view 組件方便開發(fā)者的使用。
<template> <div ref="wrapper"> <slot></slot> </div> </template>
<script type="text/ecmascript-6"> import BScroll from 'better-scroll' export default { props: { /** * 1 滾動的時候會派發(fā)scroll事件會截流。 * 2 滾動的時候?qū)崟r派發(fā)scroll事件不會截流。 * 3 除了實時派發(fā)scroll事件在swipe的情況下仍然能實時派發(fā)scroll事件 */ probeType: { type: Number, default: 1 }, /** * 點擊列表是否派發(fā)click事件 */ click: { type: Boolean, default:true }, /** * 是否開啟橫向滾動 */ scrollX: { type: Boolean, default: false }, /** * 是否派發(fā)滾動事件 */ listenScroll: { type:Boolean, default: false }, /** * 列表的數(shù)據(jù) */ data: { type: Array, default: null }, /** * 是否派發(fā)滾動到底部的事件用于上拉加載 */ pullup: { type: Boolean, default: false }, /** * 是否派發(fā)頂部下拉的事件用于下拉刷新 */ pulldown: { type:Boolean, default: false }, /** * 是否派發(fā)列表滾動開始的事件 */ beforeScroll: { type: Boolean, default: false }, /** * 當(dāng)數(shù)據(jù)更新后刷新scroll的延時。 */ refreshDelay: { type: Number, default: 20 } }, mounted() { // 保證在DOM渲染完畢后初始化better-scroll setTimeout(() => { this._initScroll() }, 20) }, methods: { _initScroll() { if (!this.$refs.wrapper) { return }// better-scroll的初始化 this.scroll = new BScroll(this.$refs.wrapper, { probeType: this.probeType, click: this.click, scrollX: this.scrollX }) // 是否派發(fā)滾動事件 if (this.listenScroll) { let me = this this.scroll.on('scroll', (pos) => { me.$emit('scroll', pos) }) } // 是否派發(fā)滾動到底部事件用于上拉加載 if (this.pullup) { this.scroll.on('scrollEnd', () => { // 滾動到底部 if (this.scroll.y <= (this.scroll.maxScrollY + 50)) { this.$emit('scrollToEnd') } }) } // 是否派發(fā)頂部下拉事件用于下拉刷新 if (this.pulldown) { this.scroll.on('touchend', (pos) => { // 下拉動作 if (pos.y > 50) { this.$emit('pulldown') } }) } // 是否派發(fā)列表滾動開始的事件 if (this.beforeScroll) { this.scroll.on('beforeScrollStart', () => {this.$emit('beforeScroll') }) } }, disable() { // 代理better-scroll的disable方法 this.scroll && this.scroll.disable() }, enable() { // 代理better-scroll的enable方法 this.scroll && this.scroll.enable() }, refresh() { // 代理better-scroll的refresh方法this.scroll && this.scroll.refresh() }, scrollTo() { // 代理better-scroll的scrollTo方法 this.scroll &&this.scroll.scrollTo.apply(this.scroll, arguments) }, scrollToElement() { // 代理better-scroll的scrollToElement方法this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments) } }, watch: { // 監(jiān)聽數(shù)據(jù)的變化延時refreshDelay時間后調(diào)用refresh方法重新計算保證滾動效果正常 data() { setTimeout(() => { this.refresh() }, this.refreshDelay) } } } </script>
有了這一層 scroll 組件的封裝我們來修改剛剛最復(fù)雜的代碼假設(shè)我們已經(jīng)全局注冊了 scroll 組件。
<template> <scroll class="wrapper" :data="data" :pulldown="pulldown" @pulldown="loadData"> <ul class="content"> <li v-for="item in data">`item`</li> </ul> <div class="loading-wrapper"></div> </scroll></template><script> import BScroll from 'better-scroll' export default { data() { return { data: [], pulldown: true } }, created() { this.loadData() }, methods: { loadData() { requestData().then((res) => { this.data = res.data.concat(this.data) }) } } }</script>
插件 Vue 化引發(fā)的一些思考 這篇文章我不僅僅是要教會大家封裝一個 scroll 組件還想傳遞一些把第三方插件原生 JS 實現(xiàn)Vue 化的思考過程。很多學(xué)習(xí) Vue.js 的同學(xué)可能還停留在 “XX 效果如何用 Vue.js 實現(xiàn)” 的程度其實把插件 Vue 化有兩點很關(guān)鍵一個是對插件本身的實現(xiàn)原理很了解另一個是對 Vue.js 的特性很了解。對插件本身的實現(xiàn)原理了解需要的是一個思考和鉆研的過程這個過程可能困難但是收獲也是巨大的而對 Vue.js 的特性的了解是需要大家對 Vue.js 多多使用學(xué)會從平時的項目中積累和總結(jié)也要善于查閱 Vue.js 的官方文檔關(guān)注一些 Vue.js 的升級等。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。