Title: fastclick源码解析 · Issue #6 · wython/wython.github.io · GitHub
Open Graph Title: fastclick源码解析 · Issue #6 · wython/wython.github.io
X Title: fastclick源码解析 · Issue #6 · wython/wython.github.io
Description: fastclick是什么,它解决了什么问题呢?fastclick解决了移动端点击的约300ms延迟问题。当触发click事件时,无法判断用户是想进行双击还是单击,所以有个约300ms的判断是否会进行第二次点击操作。 基本结构 function FastClick(layer, options) {} // ...... FastClick.attach = function(layer, options) { return new FastClick(layer, op...
Open Graph Description: fastclick是什么,它解决了什么问题呢?fastclick解决了移动端点击的约300ms延迟问题。当触发click事件时,无法判断用户是想进行双击还是单击,所以有个约300ms的判断是否会进行第二次点击操作。 基本结构 function FastClick(layer, options) {} // ...... FastClick.attach = function(layer, o...
X Description: fastclick是什么,它解决了什么问题呢?fastclick解决了移动端点击的约300ms延迟问题。当触发click事件时,无法判断用户是想进行双击还是单击,所以有个约300ms的判断是否会进行第二次点击操作。 基本结构 function FastClick(layer, options) {} // ...... FastClick.attach = function(layer, o...
Opengraph URL: https://github.com/wython/wython.github.io/issues/6
X: @github
Domain: patch-diff.githubusercontent.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"fastclick源码解析","articleBody":"fastclick是什么,它解决了什么问题呢?fastclick解决了移动端点击的约300ms延迟问题。当触发click事件时,无法判断用户是想进行双击还是单击,所以有个约300ms的判断是否会进行第二次点击操作。\r\n\r\n### 基本结构\r\n```javascript\r\nfunction FastClick(layer, options) {}\r\n// ......\r\nFastClick.attach = function(layer, options) {\r\n return new FastClick(layer, options);\r\n};\r\n// 这个方法用于判断是否需要fastclick,因为如果用户设置了如禁止播放的功能,\r\n// 那么默认将不需要fastClick, 这个方法代码长,这里不贴,里面是关于不同版本\r\n// 浏览器判断和对user-scalable的获取判断\r\nFastClick.notNeeded = function(layer) { // ... }\r\n```\r\n可以看到基本结构很简单,定义一个构造函数,并且挂载一个静态函数attach(js没有面向对象语法,但是如果在构造函数挂载函数,实际上等同于面向对象的Class.staticMethod,即不需要实例即可访问的函数,同理,原型上的方法是需要创建实例才能使用)。\r\nattach方法就是创建一个FastClick实例,所以基本上,fastClick的操作就在构造函数初始化内完成。接下来继续看里面的内容。\r\n\r\n### 对象结构\r\nattach方法已经拜读,可以先忽略了。\r\n```javascript\r\n// 对象属性\r\nfunction FastClick(layer, options) {\r\n this.trackingClick = false; // 当touchStart时候置为true\r\n this.trackingClickStart = 0; // 当touchStart时候的时间错 \r\n this.targetElement = null; // 触发的元素\r\n this.touchStartX = 0; // touch位置x轴\r\n this.touchStartY = 0; // touch位置y轴\r\n this.lastTouchIdentifier = 0; // 注释:最后触摸的id,取自Touch.identifier.\r\n this.touchBoundary = options.touchBoundary || 10; // move边界\r\n this.layer = layer; // 挂载的dom元素,该dom元素下fastclick将生效\r\n this.tapDelay = options.tapDelay || 200;\r\n this.tapTimeout = options.tapTimeout || 700;\r\n // ... 执行过程\r\n}\r\n```\r\n构造函数执行过程\r\n```javascript\r\nfunction FastClick(layer, options) {\r\n // ...属性已省略\r\n var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel']; // 列举需要挂载实例中回到到layer上的事件\r\n var context = this;\r\n for (var i = 0, l = methods.length; i \u003c l; i++) {\r\n context[methods[i]] = bind(context[methods[i]], context); // 绑定fastclick对象的上下文给回调函数, 这些回调是定义在构造函数的prototype上,后面会看到\r\n }\r\n // Set up event handlers as required 按要求挂载处理事件\r\n if (deviceIsAndroid) {\r\n layer.addEventListener('mouseover', this.onMouse, true);\r\n layer.addEventListener('mousedown', this.onMouse, true);\r\n layer.addEventListener('mouseup', this.onMouse, true);\r\n }\r\n layer.addEventListener('click', this.onClick, true);\r\n layer.addEventListener('touchstart', this.onTouchStart, false);\r\n layer.addEventListener('touchmove', this.onTouchMove, false);\r\n layer.addEventListener('touchend', this.onTouchEnd, false);\r\n layer.addEventListener('touchcancel', this.onTouchCancel, false);\r\n // ...\t\r\n}\r\n```\r\n构造函数核心过程如图,它会给layer挂载需要处理的回调事件,layer作为上层节点,能够获取到target节点所发生的事件,至于在各种事件中如何处理这个问题,我们会在后续的代码中看到。\r\n\r\nbind方法的实现如下, 简单看下,没啥说的,比较简单:\r\n```javascript\r\n// Some old versions of Android don't have Function.prototype.bind\r\nfunction bind(method, context) {\r\n return function() { return method.apply(context, arguments); };\r\n}\r\n```\r\n### 设计思路\r\n关于设计思路,就是说如果是我们模拟这样一个事件,我们需要怎么去判断为点击事件,我想大概可以从几个点去考虑。\r\n\r\n1. 一个点击事件,它必须包含一个点,一个起的动作(start, end)。\r\n2. 一个点击事件,它必须是迅速的,也就是说,如果你一直按着不动,那么这不是一个点击事件,而是一个长按事件,所以我们还需要一个时间区间,对于在这个时间区间范围内我们才认为是点击事件。所以我们看fastclick的源码属性中,tapDelay就是这样的作用,默认是200ms,可以人为配置。\r\n3. 一个点击事件,它必须是短距的,也就是说,如果你移动了一个比较长的距离,那么我们认定为是一个移动事件,这也不算是点击事件,可以在touchMove里面去判断。\r\n4. 如果满足以上条件,我们就阻止默认click事件,并且自定义一个事件click\r\n### Fastclick的原型方法\r\n原型方法即实例方法\r\n``` javascript\r\nFastClick.prototype.focus = function(targetEle) { // ... };\r\nFastClick.prototype.updateScrollParent = function(targetEle) { // .. };\r\nFastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) { // ... };\r\nFastClick.prototype.touchHasMoved = function(event) { // ... };\r\nFastClick.prototype.findControl = function(event) { // ... };\r\nFastClick.prototype.onTouch = function(event) { // ... };\r\nFastClick.prototype.destroy = function(event) { // ... };\r\n\r\nFastClick.prototype.onTouchStart = function(event) { // ... };\r\nFastClick.prototype.onTouchMove = function(event) { // ... };\r\nFastClick.prototype.onMouse = function(event) { // ... };\r\nFastClick.prototype.onClick = function(event) { // ... };\r\nFastClick.prototype.onTouchCancel = function(event) { // ... };\r\n```\r\n\r\n首先,按照我们预想的思路, 应该想看touchStart方法.\r\n```javascript\r\nFastClick.prototype.onTouchStart = function(event) {\r\n var targetElement, touch, selection;\r\n\r\n // Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).\r\n if (event.targetTouches.length \u003e 1) {\r\n\treturn true;\r\n }\r\n\r\n targetElement = this.getTargetElementFromEventTarget(event.target);\r\n touch = event.targetTouches[0];\r\n if (deviceIsIOS) { // ... 此省略部分issue兼容代码 }\r\n this.trackingClick = true;\r\n this.trackingClickStart = event.timeStamp;\r\n this.targetElement = targetElement;\r\n\r\n this.touchStartX = touch.pageX;\r\n this.touchStartY = touch.pageY;\r\n \r\n // Prevent phantom clicks on fast double-tap (issue #36)\r\n if ((event.timeStamp - this.lastClickTime) \u003c this.tapDelay) {\r\n event.preventDefault();\r\n }\r\n return true;\r\n}\r\n```\r\ntouchStart方法主要存储几个状态属性, trackingClick用于其他事件里面追逐,是否触发click以这个属性为准,trackingClickStart 用于存储点下去时候的时间戳, touchStartX, touchStartY分别为点击事件的X, Y轴。至于其他工作,比如忽略多指操作(即不追踪)。\r\n\r\n```javascript\r\nFastClick.prototype.onTouchHove = function(event) {\r\n // trackingClick是是否追踪,不追踪直接返回\r\n if (!this.trackingClick) {\r\n return true;\r\n }\r\n\r\n // If the touch has moved, cancel the click tracking\r\n if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {\r\n this.trackingClick = false;\r\n this.targetElement = null;\r\n}\r\n\r\n return true;\r\n}\r\n```\r\n\r\n这是touchMove的处理事件,主要就是两个判断,一个是如果move事件触发元素和touchStart的元素不是一个元素,即已经有移动,则不触发,touchHasMoved方法必然是用于算移动距离是否已经超过限制,超过则不追踪click。\r\ntouchHasMoved实现如下:\r\n```javascript\r\nFastClick.prototype.touchHasMoved = function(event) {\r\n var touch = event.changedTouches[0], boundary = this.touchBoundary;\r\n\r\n if (Math.abs(touch.pageX - this.touchStartX) \u003e boundary || Math.abs(touch.pageY - this.touchStartY) \u003e boundary) {\r\n return true;\r\n }\r\n\r\n return false;\r\n};\r\n```\r\n这里有个值得了解的e.changedTouches属性, 属于touch event属性,学习了。\r\n\r\n接下来就是最后一步,touchEnd,按我们预计,最后应该是自定义click事件。我看了下,整个end事件处理很多东西,但是最终做了这些事情,判断是否需要click,(包括长按,之前move,start不满足click条件的都return ture), 然后阻止默认click事件,自定义click事件,fastClick会对class为needsClick的元素进行过滤,即不需要fastclick的元素将不会执行自定义click事件。关于其兼容判断,我暂时删除。删除完的代码如下:\r\n```javascript \r\nFastClick.prototype.onTouchEnd = function (event) {\r\n if (!this.trackingClick) {\r\n return true;\r\n }\r\n \r\n // 长按校验\r\n if ((event.timeStamp - this.trackingClickStart) \u003e this.tapTimeout) {\r\n return true;\r\n }\r\n \r\n // 阻止默认click事件, 自定义click事件\r\n if (!this.needsClick(event.target)) {\r\n event.preventDefault();\r\n this.sendClick(targetElement, event);\r\n }\r\n}\r\n```\r\n\r\n到这里流程其实已经结束了,最后我们还可以看下如何去模拟click实现,关于sendClick,fastClick是这样实现的:\r\n\r\n```javascript\r\nFastClick.prototype.sendClick = function(targetElement, event) {\r\n var clickEvent, touch;\r\n\r\n // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)\r\n if (document.activeElement \u0026\u0026 document.activeElement !== targetElement) {\r\n document.activeElement.blur();\r\n }\r\n\r\n touch = event.changedTouches[0];\r\n\r\n\t\t// Synthesise a click event, with an extra attribute so it can be tracked\r\n clickEvent = document.createEvent('MouseEvents');\r\n clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);\r\n clickEvent.forwardedTouchEvent = true;\r\n targetElement.dispatchEvent(clickEvent);\r\n};\r\n\r\nFastClick.prototype.determineEventType = function(targetElement) {\r\n\r\n\t\t//Issue #159: Android Chrome Select Box does not open with a synthetic click event\r\n if (deviceIsAndroid \u0026\u0026 targetElement.tagName.toLowerCase() === 'select') {\r\n return 'mousedown';\r\n }\r\n\r\n return 'click';\r\n}; \r\n```\r\n\r\n实际上,就是通过DOM接口的createEvent创建一个模拟事件,DOM提供了模拟事件的api: createEvent(TYPE), 此时会返回一个initMouseEvent方法,需要通过这个方法定义相关的事件名称,和一些额外的参数,最后需要dispatchEvent这个事件。自定义事件的流程为:\r\n createEvent ---\u003e initMounseEvent ---\u003e dispatchEvent\r\n\r\n叫做click覆盖默认的click,这个事件依然会冒泡,依然可以委托。这就是整个fastclick的思路,到这里,就结束了。但是fastclick还有很多兼容性工作,感兴趣的可以移步到fastclick仓库拜读。\r\n\r\n### 其他\r\n最后,补充几个点,\r\n1. fastclick中,在构造函数阶段,会判断meta标签,如果移动端已经禁止缩放,fastclick不会执行任何操作,以上没有贴出了。\r\n2. 通过在touchstart和end中,使用preventDefault,会阻止默认的click事件,但是不会影响start --\u003e end的触发。","author":{"url":"https://github.com/wython","@type":"Person","name":"wython"},"datePublished":"2019-08-21T16:39:40.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/6/wython.github.io/issues/6"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:afbc3eec-9bff-1d45-891a-0b93c0ca95ff |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | A490:38680B:677806:837A1F:69757F0E |
| html-safe-nonce | e903a38d6d8c81de0c62b87e6c82ed4738687c8efce4262b2a41e90482379a28 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBNDkwOjM4NjgwQjo2Nzc4MDY6ODM3QTFGOjY5NzU3RjBFIiwidmlzaXRvcl9pZCI6IjgzNjIxNTMwNTYyMjAzMTU0MDYiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 959d0baf6c41d9e0c470ab95080ce63b7b92c84284542ff2ee42dfd888dff2cd |
| hovercard-subject-tag | issue:483530552 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/wython/wython.github.io/6/issue_layout |
| twitter:image | https://opengraph.githubassets.com/003a35aa3302aba8d93311c3028d31d0bfee18f2cb89aa9af48997a4d261f9e0/wython/wython.github.io/issues/6 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/003a35aa3302aba8d93311c3028d31d0bfee18f2cb89aa9af48997a4d261f9e0/wython/wython.github.io/issues/6 |
| og:image:alt | fastclick是什么,它解决了什么问题呢?fastclick解决了移动端点击的约300ms延迟问题。当触发click事件时,无法判断用户是想进行双击还是单击,所以有个约300ms的判断是否会进行第二次点击操作。 基本结构 function FastClick(layer, options) {} // ...... FastClick.attach = function(layer, o... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | wython |
| hostname | github.com |
| expected-hostname | github.com |
| None | 4a4bf5f4e28041a9d2e5c107d7d20b78b4294ba261cab243b28167c16a623a1f |
| turbo-cache-control | no-preview |
| go-import | github.com/wython/wython.github.io git https://github.com/wython/wython.github.io.git |
| octolytics-dimension-user_id | 15258919 |
| octolytics-dimension-user_login | wython |
| octolytics-dimension-repository_id | 142893945 |
| octolytics-dimension-repository_nwo | wython/wython.github.io |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 142893945 |
| octolytics-dimension-repository_network_root_nwo | wython/wython.github.io |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 488b30e96dfd057fbbe44c6665ccbc030b729dde |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width