詳解vue mint-ui源碼解析之loadmore組件
本文介紹了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>
<!-- 引入組件庫(kù) -->
<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 () { // 刷新數(shù)據(jù)的操作
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 () { // 加載更多數(shù)據(jù)的操作
//load data
//this.allLoaded = true;// 若數(shù)據(jù)已全部獲取完畢
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>
實(shí)現(xiàn)原理解析
布局原理
- loadmore組件內(nèi)部由三個(gè)slot組成,分別為name=top,name=bottom,default;
- top用于展示下拉刷新不同狀態(tài)展示的內(nèi)容,初始設(shè)置margin-top為-top的高度來(lái)將自己隱藏
- bottom同top,用于展示上拉加載更多不同狀態(tài)展示的內(nèi)容
- default填充滾動(dòng)詳細(xì)內(nèi)容
實(shí)現(xiàn)原理
- 主要是通過(guò)js的touch事件的監(jiān)聽(tīng)來(lái)實(shí)現(xiàn)
- 在touchmove事件,如果是向下滑動(dòng)并且滾動(dòng)的dom的scrollTop為0,那么整個(gè)組件向下偏移(滑動(dòng)的距離/比值)展示出top solt的內(nèi)容
- 在touchmove時(shí)間,如果是向上滑動(dòng)并且滑動(dòng)到了底部,再繼續(xù)滑動(dòng)整個(gè)組件向上偏移(滑動(dòng)的距離/比值)展示出bottom solt的內(nèi)容
源碼解析
組件的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>
關(guān)于 上面的spinner標(biāo)簽,是一個(gè)組件,這里不做詳細(xì)介紹。top solt和bottom slot中的內(nèi)容是展示的內(nèi)容,可以通過(guò)外部自定義的方式傳入。
其實(shí)它的實(shí)現(xiàn)有一個(gè)很嚴(yán)重的弊端,就是寫死了top solt和bottom slot的高度為50px,而且js中的處理也是使用50px進(jìn)行的邏輯處理。所以并滿足我們開(kāi)發(fā)中自定義top slot 和bottom slot的需求。
js核心解析
- props解析:關(guān)于props的解析,可以參考mint-ui的官方文檔
- data解析
data() {
return {
translate: 0, // 此變量決定當(dāng)前組件上下移動(dòng),
scrollEventTarget: null, // 滾動(dòng)的dom節(jié)點(diǎn)
containerFilled: false, // 當(dāng)前滾動(dòng)的內(nèi)容是否填充完整,不完成會(huì)調(diào)用 loadmore的回調(diào)函數(shù)
topText: '', // 下拉刷新,顯示的文本
topDropped: false, // 記錄當(dāng)前drop狀態(tài),用給組件dom添加is-dropped class(添加回到原點(diǎn)的動(dòng)畫)
bottomText: '', // 上拉加載更多 顯示的文本
bottomDropped: false, // 同topDropped
bottomReached: false, // 當(dāng)前滾動(dòng)是否滾動(dòng)到了底部
direction: '', // touch-move過(guò)程中, 當(dāng)前滑動(dòng)的方向
startY: 0, // touch-start 起始的y的坐標(biāo)值
startScrollTop: 0, // touch-start 起始的滾動(dòng)dom的 scrollTop
currentY: 0, // touch-move 過(guò)程中的 y的坐標(biāo)
topStatus: '', // 下拉刷新的狀態(tài): pull(下拉) drop(釋放) loading(正在加載數(shù)據(jù))
bottomStatus: '' // 上拉加載更多的狀態(tài): 狀態(tài)同上
};
}
上面的關(guān)于每個(gè)data數(shù)據(jù)的具體作用通過(guò)注釋做了詳細(xì)說(shuō)明。
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;
}
}
}
上面是組件通過(guò)watch監(jiān)聽(tīng)的兩個(gè)變量,后面我們能看到他們的改變是在touchmove事件中進(jìn)行處理改變的。它的作用是通過(guò)它的變化來(lái)改變top slot和bottom slot的文本內(nèi)容;
同時(shí)發(fā)出status-change事件給外部使用,因?yàn)榭赡芡獠孔远xtop slot 和bottom slot的內(nèi)容,通過(guò)此事件來(lái)通知外部當(dāng)前狀態(tài)以便外部進(jìn)行處理。
核心函數(shù)的解析
這里就不將所有的method列出,下面就根據(jù)處理的所以來(lái)解析對(duì)應(yīng)的method函數(shù)。
首先,入口是在組件mounted生命周期的鉤子回調(diào)中執(zhí)行init函數(shù)
mounted() {
this.init();// 當(dāng)前 vue component掛載完成之后, 執(zhí)行init()函數(shù)
}
init函數(shù):
init() {
this.topStatus = 'pull';
this.bottomStatus = 'pull';
this.topText = this.topPullText;
this.scrollEventTarget = this.getScrollEventTarget(this.$el); // 獲取滾動(dòng)的dom節(jié)點(diǎn)
if (typeof this.bottomMethod === 'function') {
this.fillContainer(); // 判斷當(dāng)前滾動(dòng)內(nèi)容是否填滿,沒(méi)有執(zhí)行外部傳入的loadmore回調(diào)函數(shù)加載數(shù)據(jù)
this.bindTouchEvents(); // 為當(dāng)前組件dom注冊(cè)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) { // 如果沒(méi)有填滿內(nèi)容, 執(zhí)行l(wèi)oadmore的操作
this.bottomStatus = 'loading';
this.bottomMethod();// 調(diào)用外部的loadmore函數(shù),加載更多數(shù)據(jù)
}
});
}
}
init函數(shù)主要是初始化狀態(tài)和事件的一些操作,下面著重分析touch事件的回調(diào)函數(shù)的處理。
首先touchstart事件回調(diào)處理函數(shù)
handleTouchStart(event) {
this.startY = event.touches[0].clientY; // 手指按下的位置, 用于下面move事件計(jì)算手指移動(dòng)的距離
this.startScrollTop = this.getScrollTop(this.scrollEventTarget); // 起始scroll dom的 scrollTop(滾動(dòng)的距離)
//下面重置狀態(tài)變量
this.bottomReached = false;
if (this.topStatus !== 'loading') {
this.topStatus = 'pull';
this.topDropped = false;
}
if (this.bottomStatus !== 'loading') {
this.bottomStatus = 'pull';
this.bottomDropped = false;
}
}
主要是記錄初始位置和重置狀態(tài)變量。
下面繼續(xù)touchmove的回調(diào)處理函數(shù)
handleTouchMove(event) {
//確保當(dāng)前touch節(jié)點(diǎn)的y的位置,在當(dāng)前l(fā)oadmore組件的內(nèi)部
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.外部傳入了刷新的回調(diào)函數(shù) 2.滑動(dòng)方向是向下的 3.當(dāng)前滾動(dòng)節(jié)點(diǎn)的scrollTop為0 4.當(dāng)前topStatus不是loading)
if (typeof this.topMethod === 'function' && this.direction === 'down' &&
this.getScrollTop(this.scrollEventTarget) === 0 && this.topStatus !== 'loading') {
event.preventDefault();
event.stopPropagation();
//計(jì)算translate(將要平移的距離), 如果當(dāng)前移動(dòng)的距離大于設(shè)置的最大距離,那么此次這次移動(dòng)就不起作用了
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: 到達(dá)指定的閾值,可以執(zhí)行刷新操作了
}
// 上拉操作, 判斷當(dāng)前scroll dom是否滾動(dòng)到了底部
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);
}
上面的代碼邏輯挺簡(jiǎn)單,注釋也就相對(duì)不多。
重點(diǎn)談一下checkBottomReached()函數(shù),用來(lái)判斷當(dāng)前scroll dom是否滾動(dòng)到了底部。
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;
}
}
經(jīng)過(guò)我的測(cè)試,上面的代碼是有問(wèn)題:
當(dāng)scrollEventTarget是window的條件下,上面的判斷是不對(duì)的。因?yàn)閐ocument.body.scrollTop總是比正常值小1,所以用于無(wú)法滿足到達(dá)底部的條件;
當(dāng)scrollEventTarget不是window的條件下,上面的判斷條件也不需要在this.scrollEventTarget.getBoundingClientRect().bottom后面加1,但是加1也不會(huì)有太大視覺(jué)上的影響。
最后來(lái)看下moveend事件回調(diào)的處理函數(shù)
handleTouchEnd() {
if (this.direction === 'down' && this.getScrollTop(this.scrollEventTarget) === 0 && this.translate > 0) {
this.topDropped = true; // 為當(dāng)前組件添加 is-dropped class(也就是添加動(dòng)畫處理)
if (this.topStatus === 'drop') { // 到達(dá)了loading的狀態(tài)
this.translate = '50'; // top slot的高度
this.topStatus = 'loading';
this.topMethod(); // 執(zhí)行回調(diào)函數(shù)
} else { // 沒(méi)有到達(dá),回調(diào)原點(diǎn)
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 = '';
}
}
總結(jié)
- 下拉刷新和上拉加載更多的實(shí)現(xiàn)原理可以借鑒
- getScrollEventTarget()獲取滾動(dòng)對(duì)象,getScrollTop()獲取滾動(dòng)距離,checkBottomReached()判斷是否滾動(dòng)到底部;這三種方式可以借鑒
- 缺點(diǎn): 寫死了top slot 和 bottom slot的高度,太不靈活;這個(gè)地方可以優(yōu)化
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue2.x中h函數(shù)(createElement)與vue3中的h函數(shù)詳解
h函數(shù)本質(zhì)就是createElement(),h函數(shù)其實(shí)是createVNode的語(yǔ)法糖,返回的就是一個(gè)Js普通對(duì)象,下面這篇文章主要給大家介紹了關(guān)于vue2.x中h函數(shù)(createElement)與vue3中h函數(shù)的相關(guān)資料,需要的朋友可以參考下2022-12-12
Vue項(xiàng)目中使用百度地圖api的詳細(xì)步驟
在之前的一個(gè)小項(xiàng)目中,用到的顯示當(dāng)?shù)氐牡貓D功能,下面這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目中使用百度地圖api的詳細(xì)步驟,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
vue實(shí)現(xiàn)tab欄點(diǎn)擊高亮效果
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)tab欄點(diǎn)擊高亮效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
利用vue實(shí)現(xiàn)密碼輸入框/驗(yàn)證碼輸入框
這篇文章主要為大家詳細(xì)介紹了如何利用vue實(shí)現(xiàn)密碼輸入框或驗(yàn)證碼輸入框的效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下2023-08-08
vue-cli3+echarts實(shí)現(xiàn)漸變色儀表盤組件封裝
這篇文章主要為大家詳細(xì)介紹了vue-cli3+echarts實(shí)現(xiàn)漸變色儀表盤組件封裝,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
vue中js判斷長(zhǎng)時(shí)間不操作界面自動(dòng)退出登錄(推薦)
這篇文章主要介紹了vue中js判斷長(zhǎng)時(shí)間不操作界面自動(dòng)退出登錄,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01
vue.js中window.onresize的超詳細(xì)使用方法
這篇文章主要給大家介紹了關(guān)于vue.js中window.onresize的超詳細(xì)使用方法,window.onresize 是直接給window的onresize屬性綁定事件,只能有一個(gè),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12

