移动端模态框的机制因为与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】再谈移动端的模态框实现的更多相关文章

  1. 再谈移动端Web屏幕适配

    一个多月前水了一篇移动web屏幕适配方案,当时噼里啪啦的写了一通,自我感觉甚是良好.不过最近又有一些新的想法,和之前的有一些不同. 先说一下淘宝的方案,感觉现在好多的适配方案都是受了它的影响,上周六看 ...

  2. js学习之--Bootstrap Modals(模态框)

    http://www.runoob.com/bootstrap/bootstrap-v2-modal-plugin.html http://outofmemory.cn/bootstrap/tutor ...

  3. MVC中调用模态框之后导致JS失效

    今天在工作中碰到一个页面调用模态框之后,页面原来的JS失效的问题,由于前台经验较少,折腾了一天... 问题描述是这样,在页面,有两个下拉列表框A和B,做了下拉列表框联动,有一个button按钮会调用模 ...

  4. 使用bootstrap的JS插件实现模态框效果

    在上一篇文章中,我们使用 js+css 实现了模态框效果,在理解了模态框的基本实现方法和实现效果后,我们就要寻找更快捷的方法,又快又好的来完成模态框开发需求,从而节约时间,提高效率.一个好的轮子,不仅 ...

  5. JS /CSS 实现模态框(注册和登录组件)

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  6. 基于bootstrap模态框的日期选择器

    近来由于工作需求,以bootstrap模态框+DIV+CSS+JS做了一个适用于移动端的日期选择器,能够满足多样的需求,目前处于第一个版本,后续可能会继续更新.废话不多说,直接进入制作过程. 首先,需 ...

  7. 解决Ueditor在bootstarp 模态框中全屏问题

    基本的一些配置就不说了.先说一下要注意的问题:首先是zIndex的设置.记住最好都显示设置模态框和ueditor的zIndex.理清他们的层叠关系. 特别是用到ueditor里面的图片上传功能的更要设 ...

  8. Bootstrap 模态框(Modal)插件

    原文链接:http://www.runoob.com/bootstrap/bootstrap-modal-plugin.html Bootstrap 模态框(Modal)插件 模态框(Modal)是覆 ...

  9. Bootstrap <基础三十二>模态框(Modal)插件

    模态框(Modal)是覆盖在父窗体上的子窗体.通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动.子窗体可提供信息.交互等. 如果您想要单独引用该插件的功能,那么您需要引用  ...

随机推荐

  1. 数位dp 3943 二分法

    转载:http://blog.csdn.net/wdcjdtc/article/details/39177905 #include"cstdlib" #include"c ...

  2. UVA 140_Bandwidth

    题意: 定义一个结点的带宽是其距离所有相连结点的最远距离,一个图的带宽是图中所有结点带宽的最小值.给出一个图中各个结点的相邻情况,要求写出一个结点的排列,使得其所构成的图带宽最小. 分析: 枚举全排列 ...

  3. Extjs6(六)——增删查改之查询

    本文主要实现的效果是:点击查询按钮,根据form中的条件,在Grid中显示对应的数据(如果form为空,显示全部数据) 一.静态页面 1.查询按钮 { text:'查询', handler: 'onS ...

  4. codevs 3498 小木棍

    3498 小木棍  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description 乔治有一些同样长的小木棍,他把这些木棍随意 ...

  5. CSS聊天气泡

    概述 谷歌效果图如下: ie效果图如下: 完整代码 <!DOCTYPE html> <html> <head> <meta charset="gbk ...

  6. 纤程(FIBER)

    Indy 10 还包含对纤程的支持.纤程是什么?简单来说,它也是 一个“线程”,但是它是由代码控制的,而不是由操作系统控制的.实际上,可以认为线程 是一个高级纤程.纤程和 Unix 用户线程(Unix ...

  7. 慎用Outline ,UGUI Outline实现原理分析

    使用 UGUI 制作背包的时候.同事发现假设背包中加入了大量的物品.比方两百个.Unity就会出错,提示 Canvas element contains more than 65535 vertice ...

  8. uva 439 Knight Moves 骑士移动

    这道题曾经写过,bfs.用队列,不多说了,上代码: #include<stdio.h> #include<stdlib.h> #include<string.h> ...

  9. openstack (3)---------部署memcached缓存服务,keystone服务

    一.memcached概念 Memcached 是一个开源的.高性能的分布式内存对象缓存系统.通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高网站访问速度,加速动态WEB应用.减轻数据库负载 ...

  10. 【MySQL】Win7下修改MySQL5.5默认编码格式

    一般安装MySQL程序过程中,有一步骤是选择MySQL的默认编码格式的,程序默认为Latin1编码格式,当然也可以选择第三个选项,手动选择gbk或utf8编码格式,以支持中文数据.如下图: 现在问题出 ...