如果你用vue弄移動端的頁面,那么下拉刷新還是比較常見的場景,下面來研究如何寫一個下拉刷新的組件(先上圖);
由於節省大家的時間,樣式就不貼出來了。
html結構也不必介紹了,直接看代碼吧-.-
<transition>
<div class="refresh-wrapper" ref="refresh">
<div class="refresh-inner">
<div class="refresh-pull" v-show="state==='pull'">
<span>下拉刷新-.-</span>
</div>
<div class="refresh-loading" v-show="state==='loading'">
<span>刷新中...~.~</span>
</div>
<div class="refresh-end" v-show="state==='end'">
<span>刷新完成!^.^</span>
</div>
</div>
</div>
</transition>
核心思路及步驟
- 給
document
綁定touch
事件
document.addEventListener('touchstart',this.touchStart,false);
document.addEventListener('touchmove',this.touchMove,false);
document.body.addEventListener('touchend',this.touchEnd,false);
touchStart
細節
2.1 判斷狀態,這是為了防止在刷新時多次觸發。可以定義一個變量保存狀態,狀態值為pull, loading, end
;
2.2 記錄開始的位置,Y軸的就可以了;
2.3 獲取當前touch
的對象? 雖然我們已經把事件綁定在document
上了,但是在有局部滾動的時候,那么向下滑動的時候就會有沖突,這個時候可以獲取到當前touch
的對象,后面做處理;
touchStart(e){
if(this.state === 'loading') return;
this.startY = e.touches[0].clientY;
this.getTouchTarget(e.target);
}
getTouchTarget(elm){
let currentNode = elm;
while(currentNode && currentNode.tagName !== "HTML" &&
currentNode.tagName !== "BODY" && currentNode.nodeType === 1){
let overflowY = window.getComputedStyle(currentNode).overflowY;
if(overflowY === "scroll" || overflowY === "auto"){
this.currentNode = currentNode;
this.firstNode = currentNode.firstElementChild; //記錄局部滾動的第一個子元素
break;
}
currentNode = currentNode.parentNode;
}
}
touchMove
細節
3.1 判斷當前滑動的區域是否局部滾動,如果是,通過判斷父元素和子元素的getBoundingClientRect().top
的差值是否小於0來判斷是否滾動到頂部
3.2 判斷一些其他的條件
3.3 記錄滑動的距離
3.4 改變視圖
touchMove(e){
let firstTop=0, currentTop=0;
if(this.firstNode){
firstTop = this.firstNode.getBoundingClientRect().top;
currentTop = this.currentNode.getBoundingClientRect().top;
}
let range = (e.touches[0].clientY - this.startY);
if( document.documentElement.scrollTop>0 || this.state === 'loading' || firstTop-currentTop <0 || range<0) return;
range = range*0.75 > this.maxRange? this.maxRange : range;
this.translate = range;
this.changeView();
}
changeView(){
//這里針對transfrom對fixed定位的bug做的降級處理
if(this.isFixed){
this.$refs.refresh.style.transform=`translate3d(0,${this.translate}px,0)`;
}else{
document.body.style.transform = `translate3d(0,${this.translate}px,0)`;
}
}
touchEnd
細節
4.1 判斷狀態
4.2 判斷滑動距離是否到可刷新距離,如果是,調用刷新api
4.3 改變視圖
touchEnd(e){
if(this.state === 'loading') return;
if(this.translate && this.translate >= this.maxRange){
this.translate = this.maxRange/2;
this.refresh();
}else{
this.translate = 0;
}
this.rotate = 0;
this.changeView();
}
refresh(){
this.state = 'loading';
console.log('更新中...');
this.$emit('refresh'); //父組件監聽refresh方法,並在異步回調中調用子組件的refreshEnd方法,可通過this.$refs.名稱.refreshEnd()方法調用
}
refreshEnd(){
let _this = this;
this.state = 'end';
setTimeout(()=>{
_this.translate = 0;
_this.changeView();
},1000);
setTimeout(()=>{
_this.state = 'pull';
},1300)
console.log('更新完成...');
}
注意
1.如果有fixed布局,要傳isFixed='true',否則會有bug。關於transform 與fixed的bug,可以參考這里
2. 不建議用在過於復雜的布局,可能有未知bug -.-