移动端click事件延迟300ms的原因以及解决办法
这要追溯至 2007 年初。苹果公司在发布首款 iPhone 前夕,遇到一个问题 —— 当时的网站都是为大屏幕设备所设计的。于是苹果的工程师们做了一些约定,应对 iPhone 这种小屏幕浏览桌面端站点的问题。这当中最出名的,当属双击缩放(double tap to zoom)。这也是会有上述 300 毫秒延迟的主要原因。
当用户一次点击屏幕之后,浏览器并不能立刻判断用户是要进行双击缩放,还是想要进行单击操作。因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。
于是,300 毫秒延迟就这么诞生了。
FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。简而言之,FastClick 在检测到 touchend
事件的时候,会通过 DOM 自定义事件立即触发一个模拟click
事件的click事件(自定义事件),并把浏览器在 300 毫秒之后真正触发的 click
事件阻止掉。
FastClick 非常实际地解决 300 毫秒点击延迟的问题。唯一的缺点可能也就是该脚本的文件尺寸 (尽管它不大)。如果你非常在意这点文件大小,可以尝试一下 Filament Group 的 Tappy!,或者 tap.js。两者都相当轻量,能够通过监听 tap
而非 click
事件来绕过 300 毫秒延迟。
当然,zepto库函数中,也有一个touch模块,此模块也包含tap事件,可以通过绑定tap来取代click事件。
但是zepto的tap事件会有点透问题。如何解决,请看下篇分解。
接下来,我们来详细了解一个问题:FastClick解决延迟点击的源码解析。
FastClick解决延迟点击的源码解析。
首先,我们来看FastClick的使用。
window.addEventListener( "load", function() {
FastClick.attach( document.body );
}, false );
这样就解决了 300 毫秒点击延迟的问题。
FastClick的源码:
FastClick.attach = function(layer) {
'use strict';
return new FastClick(layer);
};
在FastClick的构造函数中,会有下面这段代码:
this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); }; this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); }; this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); }; this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); }; this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); }; if (FastClick.notNeeded(layer)) {
return;
}
if (this.deviceIsAndroid) {
layer.addEventListener('mouseover', this.onMouse, true);
layer.addEventListener('mousedown', this.onMouse, true);
layer.addEventListener('mouseup', this.onMouse, true);
}
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, false);
layer.addEventListener('touchend', this.onTouchEnd, false);
layer.addEventListener('touchcancel', this.onTouchCancel, false);
也就是在document.body上绑定了click,touchstart,touchend,touchcancel事件。
这里假设,我们的页面有一个button,绑定了click事件。
当用户点击此button时,会先触发touchstart事件,这时,会冒泡到document.body中,于是就会执行:
FastClick.prototype.onTouchStart = function(event) {
'use strict';
var targetElement, touch, selection; if (event.targetTouches.length > 1) {
return true;
}
targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0]; if (this.deviceIsIOS) { selection = window.getSelection();
if (selection.rangeCount && !selection.isCollapsed) {
return true;
} if (!this.deviceIsIOS4) { if (touch.identifier === this.lastTouchIdentifier) {
event.preventDefault();
return false;
} this.lastTouchIdentifier = touch.identifier;
this.updateScrollParent(targetElement);
}
}
this.trackingClick = true;
this.trackingClickStart = event.timeStamp;
this.targetElement = targetElement;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
if ((event.timeStamp - this.lastClickTime) < 200) {
event.preventDefault();
}
return true;
};
这个回调函数主要做了以下事情:
获取我们当前触发touchstart的元素,这里是button。
然后将鼠标的信息记录了下来,记录鼠标的信息主要是为了在后面touchend触发时,根据这里得到的x、y判断是否为click。
之后,会触发touchend事件,然后冒泡到document.body上,执行以下代码:
FastClick.prototype.onTouchEnd = function(event) {
'use strict';
var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
if (this.touchHasMoved(event) || (event.timeStamp - this.trackingClickStart) > 300) {
this.trackingClick = false;
this.targetElement = null;
}
if (!this.trackingClick) {
return true;
}
if ((event.timeStamp - this.lastClickTime) < 200) {
this.cancelNextClick = true;
return true;
} this.lastClickTime = event.timeStamp;
trackingClickStart = this.trackingClickStart;
this.trackingClick = false;
this.trackingClickStart = 0;
if (this.deviceIsIOSWithBadTarget) {
touch = event.changedTouches[0];
targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset);
}
targetTagName = targetElement.tagName.toLowerCase();
if (targetTagName === 'label') {
forElement = this.findControl(targetElement);
if (forElement) {
this.focus(targetElement);
if (this.deviceIsAndroid) {
return false;
} targetElement = forElement;
}
} else if (this.needsFocus(targetElement)) {
if ((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top !== window && targetTagName === 'input')) {
this.targetElement = null;
return false;
} this.focus(targetElement);
if (!this.deviceIsIOS4 || targetTagName !== 'select') {
this.targetElement = null;
event.preventDefault();
}
return false;
}
if (this.deviceIsIOS && !this.deviceIsIOS4) {
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
}
if (!this.needsClick(targetElement)) {
event.preventDefault();
this.sendClick(targetElement, event);
}
return false;
};
注意上面的代码中,event.preventDefault();会阻止真实的click事件的触发,因此,在button上面的click事件不会触发。
接下来,我们只需要查看sendClick方法。
FastClick.prototype.sendClick = function(targetElement, event) {
'use strict';
var clickEvent, touch;
if (document.activeElement && document.activeElement !== targetElement) {
document.activeElement.blur();
}
touch = event.changedTouches[0]; clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
};
在此方法中,会创建一个自定义的click事件,然后在button上立即触发,于是,button绑定的click的事件回调函数马上执行,因此就没有300ms延迟了。
上面的initMouseEvent方法的前三个参数的意思:1.事件类型,2.是否冒泡,3.是否阻止浏览器的默认行为。
自定义的click事件阻止了浏览器的默认行为,事件冒泡,于是执行document.body的click事件回调函数。代码如下:
FastClick.prototype.onClick = function(event) {
'use strict';
var permitted;
if (this.trackingClick) {
this.targetElement = null;
this.trackingClick = false;
return true;
}
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
permitted = this.onMouse(event);
if (!permitted) {
this.targetElement = null;
}
return permitted;
};
然后里面有一句 permitted = this.onMouse(event);于是,我们查看onMouse方法:
FastClick.prototype.onMouse = function(event) {
'use strict';
if (!this.targetElement) {
return true;
}
if (event.forwardedTouchEvent) {
return true;
}
if (!event.cancelable) {
return true;
}
if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
} else {
event.propagationStopped = true;
}
event.stopPropagation();
event.preventDefault();
return false;
}
return true;
};
此方法,会阻止模拟的click事件的冒泡以及默认行为。
到此,解决了移动端浏览器click事件延迟300ms的问题。
加油!
移动端click事件延迟300ms的原因以及解决办法的更多相关文章
- 移动端click事件延迟300ms的原因以及解决办法[转载]
原文:http://www.bubuko.com/infodetail-822565.html 这要追溯至 2007 年初.苹果公司在发布首款 iPhone 前夕,遇到一个问题 —— 当时的网站都是为 ...
- 苹果浏览器移动端click事件延迟300ms的原因以及解决办法
这要追溯至 2007 年初.苹果公司在发布首款 iPhone 前夕,遇到一个问题 —— 当时的网站都是为大屏幕设备所设计的.于是苹果的工程师们做了一些约定,应对 iPhone 这种小屏幕浏览桌面端站点 ...
- 移动端click事件延迟300ms到底是怎么回事,该如何解决?
不管在移动端还是PC端,我们都需要处理用户点击,这个最常用的事件.但在touch端click事件响应速度会比较慢,在较老的手机设备上会更为明显(300ms的延迟). 问题由来 这要追溯至 2007 年 ...
- 移动端click事件延迟300ms问题
因为历史原因,移动端点击事件会有300ms延迟,来判断用户是连续双击缩放还是点击跳转.即如果300ms内连续点击两次,则会理解为对页面进行缩放操作(当然前提是移动端页面设置为可缩放的):在一次点击之后 ...
- 移动端click事件延迟300ms该如何解决
window.addEventListener( "load", function() { FastClick.attach( document.body ); }, fa ...
- 移动端click事件出现300ms延迟
问题分析: 双击缩放是指手在屏幕上快速点击两次,iOS自带的Safari浏览器会将网页缩放至原始比例.当用户在屏幕上单击某元素时,浏览器会先捕获此处单击,但浏览器不知道用户是要单击链接还是要双击该部分 ...
- 移动端click事件300ms延迟
移动端click 事件延迟300ms 一般情况下,如果没有经过特殊处理,移动端浏览器在派发点击事件的时候,通常会出现300ms左右的延迟.也就是说,当我们点击页面的时候移动端浏览器并不是立即作出反应, ...
- 设计方案--移动端延迟300ms的原因以及解决方案
一.前言 移动端浏览器提供一个特殊的功能:双击(double tap)缩放. 二.移动端延迟300ms的原因 为什么要用触摸事件?触摸事件是移动端浏览器特有的html5事件. 因为移动端的clic ...
- 移动端的传统click事件延迟和点透现象
一.场景描述: 1.A/B两个层上下z轴重叠. 2.上层的A点击后消失或移开.(这一点很重要) 3.B元素本身有默认click事件(如a标签) 或 B绑定了click事件. 在以上情况下,点击A/B重 ...
随机推荐
- 数据库整合数据报表SQL实战
协助同事整理sql统计报表. ---建立由avalue的视图,要过滤重复数据 CREATE VIEW vLectAnswerRecord as SELECT t2.OpenID,t2.Qguid,t1 ...
- WPF DataGrid常用属性记录
WPF DataGrid常用属性记录 组件常用方法: BeginEdit:使DataGrid进入编辑状态. CancelEdit:取消DataGrid的编辑状态. CollapseRowGroup:闭 ...
- table的自动增加
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- 用python脚本通过excel生成文件夹树结构
大概这样写标题是对的吧... 目标: 通过excel目录结构文档生成文件夹树结构. 也就是: 通过下面的excel
- xcode 工具学习笔记
1. 快速打开辅助界面 快捷键:使用Option + 单击文件 2. 辅助编辑器更多打开方式 快捷键: Option+shift +单击文件 3. tab页面快捷键 快捷键: Co ...
- TCP/IP详解 (转)
TCP/IP详解学习笔记(1)-基本概念 为什么会有TCP/IP协议 在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别.就好像圣经中 ...
- Mvc public virtual DbQuery<TResult> Include("")
参数 path 类型:System.String要在查询结果中返回的相关对象列表(以点号分隔). 返回值 类型:System.Data.Entity.Infrastructure.DbQuery< ...
- Notepad++的xml文本格式化
1.需要使用插件 2.使用插件
- 腾讯云Centos下Nginx反向代理Apache+Tomcat
1. 安装Apahce, PHP, MySQL以及php连接mysql库的组件#yum -y install httpd php mysql mysql-server php-mysql // ...
- redhat7下配置tomcat7,更改端口8080后无法访问页面
搞了一下午,居然是防火墙的事情,redhat7设置方法如下 sudo firewall-cmd --add-port=8081/tcp