[Javascript] Quickly.js : Touch Device에서 Click Delay를 없애는 Library.

 스마트 폰이나 태블릿과 같이 터치 환경의 Device를 위한 WebApp / WebPage를 작성하다보면 클릭 이벤트를 수신하기까지 Delay가 존재한다는 것을 느낄 수 있다.

실제로 테스트를 해보면 약 300~500ms 정도의 Delay가 존재한다.




버튼에 클릭 효과가 발생할 때까지 300~500ms 정도의 Delay가 발생한다면 사용자가 답답하다고 느낄 수 있기 때문에 터치가 시작되는 시점에 클릭 효과를 추가하고 터치가 끝나는 시점에 클릭 효과를 제거하는 방법으로 Delay를 제거할 수 있다.




이러한 작업을 위해 iScroll의 개발자로 유명한 Matteo Spinell씨가 NoClickDelay라는 Object를 만들어놨다. (http://cubiq.org/remove-onclick-delay-on-webkit-for-iphone)




하지만 실제로 사용해보니 약간의 버그?(터치가 시작되는 Element와 터치가 끝나는 Element가 서로 다를 경우 추가된 css class가 제대로 제거되지 않는 문제)가 있고 PC 환경은 지원하지 않기 때문에 모바일/PC 환경에서 동일하게 사용할 수 있도록 Quickly라는 이름으로 Custom 하였다.


PS) touchend/mouseup 이벤트 리스너를 클릭된 element가 아닌 window object에 등록하였기 때문에 터치가 시작되는 Element와 터치가 끝나는 Element가 달라도 정상 동작한다.


- Source Code -

https://github.com/RegularMotion/Quickly/


- Demo -

http://the-next.tv/quickly/


- 사용법 -

1. new Quickly(‘element_id’);

   Element의 ID 또는 Element Object를 이용하여 Quickly Object를 생성한다.


- 내부동작 -


1. 터치/클릭 이벤트가 시작되는 시점에 element에 clicked Class를 추가.


2. 터치/클릭 이벤트가 끝나는 시점에 element에서 clicked Class 제거.




- clicked CSS Class 정의 -

div#element_id {
	position: relative;
	margin: 0 auto;
	top: 25px;
	width: 150px;
	height: 150px;
	background: #548937;
}

div#element_id.clicked {
	opacity: .5;
}


- Javascript 구현부 -

function Quickly(el, callback) {
	this.callback = callback;
	this.clicked = (typeof el === 'object')? el : document.getElementById(el);
	this.clicked.addEventListener(Quickly.EVT_START, this, false);
}

Quickly.isTouch = ('ontouchstart' in window)? true : false;
Quickly.EVT_START = Quickly.isTouch? 'touchstart' : 'mousedown';
Quickly.EVT_END = Quickly.isTouch? 'touchend' : 'mouseup';

Quickly.prototype = {
	handleEvent: function(e) {
		switch (e.type) {
			case Quickly.EVT_START: this.onTouchStart(e); break;
			case Quickly.EVT_END: this.onTouchEnd(e); break;
		}
		e.stopPropagation();
		e.preventDefault();
	},

	onTouchStart: function(e) {
		this.clicked.className += (this.clicked.className === '')? 'clicked':' clicked';
		document.documentElement.addEventListener(Quickly.EVT_END, this, false);
	},

	onTouchEnd: function(e) {
		document.documentElement.removeEventListener(Quickly.EVT_END, this, false);

		if ( this.clicked ) this.clicked.className = this.clicked.className.replace(/ ?clicked/gi, '');
		if ( typeof this.callback === 'function' ) this.callback(e);
	}
};

new Quickly('element_id || dom element', function(e) {

});

- Hover Effect까지 처리해주는 Version -

function Quickly(el, callback) {
	this.callback = callback;
	this.clicked = (typeof el === 'object')? el : document.getElementById(el);
	this.subscribe(Quickly.EVT_START, this.clicked);

	if ( !Quickly.isTouch ) {
		this.hovered = this.clicked;
		this.subscribe(Quickly.EVT_HOVER, this.hovered);
	}
}

Quickly.isTouch = ('ontouchstart' in window)? true : false;
Quickly.EVT_START = Quickly.isTouch? 'touchstart' : 'mousedown';
Quickly.EVT_END   = Quickly.isTouch? 'touchend' : 'mouseup';
Quickly.EVT_HOVER = Quickly.isTouch? null : 'mouseover';
Quickly.EVT_LEAVE = Quickly.isTouch? null : 'mouseout';

Quickly.prototype = {
	handleEvent: function(e) {
		switch (e.type) {
			case Quickly.EVT_START: this.onTouchStart(e); break;
			case Quickly.EVT_END  : this.onTouchEnd(e); break;
			case Quickly.EVT_HOVER: this.onHovered(e); break;
			case Quickly.EVT_LEAVE: this.onLeaved(e); break;
		}
		e.stopPropagation();
		e.preventDefault();
	},

	onTouchStart: function(e) {
		this.clicked.className += (this.clicked.className === '')? 'clicked':' clicked';
		this.subscribe(Quickly.EVT_END);
	},

	onTouchEnd: function(e) {
		this.unsubscribe(Quickly.EVT_END);

		if ( this.clicked ) this.clicked.className = this.clicked.className.replace(/ ?clicked/gi, '');
		if ( typeof this.callback === 'function' ) this.callback(e);
	},

	onHovered: function(e) {
		this.hovered.className += (this.hovered.className === '')? 'hovered':' hovered';
		this.subscribe(Quickly.EVT_LEAVE, this.hovered);
	},

	onLeaved: function(e) {
		this.unsubscribe(Quickly.EVT_LEAVE, this.hovered);
		this.hovered.className = this.hovered.className.replace(/ ?hovered/gi, '');
	},

	subscribe: function(event, el) {
		var element = el || document.documentElement;
		element.addEventListener(event, this, false);
	},

	unsubscribe: function(event, el) {
		var element = el || document.documentElement;
		element.removeEventListener(event, this, false);
	}
};

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS