点透 & 解决方案
点透 & 解决方案
学习map:
- 现象:再现现象,总结导致点透出现的情况
- 分析原因
- 解决办法
现象
再现点透现象请使用一下方式:
- 手机访问传送门
- 复制链接到连图生成二维码后扫一扫
- 或者打开chrome手机视图并打开touch screen。
zepto-隐藏元素-点透
代码:
zepto-最新-没有点透
代码:
原生js-隐藏元素-点透
代码:
原生js-元素出现-点透
代码:
综上,导致点透现象出现的场景:
- 元素z轴重叠;
- 绑定了touch事件的元素消失或移走;
- 绑定有类click事件的元素出现在点击区域;
PS:
a链接的href跳转、input、select等表单元素的聚焦并弹起软键盘,等触发的事件和click一样,由于历史原因在手机端也会表现出300ms延迟,因此也都可以看做是click事件,我叫他类click事件,demo可用爪机狠戳事件触发顺序&click延迟demo或者复制链接到连图生成二维码后扫一扫。类click事件,也是一种浏览器默认行为,可被event.preventDefault()阻止。
代码:
分析
从事件触发顺序&click延迟demo这个demo可以看出:
- iphone系手机:mouse事件在touchend之后才开始,mouse事件覆盖了click事件,屏蔽了input弹出键盘的默认行为
- 安卓手机:mouse事件会先于touch事件开始,而迟于其结束,mouse事件没有覆盖input的聚焦弹软键盘的浏览器默认行为
- 在ios设备上的浏览器[UC浏览器除外]能明显感受到click延迟
- 无论是android还是ios还是PC的touch screen的click(or mouse)事件都是迟于touchend事件被触发的
- 因此,从手指触摸屏幕到离开屏幕,先后触发了touchstart、touchend、click
由于我们在touchend阶段z轴层级已经发生了变化,当click被触发时候,能够被点击的元素则是当前z轴离用户最近的层,根据click事件的触发规则:
在被触发时,当前有绑定click事件的元素显示,且在面朝用户的最前端时,才触发click事件
因此touchend之后符合条件的绑定了click事件的元素被点透。
总而言之:出现点透是由于移动端click事件迟于touch事件被触发导致的。
解决办法
不优雅的办法:
1. 对于默认绑定了类click事件的元素(如a、input、select等)
- touchend + preventDefault及时取消touch元素的默认click事件,即- if(eve == "touchend") e.preventDefault();。
- 如果牺牲点性能无所谓的话,可以将可能在z轴方向上引起点透现象的元素绑定成click事件,比如遮罩层之类的,不过还可以增加些许有趣的交互抵消用户的焦躁心理,比如:demo-ripple。 
2. 对于没有默认绑定类click事件的元素
统一使用touch事件。z轴上都绑定touchstart 和 touchend、 tap不用阻止默认行为也不会穿透。
使用上述方法有很明显的缺点和不方便:
使用touchend + preventDefault要在同一个元素上绑定2个事件,zepto可以封装成tap事件,我们也可以,自定义tap事件阻止点透
代码:
在本demo中,虽然没有出现点透现象,但是点击出现弹层以后你会发现点击a链接、span、input都没有任何反映了,这是因为在touchend里阻止浏览器默认行为,触发自定义tap事件,不仅会阻止掉了input的软键盘弹出,还会阻止一切非tap事件,解决办法就是使用合成的click事件去覆盖会延迟的click事件。
代码:
event.initEvent('click', bubbles, true);
touch_target.addEventListener("click", handle, false);
这样再次点击,弹层上的a链接和span的click事件都响应地很迅速,然而input和select的弹出软键盘的功能被阉割了,实际上input弹出软键盘的事件是focus()事件
3. fastclick
读fastclick源码
layer.removeEventListener('touchend', this.onTouchEnd, false);
FastClick.prototype.onTouchEnd = function(event) {
    ...
    targetTagName = targetElement.tagName.toLowerCase();
    if (targetTagName === 'label') {
        forElement = this.findControl(targetElement);
        if (forElement) {
            this.focus(targetElement);
            if (deviceIsAndroid) {
                return false;
            }
            targetElement = forElement;
        }
    } else if (this.needsFocus(targetElement)) {
        if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
            this.targetElement = null;
            return false;
        }
        this.focus(targetElement);
        this.sendClick(targetElement, event);
        if (!deviceIsIOS || targetTagName !== 'select') {
            this.targetElement = null;
            event.preventDefault();
        }
        return false;
    }
// needsFocus
FastClick.prototype.needsFocus = function(target) {
    switch (target.nodeName.toLowerCase()) {
    case 'textarea':
        return true;
    case 'select':
        return !deviceIsAndroid;
    case 'input':
        switch (target.type) {
        case 'button':
        case 'checkbox':
        case 'file':
        case 'image':
        case 'radio':
        case 'submit':
            return false;
        }
        // No point in attempting to focus disabled inputs
        return !target.disabled && !target.readOnly;
    default:
        return (/\bneedsfocus\b/).test(target.className);
    }
};
// sendClick
FastClick.prototype.sendClick = function(targetElement, event) {
    var clickEvent, touch;
    // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
    if (document.activeElement && document.activeElement !== targetElement) {
        document.activeElement.blur();
    }
    touch = event.changedTouches[0];
    // Synthesise a click event, with an extra attribute so it can be tracked
    clickEvent = document.createEvent('MouseEvents');
    clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
    clickEvent.forwardedTouchEvent = true;
    targetElement.dispatchEvent(clickEvent);
};    
上面代码的意思就是:在目标元素上绑定touchend事件,在事件处理函数里,如果是需要focus的表单元素被点击,则先触发他们的focus事件,再触发自定义的click事件,fastclick之所以大,就是因为对很多表单元素在各个系统的各个版本的不同表现做了兼容,不仅解决了click以及类click的延迟问题,而且当检测到当前页面使用了基于 <meta> 标签或者 touch-action 属性的解决方案时,会静默退出。可以说,这是真正的跨平台方案出来之前一种很好的变通方案。而zepto 只是为普通的点击事件封装了一个更快的tap事件,类click事件的延迟问题并没有得到解决,而且移动端使用的tap事件,如果没做设备判断兼容PC的话,PC端的点击事件将得不到响应,这会很影响网站的可用性和可访问性。不过zepto封装了一系列移动端很需要的功能,比如swipeLeft、swipeRight、swipeUp、等等,二者各有春秋,兼并两者优势的库我目前没遇到,不过可以尝试自己写一个,加个todo吧。
4. tap.js
tap.js源码只有不到200行,大致看了下,并不能解决类click的延迟问题,鸡肋!
点透 & 解决方案的更多相关文章
- Android Fragment 多层叠加时点击穿透解决方案
		一.问题现象 多层fragment叠加时,点击上层fragment会使下层fragment的控件对应点击事件响应,这种现象就是点击穿透. 对于这种情况,我们一般都是对baseFragment进行vie ... 
- 【移动端兼容问题研究】javascript事件机制详解(涉及移动兼容)
		前言 这篇博客有点长,如果你是高手请您读一读,能对其中的一些误点提出来,以免我误人子弟,并且帮助我提高 如果你是javascript菜鸟,建议您好好读一读,真的理解下来会有不一样的收获 在下才疏学浅, ... 
- 面试题HTML +CSS
		HTML+CSS部分1.行内元素和块级元素?img算什么?行内元素怎么转化为块级元素?行内元素:和有他元素都在一行上,高度.行高及外边距和内边距都不可改变,文字图片的宽度不可改变,只能容纳文本或者其他 ... 
- Fragment 点击事件的穿透和重叠bug
		从A fragment跳转到B fragment ,为了返回时不从新加载A fragment内容,通常使用add方法来将a添加到后退栈. 在B Fragment 中点击一个空白区域,如果A Fragm ... 
- 前端基础面试题(JS部分)
		1.几种基本数据类型?复杂数据类型?值类型和引用数据类型?堆栈数据结构? 基本数据类型:Undefined.Null.Boolean.Number.String 值类型:数值.布尔值.null.und ... 
- 移动端的300ms延迟和点击穿透
		移动端300ms延迟:假定这么一个场景.用户在 浏览器里边点击了一个链接.由于用户可以进行双击缩放或者双击滚动的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行 ... 
- 前端基础面试题(js部分)
		前端基础面试题(JS部分) 1.几种基本数据类型?复杂数据类型?值类型和引用数据类型?堆栈数据结构? 基本数据类型:Undefined.Null.Boolean.Number.String值类 ... 
- zepto之tap事件点透问题分析及解决方案
		点透现象出现的场景: 当A/B两个层上下z轴重叠,上层的A点击后消失或移开(这一点很重要),并且B元素本身有默认click事件(如a标签)或绑定了click事件.在这种情况下,点击A/B重叠的部分,就 ... 
- zepto的tap事件的点透问题的几种解决方案
		你可能碰到过在页面上创建一个弹出层,弹出层有个关闭的按钮,你点了这个按钮关闭弹出层后,这个按钮正下方的内容也会执行点击事件(或打开链接).这个被定义为这是一个“点透”现象. 以前,我也听到过tap的点 ... 
随机推荐
- SVN 常识
			1.相关博客 http://my.oschina.net/u/1780920/blog/425792 2. 文件红色:表示文件没有添加到服务器 绿色:表示没有更新新的修改到服务器 普通黑色:表示和服务 ... 
- 浅谈TabLayout(ViewPager+Tab联动)
			google发布了的Android Support Design库中提供了TabLayout 通过TabLayout+ViewPager实现导航栏效果,点击Tab ,ViewPager跟随变化,滑动V ... 
- redis配置文件参数说明及命令操作
			redis下载地址:https://github.com/MSOpenTech/redis/releases. Redis 的配置文件位于 Redis 安装目录下,文件名为redis.windows. ... 
- HTML5设计网页熔岩灯导航(navigation bar)插件 已经加上完整源代码
			导航栏(navigation bar): 1.指位于页眉区域的,在页眉横幅图片上边或下边的一排水平导航按钮,它起着链接博客的各个页面的作用. 2.网页设计中不可缺少的部分,它是指通过一定的技术手段,为 ... 
- 转载文章----C#基础概念
			转载地址:http://www.cnblogs.com/zhouzhou-aspnet/articles/2591596.html 1.值类型和引用类型 1.1堆和栈 简单的说值类型存放在堆栈上面,引 ... 
- C#语言——类
			C#——类 一.String 类 系统内置的处理字符串类型的函数方法类.方便我们对字符串类型进行一系列的处理. 1.Length:获取字符串的长度,返回一个int类型的值 string x=Conso ... 
- 一篇博客理解Recyclerview的使用
			从Android 5.0开始,谷歌公司推出了RecylerView控件,当看到RecylerView这个新控件的时候,大部分人会首先发出一个疑问,recylerview是什么?为什么会有recyler ... 
- PHP MSSQL 分页实例(刷新)
			<?php/* '页面说明:*/ $link=mssql_connect("MYSQL2005","sa","123456") or ... 
- MySQL基础学习(一) 命令行命令
			1. 命令行登录 mysql -uroot -p 按照提示输入密码 常用登录选项 -u 指定用户 -p 密码 -h 数据库所在主机 -P 端口 -D 指定数据库 2.命令行退出 exit quit \ ... 
- linux 添加用户、权限
			# useradd –d /usr/sam -m sam 此命令创建了一个用户sam,其中-d和-m选项用来为登录名sam产生一个主目录/usr/sam(/usr为默认的用户主目录所在的父目录). 假 ... 
