1/* 2* Hammer.JS 3* version 0.6.4 4* author: Eight Media 5* https://github.com/EightMedia/hammer.js 6* Licensed under the MIT license. 7*/ 8function Hammer(element, options, undefined) 9{ 10 var self = this; 11 12 var defaults = { 13 // prevent the default event or not... might be buggy when false 14 prevent_default : false, 15 not_prevent_tags : {id: [], className: [], tagName: []}, 16 css_hacks : true, 17 18 swipe : true, 19 swipe_time : 200, // ms 20 swipe_min_distance : 20, // pixels 21 22 drag : true, 23 drag_vertical : true, 24 drag_horizontal : true, 25 // minimum distance before the drag event starts 26 drag_min_distance : 20, // pixels 27 28 // pinch zoom and rotation 29 transform : true, 30 scale_treshold : 0.1, 31 rotation_treshold : 15, // degrees 32 33 tap : true, 34 tap_double : true, 35 tap_max_interval : 300, 36 tap_max_distance : 10, 37 tap_double_distance: 20, 38 39 hold : true, 40 hold_timeout : 500 41 }; 42 options = mergeObject(defaults, options); 43 44 // some css hacks 45 (function() { 46 if(!options.css_hacks) { 47 return false; 48 } 49 50 var vendors = ['webkit','moz','ms','o','']; 51 var css_props = { 52 "userSelect": "none", 53 "touchCallout": "none", 54 "userDrag": "none", 55 "tapHighlightColor": "rgba(0,0,0,0)" 56 }; 57 58 var prop = ''; 59 for(var i = 0; i < vendors.length; i++) { 60 for(var p in css_props) { 61 prop = p; 62 if(vendors[i]) { 63 prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); 64 } 65 element.style[ prop ] = css_props[p]; 66 } 67 } 68 })(); 69 70 // holds the distance that has been moved 71 var _distance = 0; 72 73 // holds the exact angle that has been moved 74 var _angle = 0; 75 76 // holds the direction that has been moved 77 var _direction = 0; 78 79 // holds position movement for sliding 80 var _pos = { }; 81 82 // how many fingers are on the screen 83 var _fingers = 0; 84 85 var _first = false; 86 87 var _gesture = null; 88 var _prev_gesture = null; 89 90 var _touch_start_time = null; 91 var _prev_tap_pos = {x: 0, y: 0}; 92 var _prev_tap_end_time = null; 93 94 var _hold_timer = null; 95 96 var _offset = {}; 97 98 // keep track of the mouse status 99 var _mousedown = false; 100 101 var _event_start; 102 var _event_move; 103 var _event_end; 104 105 var _has_touch = ('ontouchstart' in window); 106 107 var _can_tap = false; 108 109 110 /** 111* option setter/getter 112* @param string key 113* @param mixed value 114* @return mixed value 115*/ 116 this.option = function(key, val) { 117 if(val !== undefined) { 118 options[key] = val; 119 } 120 121 return options[key]; 122 }; 123 124 125 /** 126* angle to direction define 127* @param float angle 128* @return string direction 129*/ 130 this.getDirectionFromAngle = function( angle ) { 131 var directions = { 132 down: angle >= 45 && angle < 135, //90 133 left: angle >= 135 || angle <= -135, //180 134 up: angle < -45 && angle > -135, //270 135 right: angle >= -45 && angle <= 45 //0 136 }; 137 138 var direction, key; 139 for(key in directions){ 140 if(directions[key]){ 141 direction = key; 142 break; 143 } 144 } 145 return direction; 146 }; 147 148 149 /** 150* destroy events 151* @return void 152*/ 153 this.destroy = function() { 154 if(_has_touch) { 155 removeEvent(element, "touchstart touchmove touchend touchcancel", handleEvents); 156 } 157 // for non-touch 158 else { 159 removeEvent(element, "mouseup mousedown mousemove", handleEvents); 160 removeEvent(element, "mouseout", handleMouseOut); 161 } 162 }; 163 164 165 /** 166* count the number of fingers in the event 167* when no fingers are detected, one finger is returned (mouse pointer) 168* @param event 169* @return int fingers 170*/ 171 function countFingers( event ) 172 { 173 // there is a bug on android (until v4?) that touches is always 1, 174 // so no multitouch is supported, e.g. no, zoom and rotation... 175 return event.touches ? event.touches.length : 1; 176 } 177 178 179 /** 180* get the x and y positions from the event object 181* @param event 182* @return array [{ x: int, y: int }] 183*/ 184 function getXYfromEvent( event ) 185 { 186 event = event || window.event; 187 188 // no touches, use the event pageX and pageY 189 if(!_has_touch) { 190 var doc = document, 191 body = doc.body; 192 193 return [{ 194 x: event.pageX || event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && doc.clientLeft || 0 ), 195 y: event.pageY || event.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && doc.clientTop || 0 ) 196 }]; 197 } 198 // multitouch, return array with positions 199 else { 200 var pos = [], src; 201 for(var t=0, len=event.touches.length; t<len; t++) { 202 src = event.touches[t]; 203 pos.push({ x: src.pageX, y: src.pageY }); 204 } 205 return pos; 206 } 207 } 208 209 210 /** 211* calculate the angle between two points 212* @param object pos1 { x: int, y: int } 213* @param object pos2 { x: int, y: int } 214*/ 215 function getAngle( pos1, pos2 ) 216 {
217 return Math.atan2(pos2.y - pos1.y, pos2.x - pos1.x) * 180 / Math.PI; 218 } 219 220 /** 221* calculate the distance between two points 222* @param object pos1 { x: int, y: int } 223* @param object pos2 { x: int, y: int } 224*/ 225 function getDistance( pos1, pos2 ) 226 { 227 var x = pos2.x - pos1.x, y = pos2.y - pos1.y; 228 return Math.sqrt((x * x) + (y * y)); 229 } 230 231 232 /** 233* calculate the scale size between two fingers 234* @param object pos_start 235* @param object pos_move 236* @return float scale 237*/ 238 function calculateScale(pos_start, pos_move) 239 { 240 if(pos_start.length == 2 && pos_move.length == 2) { 241 var start_distance = getDistance(pos_start[0], pos_start[1]); 242 var end_distance = getDistance(pos_move[0], pos_move[1]); 243 244 245 246 247 248 249 250 251 252 253 return end_distance / start_distance; 254 } 255 256 return 0; 257 } 258 259 260 /** 261* calculate the rotation degrees between two fingers 262* @param object pos_start 263* @param object pos_move 264* @return float rotation 265*/ 266 function calculateRotation(pos_start, pos_move) 267 { 268 if(pos_start.length == 2 && pos_move.length == 2) { 269 var start_rotation = getAngle(pos_start[1], pos_start[0]); 270 var end_rotation = getAngle(pos_move[1], pos_move[0]); 271 272 273 274 275 276 277 278 279 280 281 return end_rotation - start_rotation; 282 } 283 284 return 0; 285 } 286 287 288 /**
289* trigger an event/callback by name with params 290* @param string name 291* @param array params 292*/ 293 function triggerEvent( eventName, params ) 294 { 295 // return touches object 296 params.touches = getXYfromEvent(params.originalEvent); 297 params.type = eventName; 298 299 // trigger callback 300 if(isFunction(self["on"+ eventName])) { 301 self["on"+ eventName].call(self, params); 302 } 303 } 304 305 306 /** 307* cancel event 308* @param object event 309* @return void 310*/ 311 312 function cancelEvent(event) 313 { 314 315 event = event || window.event; 316 if(event.preventDefault){ 317 for(i = 0; i < options.not_prevent_tags.className.length -1 ; i++) 318 if(event.target.className == options.not_prevent_tags.className[i]){ 319 return; 320 } 321 for(i = 0; i < options.not_prevent_tags.tagName.length -1 ; i++) 322 if(event.target.tagName == options.not_prevent_tags.tagName[i] ){ 323 return; 324 } 325 for(i = 0; i < options.not_prevent_tags.id.length -1 ; i++) 326 if(event.target.id == options.not_prevent_tags.id[i]){ 327 return; 328 } 329 if(countFingers(event)== 2){return;} 330 event.preventDefault(); 331 event.stopPropagation(); 332 }else{ 333 event.returnValue = false; 334 event.cancelBubble = true; 335 } 336 } 337 338 339 340 /** 341* reset the internal vars to the start values 342*/ 343 function reset() 344 { 345 _pos = {}; 346 _first = false; 347 _fingers = 0; 348 _distance = 0; 349 _angle = 0; 350 _gesture = null; 351 } 352 353 354 var gestures = { 355 // hold gesture 356 // fired on touchstart 357 hold : function(event) 358 { 359 // only when one finger is on the screen 360 if(options.hold) { 361 _gesture = 'hold'; 362 clearTimeout(_hold_timer); 363 364 _hold_timer = setTimeout(function() { 365 if(_gesture == 'hold') { 366 triggerEvent("hold", { 367 originalEvent : event, 368 position : _pos.start 369 }); 370 } 371 }, options.hold_timeout); 372 } 373 }, 374 375 // swipe gesture 376 // fired on touchend 377 swipe : function(event) 378 { 379 if (!_pos.move || _gesture === "transform") { 380 381 return; 382 } 383 384 // get the distance we moved 385 var _distance_x = _pos.move[0].x - _pos.start[0].x; 386 var _distance_y = _pos.move[0].y - _pos.start[0].y; 387 _distance = Math.sqrt(_distance_x*_distance_x + _distance_y*_distance_y); 388 389 // compare the kind of gesture by time 390 var now = new Date().getTime(); 391 var touch_time = now - _touch_start_time; 392 393 if(options.swipe && (options.swipe_time > touch_time) && (_distance > options.swipe_min_distance)) { 394 // calculate the angle 395 _angle = getAngle(_pos.start[0], _pos.move[0]); 396 _direction = self.getDirectionFromAngle(_angle); 397 398 _gesture = 'swipe'; 399 400 var position = { x: _pos.move[0].x - _offset.left, 401 y: _pos.move[0].y - _offset.top }; 402 403 var event_obj = { 404 originalEvent : event, 405 position : position, 406 direction : _direction, 407 distance : _distance, 408 distanceX : _distance_x, 409 distanceY : _distance_y, 410 angle : _angle 411 }; 412 413 // normal slide event 414 triggerEvent("swipe", event_obj); 415 } 416 }, 417 418 419 // drag gesture 420 // fired on mousemove 421 drag : function(event) 422 { 423 // get the distance we moved 424 var _distance_x = _pos.move[0].x - _pos.start[0].x; 425 var _distance_y = _pos.move[0].y - _pos.start[0].y; 426 _distance = Math.sqrt(_distance_x * _distance_x + _distance_y * _distance_y); 427 428 // drag 429 // minimal movement required 430 if(options.drag && (_distance > options.drag_min_distance) || _gesture == 'drag') { 431 // calculate the angle 432 _angle = getAngle(_pos.start[0], _pos.move[0]); 433 _direction = self.getDirectionFromAngle(_angle); 434 435 // check the movement and stop if we go in the wrong direction 436 var is_vertical = (_direction == 'up' || _direction == 'down'); 437 438 if(((is_vertical && !options.drag_vertical) || (!is_vertical && !options.drag_horizontal)) && (_distance > options.drag_min_distance)) { 439 440 return; 441 } 442 443 _gesture = 'drag'; 444 445 var position = { x: _pos.move[0].x - _offset.left, 446 y: _pos.move[0].y - _offset.top }; 447 448 var event_obj = { 449 originalEvent : event, 450 position : position, 451 direction : _direction, 452 distance : _distance, 453 distanceX : _distance_x, 454 distanceY : _distance_y, 455 angle : _angle 456 }; 457 458 // on the first time trigger the start event 459 if(_first) { 460 triggerEvent("dragstart", event_obj); 461 462 _first = false;
463 } 464 465 // normal slide event 466 triggerEvent("drag", event_obj); 467 468 cancelEvent(event); 469 } 470 }, 471 472 473 // transform gesture 474 // fired on touchmove 475 transform : function(event) 476 { 477 if(options.transform) { 478 var count = countFingers(event); 479 if (count !== 2) { 480 return false; 481 } 482 483 var rotation = calculateRotation(_pos.start, _pos.move); 484 var scale = calculateScale(_pos.start, _pos.move); 485 486 487 if (_gesture === 'transform' || 488 Math.abs(1 - scale) > options.scale_treshold || 489 Math.abs(rotation) > options.rotation_treshold) { 490 491 _gesture = 'transform'; 492 _pos.center = { 493 x: ((_pos.move[0].x + _pos.move[1].x) / 2) - _offset.left, 494 y: ((_pos.move[0].y + _pos.move[1].y) / 2) - _offset.top 495 }; 496 497 if(_first) 498 _pos.startCenter = _pos.center; 499 500 var _distance_x = _pos.center.x - _pos.startCenter.x; 501 var _distance_y = _pos.center.y - _pos.startCenter.y; 502 _distance = Math.sqrt(_distance_x*_distance_x + _distance_y*_distance_y); 503 504 505 506 var event_obj = { 507 originalEvent : event, 508 position : _pos.center, 509 scale : scale, 510 511 rotation : rotation, 512 distance : _distance, 513 distanceX : _distance_x, 514 distanceY : _distance_y 515 }; 516 517 // on the first time trigger the start event 518 if (_first) { 519 520 triggerEvent("transformstart", event_obj); 521 _first = false; 522 } 523 524 triggerEvent("transform", event_obj); 525 526 cancelEvent(event); 527 528 return true; 529 } 530 } 531 532 return false; 533 }, 534 535 536 // tap and double tap gesture 537 // fired on touchend 538 tap : function(event) 539 { 540 // compare the kind of gesture by time 541 var now = new Date().getTime(); 542 var touch_time = now - _touch_start_time; 543 544 // dont fire when hold is fired 545 if(options.hold && !(options.hold && options.hold_timeout > touch_time)) { 546 return; 547 } 548 549 // when previous event was tap and the tap was max_interval ms ago 550 var is_double_tap = (function(){ 551 if (_prev_tap_pos && 552 options.tap_double && 553 _prev_gesture == 'tap' && 554 _pos.start && 555 (_touch_start_time - _prev_tap_end_time) < options.tap_max_interval) 556 { 557 var x_distance = Math.abs(_prev_tap_pos[0].x - _pos.start[0].x); 558 var y_distance = Math.abs(_prev_tap_pos[0].y - _pos.start[0].y); 559 return (_prev_tap_pos && _pos.start && Math.max(x_distance, y_distance) < options.tap_double_distance); 560 } 561 return false; 562 })(); 563 564 if(is_double_tap) { 565 _gesture = 'double_tap'; 566 _prev_tap_end_time = null; 567 568 triggerEvent("doubletap", { 569 originalEvent : event, 570 position : _pos.start 571 }); 572 cancelEvent(event); 573 } 574 575 // single tap is single touch 576 else { 577 var x_distance = (_pos.move) ? Math.abs(_pos.move[0].x - _pos.start[0].x) : 0; 578 var y_distance = (_pos.move) ? Math.abs(_pos.move[0].y - _pos.start[0].y) : 0; 579 _distance = Math.max(x_distance, y_distance); 580 581 if(_distance < options.tap_max_distance) { 582 _gesture = 'tap'; 583 _prev_tap_end_time = now; 584 _prev_tap_pos = _pos.start; 585 586 if(options.tap) { 587 triggerEvent("tap", { 588 originalEvent : event, 589 position : _pos.start 590 }); 591 cancelEvent(event); 592 } 593 } 594 } 595 596 } 597 598 }; 599 600 601 function handleEvents(event) 602 { 603 var count; 604 switch(event.type) 605 { 606 case 'mousedown': 607 case 'touchstart': 608 count = countFingers(event); 609 _can_tap = count === 1; 610 611 612 613 614 615 616 //We were dragging and now we are zooming. 617 if (count === 2 && _gesture === "drag") { 618 619 620 621 622 623 624 625 //The user needs to have the dragend to be fired to ensure that 626 //there is proper cleanup from the drag and move onto transforming. 627 triggerEvent("dragend", { 628 originalEvent : event, 629 direction : _direction, 630 distance : _distance, 631 angle : _angle 632 }); 633 } 634 _setup(); 635 636 637 638 639 640 641 642 643 644 645 if(options.prevent_default) { 646 cancelEvent(event); 647 } 648 break; 649 650 case 'mousemove': 651 case 'touchmove': 652 count = countFingers(event); 653 654 //The user has gone from transforming to dragging. The 655 //user needs to have the proper cleanup of the state and 656 //setup with the new "start" points. 657 if (!_mousedown && count === 1) { 658 659 return false;
660 } else if (!_mousedown && count === 2) { 661 _can_tap = false; 662 663 reset(); 664 _setup(); 665 } 666 667 _event_move = event; 668 _pos.move = getXYfromEvent(event); 669 670 if(!gestures.transform(event)) { 671 gestures.drag(event); 672 } 673 break; 674 675 case 'mouseup': 676 case 'mouseout': 677 case 'touchcancel': 678 case 'touchend': 679 var callReset = true; 680 681 682 683 684 _mousedown = false; 685 _event_end = event; 686 687 688 // swipe gesture 689 gestures.swipe(event); 690 691 692 // drag gesture 693 // dragstart is triggered, so dragend is possible 694 if(_gesture == 'drag') { 695 triggerEvent("dragend", { 696 originalEvent : event, 697 direction : _direction, 698 distance : _distance, 699 angle : _angle 700 701 }); 702 } 703 704 // transform 705 // transformstart is triggered, so transformed is possible 706 else if(_gesture == 'transform') { 707 // define the transform distance 708 var _distance_x = _pos.center.x - _pos.startCenter.x; 709 var _distance_y = _pos.center.y - _pos.startCenter.y; 710 711 triggerEvent("transformend", { 712 originalEvent : event, 713 position : _pos.center, 714 scale : calculateScale(_pos.start, _pos.move), 715 rotation : calculateRotation(_pos.start, _pos.move), 716 distance : _distance, 717 distanceX : _distance_x, 718 distanceY : _distance_y 719 }); 720 721 //If the user goes from transformation to drag there needs to be a 722 //state reset so that way a dragstart/drag/dragend will be properly 723 //fired. 724 if (countFingers(event) === 1) { 725 reset(); 726 _setup(); 727 callReset = false; 728 } 729 } else if (_can_tap) { 730 731 732 gestures.tap(_event_start); 733 } 734 735 _prev_gesture = _gesture; 736 737 // trigger release event 738 // "release" by default doesn't return the co-ords where your 739 // finger was released. "position" will return "the last touched co-ords" 740 741 triggerEvent("release", { 742 originalEvent : event, 743 gesture : _gesture, 744 position : _pos.move || _pos.start 745 }); 746 747 // reset vars if this was not a transform->drag touch end operation. 748 if (callReset) { 749 reset(); 750 } 751 752 753 break; 754 } // end switch 755 756 /** 757* Performs a blank setup. 758* @private 759*/ 760 function _setup() { 761 _pos.start = getXYfromEvent(event); 762 _touch_start_time = new Date().getTime(); 763 _fingers = countFingers(event); 764 _first = true; 765 _event_start = event; 766 767 // borrowed from jquery offset https://github.com/jquery/jquery/blob/master/src/offset.js 768 var box = element.getBoundingClientRect(); 769 var clientTop = element.clientTop || document.body.clientTop || 0; 770 var clientLeft = element.clientLeft || document.body.clientLeft || 0; 771 var scrollTop = window.pageYOffset || element.scrollTop || document.body.scrollTop; 772 var scrollLeft = window.pageXOffset || element.scrollLeft || document.body.scrollLeft; 773 774 _offset = { 775 top: box.top + scrollTop - clientTop, 776 left: box.left + scrollLeft - clientLeft 777 }; 778 779 _mousedown = true; 780 781 // hold gesture 782 gestures.hold(event); 783 } 784 } 785 786 787 function handleMouseOut(event) { 788 if(!isInsideHammer(element, event.relatedTarget)) { 789 handleEvents(event); 790 } 791 } 792 793 794 // bind events for touch devices 795 // except for windows phone 7.5, it doesnt support touch events..! 796 if(_has_touch) { 797 addEvent(element, "touchstart touchmove touchend touchcancel", handleEvents); 798 } 799 // for non-touch 800 else { 801 addEvent(element, "mouseup mousedown mousemove", handleEvents); 802 addEvent(element, "mouseout", handleMouseOut); 803 } 804 805 806 /** 807* find if element is (inside) given parent element 808* @param object element 809* @param object parent 810* @return bool inside 811*/ 812 function isInsideHammer(parent, child) { 813 // get related target for IE 814 if(!child && window.event && window.event.toElement){ 815 child = window.event.toElement; 816 } 817 818 if(parent === child){ 819 return true; 820 } 821 822 // loop over parentNodes of child until we find hammer element 823 if(child){ 824 var node = child.parentNode; 825 while(node !== null){ 826 if(node === parent){ 827 return true; 828 } 829 830 node = node.parentNode; 831 } 832 } 833 return false;
834 } 835 836 837 /** 838* merge 2 objects into a new object 839* @param object obj1 840* @param object obj2 841* @return object merged object 842*/ 843 function mergeObject(obj1, obj2) { 844 var output = {}; 845 846 if(!obj2) { 847 return obj1; 848 } 849 850 for (var prop in obj1) { 851 if (prop in obj2) { 852 output[prop] = obj2[prop]; 853 } else { 854 output[prop] = obj1[prop]; 855 } 856 } 857 return output; 858 } 859 860 861 /** 862* check if object is a function 863* @param object obj 864* @return bool is function 865*/ 866 function isFunction( obj ){ 867 return Object.prototype.toString.call( obj ) == "[object Function]"; 868 } 869 870 871 /** 872* attach event 873* @param node element 874* @param string types 875* @param object callback 876*/ 877 function addEvent(element, types, callback) { 878 types = types.split(" "); 879 for(var t= 0,len=types.length; t<len; t++) { 880 if(element.addEventListener){ 881 element.addEventListener(types[t], callback, false); 882 } 883 else if(document.attachEvent){ 884 element.attachEvent("on"+ types[t], callback); 885 } 886 } 887 } 888 889 890 /** 891* detach event 892* @param node element 893* @param string types 894* @param object callback 895*/ 896 function removeEvent(element, types, callback) { 897 types = types.split(" "); 898 for(var t= 0,len=types.length; t<len; t++) { 899 if(element.removeEventListener){ 900 element.removeEventListener(types[t], callback, false); 901 } 902 else if(document.detachEvent){ 903 element.detachEvent("on"+ types[t], callback); 904 } 905 } 906 } 907}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.