移动端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的问题。

时间: 2024-10-25 20:03:22

移动端click事件延迟300ms的原因以及解决办法[转载]的相关文章

移动端click事件延迟300ms的原因以及解决办法

这要追溯至 2007 年初.苹果公司在发布首款 iPhone 前夕,遇到一个问题 —— 当时的网站都是为大屏幕设备所设计的.于是苹果的工程师们做了一些约定,应对 iPhone 这种小屏幕浏览桌面端站点的问题.这当中最出名的,当属双击缩放(double tap to zoom).这也是会有上述 300 毫秒延迟的主要原因. 当用户一次点击屏幕之后,浏览器并不能立刻判断用户是要进行双击缩放,还是想要进行单击操作.因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕. 于是,

js移动端click事件延迟

今天做一个移动端网站,一块内容中包含两个js效果,一直以为是冲突,导致只能使用一个,后面发现是click事件延迟引起,解决代码如下:(function(){        var isTouch = ('ontouchstart' in document.documentElement) ? 'touchstart' : 'click';    if(!$.fn.quickOn){            $.fn.quickOn= function(){                argum

移动端延迟300ms的原因以及解决方案

一.前言 移动端浏览器提供一个特殊的功能:双击(double tap)缩放.   二.移动端延迟300ms的原因 为什么要用触摸事件?触摸事件是移动端浏览器特有的html5事件. 因为移动端的click有很大延迟(大约300ms),300ms延迟来自判断双击和长按,因为只有默认等待时间结束以确定没有后续动作发生时,才会触发click事件.而触摸事件的延迟则是非常短的,使用触摸事件的能够提高页面响应速度,带来更好的用户体验. 重点:由于移动端会有双击缩放的这个操作,因此浏览器在click之后要等待

js报TypeError $(...) is null错误,jquery失效的原因及解决办法

最近在工作中发现个问题,原本好好的网页,写了一些自己的jquery代 码之后,竟然总是不起作用,无论写的多么简单,都不起作用,似乎jquery失效了一般,在火狐下调试看了下,页面报TypeError $(...) is null这种错误,找了半天原因最后发现竟是页面中加载的一个插件给捣的鬼,是它将jquery的$方法给覆盖了.对于这个问题,现在分享两种解决方法. (1)删冲突插件,jquery作为基础库,当然是没有理由被删了.这个方法最直接了. (2)将jquery的$方法改名,具体改名方法如下

php_curl.dll libssh2.dll 始终无法加载的原因 及解决办法

在StackOverflow得到最终原因及解决办法 http://stackoverflow.com/questions/16424117/php-unable-to-load-php-curl-dll-extension libeay32.dll and ssleay32.dll have to be path-accessible for php_curl.dll loading to succeed. Copying them into System32 (or even into the

Android App 启动页(Splash)黑/白闪屏现象产生原因与解决办法(转)

转载: Android App 启动页(Splash)黑/白闪屏现象产生原因与解决办法   首先感谢博主分享,本文作为学习记录 惊鸿一瞥 微信的启动页,相信大家都不陌生. 不知道大家有没有发现一个现象,微信每次启动的时候,是直接进入这个启动页面. 我的意思是,很多应用,往往会先白屏停顿一下后再进入启动页面(Splash).为了印证这一点,我把手机上所有的App都点了一遍.选几个例子 如下图: 微信:  斗鱼:  斗鱼和微信是直接进入了,他们的Splash页面. 知乎:  B站:  知乎和B站要先

HttpClient的CircularRedirectException异常原因及解决办法

HttpClient的CircularRedirectException异常原因及解决办法 这两天在使用我自己爬虫抓取网页的时候总是出现 org.apache.http.client.ClientProtocolException at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:909) at org.apache.http.impl.client.AbstractHttpClie

Android intent 传值不更新的原因和解决办法

当 Activity 的启动模式是 singleTask 或者 singleInstance 的时候.如果使用了 intent 传值,则可能出现 intent 的值无法更新的问题.也就是说每次 intent 接收到的值都是第一次接到的值.因为 intent 没有被更新.想要更新需要做两件事情. 1. 发送方 Activity,加上一句话 ? 1 PendingIntent pendingIntent = PendingIntent.getActivity(context,0,intent,Pen

[转]&quot;error while loading shared libraries: xxx.so.x&quot; 错误的原因和解决办法

[转]"error while loading shared libraries: xxx.so.x" 错误的原因和解决办法 http://blog.csdn.net/sahusoft/article/details/7388617 一般我们在Linux下执行某些外部程序的时候可能会提示找不到共享库的错误, 比如: tmux: error while loading shared libraries: libevent-1.4.so.2: cannot open shared obje