浅谈FastClick 填坑及源码解析(4)
顺道看下这里的 this.updateScrollParent:
/** * 检查target是否一个滚动容器里的子元素,如果是则给它加个标记 */ FastClick.prototype.updateScrollParent = function(targetElement) { var scrollParent, parentElement; scrollParent = targetElement.fastClickScrollParent; 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; } };
另外要注意的是,在 onTouchStart 里被标记为 true 的 this.trackingClick 属性,都会在其它事件回调(比如 ontouchmove )的开头做检测,如果没被赋值过,则直接忽略:
if (!this.trackingClick) { return true; }
当然在 ontouchend 事件里会把它重置为 false。
2. this.onTouchMove
这段代码量好少:
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; };
看下这里用到的 this.touchHasMoved 原型方法:
//判断是否移动了 //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; };
3. onTouchEnd
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; };
内容版权声明:除非注明,否则皆为本站原创文章。