之前在面試的時候瓜晤,有些公司直接問我寫過什么組件,而當時的我的回答是:封裝ajax腹纳,封裝事件處理(兼容性)痢掠,前端路由,這些方法組件嘲恍,而并沒有一個比如下拉加載足画,輪播,加載中蛔钙,拖拽這樣的配合css以及js的這種組件锌云。當時最后的結果就是現(xiàn)場思考:一個輪播組件荠医,該有哪些方法吁脱,應該暴露什么方法桑涎,暴露什么屬性。所以最近靜下心來兼贡,也剛好在自己做的一個前后端分離的小項目中用到了下拉加載攻冷,就順帶把這個給抽離出來。記錄一下寫組件的過程遍希,希望能夠加深自己的感悟等曼。
下拉加載的實現(xiàn)原理
下拉加載的原理其實并不復雜,實現(xiàn)方法也有很多種凿蒜,這里我采用設置margin-top為負值禁谦,監(jiān)聽touchmove或者mousemove事件來檢測手指或者鼠標的位移,從而移動margin-top废封,在達到下拉閾值之后州泊,touchend或者mouseup事件進行回調函數(shù)的處理。從而完成重新加載漂洋。原理很簡單遥皂,但是在我實現(xiàn)的過程中,還是碰到了不少問題刽漂。下面一一說明演训。
具體的實現(xiàn)
按照需求驅動型的思路,我要新建一個上拉加載類贝咙,這個類應該至少具有兩個方法:
- start:通過這個方法样悟,上拉加載啟動。
- remove:通過這個方法庭猩,移除上拉加載相關事件乌奇。
同時,需要有一些參數(shù): - content:在哪個元素上下拉進行加載
- callback: 當下拉加載觸發(fā)的時候要進行什么樣的回調
- ptr: 顯示下拉的loading的元素
根據(jù)上述的說法眯娱,大致的結構應該是這樣:
var callback
var pullreload = function(options){
this.content = options.content;//目標下拉元素
callback = options.callback || (default自己設計一個默認的回調)//下拉觸發(fā)后的回調
this.ptr = options.ptr礁苗;
this.start = function(){}//綁定事件
this.remove = function(){} //清除綁定
}
可以看到,上述callback定義在外部徙缴,這是因為touchened,mouseup這些事件中需要對其進行操作试伙,定義在內部,一方面是事件回調不好進行訪問于样,另一方面疏叨,用戶能夠通過this.callback進行訪問,這個可以有穿剖,但是我并不希望用戶能夠通過這樣的方式修改callback蚤蔓,將其放在外部,除了內部函數(shù)糊余,沒有別人可以訪問到秀又,能夠私有這個變量单寂,達到私有的效果。事實上吐辙,個人感覺宣决,用原生js寫組件的話,有些方法需要放在外部昏苏,而有些方法需要放在屬性尊沸,有些方法放在prototype上。這些取決于實際的應用贤惯,不希望實例訪問洼专,當然是放在外部合適,而希望每一個實例都擁有孵构,放在屬性上更合適壶熏,而公用的方法,適合放在原型上浦译。最后棒假,外部通過立即執(zhí)行函數(shù)包裹或者webpack等工具進行打包,都可以避免外部私有變量污染全局精盅。
除了上述變量帽哑,還定義了一些私有變量以及方法,最終的代碼框架如下:
//檢測是否正在拉動,start設為true,end設為false叹俏,防止移動二次滑動產(chǎn)生干擾
var isDragging = false;
//是否達到閾值妻枕,下拉到一定程度之后,為true,否則為false
var isThresholdReached = false;
//記錄下拉初始鼠標位置
var popStart = 0;
//設置初始閾值
var threshold = 20;
//記錄上一次end回調是否執(zhí)行完畢
var isend = true;
//記錄當前是否處于最頂端
var isTop = true;
function getheight(event) {
//通過event獲取高度粘驰,因為pc端和移動端不同屡谐,單獨寫一個方法來解決兼容問題
};
function movestart(event){
//記錄初始位置,判斷是否位于頂端
}
function moving(event){
//檢測滑動方向蝌数,進行相應的位移
}
function moveend(){
//檢測是否達到下拉閾值愕掏,達到則進行回調,否則恢復初始狀態(tài)
}
var callback;
var pullReload = function(options){
this.content = document.getElementById(options.content);
this.ptr = document.getElementById('ptr');
callback = options.callback || function(){};
this.start = function(){
//綁定事件
}
this.remove = function(){
//移除事件
}
return this;
}
export default pullReload;
源碼見這里,里面是js和css分開寫的顶伞,最后通過webpack進行一個打包從而完成組件化饵撑。
碰到的問題
- content加載好之后,start中應該進行所有事件(touchmove)的綁定還是當touchstart或者mousedown之后進行事件的綁定唆貌?
這里的話滑潘,有幾個考慮,一方面锨咙,touchmove觸發(fā)頻率特別快语卤,一直綁定給的話,對資源浪費很大,另一方面粹舵,touchmove并不需要實時綁定钮孵,因為當touchstart之后,如果此時并不處于最頂端齐婴,那么touchmove是完全不需要觸發(fā)的油猫,基于上述兩點稠茂,我對touchmove,touchend事件的綁定在touchstart中當檢測到用戶處于最頂端的時候進行綁定柠偶。 - event.preventDefault(),主要在touchmove事件中存在問題,因為touchmove事件的默認行為是滾動,如果不進行event.preventDefault()事件,那么在最頂部的時候澜掩,用戶下拉造成滾動叼耙,那么此時滾動條會發(fā)生一些奇怪的行為,進而導致檢測到的用戶觸摸點的位置發(fā)生偏差类茂,從而導致閾值的不正確觸發(fā)。而如果執(zhí)行event.preventDefault()的話,又會帶來另外一個問題摇邦,當用戶處于最頂端的時候,上拉屎勘,因為event,preventDefault()又帶來另一個問題施籍,拉不動了,這個時候用戶無法上拉概漱。很尷尬丑慎,迫于無奈,我最終的解決方案是在touchmove中進行一個上拉下拉的檢測瓤摧,上拉就不禁止默認竿裂,下拉禁止默認行為。從而解決了這個問題照弥。
- touchmove,touchend事件綁定在content元素上還是document上腻异?
這里主要是因為用戶有可能在電腦上一滑,滑出了content的范圍这揣,就會導致touchmove,touchend沒有觸發(fā)捂掰,進而帶來一系列的問題,所以穩(wěn)妥起見曾沈,我綁定在了document上这嚣,當然,因為只有在最頂端的時候才會綁定個塞俱,所以性能上的損耗其實并不大姐帚,并不會影響用戶非下拉狀態(tài)下的體驗。
小結
組件的考慮障涯,其實還是在暴露的方法罐旗、屬性膳汪,以及私有的方法,屬性上的取舍九秀,根據(jù)具體的情況進行取舍遗嗽,才能上組件更加通用,可靠度更高鼓蜒。個人認為:不希望實例訪問痹换,當然是放在外部進行私有更合適,而希望每一個實例都擁有都弹,放在屬性上更合適娇豫,而公用的需要暴露的方法,適合放在原型上畅厢。
下一步的計劃希望能將這個小小的組件放到npm上冯痢,之前也一直沒有試過,用來體驗一把npm install還是很好玩的框杜。