【IScroll深刻學習】解決IScroll疑難雜症

前言

在去年,咱們對IScroll的源碼進行了學習,而且分離出了一段代碼本身使用,在使用學習過程當中發現幾個致命問題:css

① 光標移位html

② 文本框找不到(先讓文本框獲取焦點,再滑動一下,輸入文字即可重現)前端

③ 偶爾致使頭部消失,頭部可不是fixed哦node

 

因爲以上問題,加之去年咱們團隊的工做量極大,和中間一些組織架構調整,這個事情一直被放到了今天,內心一直對此耿耿於懷,由於IScroll讓人忘不了的好處ios

小釵堅信,IScroll能夠帶來前端體驗上的革命,由於他能夠解決如下問題css3

  • 區域滑動順滑感的體驗
  • 解決fixed移位問題
  • 解決動畫過程當中長短頁的問題,而且能夠優化view切換動畫的順暢度

咱們不能由於一兩個小問題而放棄如此牛逼的點子,因此咱們要處理其中的問題,那麼這些問題是否真的不可解決,而引發這些問題的緣由又究竟是什麼,咱們今天來一一探索web

PS:該問題已有更好的解決方案,待續架構

抽離IScroll

第一步依舊是抽離IScroll核心邏輯,咱們這裏先在簡單層面上探究問題,以避免被旁枝末節的BUG困擾,這裏造成的一個庫只支持縱向滾動,代碼量比較少app

Demo
核心代碼

代碼中引入了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

_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,即是一個頁面出現了兩個IScroll組件的問題,這個問題前段時間發生在了咱們一個團隊身上,其情況具體爲

他在一個view上面有兩個地方使用了IScroll,結果就是感受滑動很卡,而且不能很好的定位緣由,其實致使這個緣由的主要因素是:

他將事件綁定到了document上,而不是具體包裹層元素上,這樣的話,就算一個IScroll隱藏了,他滑動時候已經執行了兩個邏輯,從而出現了卡的現象

固然,一個IScroll隱藏的話其實應該釋放其中的事件句柄,當時他沒有那麼作,因此之後你們遇到對應的功能,須要將事件綁定對位置

咱們這裏舉個例子:

  View Code

這裏,咱們將滑動事件綁定到了各個wrapper上,因此不會出現卡的現象,之後各位本身要注意:

 

異步DOM加載,不可滑動

這個問題其實比較簡單,只須要每次操做後執行一次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

相關文章
相關標籤/搜索