fastclick-原始碼解析

舞動乾坤發表於2018-10-22
;
(function() {
	'use strict';
	//建構函式
	function FastClick(layer, options) {
		var oldOnClick;
		options = options || {};

		//是否開始追蹤click事件
		this.trackingClick = false;

		//儲存第一次按下時間戳
		this.trackingClickStart = 0;

		//目標元素
		this.targetElement = null;

		//存放座標值X
		this.touchStartX = 0;

		//存放座標值Y
		this.touchStartY = 0;

		//主要hack iOS4下的一個怪異問題
		this.lastTouchIdentifier = 0;

		//用於區分是click還是Touchmove,若出點移動超過該值則視為touchmove
		this.touchBoundary = options.touchBoundary || 10;

		// 繫結了FastClick的元素,一般是是body
		this.layer = layer;

		//雙擊最小點選時間差
		this.tapDelay = options.tapDelay || 200;

		//長按最大時間
		this.tapTimeout = options.tapTimeout || 700;

		//如果是屬於不需要處理的元素型別,則直接返回
		if(FastClick.notNeeded(layer)) {
			return;
		}

		//語法糖,相容一些用不了 Function.prototype.bind 的舊安卓
		//所以後面不走 layer.addEventListener('click', this.onClick.bind(this), true);
		function bind(method, context) {
			return function() {
				return method.apply(context, arguments);
			};
		}

		var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];
		var context = this;
		for(var i = 0, l = methods.length; i < l; i++) {
			context[methods[i]] = bind(context[methods[i]], context);
		}

		//安卓則做額外處理
		if(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('touchmove', this.onTouchMove, false);
		layer.addEventListener('touchend', this.onTouchEnd, false);
		layer.addEventListener('touchcancel', this.onTouchCancel, false);

		// 相容不支援 stopImmediatePropagation 的瀏覽器(比如 Android 2)
		if(!Event.prototype.stopImmediatePropagation) {
			layer.removeEventListener = function(type, callback, capture) {
				var rmv = Node.prototype.removeEventListener;
				if(type === 'click') {
					rmv.call(layer, type, callback.hijacked || callback, capture);
				} else {
					rmv.call(layer, type, callback, capture);
				}
			};

			layer.addEventListener = function(type, callback, capture) {
				var adv = Node.prototype.addEventListener;
				if(type === 'click') {
					//留意這裡 callback.hijacked 中會判斷 event.propagationStopped 是否為真來確保(安卓的onMouse事件)只執行一次
					//在 onMouse 事件裡會給 event.propagationStopped 賦值 true
					adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
						if(!event.propagationStopped) {
							callback(event);
						}
					}), capture);
				} else {
					adv.call(layer, type, callback, capture);
				}
			};
		}

		// 如果layer直接在DOM上寫了 onclick 方法,那我們需要把它替換為 addEventListener 繫結形式
		if(typeof layer.onclick === 'function') {
			oldOnClick = layer.onclick;
			layer.addEventListener('click', function(event) {
				oldOnClick(event);
			}, false);
			layer.onclick = null;
		}
	}

	/**
	 * Windows Phone 8.1 fakes user agent string to look like Android and iPhone.
	 *
	 * @type boolean
	 */
	var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0;

	/**
	 * Android requires exceptions.
	 *
	 * @type boolean
	 */
	var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0 && !deviceIsWindowsPhone;

	/**
	 * iOS requires exceptions.
	 *
	 * @type boolean
	 */
	var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone;

	/**
	 * iOS 4 requires an exception for select elements.
	 *
	 * @type boolean
	 */
	var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);

	/**
	 * iOS 6.0-7.* requires the target element to be manually derived
	 *
	 * @type boolean
	 */
	var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS [6-7]_\d/).test(navigator.userAgent);

	/**
	 * BlackBerry requires exceptions.
	 *
	 * @type boolean
	 */
	var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0;

	//判斷元素是否要保留穿透功能
	FastClick.prototype.needsClick = function(target) {
		switch(target.nodeName.toLowerCase()) {

			// disabled的input
			case 'button':
			case 'select':
			case 'textarea':
				if(target.disabled) {
					return true;
				}

				break;
			case 'input':

				// file元件必須通過原生click事件點選才有效
				if((deviceIsIOS && target.type === 'file') || target.disabled) {
					return true;
				}

				break;
			case 'label':
			case 'iframe':
			case 'video':
				return true;
		}

		//元素帶了名為“bneedsclick”的class也返回true
		return(/\bneedsclick\b/).test(target.className);
	};

	//判斷給定元素是否需要通過合成click事件來模擬聚焦
	FastClick.prototype.needsFocus = function(target) {
		switch(target.nodeName.toLowerCase()) {
			case 'textarea':
				return true;
			case 'select':
				return !deviceIsAndroid; //iOS下的select得走穿透點選才行
			case 'input':
				switch(target.type) {
					case 'button':
					case 'checkbox':
					case 'file':
					case 'image':
					case 'radio':
					case 'submit':
						return false;
				}

				return !target.disabled && !target.readOnly;
			default:
				//帶有名為“bneedsfocus”的class則返回true
				return(/\bneedsfocus\b/).test(target.className);
		}
	};

	//合成一個click事件並在指定元素上觸發
	FastClick.prototype.sendClick = function(targetElement, event) {
		var clickEvent, touch;

		// 在一些安卓機器中,得讓頁面所存在的 activeElement(聚焦的元素,比如input)失焦,否則合成的click事件將無效
		if(document.activeElement && document.activeElement !== targetElement) {
			document.activeElement.blur();
		}

		touch = event.changedTouches[0];

		// 合成(Synthesise) 一個 click 事件
		// 通過一個額外屬性確保它能被追蹤(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; // fastclick的內部變數,用來識別click事件是原生還是合成的
		targetElement.dispatchEvent(clickEvent); //立即觸發其click事件
	};

	FastClick.prototype.determineEventType = function(targetElement) {

		//安卓裝置下 Select 無法通過合成的 click 事件被展開,得改為 mousedown
		if(deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
			return 'mousedown';
		}

		return 'click';
	};

	//設定元素聚焦事件
	FastClick.prototype.focus = function(targetElement) {
		var length;

		// 元件建議通過setSelectionRange(selectionStart, selectionEnd)來設定游標範圍(注意這樣還沒有聚焦
		// 要等到後面觸發 sendClick 事件才會聚焦)
		// 另外 iOS7 下有些input元素(比如 date datetime month) 的 selectionStart 和 selectionEnd 特性是沒有整型值的,
		// 導致會丟擲一個關於 setSelectionRange 的模糊錯誤,它們需要改用 focus 事件觸發
		if(deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') {
			length = targetElement.value.length;
			targetElement.setSelectionRange(length, length);
		} else {
			//直接觸發其focus事件
			targetElement.focus();
		}
	};

	/**
	 * 檢查target是否一個滾動容器裡的子元素,如果是則給它加個標記
	 */
	FastClick.prototype.updateScrollParent = function(targetElement) {
		var scrollParent, parentElement;

		scrollParent = targetElement.fastClickScrollParent;

		// Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the
		// target element was moved to another parent.
		if(!scrollParent || !scrollParent.contains(targetElement)) {
			parentElement = targetElement;
			do {
				if(parentElement.scrollHeight > parentElement.offsetHeight) {
					scrollParent = parentElement;
					targetElement.fastClickScrollParent = parentElement;
					break;
				}

				parentElement = parentElement.parentElement;
			} while (parentElement);
		}

		// 給滾動容器加個標誌fastClickLastScrollTop,值為其當前垂直滾動偏移
		if(scrollParent) {
			scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;
		}
	};

	/**
	 * 返回目標元素
	 */
	FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {

		// 一些較老的瀏覽器,target 可能會是一個文字節點,得返回其DOM節點
		if(eventTarget.nodeType === Node.TEXT_NODE) {
			return eventTarget.parentNode;
		}

		return eventTarget;
	};

	FastClick.prototype.onTouchStart = function(event) {
		var targetElement, touch, selection;

		// 多指觸控的手勢則忽略
		if(event.targetTouches.length > 1) {
			return true;
		}

		targetElement = this.getTargetElementFromEventTarget(event.target); //一些較老的瀏覽器,target 可能會是一個文字節點,得返回其DOM節點
		touch = event.targetTouches[0];

		if(deviceIsIOS) { //IOS處理

			// 若使用者已經選中了一些內容(比如選中了一段文字打算複製),則忽略
			selection = window.getSelection();
			if(selection.rangeCount && !selection.isCollapsed) {
				return true;
			}

			if(!deviceIsIOS4) { //是否IOS4

				//怪異特性處理——若click事件回撥開啟了一個alert/confirm,使用者下一次tap頁面的其它地方時,新的touchstart和touchend
				//事件會擁有同一個touch.identifier(新的 touch event 會跟上一次觸發alert點選的 touch event 一樣),
				//為避免將新的event當作之前的event導致問題,這裡需要禁用預設事件
				//另外chrome的開發工具啟用'Emulate touch events'後,iOS UA下的 identifier 會變成0,所以要做容錯避免除錯過程也被禁用事件了
				if(touch.identifier && touch.identifier === this.lastTouchIdentifier) {
					event.preventDefault();
					return false;
				}

				this.lastTouchIdentifier = touch.identifier;

				// 如果target是一個滾動容器裡的一個子元素(使用了 -webkit-overflow-scrolling: touch) ,而且滿足:
				// 1) 使用者非常快速地滾動外層滾動容器
				// 2) 使用者通過tap停止住了這個快速滾動
				// 這時候最後的'touchend'的event.target會變成使用者最終手指下的那個元素
				// 所以當快速滾動開始的時候,需要做檢查target是否滾動容器的子元素,如果是,做個標記
				// 在touchend時檢查這個標記的值(滾動容器的scrolltop)是否改變了,如果是則說明頁面在滾動中,需要取消fastclick處理
				this.updateScrollParent(targetElement);
			}
		}

		this.trackingClick = true; //做個標誌表示開始追蹤click事件了
		this.trackingClickStart = event.timeStamp; //標記下touch事件開始的時間戳
		this.targetElement = targetElement;

		//標記touch起始點的頁面偏移值
		this.touchStartX = touch.pageX;
		this.touchStartY = touch.pageY;

		// this.lastClickTime 是在 touchend 裡標記的事件時間戳
		// this.tapDelay 為常量 200 (ms)
		// 此舉用來避免 phantom 的雙擊(200ms內快速點了兩次)觸發 click
		// 反正200ms內的第二次點選會禁止觸發點選的預設事件
		if((event.timeStamp - this.lastClickTime) < this.tapDelay) {
			event.preventDefault();
		}

		return true;
	};

	//判斷是否移動了
	//this.touchBoundary是常量,值為10
	//如果touch已經移動了10個偏移量單位,則應當作為移動事件處理而非click事件
	FastClick.prototype.touchHasMoved = function(event) {
		var touch = event.changedTouches[0],
			boundary = this.touchBoundary;

		if(Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
			return true;
		}

		return false;
	};

	FastClick.prototype.onTouchMove = function(event) {
		//不是需要被追蹤click的事件則忽略
		if(!this.trackingClick) {
			return true;
		}

		// 如果target突然改變了,或者使用者其實是在移動手勢而非想要click
		// 則應該清掉this.trackingClick和this.targetElement,告訴後面的事件你們也不用處理了
		if(this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
			this.trackingClick = false;
			this.targetElement = null;
		}

		return true;
	};

	//找到label標籤所對映的元件,方便讓使用者點label的時候直接啟用該元件
	FastClick.prototype.findControl = function(labelElement) {

		// 有快取則直接讀快取著的
		if(labelElement.control !== undefined) {
			return labelElement.control;
		}

		// 獲取指向的元件
		if(labelElement.htmlFor) {
			return document.getElementById(labelElement.htmlFor);
		}

		// 沒有for屬性則啟用頁面第一個元件(labellable 元素)
		return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
	};

	FastClick.prototype.onTouchEnd = function(event) {
		var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;

		if(!this.trackingClick) {
			return true;
		}

		// 避免 phantom 的雙擊(200ms內快速點了兩次)觸發 click
		// 我們在 ontouchstart 裡已經做過一次判斷了(僅僅禁用預設事件),這裡再做一次判斷
		if((event.timeStamp - this.lastClickTime) < this.tapDelay) {
			this.cancelNextClick = true; //該屬性會在 onMouse 事件中被判斷,為true則徹底禁用事件和冒泡
			return true;
		}

		//this.tapTimeout是常量,值為700
		//識別是否為長按事件,如果是(大於700ms)則忽略
		if((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
			return true;
		}

		// 得重置為false,避免input事件被意外取消
		// 例子見 https://github.com/ftlabs/fastclick/issues/156
		this.cancelNextClick = false;

		this.lastClickTime = event.timeStamp; //標記touchend時間,方便下一次的touchstart做雙擊校驗

		trackingClickStart = this.trackingClickStart;
		//重置 this.trackingClick 和 this.trackingClickStart
		this.trackingClick = false;
		this.trackingClickStart = 0;

		// iOS 6.0-7.*版本下有個問題 —— 如果layer處於transition或scroll過程,event所提供的target是不正確的
		// 所以我們們得重找 targetElement(這裡通過 document.elementFromPoint 介面來尋找)
		if(deviceIsIOSWithBadTarget) { //iOS 6.0-7.*版本
			touch = event.changedTouches[0]; //手指離開前的觸點

			// 有些情況下 elementFromPoint 裡的引數是預期外/不可用的, 所以還得避免 targetElement 為 null
			targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
			// target可能不正確需要重找,但fastClickScrollParent是不會變的
			targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
		}

		targetTagName = targetElement.tagName.toLowerCase();
		if(targetTagName === 'label') { //是label則啟用其指向的元件
			forElement = this.findControl(targetElement);
			if(forElement) {
				this.focus(targetElement);
				//安卓直接返回(無需合成click事件觸發,因為點選和啟用元素不同,不存在點透)
				if(deviceIsAndroid) {
					return false;
				}

				targetElement = forElement;
			}
		} else if(this.needsFocus(targetElement)) { //非label則識別是否需要focus的元素

			//手勢停留在元件元素時長超過100ms,則置空this.targetElement並返回
			//(而不是通過呼叫this.focus來觸發其聚焦事件,走的原生的click/focus事件觸發流程)
			//這也是為何文章開頭提到的問題中,稍微久按一點(超過100ms)textarea是可以把游標定位在正確的地方的原因
			//另外iOS下有個意料之外的bug——如果被點選的元素所在文件是在iframe中的,手動呼叫其focus的話,
			//會發現你往其中輸入的text是看不到的(即使value做了更新),so這裡也直接返回
			if((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
				this.targetElement = null;
				return false;
			}

			this.focus(targetElement);
			this.sendClick(targetElement, event); //立即觸發其click事件,而無須等待300ms

			//iOS4下的 select 元素不能禁用預設事件(要確保它能被穿透),否則不會開啟select目錄
			//有時候 iOS6/7 下(VoiceOver開啟的情況下)也會如此
			if(!deviceIsIOS || targetTagName !== 'select') {
				this.targetElement = null;
				event.preventDefault();
			}

			return false;
		}

		if(deviceIsIOS && !deviceIsIOS4) {

			// 滾動容器的垂直滾動偏移改變了,說明是容器在做滾動而非點選,則忽略
			scrollParent = targetElement.fastClickScrollParent;
			if(scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
				return true;
			}
		}

		// 檢視元素是否無需處理的白名單內(比如加了名為“needsclick”的class)
		// 不是白名單的則照舊預防穿透處理,立即觸發合成的click事件
		if(!this.needsClick(targetElement)) {
			event.preventDefault();
			this.sendClick(targetElement, event);
		}

		return false;
	};

	FastClick.prototype.onTouchCancel = function() {
		this.trackingClick = false;
		this.targetElement = null;
	};

	//用於決定是否允許穿透事件(觸發layer的click預設事件)
	FastClick.prototype.onMouse = function(event) {

		// touch事件一直沒觸發
		if(!this.targetElement) {
			return true;
		}

		if(event.forwardedTouchEvent) { //觸發的click事件是合成的
			return true;
		}

		// 程式設計派生的事件所對應元素事件可以被允許
		// 確保其沒執行過 preventDefault 方法(event.cancelable 不為 true)即可
		if(!event.cancelable) {
			return true;
		}

		// 需要做預防穿透處理的元素,或者做了快速(200ms)雙擊的情況
		if(!this.needsClick(this.targetElement) || this.cancelNextClick) {
			//停止當前預設事件和冒泡
			if(event.stopImmediatePropagation) {
				event.stopImmediatePropagation();
			} else {

				// 不支援 stopImmediatePropagation 的裝置(比如Android 2)做標記,
				// 確保該事件回撥不會執行(見126行)
				event.propagationStopped = true;
			}

			// 取消事件和冒泡
			event.stopPropagation();
			event.preventDefault();

			return false;
		}

		//允許穿透
		return true;
	};

	//click事件常規都是touch事件衍生來的,也排在touch後面觸發。
	//對於那些我們在touch事件過程沒有禁用掉預設事件的event來說,我們還需要在click的捕獲階段進一步
	//做判斷決定是否要禁掉點選事件(防穿透)
	FastClick.prototype.onClick = function(event) {
		var permitted;

		// 如果還有 trackingClick 存在,可能是某些UI事件阻塞了touchEnd 的執行
		if(this.trackingClick) {
			this.targetElement = null;
			this.trackingClick = false;
			return true;
		}

		// 依舊是對 iOS 怪異行為的處理 —— 如果使用者點選了iOS模擬器裡某個表單中的一個submit元素
		// 或者點選了彈出來的鍵盤裡的“Go”按鈕,會觸發一個“偽”click事件(target是一個submit-type的input元素)
		if(event.target.type === 'submit' && event.detail === 0) {
			return true;
		}

		permitted = this.onMouse(event);

		if(!permitted) { //如果點選是被允許的,將this.targetElement置空可以確保onMouse事件裡不會阻止預設事件
			this.targetElement = null;
		}

		//沒有多大意義
		return permitted;
	};

	//銷燬Fastclick所註冊的監聽事件。是給外部例項去呼叫的
	FastClick.prototype.destroy = function() {
		var layer = this.layer;

		if(deviceIsAndroid) {
			layer.removeEventListener('mouseover', this.onMouse, true);
			layer.removeEventListener('mousedown', this.onMouse, true);
			layer.removeEventListener('mouseup', this.onMouse, true);
		}

		layer.removeEventListener('click', this.onClick, true);
		layer.removeEventListener('touchstart', this.onTouchStart, false);
		layer.removeEventListener('touchmove', this.onTouchMove, false);
		layer.removeEventListener('touchend', this.onTouchEnd, false);
		layer.removeEventListener('touchcancel', this.onTouchCancel, false);
	};

	//是否沒必要使用到 Fastclick 的檢測
	FastClick.notNeeded = function(layer) {
		var metaViewport;
		var chromeVersion;
		var blackberryVersion;
		var firefoxVersion;

		// 不支援觸控的裝置
		if(typeof window.ontouchstart === 'undefined') {
			return true;
		}

		// 獲取Chrome版本號,若非Chrome則返回0
		chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [, 0])[1];

		if(chromeVersion) {

			if(deviceIsAndroid) { //安卓
				metaViewport = document.querySelector('meta[name=viewport]');

				if(metaViewport) {
					// 安卓下,帶有 user-scalable="no" 的 meta 標籤的 chrome 是會自動禁用 300ms 延遲的,所以無需 Fastclick
					if(metaViewport.content.indexOf('user-scalable=no') !== -1) {
						return true;
					}
					// 安卓Chrome 32 及以上版本,若帶有 width=device-width 的 meta 標籤也是無需 FastClick 的
					if(chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) {
						return true;
					}
				}

				// 其它的就肯定是桌面級的 Chrome 了,更不需要 FastClick 啦
			} else {
				return true;
			}
		}

		if(deviceIsBlackBerry10) { //黑莓,和上面安卓同理,就不寫註釋了
			blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/);

			if(blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) {
				metaViewport = document.querySelector('meta[name=viewport]');

				if(metaViewport) {
					if(metaViewport.content.indexOf('user-scalable=no') !== -1) {
						return true;
					}

					if(document.documentElement.scrollWidth <= window.outerWidth) {
						return true;
					}
				}
			}
		}

		// 帶有 -ms-touch-action: none / manipulation 特性的 IE10 會禁用雙擊放大,也沒有 300ms 時延
		if(layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') {
			return true;
		}

		// Firefox檢測,同上
		firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [, 0])[1];

		if(firefoxVersion >= 27) {

			metaViewport = document.querySelector('meta[name=viewport]');
			if(metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) {
				return true;
			}
		}

		// IE11 推薦使用沒有“-ms-”字首的 touch-action 樣式特性名
		if(layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
			return true;
		}

		return false;
	};

	FastClick.attach = function(layer, options) {
		return new FastClick(layer, options);
	};

	if(typeof define === 'function' && typeof define.amd === 'object' && define.amd) {

		// AMD. Register as an anonymous module.
		define(function() {
			return FastClick;
		});
	} else if(typeof module !== 'undefined' && module.exports) {
		module.exports = FastClick.attach;
		module.exports.FastClick = FastClick;
	} else {
		window.FastClick = FastClick;
	}
}());
複製程式碼
<!DOCTYPE html>
<html lang="en">

	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>Demo</title>
		<script src="./fastclick.js"></script>
		<style>
			div {
				width: 200px;
				background: red;Y
				margin: 0 auto;
				height: 200px;
				color: wheat;
				font-size: 25px;
				display: flex;
				justify-content: center;
				align-items: center;
			}
		</style>
	</head>

	<body>
		<div id="main">FastClick</div>

		<script>
			FastClick.attach(document.body);
			document.getElementById("main").addEventListener("click", function(event) {
				console.log(event.target.innerText)
			}, false)
		</script>
	</body>

</html>
複製程式碼

相關文章