博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【IScroll深入学习】解决IScroll疑难杂症
阅读量:5280 次
发布时间:2019-06-14

本文共 8740 字,大约阅读时间需要 29 分钟。

前言

在去年,我们对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: 405 });

这个便要求每次都得移动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隐藏的话其实应该释放其中的事件句柄,当时他没有那么做,所以以后大家遇到对应的功能,需要将事件绑定对位置

我们这里举个例子:

1   2   3   4     5   
6
7
8
9
10 192 193 194 195 196
197
198
199 滚轮滚轮滚轮
200
201
202
203
204
    205
  • 选项1
  • 206
  • 选项2
  • 207
  • 选项3
  • 208
  • 选项4
  • 209
  • 选项5
  • 210
  • 选项6
  • 211
  • 选项7
  • 212
  • 选项8
  • 213
  • 选项9
  • 214
  • 选项10
  • 215
216
217 218
219
    220
  • 选项11
  • 221
  • 选项12
  • 222
  • 选项13
  • 223
  • 选项14
  • 224
  • 选项15
  • 225
  • 选项16
  • 226
  • 选项17
  • 227
  • 选项18
  • 228
  • 选项19
  • 229
  • 选项20
  • 230
231
232 233
234  
235
236

237 提示信息

238
239
取消
确定240
241
242
243 969 988 989
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的研究暂时告一段落,希望此文对各位有帮助,经过这次的深入学习同时也对小钗的一些问题得到了处理

我相信将之用于项目重的点会越来越多!

若是各位在实际工作中遇到什么IScroll的疑难杂症可以留言,有时间小钗愿意整理解决方案

转载于:https://www.cnblogs.com/yexiaochai/p/3764503.html

你可能感兴趣的文章
雅虎的用户注册页面抽风了
查看>>
卷积神经网络数据识别
查看>>
CSU-2046: sequence
查看>>
【mongo】可以用localhost启动,无法用ip启动问题的解决
查看>>
【QT】视频播放
查看>>
HTML中使用javascript解除禁止input输入框代码:
查看>>
揭开Redis的神秘面纱
查看>>
Object流
查看>>
网关服务器——个人学习
查看>>
bzoj1293 [SCOI2009]生日礼物
查看>>
转 10 个 Nginx 的安全提示
查看>>
jQuery UI-draggable参数学习
查看>>
set
查看>>
js方法encodeURI后,关于get请求url长度的限制测试与总结
查看>>
Windows Phone开发(8):关于导航的小技巧 转:http://blog.csdn.net/tcjiaan/article/details/7285062...
查看>>
React零碎知识点回顾
查看>>
字符串类型 字符串下标 字符串的方法 切片 for循环的一些总结
查看>>
Redis
查看>>
记一次mysql的preparedStatement使用超限问题
查看>>
Ajax学习笔记1之第一个Ajax应用程序
查看>>