FastClick 메 우기 및 소스 코드 분석
위의 그림 과 같이 구체 적 인 표현 은 빠 른 클릭 시 커서 가 textarea 내용 의 끝으로 이동 하 는 것 이다.머 무 르 는 시간 이 좀 오래 걸 릴 때(예 를 들 어 150 ms 초과)커서 를 올 바른 위치 로 정상적으로 찾 을 수 있 습 니 다.
처음에 나 는 iOS 원생 의 상호작용 문제 라 고 생각 했 지만 나중에 일부 페이지 를 방문 하 는 것 은 이런 이상 한 체험 이 없 었 다.
그리고 JS 가 어떤 사건 으로 인 한 문 제 를 등 록 했 는 지 의 심 스 러 워 업무 모듈 을 제거 하고 다시 한 번 뛰 어 보 니 문제 가 예전 과 같 았 다.
그래서 배제 법 을 계속 할 수 밖 에 없 었 습 니 다.페이지 에 있 는 라 이브 러 리 를 조금씩 옮 기 고 페이지 를 실 행 했 습 니 다.그 결과 방해 하 는 임 프 가 과연 혐의 가 가장 큰 Fastclick 인 것 을 발 견 했 습 니 다.
그 다음 에 저 는 API 의 말 에 따라 textarea 에'needsclick'이라는 유형 명 을 붙 여 보 았 습 니 다.fastclick 의 처 리 를 피해 원생 클릭 사건 으로 가 려 고 했 는데 놀 라 서 쓸모 가 없다 는 것 을 알 게 되 었 습 니 다.
이에 감 사 드 립 니 다.저희 팀 의 kindeng 어린이 신발 이 연구 와 해결 방안 을 제 공 했 습 니 다.그런데 저 는 어떤 이유 로 이 구덩이 가 생 겼 는 지 더 연구 하고 싶 습 니 다.Fastclick 은 제 페이지 에 신마 가 되 었 습 니 다.
그래서 어젯밤 에 시간 이 좀 걸 려 서
이것 은 매우 긴 문장 이지 만 주석 이 매우 상세 한 분석 문 일 것 이다.
글 에서 분석 한 소스 코드 를 나 도 내 것 에 걸 었 다github 창고관심 있 는 동 화 는 다운로드 해서 볼 수 있다.
잡담 은 그만 하고 패스 트 클릭 소스 진영 에 깊이 들 어가 자.
우 리 는 FastClick 이 벤트 를 등록 하 는 것 이 매우 간단 하 다 는 것 을 알 고 있 습 니 다.이것 은 다음 과 같 습 니 다.
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
var fc = FastClick.attach(document.body); //
}, false);
}
그래서 우 리 는 여기 서부 터 소스 코드 를 열 어 FastClick.attach 방법 을 보 겠 습 니 다.
FastClick.attach = function(layer, options) {
return new FastClick(layer, options);
};
여기 서 FastClick 인 스 턴 스 를 되 돌려 주 었 습 니 다.그래서 우 리 는 앞으로 끌 고 가서 FastClick 구조 함 수 를 봅 시다.
function FastClick(layer, options) {
var oldOnClick;
options = options || {};
// ...
// ,
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;
}
}
처음에 FastClick.notNeeded 방법 을 통 해 후속 처리 가 필요 한 지 판단 합 니 다.
// ,
if (FastClick.notNeeded(layer)) {
return;
}
이 FastClick.notNeeded 가 어떤 판단 을 했 는 지 살 펴 보 겠 습 니 다.
// 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;
};
기본적으로 300 ms 지연 을 사용 하지 않 는 브 라 우 저 탐색 입 니 다.Fastclick 을 사용 할 필요 가 없 기 때문에 true 리 턴 구조 함수 로 돌아 가 다음 실행 을 중단 합 니 다.안 드 로 이 드 손 Q 의 ua 가/chrome\/([0-9]+)/와 일치 하기 때문에'user-scalable=no'meta 태그 가 있 는 안 드 로 이 드 손 Q 페이지 는 FastClick 에 의 해 처리 할 필요 가 없 는 페이지 로 간 주 됩 니 다.
안 드 로 이 드 의 손 Q 에서 문 제 를 처음 언급 하지 않 은 이유 다.
우 리 는 구조 함 수 를 계속 보 았 습 니 다.레이 어(즉 body)에 click,touch start,touch move,touch end,touch cancel(안 드 로 이 드 와 mouseover,mousedown,mouseup)사건 감청 을 직접 추 가 했 습 니 다.
//
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);
이 코드 에 bid 방법 을 사용 하여 처리 하 였 음 을 주의 하 십시오.이 이벤트 리 셋 의 this 는 모두 Fastclick 인 스 턴 스 컨 텍스트 가 됩 니 다.또 onclick 사건 과 안 드 로 이 드 의 추가 처리 부분 은 모두 캡 처 감청 이 었 음 을 유의 해 야 한다.
우 리 는 이 사건 들 이 각각 무엇 을 했 는 지 보 자.
1. this.onTouchStart
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.updateScroll Parent 를 보 세 요.
/**
* 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.tracking Click 속성 은 다른 이벤트 리 셋(예 를 들 어 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.touch HasMoved 원형 방법 을 보 세 요:
//
//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;
};
이 단락 은 비교적 길다.우 리 는 주로 이 단락 을 본다.
} 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;
}
그 중에서 this.needs Focus 는 주어진 요소 가 click 이 벤트 를 합성 하여 초점 을 모 의 해 야 하 는 지 판단 하 는 데 사 용 됩 니 다.
// 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);
}
};
또한 이 부분 은 왜 조금 만 오래 누 르 는 지 설명 합 니 다(100 ms 초과)textarea.우 리 는 커서 를 정확 한 곳 에 위치 시 킬 수 있 습 니 다(뒤 를 돌아 this.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
관련 된 두 가지 원형 방법 은 다음 과 같다.⑴ this.focus
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();
}
};
주의,우 리 는 textarea 를 클릭 할 때 이 방법 을 호출 했 습 니 다.targetElement.set Selection Range(length,length)를 통 해 커서 의 위 치 를 내용 의 끝 에 결정 하 였 습 니 다(그러나 주의,이 때 는 초점 을 맞 추 지 않 았 습 니 다!!).⑵ this.sendClick
진정 으로 textarea 를 초점 을 맞 추 는 것 은 이 방법 입 니 다.click 방법 을 합성 하여 즉시 textarea 요소 에 초점 을 맞 추 는 것 입 니 다.
// 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';
};
이렇게 고생 한 후에 우 리 는 textarea 를 가볍게 한 후에 커서 는 자 연 스 럽 게 그 내용 의 끝 부분 에 위치 하 게 되 었 다.그런데 여기 문제 가 있 습 니 다.터치 엔 드 뒤에 있 는 포커 스 사건 은 왜 촉발 되 지 않 았 습 니까?focus 이벤트 가 촉발 된다 면 커서 를 정확 한 위치 로 다시 찾 을 수 있 을 것 입 니 다.
우리 아래 의 이 단락 을 보 자.
//iOS4 select ( ), select
// iOS6/7 (VoiceOver )
if (!deviceIsIOS || targetTagName !== 'select' ) {
this.targetElement = null;
event.preventDefault();
}
preventDefault 의 차단 을 통 해 textarea 는 더 이상 focus 아 기 를 안 을 수 없습니다~그래서 우 리 는 여기 서 변경 을 하면 이 문 제 를 복구 할 수 있 습 니 다.
var _isTextInput = function(){
return targetTagName === 'textarea' || (targetTagName === 'input' && targetElement.type === 'text');
};
if ((!deviceIsIOS || targetTagName !== 'select') && !_isTextInput()) {
this.targetElement = null;
event.preventDefault();
}
또는:
if (!deviceIsIOS4 || targetTagName !== 'select') {
this.targetElement = null;
// textarea “needsclick” class
if((!/\bneedsclick\b/).test(targetElement.className)){
event.preventDefault();
}
}
여기 서 토로 할 것 은 Fastclick 이 this.needs Click 을 ontouchEnd 끝 에 놓 고 실행 해서 앞에서 말 한 것 에'needs click'이라는 이름 을 붙 여도 무효 라 는 문제 가 생 겼 다 는 것 이다.비록 문제 의 원인 을 찾 아 도 해결 되 었 지만,우 리 는 남 은 부분 을 계속 보 자.
4.onMouse 와 onClick
// ( 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);
};
일반적인 클릭 이벤트 의 조작 을 차단 해 야 합 니 다.저 희 는 touch 감청 이벤트 리 셋 에서 처 리 했 습 니 다.여 기 는 주로 touch 과정(일부 장치 가 touch 이벤트 가 촉발 되 지 않 았 을 수도 있 습 니 다)에 대해 기본 이벤트 의 이 벤트 를 사용 하지 않 고 더 이상 처리 하지 않 았 습 니 다.원생 의 click 이 벤트 를 촉발 할 지 여 부 를 결정 합 니 다.작은 매듭
1.fastclick 소스 코드 의 addEventListener 리 셋 이벤트 에는 return false/true 가 많 습 니 다.그것들 은 뒤에 있 는 스 크 립 트 논 리 를 돌아 가 는 데 주로 사용 되 며,다른 의미 가 없습니다.(기본 이 벤트 를 막 지 않 습 니 다.)
그 러 니 jQuery 사건 이나 DOM 0 급 사건 의 리 턴 false 개념 을 addEventListener 와 섞 지 마 세 요.
2.fastclick 의 소스 코드 는 매우 간단 합 니 다.일부 기괴 한 행위 에 대해 hack 를 하 는 것 외 에 그 핵심 이념 은 target 사건 을 캡 처 하고 target 이 투과 문 제 를 해결 하려 는 요소 라 고 판단 하 며 하나의 click 사건 을 합성 하여 target 에서 촉발 하 는 동시에 preventDefault 를 통 해 기본 사건 을 사용 하지 않 습 니 다.
3.fastclick 는 좋 지만 구덩이 도 있 습 니 다.수요 에 따라 수정 해 야 합 니 다.그러면 소스 코드 를 알 아 보 는 것 이 필요 합 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
iPhone에서 WEB 페이지의 소스 코드를보고 싶을 때~통근중의 전철내에서~ 이번에 닛케이 평균 주가를 스크래핑하여 CSV에 기록하자. 빨리 사이트의 소스 코드를 보자. 뭐야? iPhone으로는 볼 수 없습니까? 곤란했다… 통근 시간 1시간도 있는데… 이제 진짜 무리…...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.