前言

在去年,我们对IScroll的源码进行了学习,并且分离出了一段代码自己使用,在使用学习过程中发现几个致命问题:

① 光标移位

② 文本框找不到(先让文本框获取焦点,再滑动一下,输入文字便可重现)

③ 偶尔导致头部消失,头部可不是fixed哦

 

由于以上问题,加之去年我们团队的工作量极大,和中间一些组织架构调整,这个事情一直被放到了今天,心里一直对此耿耿于怀,因为IScroll让人忘不了的好处

小钗坚信,IScroll可以带来前端体验上的革命,因为他可以解决以下问题

  • 区域滑动顺滑感的体验
  • 解决fixed移位问题
  • 解决动画过程中长短页的问题,并且可以优化view切换动画的顺畅度

我们不能因为一两个小问题而放弃如此牛逼的点子,所以我们要处理其中的问题,那么这些问题是否真的不可解决,而引起这些问题的原因又到底是什么,我们今天来一一探索

PS:该问题已有更好的解决方案,待续

抽离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的问题

所谓双IScroll,便是一个页面出现了两个IScroll组件的问题,这个问题前段时间发生在了我们一个团队身上,其状况具体为

他在一个view上面有两个地方使用了IScroll,结果就是感觉滑动很卡,并且不能很好的定位原因,其实导致这个原因的主要因素是:

他将事件绑定到了document上,而不是具体包裹层元素上,这样的话,就算一个IScroll隐藏了,他滑动时候已经执行了两个逻辑,从而出现了卡的现象

当然,一个IScroll隐藏的话其实应该释放其中的事件句柄,当时他没有那么做,所以以后大家遇到对应的功能,需要将事件绑定对位置

我们这里举个例子:

 

这里,我们将滑动事件绑定到了各个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

最新文章

  1. 《OOC》笔记(3)——C语言变长参数va_list的用法
  2. 第二章 部署war包到tomcat
  3. hdu2457DNA repair(ac自动机+dp)
  4. jce
  5. Hbase Interface HConnection
  6. HW2.2
  7. (3)tomcat源代码分析环境的搭建
  8. U盘开发之GPIF Master模式
  9. WildMagic 简单图元(一)
  10. C/C++常考面试题(一)
  11. I - Intersection HDU - 5120(圆环相交面积)
  12. [Other] Nuget 构建服务器与常用命令
  13. 如何在 Docker 容器中运行 Kali Linux 2.0
  14. flex布局常见用法小结
  15. codeforces365B
  16. petapoco 新手上路
  17. IntelliJ IDEA 破解 - pycharm
  18. vue 解决报错1
  19. MacBook搭建go语言开发环境
  20. 「小程序JAVA实战」swagger2的使用与接口测试(34)

热门文章

  1. vim 编辑器简单使用总结
  2. python类的实例方法\静态方法\类方法区别解析(附代码)
  3. iOS 触摸事件与UIResponder(内容根据iOS编程编写)
  4. Kafka无消息丢失配置
  5. TagHelper是怎么实现的
  6. 关于 ASP.NET MVC 中的视图生成
  7. 关于Net Core 多平台程序的Framework问题
  8. bzoj1878--离线+树状数组
  9. vs2013\2015UML系列之-类图
  10. 关于Karaf Container 4.0.7