Touch.js是移動設備上的手勢識別與事件庫, 由百度雲Clouda團隊維護,也是在百度內部廣泛使用的開發工具.
Touch.js的代碼已託管於github並開源,希望能幫助國內更多的開發者學習和開發出優秀的App產品.
Touch.js手勢庫專爲移動設備設計, 請在Webkit內核瀏覽器中使用.
極速CDN
<script src="http://code.baidu.com/touch-0.2.14.min.js"></script>
Examples
//swipe example
touch.on('.target', 'swipeleft swiperight', function(ev){
console.log("you have done", ev.type);
});
向左及向右 滑動更靈活的解決方案
參考牛人博客的代碼,據說相當好用。
1(function(global,doc,factoryFn){
2 //初始化toucher主方法
3 var factory = factoryFn(global,doc);
4
5 //提供window.util.toucher()接口
6 global.util = global.util || {};
7 global.util.toucher = global.util.toucher || factory;
8
9 //提供CommonJS規範的接口
10 global.define && define(function(require,exports,module){
11 //對外接口
12 return factory;
13 });
14 })(this,document,function(window,document){
15 /**
16 * 判斷是否擁有某個class
17 */
18 function hasClass(dom,classSingle){
19 return dom.className.match(new RegExp('(\\s|^)' + classSingle +'(\\s|$)'));
20 }
21
22 /**
23 * @method 向句柄所在對象增加事件監聽
24 * @description 支持鏈式調用
25 *
26 * @param string 事件名
27 * @param [string] 事件委託至某個class(可選)
28 * @param function 符合條件的事件被觸發時需要執行的回調函數
29 *
30 */
31 function ON(eventStr,a,b){
32 this._events = this._events || {};
33 var className,fn;
34 if(typeof(a) == 'string'){
35 className = a.replace(/^\./,'');
36 fn = b;
37 }else{
38 className = null;
39 fn = a;
40 }
41 //檢測callback是否合法,事件名參數是否存在·
42 if(typeof(fn) == 'function' && eventStr && eventStr.length){
43 var eventNames = eventStr.split(/\s+/);
44 for(var i=0,total=eventNames.length;i<total;i++){
45
46 var eventName = eventNames[i];
47 //事件堆無該事件,創建一個事件堆
48 if(!this._events[eventName]){
49 this._events[eventName] = [];
50 }
51 this._events[eventName].push({
52 'className' : className,
53 'fn' : fn
54 });
55 }
56 }
57
58 //提供鏈式調用的支持
59 return this;
60 }
61
62 /**
63 * @method 事件觸發器
64 * @description 根據事件最原始被觸發的target,逐級向上追溯事件綁定
65 *
66 * @param string 事件名
67 * @param object 原生事件對象
68 */
69 function EMIT(eventName,e){
70 this._events = this._events || {};
71 //事件堆無該事件,結束觸發
72 if(!this._events[eventName]){
73 return
74 }
75 //記錄尚未被執行掉的事件綁定
76 var rest_events = this._events[eventName];
77
78 //從事件源:target開始向上冒泡
79 var target = e.target;
80 while (1) {
81 //若沒有需要執行的事件,結束冒泡
82 if(rest_events.length ==0){
83 return;
84 }
85 //若已經冒泡至頂,檢測頂級綁定,結束冒泡
86 if(target == this.dom || !target){
87 //遍歷剩餘所有事件綁定
88 for(var i=0,total=rest_events.length;i<total;i++){
89 var classStr = rest_events[i]['className'];
90 var callback = rest_events[i]['fn'];
91 //未指定事件委託,直接執行
92 if(classStr == null){
93 event_callback(eventName,callback,target,e);
94 }
95 }
96 return;
97 }
98
99 //當前需要校驗的事件集
100 var eventsList = rest_events;
101 //置空尚未執行掉的事件集
102 rest_events = [];
103
104 //遍歷事件所有綁定
105 for(var i=0,total=eventsList.length;i<total;i++){
106 var classStr = eventsList[i]['className'];
107 var callback = eventsList[i]['fn'];
108 //符合事件委託,執行
109 if(hasClass(target,classStr)){
110 //返回false停止事件冒泡及後續事件,其餘繼續執行
111 if(event_callback(eventName,callback,target,e) == false){
112 return
113 }
114 }else{
115 //不符合執行條件,壓回到尚未執行掉的列表中
116 rest_events.push(eventsList[i]);
117 }
118 }
119 //向上冒泡
120 target = target.parentNode;
121 }
122 }
123
124 /**
125 * 執行綁定的回調函數,並創建一個事件對象
126 * @param[string]事件名
127 * @param[function]被執行掉的函數
128 * @param[object]指向的dom
129 * @param[object]原生event對象
130 */
131 function event_callback(name,fn,dom,e){
132 var touch = e.touches.length ? e.touches[0] : {};
133
134 var newE = {
135 'type' : name,
136 'target' : e.target,
137 'pageX' : touch.clientX || 0,
138 'pageY' : touch.clientY || 0
139 };
140 //爲swipe事件增加交互初始位置及移動距離
141 if(name == 'swipe' && e.startPosition){
142 newE.startX = e.startPosition['pageX'],
143 newE.startY = e.startPosition['pageY'],
144 newE.moveX = newE.pageX - newE.startX,
145 newE.moveY = newE.pageY - newE.startY
146 }
147 var call_result = fn.call(dom,newE);
148 //若綁定方法返回false,阻止瀏覽器默認事件
149 if(call_result == false){
150 e.preventDefault();
151 e.stopPropagation();
152 }
153
154 return call_result;
155 }
156 /**
157 * 判斷swipe方向
158 */
159 function swipeDirection(x1, x2, y1, y2) {
160 return Math.abs(x1 - x2) >=
161 Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
162 }
163
164 /**
165 * 監聽原生的事件,主動觸發模擬事件
166 *
167 */
168 function eventListener(DOM){
169 var this_touch = this;
170
171 //輕擊開始時間
172 var touchStartTime = 0;
173
174 //記錄上一次點擊時間
175 var lastTouchTime = 0;
176
177 //記錄初始輕擊的位置
178 var x1,y1,x2,y2;
179
180 //輕擊事件的延時器
181 var touchDelay;
182
183 //測試長按事件的延時器
184 var longTap;
185
186 //記錄當前事件是否已爲等待結束的狀態
187 var isActive = false;
188 //記錄有座標信息的事件
189 var eventMark = null;
190 //單次用戶操作結束
191 function actionOver(e){
192 isActive = false;
193 clearTimeout(longTap);
194 clearTimeout(touchDelay);
195 }
196
197 //觸屏開始
198 function touchStart(e){
199 //緩存事件
200 eventMark = e;
201
202 x1 = e.touches[0].pageX;
203 y1 = e.touches[0].pageY;
204 x2 = 0;
205 y2 = 0;
206 isActive = true;
207 touchStartTime = new Date();
208 EMIT.call(this_touch,'swipeStart',e);
209 //檢測是否爲長按
210 clearTimeout(longTap);
211 longTap = setTimeout(function(){
212 actionOver(e);
213 //斷定此次事件爲長按事件
214 EMIT.call(this_touch,'longTap',e);
215 },500);
216 }
217 //觸屏結束
218 function touchend(e){
219 //touchend中,拿不到座標位置信息,故使用全局保存下的事件
220 EMIT.call(this_touch,'swipeEnd',eventMark);
221 if(!isActive){
222 return
223 }
224 var now = new Date();
225 if(now - lastTouchTime > 260){
226 touchDelay = setTimeout(function(){
227 //斷定此次事件爲輕擊事件
228 actionOver();
229 EMIT.call(this_touch,'singleTap',eventMark);
230 },250);
231 }else{
232 clearTimeout(touchDelay);
233 actionOver(e);
234 //斷定此次事件爲連續兩次輕擊事件
235 EMIT.call(this_touch,'doubleTap',eventMark);
236 }
237 lastTouchTime = now;
238 }
239
240 //手指移動
241 function touchmove(e){
242 //緩存事件
243 eventMark = e;
244 //在原生事件基礎上記錄初始位置(爲swipe事件增加參數傳遞)
245 e.startPosition = {
246 'pageX' : x1,
247 'pageY' : y1
248 };
249 //斷定此次事件爲移動事件
250 EMIT.call(this_touch,'swipe',e);
251
252 if(!isActive){
253 return
254 }
255 x2 = e.touches[0].pageX
256 y2 = e.touches[0].pageY
257 if(Math.abs(x1-x2)>2 || Math.abs(y1-y2)>2){
258 //斷定此次事件爲移動手勢
259 var direction = swipeDirection(x1, x2, y1, y2);
260 EMIT.call(this_touch,'swipe' + direction,e);
261 }else{
262 //斷定此次事件爲輕擊事件
263 actionOver(e);
264 EMIT.call(this_touch,'singleTap',e);
265 }
266 actionOver(e);
267 }
268
269 /**
270 * 對開始手勢的監聽
271 */
272 DOM.addEventListener('touchstart',touchStart);
273 DOM.addEventListener('MSPointerDown',touchStart);
274 DOM.addEventListener('pointerdown',touchStart);
275
276 /**
277 * 對手勢結束的監聽(輕擊)
278 */
279 DOM.addEventListener('touchend',touchend);
280 DOM.addEventListener('MSPointerUp',touchend);
281 DOM.addEventListener('pointerup',touchend);
282
283 /**
284 * 對移動手勢的監聽
285 */
286 DOM.addEventListener('touchmove',touchmove);
287 DOM.addEventListener('MSPointerMove',touchmove);
288 DOM.addEventListener('pointermove',touchmove);
289
290 /**
291 * 對移動結束的監聽
292 */
293 DOM.addEventListener('touchcancel',actionOver);
294 DOM.addEventListener('MSPointerCancel',actionOver);
295 DOM.addEventListener('pointercancel',actionOver);
296 }
297
298 /**
299 * touch類
300 *
301 */
302 function touch(DOM,param){
303 var param = param || {};
304
305 this.dom = DOM;
306 //監聽DOM原生事件
307 eventListener.call(this,this.dom);
308 }
309 //拓展事件綁定方法
310 touch.prototype['on'] = ON;
311
312 //對外提供接口
313 return function (dom){
314 return new touch(dom);
315 };
316 });
解決除谷歌瀏覽器之外的瀏覽器兼容性方案
1:jquery mobile裏面的touch組件。
1:百度的童鞋們實現的touch.js.網址也貼一下吧: http://touch.code.baidu.com/
3:參考大神的:
(function($) {
var options, Events, Touch;
options = {
x: 20,
y: 20
};
Events = ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'tap', 'longTap', 'drag'];
Events.forEach(function(eventName) {
$.fn[eventName] = function() {
var touch = new Touch($(this), eventName);
touch.start();
if (arguments[1]) {
options = arguments[1]
}
return this.on(eventName, arguments[0])
}
});
Touch = function() {
var status, ts, tm, te;
this.target = arguments[0];
this.e = arguments[1]
};
Touch.prototype.framework = function(e) {
e.preventDefault();
var events;
if (e.changedTouches) events = e.changedTouches[0];
else events = e.originalEvent.touches[0];
return events
};
Touch.prototype.start = function() {
var self = this;
self.target.on("touchstart",
function(event) {
event.preventDefault();
var temp = self.framework(event);
var d = new Date();
self.ts = {
x: temp.pageX,
y: temp.pageY,
d: d.getTime()
}
});
self.target.on("touchmove",
function(event) {
event.preventDefault();
var temp = self.framework(event);
var d = new Date();
self.tm = {
x: temp.pageX,
y: temp.pageY
};
if (self.e == "drag") {
self.target.trigger(self.e, self.tm);
return
}
});
self.target.on("touchend",
function(event) {
event.preventDefault();
var d = new Date();
if (!self.tm) {
self.tm = self.ts
}
self.te = {
x: self.tm.x - self.ts.x,
y: self.tm.y - self.ts.y,
d: (d - self.ts.d)
};
self.tm = undefined;
self.factory()
})
};
Touch.prototype.factory = function() {
var x = Math.abs(this.te.x);
var y = Math.abs(this.te.y);
var t = this.te.d;
var s = this.status;
if (x < 5 && y < 5) {
if (t < 300) {
s = "tap"
} else {
s = "longTap"
}
} else if (x < options.x && y > options.y) {
if (t < 250) {
if (this.te.y > 0) {
s = "swipeDown"
} else {
s = "swipeUp"
}
} else {
s = "swipe"
}
} else if (y < options.y && x > options.x) {
if (t < 250) {
if (this.te.x > 0) {
s = "swipeLeft"
} else {
s = "swipeRight"
}
} else {
s = "swipe"
}
}
if (s == this.e) {
this.target.trigger(this.e);
return
}
}
})(window.jQuery || window.Zepto);