【js】再谈移动端的模态框实现
移动端模态框的机制因为与PC的模态框机制一直有所区别,一直是许多新人很容易踩坑的地方,最近笔者作为一条老咸鱼也踩进了一个新坑中,真是平日里代码读得太粗略,故而写上几笔,以儆效尤。
故事的起因是这样的,兄弟团的童鞋的页面出现了模块框内需要滚动元素的需求,但是实际情况是他调试了很久,却没有找到确定的解决问题,这也引起了笔者的注意,虽然有现成的组件,但是因为相关代码是有一些历史的了,并没有迁移,于是笔者就和他以前联调了一番。
我们知道常见的pc端模块框阻止滚动的方式是在html或者body标签上添加overflow:hidden,以及margin:0等来实例上将页面置为一个不可滚动的页面,而在移动端,则需要我们手动的阻止相关dom的touchmove事件的冒泡,来达到目的,示意代码如下:
/*html code*/
<div class='modal'>
<div class="overlay" id="overlayId"></div>
<div class='modal-content'id='YourModalContentId'></div>
</div>
/*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*/
<div class='modal'>
<div class="overlay" id="overlayId"></div>
<div class='modal-content'id='YourModalContentId'>
<ul>
...
</ul>
</div>
</div>
/*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引用了另一个滚动的实现——scroller,再往下看了下实现,发现是因为iscroll的touch事件识别虽然是放在传入的容器上到,但是具体的行为为了体验的优化,却将touchmove和touchend事件添加到了window(如前文所述),而android 7似乎对这种操作的支持并不是很友好,导致了touchmove事件最先响应在了外层容器。。导致原本容器内部滚动的事件不能被正确处理。
其实,没想到iscroll本来一个为了优化体验而想出的小技巧,到了android 7时代,反而弄巧成拙。
【js】再谈移动端的模态框实现的更多相关文章
- 再谈移动端Web屏幕适配
一个多月前水了一篇移动web屏幕适配方案,当时噼里啪啦的写了一通,自我感觉甚是良好.不过最近又有一些新的想法,和之前的有一些不同. 先说一下淘宝的方案,感觉现在好多的适配方案都是受了它的影响,上周六看 ...
- js学习之--Bootstrap Modals(模态框)
http://www.runoob.com/bootstrap/bootstrap-v2-modal-plugin.html http://outofmemory.cn/bootstrap/tutor ...
- MVC中调用模态框之后导致JS失效
今天在工作中碰到一个页面调用模态框之后,页面原来的JS失效的问题,由于前台经验较少,折腾了一天... 问题描述是这样,在页面,有两个下拉列表框A和B,做了下拉列表框联动,有一个button按钮会调用模 ...
- 使用bootstrap的JS插件实现模态框效果
在上一篇文章中,我们使用 js+css 实现了模态框效果,在理解了模态框的基本实现方法和实现效果后,我们就要寻找更快捷的方法,又快又好的来完成模态框开发需求,从而节约时间,提高效率.一个好的轮子,不仅 ...
- JS /CSS 实现模态框(注册和登录组件)
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- 基于bootstrap模态框的日期选择器
近来由于工作需求,以bootstrap模态框+DIV+CSS+JS做了一个适用于移动端的日期选择器,能够满足多样的需求,目前处于第一个版本,后续可能会继续更新.废话不多说,直接进入制作过程. 首先,需 ...
- 解决Ueditor在bootstarp 模态框中全屏问题
基本的一些配置就不说了.先说一下要注意的问题:首先是zIndex的设置.记住最好都显示设置模态框和ueditor的zIndex.理清他们的层叠关系. 特别是用到ueditor里面的图片上传功能的更要设 ...
- Bootstrap 模态框(Modal)插件
原文链接:http://www.runoob.com/bootstrap/bootstrap-modal-plugin.html Bootstrap 模态框(Modal)插件 模态框(Modal)是覆 ...
- Bootstrap <基础三十二>模态框(Modal)插件
模态框(Modal)是覆盖在父窗体上的子窗体.通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动.子窗体可提供信息.交互等. 如果您想要单独引用该插件的功能,那么您需要引用 ...
随机推荐
- 567. Permutation in String
Problem statement: Given two strings s1 and s2, write a function to return true if s2 contains the p ...
- C++字符串读入
int read() { ,f=;char ch=getchar(); ;ch=getchar();} +ch-';ch=getchar();} return x*f; } int main() { ...
- 本地配置nginx的https
前文:因为要用谷歌下的getUserMedia方法,而getUserMedia方法只能在https下才能调用,所以在本地搭建https来测试,现在说说步骤. 步骤1:下载nginx-1.10.3.zi ...
- [bzoj4519][Cqoi2016]不同的最小割_网络流_最小割_最小割树
不同的最小割 bzoj-4519 Cqoi-2016 题目大意:题目链接. 注释:略. 想法: 我们发现这和最小割那题比较像. 我们依然通过那个题说的办法一样,构建最小割树即可. 接下来就是随便怎么处 ...
- HTML5 全屏化操作功能
由于项目中用到了全屏化挫折功能,查看了API后写了一个简单的全屏化model <!DOCTYPE html> <html> <head> <meta http ...
- MongoDB学习day01--非关系型数据库
1.数据库和文件的主要区别: 1.1数据库有数据库表/行和列的概念,让我们存储操作数据方便 1.2数据库提供了方便的接口,让java.php..net.nodejs很方便的实现增删改查 2.NoSQL ...
- 所有的异常都要使用try catch 语句捕获?
在开发应用程序过程中必须检测代码可能发生的错误并进行正确的处理,这个在理想的情况下,应用程序中的每行 代码都按照预想的执行,要用到的每种资源总是可以利用,但是在实际的开发过程中,写代码难免会出错,或是 ...
- 未能找出类型或命名空间名称“T” 问题的解决方案
在已经引用“using System.Collections.Generic;”时,还是提示急未能找出类型或命名空间名称“T”的问题.
- Utuntu下Xshell使用+vi使用
记得上次Utuntu下Xshell环境没有搭建成功,这个又试试! 按照以前的方法测试Utuntu的ip;在本机安装Xshell 然后出现xshell连接虚拟机时,提示SSH服务器拒绝了密码 修改方法: ...
- Hdoj 3697 Selecting courses 【贪心】
Selecting courses Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 62768/32768 K (Java/Others ...