在去年,咱們對IScroll的源碼進行了學習,而且分離出了一段代碼本身使用,在使用學習過程當中發現幾個致命問題:css
① 光標移位html
② 文本框找不到(先讓文本框獲取焦點,再滑動一下,輸入文字即可重現)前端
③ 偶爾致使頭部消失,頭部可不是fixed哦node
因爲以上問題,加之去年咱們團隊的工做量極大,和中間一些組織架構調整,這個事情一直被放到了今天,內心一直對此耿耿於懷,由於IScroll讓人忘不了的好處ios
小釵堅信,IScroll能夠帶來前端體驗上的革命,由於他能夠解決如下問題css3
咱們不能由於一兩個小問題而放棄如此牛逼的點子,因此咱們要處理其中的問題,那麼這些問題是否真的不可解決,而引發這些問題的緣由又究竟是什麼,咱們今天來一一探索web
PS:該問題已有更好的解決方案,待續架構
第一步依舊是抽離IScroll核心邏輯,咱們這裏先在簡單層面上探究問題,以避免被旁枝末節的BUG困擾,這裏造成的一個庫只支持縱向滾動,代碼量比較少app
代碼中引入了fastclick解決其移動端點擊問題,demo效果在此:異步
http://sandbox.runjs.cn/show/xq2fbetv
基本代碼出來了,咱們如今來一個個埋坑,首先解決難的問題!
光標跳動是什麼現象你們都知道了,至於致使的緣由又咱們測試下來,便可肯定罪魁禍首爲:transform,因而咱們看看滑動過程當中發生了什麼
① 每次滑動會涉及到位置的變化
this._translate(0, newY);
② 每次變化會改變transform屬性
1 //移動x,y這裏比較簡單就不分離y了 2 _translate: function (x, y) { 3 this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; 4 5 this.x = x; 6 this.y = y; 7 8 if (this.options.scrollbars) { 9 this.indicator.updatePosition(); 10 } 11 12 },
咱們這裏作一次剝離,將transform改爲直接改變top值看看效果
this.scrollerStyle['top'] = y + 'px';
而事實證實,一旦去除transform屬性,咱們這裏便再也不有光標閃動的問題了。
更進一步的分析,實驗,你會發現其實引發的緣由是這句:
// this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' ;
沒錯,就是css3d加速引發的,他的優點是讓動畫變得順暢,卻未曾想到會引發文本框光標閃爍的問題
針對ios閃爍有一個神奇的屬性是
-webkit-backface-visibility: hidden;
因而加入到,scroller元素上後觀察之,無效,捨棄該方案再來就是一些怪辦法了:
文本獲取焦點的狀況下,會隱藏虛擬鍵盤,連焦點都沒有了,這個問題天然不藥而癒,因而咱們只要滑動便讓其失去焦點,這樣彷佛狡猾的繞過了這個問題
在touchmove邏輯處加入如下邏輯
1 //暫時只考慮input問題,有效再擴展 2 var el = document.activeElement; 3 if (el.nodeName.toLowerCase() == 'input') { 4 el.blur(); 5 this.disable(); 6 setTimeout($.proxy(function () { 7 this.enable(); 8 }, this), 250); 9 return; 10 }
該方案最爲簡單粗暴,他在咱們意圖滑動時便直接致使虛擬鍵盤失效,從而根本不會滑動,便錯過了光標跳動的問題
甚至,解決了因爲滾動致使的文本框消失問題!!!
其中有一個250ms的延時,這個期間是虛擬鍵盤隱藏所用時間,這個時間段將不可對IScroll進行操做,該方案實驗下來效果還行
其中這個延時在200-300之間比較符合人的操做習慣,不設置滾動區域會亂閃,取什麼值各位本身去嘗試,測試地址:
http://sandbox.runjs.cn/show/8nkmlmz5
這個方案是我以爲最優的方案,其是否接受還要看產品態度
_translate是IScroll滑動的總控,這裏默認是使用transform進行移動,但如果獲取焦點的狀況下咱們能夠具備不同的方案
在文本框具備焦點是,咱們使用top代替transform!
PS:這是個爛方法不建議採用
1 //移動x,y這裏比較簡單就不分離y了 2 _translate: function (x, y) { 3 4 var el = document.activeElement; 5 if (el.nodeName.toLowerCase() == 'input') { 6 this.scrollerStyle['top'] = y + 'px'; 7 } else { 8 this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; 9 } 10 11 this.x = x; 12 this.y = y; 13 14 if (this.options.scrollbars) { 15 this.indicator.updatePosition(); 16 } 17 18 },
該方案被測試確實可行,不會出現光標閃的現象,可是有一個問題卻須要咱們處理,即是一旦文本框失去焦點,咱們要作top與transform的換算
因此這是一個爛方法!!!這裏換算事實上也不難,就是將top值從新歸還transform,可是整個這個邏輯倒是讓人以爲彆扭
並且咱們這裏還須要一個定時器去作計算,判斷什麼時候文本框失去焦點,整個這個邏輯就是一個字 坑!
1 //移動x,y這裏比較簡單就不分離y了 2 _translate: function (x, y) { 3 4 var el = document.activeElement; 5 if (el.nodeName.toLowerCase() == 'input') { 6 this.scrollerStyle['top'] = y + 'px'; 7 8 //便須要作距離換算相關清理,一旦文本框事情焦點,咱們要作top值還原 9 if (!this.TimerSrc) { 10 this.TimerSrc = setInterval($.proxy(function () { 11 var el = document.activeElement; 12 if (el.nodeName.toLowerCase() != 'input') { 13 14 pos = this.getComputedPosition(); 15 16 var top = $(scroller).css('top'); 17 this.scrollerStyle['top'] = '0px'; 18 console.log(pos); 19 20 var _x = Math.round(pos.x); 21 var _y = Math.round(pos.y); 22 _y = _y + parseInt(top); 23 24 //移動過去 25 this._translate(_x, _y); 26 27 clearInterval(this.TimerSrc); 28 this.TimerSrc = null; 29 } 30 }, this), 20); 31 } 32 } else { 33 this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; 34 } 35 36 this.x = x; 37 this.y = y; 38 39 if (this.options.scrollbars) { 40 this.indicator.updatePosition(); 41 } 42 43 },
經測試,該代碼能夠解決光標跳動問題,可是坑不坑你們內心有數,一旦須要被迫使用定時器的地方,一定會有點坑!測試地址
http://sandbox.runjs.cn/show/v9pno9d8
文本框消息是因爲滾動中產生動畫,將文本框搞到區域外了,這個時候一旦咱們輸入文字,致使input change,系統便會自動將文本定位到中間,而出現文本不可見問題
該問題的處理最好的方案,依舊是方案一,如果這裏要死磕,又會有許多問題,方案無非是給文本設置changed事件,或者定時器什麼的,當變化時,自動將文本元素
至於IScroll可視區域,樓主這裏就不獻醜了,由於我基本決定使用方案一了。
所謂步長移動即是我一次必須移動必定距離,這個與圖片橫向輪播功能有點相似,而這類需求在移動端數不勝數,那咱們的IScroll應該如何處理才能加上這一偉大特性呢?
去看IScroll的源碼,人家都已經實現了,竟然人家都實現了,哎,可是咱們這裏無論他,照舊作咱們的事情吧,加入步長功能
PS:這裏有點小小的失落,我覺得沒有實現呢,這樣我搞出來確定沒有官方的優雅了!
思路其實很簡單,咱們如果設置了一個步長屬性,暫時咱們認爲他是一個數字(其實能夠是bool值,由庫本身計算該值),而後每次移動時候便必須強制移動該屬性的整數倍便可,好比:
1 var s = new IScroll({ 2 wrapper: $('#wrapper'), 3 scroller: $('#scroller'), 4 setp: 40 5 });
這個便要求每次都得移動10px的步長,那麼這個如何實現呢?其實實現點,依然是_translate處,咱們這裏須要一點處理
1 //移動x,y這裏比較簡單就不分離y了 2 _translate: function (x, y, isStep) { 3 4 //處理步長 5 if (this.options.setp && !isStep) { 6 var flag2 = y > 0 ? 1 : -1; //這個會影響後面的計算結果 7 var top = Math.abs(y); 8 var mod = top % this.options.setp; 9 top = (parseInt(top / this.options.setp) * this.options.setp + (mod > (this.options.setp/2) ? this.options.setp : 0)) * flag2; 10 y = top; 11 } 12 13 this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; 14 15 this.x = x; 16 this.y = y; 17 18 if (this.options.scrollbars) { 19 this.indicator.updatePosition(); 20 } 21 22 },
這樣一改後,每次便要求移動40px的步長,固然,我這裏代碼寫的不是太好,整個效果在此
這裏惟一須要處理的就是touchmove了,每次move的時候,咱們不該該對其進行步長控制,然後皆能夠,這種控制步長的效果有什麼用呢?請看這個例子:
所謂雙IScroll,即是一個頁面出現了兩個IScroll組件的問題,這個問題前段時間發生在了咱們一個團隊身上,其情況具體爲
他在一個view上面有兩個地方使用了IScroll,結果就是感受滑動很卡,而且不能很好的定位緣由,其實致使這個緣由的主要因素是:
他將事件綁定到了document上,而不是具體包裹層元素上,這樣的話,就算一個IScroll隱藏了,他滑動時候已經執行了兩個邏輯,從而出現了卡的現象
固然,一個IScroll隱藏的話其實應該釋放其中的事件句柄,當時他沒有那麼作,因此之後你們遇到對應的功能,須要將事件綁定對位置
咱們這裏舉個例子:
這裏,咱們將滑動事件綁定到了各個wrapper上,因此不會出現卡的現象,之後各位本身要注意:
這個問題其實比較簡單,只須要每次操做後執行一次refresh,方法便可,這裏重啓一行有點坑爹了
每每最後介紹的方法最爲牛B,不錯,小釵還有一招大殺器能夠解決以上問題,
http://sandbox.runjs.cn/show/s3dqvlfk
1 _start: function (e) { 2 if (!this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated)) { 3 return; 4 } 5 6 7 //暫時只考慮input問題,有效再擴展 8 var el = document.activeElement; 9 if (el.nodeName.toLowerCase() == 'input') { 10 return; 11 } 12 13 14 var point = e.touches ? e.touches[0] : e, pos; 15 this.initiated = utils.eventType[e.type]; 16 17 this.moved = false; 18 19 this.distY = 0; 20 21 //開啓動畫時間,若是以前有動畫的話,便要中止動畫,這裏由於沒有傳時間,因此動畫便直接中止了 22 this._transitionTime(); 23 24 this.startTime = utils.getTime(); 25 26 //若是正在進行動畫,須要中止,而且觸發滑動結束事件 27 if (this.isInTransition) { 28 this.isInTransition = false; 29 pos = this.getComputedPosition(); 30 var _x = Math.round(pos.x); 31 var _y = Math.round(pos.y); 32 33 if (_y < 0 && _y > this.maxScrollY && this.options.adjustXY) { 34 _y = this.options.adjustXY.call(this, _x, _y).y; 35 } 36 37 //移動過去 38 this._translate(_x, _y); 39 this._execEvent('scrollEnd'); 40 } 41 42 this.startX = this.x; 43 this.startY = this.y; 44 this.absStartX = this.x; 45 this.absStartY = this.y; 46 this.pointX = point.pageX; 47 this.pointY = point.pageY; 48 49 this._execEvent('beforeScrollStart'); 50 51 e.preventDefault(); 52 53 },
每次touchStart的時候如果發現當前得到焦點的是input,便不予理睬了,這個時候滑動效果是系統滑動,各位能夠一試
關於IScroll的研究暫時告一段落,但願此文對各位有幫助,通過此次的深刻學習同時也對小釵的一些問題獲得了處理
我相信將之用於項目重的點會愈來愈多!
原文http://www.cnblogs.com/yexiaochai/p/3764503.html