在去年,咱们对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