博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【js】再谈移动端的模态框实现
阅读量:6279 次
发布时间:2019-06-22

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

  移动端模态框的机制因为与PC的模态框机制一直有所区别,一直是许多新人很容易踩坑的地方,最近笔者作为一条老咸鱼也踩进了一个新坑中,真是平日里代码读得太粗略,故而写上几笔,以儆效尤。

  故事的起因是这样的,兄弟团的童鞋的页面出现了模块框内需要滚动元素的需求,但是实际情况是他调试了很久,却没有找到确定的解决问题,这也引起了笔者的注意,虽然有现成的组件,但是因为相关代码是有一些历史的了,并没有迁移,于是笔者就和他以前联调了一番。

  我们知道常见的pc端模块框阻止滚动的方式是在html或者body标签上添加overflow:hidden,以及margin:0等来实例上将页面置为一个不可滚动的页面,而在移动端,则需要我们手动的阻止相关dom的touchmove事件的冒泡,来达到目的,示意代码如下:

/*html code*/
/*js code*/function addEvt(dom){ dom && dom.addEventListener('touchmove', onTouchMove);}function onTouchMove(e){ e.preventDefault();}function fn(){ let overlayDom = document.querySelector('#overlayId'); let modalDom = document.querySelector('#YourModalContentId');}

  阻止所有手指可以碰触的元素的touchmove事件冒泡(以免引起比如在微信中的view滚动,也可避免不能触发click事件,因为click事件不需要touchmove,只需要touchstart和touchend),这是其中的原理,然后实际例子稍微复杂了一点,因为实际场景需要modalcontent内部的dom滚动,一般做法是引入iscroll用touchmove事件来模拟滚动事件,但是这位童鞋做了常规操作之后得到了不同的结论,里面的dom依然不能滚动,经过笔者和他仔细的比对之后,发现基本上只有一行代码的不同:

/*html code*/
/*js code*/function addEvt(dom){ dom && dom.addEventListener('touchmove', onTouchMove);}function onTouchMove(e){ e.preventDefault(); e.stopPropagation();}function fn(){ let overlayDom = document.querySelector('#overlayId'); let modalDom = document.querySelector('#YourModalContentId'); let scroller = new IScroll(modalDom, YourOptions);}

  就是上面标红的那句话,但是正常情况下stopPropagation才是阻止事件冒泡,笔者开始也以为应该是没有关系的,但是经过反复测试后发现。。没有那句话,内部dom的滚动没有任何问题,但是有了那句话之后,内部则不能滚动了。。细细思考之后,笔者觉着。。多半是iscroll内部的实现机制了。。

  于是读了下iscroll的源码,发现iscroll在initEvents时做了一个神奇的操作:

_initEvents: function(remove) {            var eventType = remove ? utils.removeEvent : utils.addEvent,                target = this.options.bindToWrapper ? this.wrapper : window;            eventType(window, 'orientationchange', this);            eventType(window, 'resize', this);            if (this.options.click) {                eventType(this.wrapper, 'click', this, true);            }            if (!this.options.disableMouse) {                eventType(this.wrapper, 'mousedown', this);                eventType(target, 'mousemove', this);                eventType(target, 'mousecancel', this);                eventType(target, 'mouseup', this);            }            if (utils.hasPointer && !this.options.disablePointer) {                eventType(this.wrapper, 'MSPointerDown', this);                eventType(target, 'MSPointerMove', this);                eventType(target, 'MSPointerCancel', this);                eventType(target, 'MSPointerUp', this);            }            if (utils.hasTouch && !this.options.disableTouch) {                eventType(this.wrapper, 'touchstart', this);                eventType(target, 'touchmove', this);                eventType(target, 'touchcancel', this);                eventType(target, 'touchend', this);            }            eventType(this.scroller, 'transitionend', this);            eventType(this.scroller, 'webkitTransitionEnd', this);            eventType(this.scroller, 'oTransitionEnd', this);            eventType(this.scroller, 'MSTransitionEnd', this);        }

  在适用方没有强制绑定wrapper的情况下,touchstart、touchmove、touchend的target都是window!看到这里聪明的你也许已经反应过来了,这就是为什么我们平常写到touch事件的代码在移动出了dom的范围之后不能正常的释放,而iscroll的可以。。因为除了touchstart之外,其他的事件都是加在全局的window对象上的,而我们遇到的这个实际问题又恰恰使用了touchmove事件,事件触发的层级关系变成了:

/*dom 示意*/window  //iscroll 处理touchmove-html-body--modal---overlay //阻止事件冒泡---modal-content //阻止事件冒泡----iScrollElement

  我们需要等待事件冒泡到了window上,才能正常的处理iscrollElement的touchmove行为,看到这里。。笔者内心也是深感“这是一个何其大的大乌龙啊”。。不过也是因为平日中太过偏重解决问题,而没有仔细研究解决问题的方法的原理与机制。

  虽然各司其职是现代化大分工的基本诉求,但是有的时候知其所以然才能更有价值的提高我们的工作效率,对于我们解决实际问题,也是颇有裨益的。

【2017.09.27】更新

  最近笔者的android系统更新到了7.0...然后发现以上的通用移动端滚动模态窗的解决方案失效了。。问题似乎出在preventDefault不再按照我们期望的方式工作了。。于是需要更新一下实现:

  由于内部的滚动一定会传播到上层,那么解决思路就只能是将上层的滚动条件彻底移除了,即需要在模态框展示的时候先标记当前的window.scrollY(这里我们只考虑上层只有一个滚动条的情况),然后直接设置body的样式,将其overflow设置为hidden,物理上禁止上层容器的滚动;

  然后,对于模态框上的滚动容器我们不再需要使用touch事件模拟滚动了,可以直接使用原生的滚动条;

  最后,在模态框消失的时候,我们需要手动还原window的滚动条为之前的状态(注意这里其实会有一些体验问题,但是我们可以使用先还原滚动条状态再隐藏模态框的hack来避免体验问题)。

【2017.11.08】更新

  最近笔者在参考其他框架实现的时候发现,支付宝的antd-mobile并没有笔者之前在android所遇到的问题,研究代码发现,因为antd引用了另一个滚动的实现——,再往下看了下实现,发现是因为iscroll的touch事件识别虽然是放在传入的容器上到,但是具体的行为为了体验的优化,却将touchmove和touchend事件添加到了window(如前文所述),而android 7似乎对这种操作的支持并不是很友好,导致了touchmove事件最先响应在了外层容器。。导致原本容器内部滚动的事件不能被正确处理。

  其实,没想到iscroll本来一个为了优化体验而想出的小技巧,到了android 7时代,反而弄巧成拙。

转载于:https://www.cnblogs.com/mfoonirlee/p/7226613.html

你可能感兴趣的文章
safari下video标签无法播放视频的问题
查看>>
01 iOS中UISearchBar 如何更改背景颜色,如何去掉两条黑线
查看>>
对象的继承及对象相关内容探究
查看>>
Spring: IOC容器的实现
查看>>
Serverless五大优势,成本和规模不是最重要的,这点才是
查看>>
Nginx 极简入门教程!
查看>>
iOS BLE 开发小记[4] 如何实现 CoreBluetooth 后台运行模式
查看>>
Item 23 不要在代码中使用新的原生态类型(raw type)
查看>>
为网页添加留言功能
查看>>
JavaScript—数组(17)
查看>>
Android 密钥保护和 C/S 网络传输安全理论指南
查看>>
以太坊ERC20代币合约优化版
查看>>
Why I Began
查看>>
同一台电脑上Windows 7和Ubuntu 14.04的CPU温度和GPU温度对比
查看>>
js数组的操作
查看>>
springmvc Could not write content: No serializer
查看>>
Python系语言发展综述
查看>>
新手 开博
查看>>
借助开源工具高效完成Java应用的运行分析
查看>>
163 yum
查看>>