前言
在去年,我们对IScroll的源码进行了学习,并且分离出了一段代码自己使用,在使用学习过程中发现几个致命问题:
① 光标移位
② 文本框找不到(先让文本框获取焦点,再滑动一下,输入文字便可重现)
③ 偶尔导致头部消失,头部可不是fixed哦
由于以上问题,加之去年我们团队的工作量极大,和中间一些组织架构调整,这个事情一直被放到了今天,心里一直对此耿耿于怀,因为IScroll让人忘不了的好处
小钗坚信,IScroll可以带来前端体验上的革命,因为他可以解决以下问题
区域滑动顺滑感的体验
解决fixed移位问题
解决动画过程中长短页的问题,并且可以优化view切换动画的顺畅度
我们不能因为一两个小问题而放弃如此牛逼的点子,所以我们要处理其中的问题,那么这些问题是否真的不可解决,而引起这些问题的原因又到底是什么,我们今天来一一探索
抽离IScroll
第一步依旧是抽离IScroll核心逻辑,我们这里先在简单层面上探究问题,以免被旁枝末节的BUG困扰,这里形成的一个库只支持纵向滚动,代码量比较少
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的问题
todo......
异步DOM加载,不可滑动
todo......
结语
今日耗时比较长了,学习暂时到此,我们下次继续。
本文转自叶小钗博客园博客,原文链接http://www.cnblogs.com/yexiaochai/p/3762338.html,如需转载请自行联系原作者