本文介紹了vue mint-ui源碼解析之loadmore組件,分享給大家,具體如下:
接入
官方接入文檔mint-ui loadmore文檔
接入使用Example
html
<div id="app"> <mt-loadmore :top-method="loadTop" :bottom-method="loadBottom" :bottom-all-loaded="allLoaded" :max-distance="150" @top-status-change="handleTopChange" ref="loadmore"> <div slot="top" class="mint-loadmore-top"> <span v-show="topStatus === 'pull'" :class="{ 'rotate': topStatus === 'drop' }">↓</span> <span v-show="topStatus === 'loading'">Loading...</span> <span v-show="topStatus === 'drop'">釋放更新</span> </div> <ul class="scroll-wrapper"> <li v-for="item in list" @click="itemClick(item)">{{ item }}</li> </ul> </mt-loadmore></div>
css
<link rel="stylesheet" rel="external nofollow" >*{ margin: 0; padding: 0;}html, body{ height: 100%;}#app{ height: 100%; overflow: scroll;}.scroll-wrapper{ margin: 0; padding: 0; list-style: none;}.scroll-wrapper li{ line-height: 120px; font-size: 60px; text-align: center;}
js
<!-- 先引入 Vue --><script src="https://unpkg.com/vue/dist/vue.js"></script><!-- 引入組件庫 --><script src="https://unpkg.com/mint-ui/lib/index.js"></script><script> new Vue({ el: '#app', data: { list: [], allLoaded: false, topStatus: '' }, created: function () { var i =0, len=20; for (; i< len; i++){ this.list.push('demo' + i); } }, methods: { loadTop: function () { // 刷新數據的操作 var self = this; setTimeout(function () { self.list.splice(0, self.list.length); var i =0, len=20; for (; i< len; i++){ self.list.push('demo' + i); } self.$refs.loadmore.onTopLoaded(); }, 2000); }, loadBottom: function () { // 加載更多數據的操作 //load data //this.allLoaded = true;// 若數據已全部獲取完畢 var self = this; setTimeout(function () { var i =0; len = 10; for (; i< len; i++){ self.list.push('dddd' + i); } self.$refs.loadmore.onBottomLoaded(); }, 2000); }, handleTopChange: function (status) { this.topStatus = status; }, itemClick: function (data) { console.log('item click, msg : ' + data); } } });</script>
實現原理解析
布局原理
實現原理
源碼解析
組件的template html
<div class="mint-loadmore"> <div class="mint-loadmore-content" :class="{ 'is-dropped': topDropped || bottomDropped}" :style="{ 'transform': 'translate3d(0, ' + translate + 'px, 0)' }"> <slot name="top"> <div class="mint-loadmore-top" v-if="topMethod"> <spinner v-if="topStatus === 'loading'" class="mint-loadmore-spinner" :size="20" type="fading-circle"></spinner> <span class="mint-loadmore-text">{{ topText }}</span> </div> </slot> <slot></slot> <slot name="bottom"> <div class="mint-loadmore-bottom" v-if="bottomMethod"> <spinner v-if="bottomStatus === 'loading'" class="mint-loadmore-spinner" :size="20" type="fading-circle"></spinner> <span class="mint-loadmore-text">{{ bottomText }}</span> </div> </slot> </div> </div>
關于 上面的spinner標簽,是一個組件,這里不做詳細介紹。top solt和bottom slot中的內容是展示的內容,可以通過外部自定義的方式傳入。
其實它的實現有一個很嚴重的弊端,就是寫死了top solt和bottom slot的高度為50px,而且js中的處理也是使用50px進行的邏輯處理。所以并滿足我們開發中自定義top slot 和bottom slot的需求。
js核心解析
data() { return { translate: 0, // 此變量決定當前組件上下移動, scrollEventTarget: null, // 滾動的dom節點 containerFilled: false, // 當前滾動的內容是否填充完整,不完成會調用 loadmore的回調函數 topText: '', // 下拉刷新,顯示的文本 topDropped: false, // 記錄當前drop狀態,用給組件dom添加is-dropped class(添加回到原點的動畫) bottomText: '', // 上拉加載更多 顯示的文本 bottomDropped: false, // 同topDropped bottomReached: false, // 當前滾動是否滾動到了底部 direction: '', // touch-move過程中, 當前滑動的方向 startY: 0, // touch-start 起始的y的坐標值 startScrollTop: 0, // touch-start 起始的滾動dom的 scrollTop currentY: 0, // touch-move 過程中的 y的坐標 topStatus: '', // 下拉刷新的狀態: pull(下拉) drop(釋放) loading(正在加載數據) bottomStatus: '' // 上拉加載更多的狀態: 狀態同上 };}
上面的關于每個data數據的具體作用通過注釋做了詳細說明。
watch解析
watch: { topStatus(val) { this.$emit('top-status-change', val); switch (val) { case 'pull': this.topText = this.topPullText; break; case 'drop': this.topText = this.topDropText; break; case 'loading': this.topText = this.topLoadingText; break; } }, bottomStatus(val) { this.$emit('bottom-status-change', val); switch (val) { case 'pull': this.bottomText = this.bottomPullText; break; case 'drop': this.bottomText = this.bottomDropText; break; case 'loading': this.bottomText = this.bottomLoadingText; break; } }}
上面是組件通過watch監聽的兩個變量,后面我們能看到他們的改變是在touchmove事件中進行處理改變的。它的作用是通過它的變化來改變top slot和bottom slot的文本內容;
同時發出status-change事件給外部使用,因為可能外部自定義top slot 和bottom slot的內容,通過此事件來通知外部當前狀態以便外部進行處理。
核心函數的解析
這里就不將所有的method列出,下面就根據處理的所以來解析對應的method函數。
首先,入口是在組件mounted生命周期的鉤子回調中執行init函數
mounted() { this.init();// 當前 vue component掛載完成之后, 執行init()函數}
init函數:
init() { this.topStatus = 'pull'; this.bottomStatus = 'pull'; this.topText = this.topPullText; this.scrollEventTarget = this.getScrollEventTarget(this.$el); // 獲取滾動的dom節點 if (typeof this.bottomMethod === 'function') { this.fillContainer(); // 判斷當前滾動內容是否填滿,沒有執行外部傳入的loadmore回調函數加載數據 this.bindTouchEvents(); // 為當前組件dom注冊touch事件 } if (typeof this.topMethod === 'function') { this.bindTouchEvents(); } }, fillContainer() { if (this.autoFill) { this.$nextTick(() => { if (this.scrollEventTarget === window) { this.containerFilled = this.$el.getBoundingClientRect().bottom >= document.documentElement.getBoundingClientRect().bottom; } else { this.containerFilled = this.$el.getBoundingClientRect().bottom >= this.scrollEventTarget.getBoundingClientRect().bottom; } if (!this.containerFilled) { // 如果沒有填滿內容, 執行loadmore的操作 this.bottomStatus = 'loading'; this.bottomMethod();// 調用外部的loadmore函數,加載更多數據 } }); } }
init函數主要是初始化狀態和事件的一些操作,下面著重分析touch事件的回調函數的處理。
首先touchstart事件回調處理函數
handleTouchStart(event) { this.startY = event.touches[0].clientY; // 手指按下的位置, 用于下面move事件計算手指移動的距離 this.startScrollTop = this.getScrollTop(this.scrollEventTarget); // 起始scroll dom的 scrollTop(滾動的距離) //下面重置狀態變量 this.bottomReached = false; if (this.topStatus !== 'loading') { this.topStatus = 'pull'; this.topDropped = false; } if (this.bottomStatus !== 'loading') { this.bottomStatus = 'pull'; this.bottomDropped = false; } }
主要是記錄初始位置和重置狀態變量。
下面繼續touchmove的回調處理函數
handleTouchMove(event) { //確保當前touch節點的y的位置,在當前loadmore組件的內部 if (this.startY < this.$el.getBoundingClientRect().top && this.startY > this.$el.getBoundingClientRect().bottom) { return; } this.currentY = event.touches[0].clientY; let distance = (this.currentY - this.startY) / this.distanceIndex; this.direction = distance > 0 ? 'down' : 'up'; // 下拉刷新,條件(1.外部傳入了刷新的回調函數 2.滑動方向是向下的 3.當前滾動節點的scrollTop為0 4.當前topStatus不是loading) if (typeof this.topMethod === 'function' && this.direction === 'down' && this.getScrollTop(this.scrollEventTarget) === 0 && this.topStatus !== 'loading') { event.preventDefault(); event.stopPropagation(); //計算translate(將要平移的距離), 如果當前移動的距離大于設置的最大距離,那么此次這次移動就不起作用了 if (this.maxDistance > 0) { this.translate = distance <= this.maxDistance ? distance - this.startScrollTop : this.translate; } else { this.translate = distance - this.startScrollTop; } if (this.translate < 0) { this.translate = 0; } this.topStatus = this.translate >= this.topDistance ? 'drop' : 'pull';// drop: 到達指定的閾值,可以執行刷新操作了 } // 上拉操作, 判斷當前scroll dom是否滾動到了底部 if (this.direction === 'up') { this.bottomReached = this.bottomReached || this.checkBottomReached(); } if (typeof this.bottomMethod === 'function' && this.direction === 'up' && this.bottomReached && this.bottomStatus !== 'loading' && !this.bottomAllLoaded) { event.preventDefault(); event.stopPropagation(); // 判斷的邏輯思路同上 if (this.maxDistance > 0) { this.translate = Math.abs(distance) <= this.maxDistance ? this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance : this.translate; } else { this.translate = this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance; } if (this.translate > 0) { this.translate = 0; } this.bottomStatus = -this.translate >= this.bottomDistance ? 'drop' : 'pull'; } this.$emit('translate-change', this.translate); }
上面的代碼邏輯挺簡單,注釋也就相對不多。
重點談一下checkBottomReached()函數,用來判斷當前scroll dom是否滾動到了底部。
checkBottomReached() { if (this.scrollEventTarget === window) { return document.body.scrollTop + document.documentElement.clientHeight >= document.body.scrollHeight; } else { return this.$el.getBoundingClientRect().bottom <= this.scrollEventTarget.getBoundingClientRect().bottom + 1; } }
經過我的測試,上面的代碼是有問題:
當scrollEventTarget是window的條件下,上面的判斷是不對的。因為document.body.scrollTop總是比正常值小1,所以用于無法滿足到達底部的條件;
當scrollEventTarget不是window的條件下,上面的判斷條件也不需要在this.scrollEventTarget.getBoundingClientRect().bottom后面加1,但是加1也不會有太大視覺上的影響。
最后來看下moveend事件回調的處理函數
handleTouchEnd() { if (this.direction === 'down' && this.getScrollTop(this.scrollEventTarget) === 0 && this.translate > 0) { this.topDropped = true; // 為當前組件添加 is-dropped class(也就是添加動畫處理) if (this.topStatus === 'drop') { // 到達了loading的狀態 this.translate = '50'; // top slot的高度 this.topStatus = 'loading'; this.topMethod(); // 執行回調函數 } else { // 沒有到達,回調原點 this.translate = '0'; this.topStatus = 'pull'; } } // 處理邏輯同上 if (this.direction === 'up' && this.bottomReached && this.translate < 0) { this.bottomDropped = true; this.bottomReached = false; if (this.bottomStatus === 'drop') { this.translate = '-50'; this.bottomStatus = 'loading'; this.bottomMethod(); } else { this.translate = '0'; this.bottomStatus = 'pull'; } } this.$emit('translate-change', this.translate); this.direction = ''; }}
總結
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。
新聞熱點
疑難解答