├── src ├── intro.js ├── outro.js ├── pointerevent.js ├── setup.js ├── core.js ├── instance.js ├── event.js ├── gesture.js ├── utils.js └── gestures.js ├── examples ├── assets │ ├── img │ │ ├── arrow.png │ │ ├── spinner.png │ │ └── wally.jpg │ └── js │ │ └── modernizr.js ├── fastclick.html ├── jquery.html ├── pinchzoom.html ├── slideshow.html ├── events.html └── pull-to-refresh.html ├── .gitignore ├── package.json ├── LICENSE ├── plugins ├── hammer.showtouches.js ├── jquery.hammer.js └── hammer.fakemultitouch.js ├── Gruntfile.coffee ├── dist ├── hammer-1.0.0rc1.min.js ├── hammer-latest.min.js ├── hammer-1.0.0rc1.js └── hammer-latest.js └── README.md /src/intro.js: -------------------------------------------------------------------------------- 1 | (function( window, undefined ) { 2 | "use strict"; -------------------------------------------------------------------------------- /src/outro.js: -------------------------------------------------------------------------------- 1 | // Expose Hammer to the global object 2 | window.Hammer = Hammer; 3 | 4 | })(window); -------------------------------------------------------------------------------- /examples/assets/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/hammer.js/v2/examples/assets/img/arrow.png -------------------------------------------------------------------------------- /examples/assets/img/spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/hammer.js/v2/examples/assets/img/spinner.png -------------------------------------------------------------------------------- /examples/assets/img/wally.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekryski/hammer.js/v2/examples/assets/img/wally.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ide 2 | .idea 3 | .iml 4 | 5 | # node 6 | lib-cov 7 | *.seed 8 | *.log 9 | *.csv 10 | *.dat 11 | *.out 12 | *.pid 13 | *.gz 14 | 15 | pids 16 | logs 17 | results 18 | 19 | npm-debug.log 20 | node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"hammer", 3 | "title":"Hammer.JS", 4 | "description":"A javascript library for multi-touch gestures", 5 | "version":"1.0.0rc1", 6 | "license":"MIT", 7 | "keywords":["touch","gestures"], 8 | "author": { 9 | "name": "Jorik Tangelder", 10 | "email": "jorik@eight.nl" 11 | }, 12 | "homepage": "http://eightmedia.github.com/hammer.js", 13 | "devDependencies":{ 14 | "grunt":"~0.4.0", 15 | "grunt-contrib-copy":"~0.4.0rc7", 16 | "grunt-contrib-connect":"~0.1.1rc6", 17 | "grunt-contrib-concat":"~0.1.2rc6", 18 | "grunt-contrib-uglify":"~0.1.1rc6", 19 | "grunt-contrib-jshint":"~0.1.x", 20 | "grunt-contrib-watch":"~0.2.x" 21 | } 22 | } -------------------------------------------------------------------------------- /src/pointerevent.js: -------------------------------------------------------------------------------- 1 | Hammer.PointerEvent = { 2 | pointers: {}, 3 | 4 | /** 5 | * get a list of pointers 6 | * @return {Array} Pointers 7 | */ 8 | getPointers: function() { 9 | var pointers = []; 10 | for(var p in this.pointers) { 11 | pointers.push(p); 12 | } 13 | return pointers; 14 | }, 15 | 16 | /** 17 | * update the position of a pointer 18 | * @param EVENTTYPE type 19 | * @param Object ev 20 | */ 21 | updatePointer: function(type, ev) { 22 | if(type == Hammer.EVENT_END) { 23 | delete this.pointers[ev.pointerId]; 24 | } 25 | else { 26 | this.pointers[ev.pointerId] = ev; 27 | } 28 | } 29 | }; -------------------------------------------------------------------------------- /src/setup.js: -------------------------------------------------------------------------------- 1 | // if the window events are set... 2 | Hammer.READY = false; 3 | 4 | /** 5 | * setup events to detect gestures on the document 6 | * @return 7 | */ 8 | function setup() { 9 | if(Hammer.READY) { 10 | return; 11 | } 12 | 13 | // find what eventtypes we add listeners to 14 | Hammer.event.determineEventTypes(); 15 | 16 | // Register all gestures inside Hammer.gestures 17 | for(var name in Hammer.gestures) { 18 | if(Hammer.gestures.hasOwnProperty(name)) { 19 | Hammer.gesture.register(Hammer.gestures[name]); 20 | } 21 | } 22 | 23 | // Add touch events on the window 24 | Hammer.event.onTouch(document, Hammer.EVENT_MOVE, Hammer.gesture.detect); 25 | Hammer.event.onTouch(document, Hammer.EVENT_END, Hammer.gesture.endDetect); 26 | 27 | // Hammer is ready...! 28 | Hammer.READY = true; 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 by Jorik Tangelder (Eight Media) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | var Hammer = function(element, options) { 2 | return new Hammer.Instance(element, options || {}); 3 | }; 4 | 5 | // default settings 6 | Hammer.defaults = { 7 | stop_browser_behavior: { // set to false to disable this 8 | userSelect: "none", // this also triggers onselectstart=false for IE 9 | touchCallout: "none", 10 | touchAction: "none", 11 | contentZooming: "none", 12 | userDrag: "none", 13 | tapHighlightColor: "rgba(0,0,0,0)" 14 | } 15 | 16 | // more settings are defined at gestures.js 17 | }; 18 | 19 | // detect touchevents 20 | Hammer.HAS_POINTEREVENTS = window.navigator.msPointerEnabled; 21 | Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window); 22 | 23 | // eventtypes per touchevent (start, move, end) 24 | // are filled by Hammer.event.determineEventTypes on setup 25 | Hammer.EVENT_TYPES = {}; 26 | 27 | // direction defines 28 | Hammer.DIRECTION_DOWN = 'down'; 29 | Hammer.DIRECTION_LEFT = 'left'; 30 | Hammer.DIRECTION_UP = 'up'; 31 | Hammer.DIRECTION_RIGHT = 'right'; 32 | 33 | // touch event defines 34 | Hammer.EVENT_START = 'start'; 35 | Hammer.EVENT_MOVE = 'move'; 36 | Hammer.EVENT_END = 'end'; 37 | 38 | // plugins namespace 39 | Hammer.plugins = {}; -------------------------------------------------------------------------------- /plugins/hammer.showtouches.js: -------------------------------------------------------------------------------- 1 | (function(Hammer) { 2 | /** 3 | * ShowTouches gesture 4 | * requires jQuery 5 | * show all touch on the screen by placing elements at there pageX and pageY 6 | */ 7 | Hammer.plugins.showTouches = function() { 8 | // the circles under your fingers 9 | var template = '
'; 12 | 13 | // elements by identifier 14 | var touch_elements = {}; 15 | 16 | Hammer.gesture.register({ 17 | name: 'show_touches', 18 | priority: 0, 19 | handler: function(ev, inst) { 20 | // get touches by ID 21 | var touches_index = {}; 22 | 23 | // place touches by index 24 | for(var t= 0,total_touches=ev.touches.length; t 2 | 3 | 4 | Hammer.js 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 |

FastClick implementation

20 |

Skip the ~300ms delay of an click.

21 | 22 | Fast click 23 | Fast click jQuery 24 | Normal click 25 | 26 | 27 | 28 | 29 | 30 | 31 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/instance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * create new hammer instance 3 | * all methods should return the instance itself, so it is chainable. 4 | * @param {HTMLElement} element 5 | * @param {Object} [options={}] 6 | * @return {Object} instance 7 | */ 8 | Hammer.Instance = function(element, options) { 9 | var self = this; 10 | 11 | // setup HammerJS window events and register all gestures 12 | // this also sets up the default options 13 | setup(); 14 | 15 | this.element = element; 16 | 17 | // merge options 18 | this.options = Hammer.utils.extend( 19 | Hammer.utils.extend({}, Hammer.defaults), 20 | options || {}); 21 | 22 | // add some css to the element to prevent the browser from doing its native behavoir 23 | if(this.options.stop_browser_behavior) { 24 | Hammer.utils.stopDefaultBrowserBehavior(this); 25 | } 26 | 27 | // start detection on touchstart 28 | Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) { 29 | return Hammer.gesture.startDetect(self, ev); 30 | }); 31 | 32 | // return instance 33 | return this; 34 | }; 35 | 36 | 37 | Hammer.Instance.prototype = { 38 | /** 39 | * bind events to the instance 40 | * @param string gestures 41 | * @param callback callback 42 | * @return {*} 43 | */ 44 | on: function onEvent(gestures, handler){ 45 | gestures = gestures.split(" "); 46 | for(var t=0; t 2 | grunt.initConfig 3 | pkg: grunt.file.readJSON 'package.json' 4 | 5 | # meta options 6 | meta: 7 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 8 | '<%= grunt.template.today("yyyy-mm-dd") %>\n ' + '<%= pkg.homepage ? "* " + pkg.homepage + "\\n *\\n " : "" %>' + 9 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> <<%= pkg.author.email %>>;\n' + 10 | ' * Licensed under the <%= pkg.license %> license */\n\n' 11 | 12 | # concat src files 13 | concat: 14 | options: 15 | banner: '<%= meta.banner %>' 16 | separator: '\n\n' 17 | dist: 18 | src: [ 19 | 'src/intro.js' 20 | 'src/core.js' 21 | 'src/setup.js' 22 | 'src/instance.js' 23 | 'src/event.js' 24 | 'src/utils.js' 25 | 'src/gesture.js' 26 | 'src/gestures.js' 27 | 'src/outro.js'] 28 | dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js' 29 | 30 | # copy src to latest version 31 | copy: 32 | latest: 33 | src: ['dist/<%= pkg.name %>-<%= pkg.version %>.js'] 34 | dest: 'dist/<%= pkg.name %>-latest.js' 35 | latestmin: 36 | src: ['dist/<%= pkg.name %>-<%= pkg.version %>.min.js'] 37 | dest: 'dist/<%= pkg.name %>-latest.min.js' 38 | 39 | # check for optimisations and errors 40 | jshint: 41 | options: 42 | browser: true 43 | files: 44 | src: ['dist/<%= pkg.name %>-<%= pkg.version %>.js'] 45 | 46 | # minify the sourcecode 47 | uglify: 48 | options: 49 | banner: '<%= meta.banner %>' 50 | build: 51 | files: 52 | 'dist/<%= pkg.name %>-<%= pkg.version %>.min.js': ['dist/<%= pkg.name %>-<%= pkg.version %>.js'] 53 | 'dist/<%= pkg.name %>-latest.min.js': ['dist/<%= pkg.name %>-<%= pkg.version %>.min.js'] 54 | 55 | # watch for changes 56 | watch: 57 | scripts: 58 | files: 'src/*.js' 59 | tasks: ['concat','copy:latest'] 60 | options: 61 | interrupt: true 62 | 63 | # simple node server 64 | connect: 65 | server: 66 | options: 67 | hostname: "0.0.0.0" 68 | 69 | 70 | # Load tasks 71 | grunt.loadNpmTasks 'grunt-contrib-copy' 72 | grunt.loadNpmTasks 'grunt-contrib-concat' 73 | grunt.loadNpmTasks 'grunt-contrib-uglify' 74 | grunt.loadNpmTasks 'grunt-contrib-watch' 75 | grunt.loadNpmTasks 'grunt-contrib-jshint' 76 | grunt.loadNpmTasks 'grunt-contrib-connect' 77 | 78 | 79 | # Default task(s). 80 | grunt.registerTask 'build', ['concat','jshint','uglify','copy'] 81 | grunt.registerTask 'default', ['connect','watch'] 82 | -------------------------------------------------------------------------------- /plugins/hammer.fakemultitouch.js: -------------------------------------------------------------------------------- 1 | (function(Hammer) { 2 | /** 3 | * enable multitouch on the desktop by pressing the shiftkey 4 | * the other touch goes in the opposite direction so the center keeps at its place 5 | * it's recommended to enable Hammer.debug.showTouches for this one 6 | */ 7 | Hammer.plugins.fakeMultitouch = function() { 8 | // keeps the start position to keep it centered 9 | var start_pos = false; 10 | 11 | /** 12 | * overwrites Hammer.event.getTouchList. 13 | * @param {Event} ev 14 | * @param TOUCHTYPE type 15 | * @return {Array} Touches 16 | */ 17 | Hammer.event.getTouchList = function(ev, eventType) { 18 | // Android, iOS etc 19 | if(Hammer.HAS_POINTEREVENTS) { 20 | return Hammer.PointerEvent.getPointers(); 21 | } 22 | else if(Hammer.HAS_TOUCHEVENTS) { 23 | return ev.touches; 24 | } 25 | 26 | // reset on start of a new touch 27 | if(eventType == Hammer.EVENT_START) { 28 | start_pos = false; 29 | } 30 | 31 | // when the shift key is pressed, multitouch is possible on desktop 32 | // why shift? because ctrl and alt are taken by osx and linux 33 | if(ev.shiftKey) { 34 | // on touchstart we store the position of the mouse for multitouch 35 | if(!start_pos) { 36 | start_pos = { 37 | pageX: ev.pageX, 38 | pageY: ev.pageY 39 | }; 40 | } 41 | 42 | // small misplacement to fix NaN/Infinity issues 43 | var distance_x = start_pos.pageX - ev.pageX; 44 | var distance_y = start_pos.pageY - ev.pageY; 45 | 46 | // fake second touch in the opposite direction 47 | return [{ 48 | identifier: 1, 49 | pageX: start_pos.pageX - distance_x - 50, 50 | pageY: start_pos.pageY - distance_y - -50, 51 | target: ev.target 52 | },{ 53 | identifier: 2, 54 | pageX: start_pos.pageX + distance_x - -50, 55 | pageY: start_pos.pageY + distance_y - 50, 56 | target: ev.target 57 | }]; 58 | 59 | // normal single touch 60 | } else { 61 | start_pos = false; 62 | return [{ 63 | identifier: 1, 64 | pageX: ev.pageX, 65 | pageY: ev.pageY, 66 | target: ev.target 67 | }]; 68 | } 69 | }; 70 | }; 71 | 72 | })(window.Hammer); -------------------------------------------------------------------------------- /examples/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hammer.js 5 | 6 | 7 | 8 | 43 | 44 | 45 | 46 | 47 |
48 | 51 | 52 |
53 |
54 |

Touch me

55 |

The tap event will be fired. Notice in the sourcecode that event delegation is also supported.
56 | In the console you can see the event data.

57 |

List items with stopPropagation

58 |
    59 |
  • List item 1
  • 60 |
  • List item 2
  • 61 |
  • List item 3
  • 62 |
  • List item 4
  • 63 |
  • List item 5
  • 64 |
65 |

66 | Add list item 67 |

68 |
69 |
70 | 71 | 72 |
73 | 74 | 75 | 76 | 77 | 78 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/pinchzoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hammer.js 5 | 6 | 7 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this holds the last move event, 3 | * used to fix empty touchend issue 4 | * see the onTouch event for an explanation 5 | * @type {Object} 6 | */ 7 | var last_move_event = {}; 8 | 9 | /** 10 | * when the mouse is hold down, this is true 11 | * @type {Boolean} 12 | */ 13 | var mousedown = false; 14 | 15 | 16 | Hammer.event = { 17 | /** 18 | * simple addEventListener 19 | * @param element 20 | * @param types 21 | * @param handler 22 | */ 23 | bindDom: function(element, types, handler) { 24 | types = types.split(" "); 25 | for(var t=0; t b.index) 156 | return 1; 157 | return 0; 158 | }); 159 | } 160 | }; -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | var PI = Math.PI; 2 | 3 | Hammer.utils = { 4 | /** 5 | * extend method, 6 | * also used for cloning when dest is an empty object 7 | * @param {Object} dest 8 | * @param {Object} src 9 | * @param {Number} [depth=0] 10 | * @return {Object} dest 11 | */ 12 | extend: function extend(dest, src, depth) { 13 | depth = depth || 0; 14 | for (var key in src) { 15 | if(src.hasOwnProperty(key)) { 16 | if(depth && typeof(src[key]) == 'object') { 17 | dest[key] = this.extend({}, src[key], depth-1); 18 | } else { 19 | dest[key] = src[key]; 20 | } 21 | } 22 | } 23 | 24 | return dest; 25 | }, 26 | 27 | 28 | /** 29 | * faster Math.abs alternative 30 | * @param value 31 | * @return value 32 | */ 33 | fastAbs: function fastAbs(value) { 34 | // equivalent to Math.abs(); 35 | return (value ^ (value >> 31)) - (value >> 31); 36 | }, 37 | 38 | 39 | /** 40 | * get the center of all the touches 41 | * @param {TouchList} touches 42 | * @return {Object} center 43 | */ 44 | getCenter: function getCenter(touches) { 45 | var valuesX = [], valuesY = []; 46 | 47 | for(var t= 0,len=touches.length; t= y) { 92 | return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT; 93 | } 94 | else { 95 | return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN; 96 | } 97 | }, 98 | 99 | 100 | /** 101 | * calculate the distance between two touches 102 | * @param Touch touch1 103 | * @param Touch touch2 104 | */ 105 | getDistance: function getDistance(touch1, touch2) { 106 | var x = touch2.pageX - touch1.pageX, 107 | y = touch2.pageY - touch1.pageY; 108 | return Math.sqrt((x*x) + (y*y)); 109 | }, 110 | 111 | 112 | /** 113 | * calculate the scale factor between two touchLists (fingers) 114 | * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out 115 | * @param TouchList start 116 | * @param TouchList end 117 | * @return float scale 118 | */ 119 | getScale: function getScale(start, end) { 120 | // need two fingers... 121 | if(start.length >= 2 && end.length >= 2) { 122 | return this.getDistance(end[0], end[1]) / 123 | this.getDistance(start[0], start[1]); 124 | } 125 | return 1; 126 | }, 127 | 128 | 129 | /** 130 | * calculate the rotation degrees between two touchLists (fingers) 131 | * @param TouchList start 132 | * @param TouchList end 133 | * @return float rotation 134 | */ 135 | getRotation: function getRotation(start, end) { 136 | // need two fingers 137 | if(start.length >= 2 && end.length >= 2) { 138 | return this.getAngle(end[1], end[0]) - 139 | this.getAngle(start[1], start[0]); 140 | } 141 | return 0; 142 | }, 143 | 144 | 145 | /** 146 | * stop browser default behavior with css props 147 | * @param Hammer.Instance inst 148 | * @return {*} 149 | */ 150 | stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(inst) { 151 | var prop, 152 | vendors = ['webkit','khtml','moz','ms','o',''], 153 | css_props = inst.options.stop_browser_behavior; 154 | 155 | if(!css_props) { 156 | return; 157 | } 158 | 159 | // with css properties for modern browsers 160 | for(var i = 0; i < vendors.length; i++) { 161 | for(var p in css_props) { 162 | if(css_props.hasOwnProperty(p)) { 163 | prop = p; 164 | if(vendors[i]) { 165 | prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); 166 | } 167 | inst.element.style[prop] = css_props[p]; 168 | } 169 | } 170 | } 171 | 172 | // also the disable onselectstart 173 | if(css_props.userSelect == 'none') { 174 | inst.element.onselectstart = function() { 175 | return false; 176 | }; 177 | } 178 | } 179 | }; -------------------------------------------------------------------------------- /examples/slideshow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hammer.js 5 | 6 | 7 | 78 | 79 | 80 | 81 | 82 |
83 |
    84 |
  • 85 |
  • 86 |
  • 87 |
  • 88 |
  • 89 |
90 |
91 |
92 |
    93 |
  • 1
  • 94 |
  • 2
  • 95 |
  • 3
  • 96 |
  • 4
  • 97 |
  • 5
  • 98 |
99 |
100 | 101 |

Simple slideshow to demo the drag event.

102 |

Notice that the drag event is non-blocking for the scrolling of your page...

103 | 104 | 105 | 106 | 107 | 108 | 109 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /dist/hammer-1.0.0rc1.min.js: -------------------------------------------------------------------------------- 1 | /*! Hammer.JS - v1.0.0rc1 - 2013-02-05 2 | * http://eightmedia.github.com/hammer.js 3 | * 4 | * Copyright (c) 2013 Jorik Tangelder ; 5 | * Licensed under the MIT license */ 6 | 7 | (function(e){"use strict";function t(){if(!n.READY){n.event.determineEventTypes();for(var e in n.gestures)n.gestures.hasOwnProperty(e)&&n.gesture.register(n.gestures[e]);n.event.onTouch(document,n.EVENT_MOVE,n.gesture.detect),n.event.onTouch(document,n.EVENT_END,n.gesture.endDetect),n.READY=!0}}var n=function(e,t){return new n.Instance(e,t||{})};n.defaults={stop_browser_behavior:{userSelect:"none",touchCallout:"none",touchAction:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}},n.HAS_POINTEREVENTS=e.navigator.msPointerEnabled,n.HAS_TOUCHEVENTS="ontouchstart"in e,n.EVENT_TYPES={},n.DIRECTION_DOWN="down",n.DIRECTION_LEFT="left",n.DIRECTION_UP="up",n.DIRECTION_RIGHT="right",n.EVENT_START="start",n.EVENT_MOVE="move",n.EVENT_END="end",n.plugins={},n.READY=!1,n.Instance=function(e,i){var r=this;return t(),this.element=e,this.options=n.utils.extend(n.utils.extend({},n.defaults),i||{}),this.options.stop_browser_behavior&&n.utils.stopDefaultBrowserBehavior(this),n.event.onTouch(e,n.EVENT_START,function(e){return n.gesture.startDetect(r,e)}),this},n.Instance.prototype={on:function(e,t){e=e.split(" ");for(var n=0;e.length>n;n++)this.element.addEventListener(e[n],t,!1)},off:function(e,t){e=e.split(" ");for(var n=0;e.length>n;n++)this.element.removeEventListener(e[n],t,!1)},trigger:function(e,t){var n=document.createEvent("Event");return n.initEvent(e,!0,!0),n.gesture=t,this.element.dispatchEvent(n)}};var i={},r=!1;n.event={bindDom:function(e,t,n){t=t.split(" ");for(var i=0;t.length>i;i++)e.addEventListener(t[i],n,!1)},onTouch:function(e,t,s){function a(r){n.HAS_POINTEREVENTS&&n.PointerEvent.updatePointer(t,r),t===n.EVENT_END?r=i:i=r,s.call(n.gesture,o.collectEventData(e,t,r))}var o=this;n.HAS_TOUCHEVENTS||n.HAS_POINTEREVENTS?this.bindDom(e,n.EVENT_TYPES[t],a):this.bindDom(e,n.EVENT_TYPES[t],function(e){1===e.which&&(r=!0,a.apply(this,arguments)),"mouseup"==e.type&&(r=!1)})},determineEventTypes:function(){var e;e=n.HAS_POINTEREVENTS?["MSPointerDown","MSPointerMove","MSPointerUp MSPointerCancel"]:n.HAS_TOUCHEVENTS?["touchstart","touchmove","touchend touchcancel"]:["mousedown","mousemove","mouseup"],n.EVENT_TYPES[n.EVENT_START]=e[0],n.EVENT_TYPES[n.EVENT_MOVE]=e[1],n.EVENT_TYPES[n.EVENT_END]=e[2]},getTouchList:function(e){return n.HAS_POINTEREVENTS?n.PointerEvent.getPointers():n.HAS_TOUCHEVENTS?e.touches:[{identifier:1,pageX:e.pageX,pageY:e.pageY,target:e.target}]},collectEventData:function(e,t,i){var r=this.getTouchList(i,t);return{center:n.utils.getCenter(r),time:(new Date).getTime(),target:i.target,touches:r,eventType:t,srcEvent:i,preventDefault:function(){return this.srcEvent.preventDefault()}}}};var s=Math.PI;n.utils={extend:function(e,t,n){n=n||0;for(var i in t)t.hasOwnProperty(i)&&(e[i]=n&&"object"==typeof t[i]?this.extend({},t[i],n-1):t[i]);return e},fastAbs:function(e){return(e^e>>31)-(e>>31)},getCenter:function(e){for(var t=[],n=[],i=0,r=e.length;r>i;i++)t.push(e[i].pageX),n.push(e[i].pageY);return{pageX:(Math.min.apply(Math,t)+Math.max.apply(Math,t))/2,pageY:(Math.min.apply(Math,n)+Math.max.apply(Math,n))/2}},getSimpleDistance:function(e,t){return this.fastAbs(t-e)},getAngle:function(e,t){var n=t.pageY-e.pageY,i=t.pageX-e.pageX;return 180*Math.atan2(n,i)/s},getDirection:function(e,t){var i=this.fastAbs(e.pageX-t.pageX),r=this.fastAbs(e.pageY-t.pageY);return i>=r?e.pageX-t.pageX>0?n.DIRECTION_LEFT:n.DIRECTION_RIGHT:e.pageY-t.pageY>0?n.DIRECTION_UP:n.DIRECTION_DOWN},getDistance:function(e,t){var n=t.pageX-e.pageX,i=t.pageY-e.pageY;return Math.sqrt(n*n+i*i)},getScale:function(e,t){return e.length>=2&&t.length>=2?this.getDistance(t[0],t[1])/this.getDistance(e[0],e[1]):1},getRotation:function(e,t){return e.length>=2&&t.length>=2?this.getAngle(t[1],t[0])-this.getAngle(e[1],e[0]):0},stopDefaultBrowserBehavior:function(e){var t,n=["webkit","khtml","moz","ms","o",""],i=e.options.stop_browser_behavior;if(i){for(var r=0;n.length>r;r++)for(var s in i)i.hasOwnProperty(s)&&(t=s,n[r]&&(t=n[r]+t.substring(0,1).toUpperCase()+t.substring(1)),e.element.style[t]=i[s]);"none"==i.userSelect&&(e.element.onselectstart=function(){return!1})}}},n.gesture={gestures:[],current:null,previous:null,startDetect:function(e,t){return this.current?undefined:(this.current={inst:e,startEvent:n.utils.extend({},t),lastEvent:!1,name:""},this.detect(t))},detect:function(e){if(this.current){for(var t=this.extendEventData(e),n=this.current.inst.options,i=0,r=this.gestures.length;r>i;i++){var s=this.gestures[i];if(n[s.name]!==!1&&s.handler.call(s,t,this.current.inst)===!1){this.stop();break}}this.current.lastEvent=t}},endDetect:function(e){this.detect(e),this.stop()},stop:function(){this.previous=n.utils.extend({},this.current),this.current=null},extendEventData:function(e){var t=this.current.startEvent;return t&&e.touches.length!=t.touches.length&&(t.touches=n.utils.extend({},e.touches,1)),n.utils.extend(e,{touchTime:e.time-t.time,angle:n.utils.getAngle(t.center,e.center),direction:n.utils.getDirection(t.center,e.center),distance:n.utils.getDistance(t.center,e.center),distanceX:n.utils.getSimpleDistance(t.center.pageX,e.center.pageX),distanceY:n.utils.getSimpleDistance(t.center.pageY,e.center.pageY),scale:n.utils.getScale(t.touches,e.touches),rotation:n.utils.getRotation(t.touches,e.touches),startEvent:t}),e},register:function(e){var t=e.defaults||{};t[e.name]===undefined&&(t[e.name]=!0),n.utils.extend(n.defaults,t),e.index=e.index||1e3,this.gestures.push(e),this.gestures.sort(function(e,t){return e.indext.index?1:0})}},n.gestures=n.gestures||{},n.gestures.Hold={name:"hold",index:10,defaults:{hold_timeout:500,hold_threshold:2},timer:null,handler:function(e,t){switch(e.eventType){case n.EVENT_START:clearTimeout(this.timer),n.gesture.current.name=this.name,this.timer=setTimeout(function(){"hold"==n.gesture.current.name&&t.trigger("hold",e)},t.options.hold_timeout);break;case n.EVENT_MOVE:e.distance>t.options.hold_treshold&&clearTimeout(this.timer);break;case n.EVENT_END:clearTimeout(this.timer)}}},n.gestures.Tap={name:"tap",index:100,defaults:{tap_max_touchtime:250,tap_max_distance:10,doubletap_distance:20,doubletap_interval:300},handler:function(e,t){if(e.eventType==n.EVENT_END){var i=n.gesture.previous;if(e.touchTime>t.options.tap_max_touchtime||e.distance>t.options.tap_max_distance)return;n.gesture.current.name=i&&"tap"==i.name&&e.time-i.lastEvent.time0&&e.touches.length>t.options.drag_max_touches)&&e.eventType==n.EVENT_MOVE){if(e.distancet.options.swipe_min_time&&e.touchTimet.options.swipe_min_distance&&(t.trigger(this.name,e),t.trigger(this.name+e.direction,e))}},n.gestures.Transform={name:"transform",index:45,defaults:{transform_min_scale:.01,transform_min_rotation:1,transform_always_block:!1},handler:function(e,t){if(t.options.transform_always_block&&2==e.touches.length&&e.preventDefault(),e.eventType==n.EVENT_MOVE&&2==e.touches.length){var i=Math.abs(1-e.scale),r=Math.abs(e.rotation);if(t.options.transform_min_scale>i&&t.options.transform_min_rotation>r)return;n.gesture.current.name=this.name,t.trigger(this.name,e),r>t.options.transform_min_rotation&&t.trigger("rotate",e),i>t.options.transform_min_scale&&(t.trigger("pinch",e),t.trigger("pinch"+(1>e.scale?"in":"out"),e))}}},n.gestures.Touch={name:"touch",index:-1/0,handler:function(e,t){e.eventType==n.EVENT_START&&t.trigger(this.name,e)}},n.gestures.Release={name:"release",index:1/0,handler:function(e,t){e.eventType==n.EVENT_END&&t.trigger(this.name,e)}},e.Hammer=n})(window); -------------------------------------------------------------------------------- /dist/hammer-latest.min.js: -------------------------------------------------------------------------------- 1 | /*! Hammer.JS - v1.0.0rc1 - 2013-02-05 2 | * http://eightmedia.github.com/hammer.js 3 | * 4 | * Copyright (c) 2013 Jorik Tangelder ; 5 | * Licensed under the MIT license */ 6 | 7 | (function(e){"use strict";function t(){if(!n.READY){n.event.determineEventTypes();for(var e in n.gestures)n.gestures.hasOwnProperty(e)&&n.gesture.register(n.gestures[e]);n.event.onTouch(document,n.EVENT_MOVE,n.gesture.detect),n.event.onTouch(document,n.EVENT_END,n.gesture.endDetect),n.READY=!0}}var n=function(e,t){return new n.Instance(e,t||{})};n.defaults={stop_browser_behavior:{userSelect:"none",touchCallout:"none",touchAction:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}},n.HAS_POINTEREVENTS=e.navigator.msPointerEnabled,n.HAS_TOUCHEVENTS="ontouchstart"in e,n.EVENT_TYPES={},n.DIRECTION_DOWN="down",n.DIRECTION_LEFT="left",n.DIRECTION_UP="up",n.DIRECTION_RIGHT="right",n.EVENT_START="start",n.EVENT_MOVE="move",n.EVENT_END="end",n.plugins={},n.READY=!1,n.Instance=function(e,i){var r=this;return t(),this.element=e,this.options=n.utils.extend(n.utils.extend({},n.defaults),i||{}),this.options.stop_browser_behavior&&n.utils.stopDefaultBrowserBehavior(this),n.event.onTouch(e,n.EVENT_START,function(e){return n.gesture.startDetect(r,e)}),this},n.Instance.prototype={on:function(e,t){e=e.split(" ");for(var n=0;e.length>n;n++)this.element.addEventListener(e[n],t,!1)},off:function(e,t){e=e.split(" ");for(var n=0;e.length>n;n++)this.element.removeEventListener(e[n],t,!1)},trigger:function(e,t){var n=document.createEvent("Event");return n.initEvent(e,!0,!0),n.gesture=t,this.element.dispatchEvent(n)}};var i={},r=!1;n.event={bindDom:function(e,t,n){t=t.split(" ");for(var i=0;t.length>i;i++)e.addEventListener(t[i],n,!1)},onTouch:function(e,t,s){function a(r){n.HAS_POINTEREVENTS&&n.PointerEvent.updatePointer(t,r),t===n.EVENT_END?r=i:i=r,s.call(n.gesture,o.collectEventData(e,t,r))}var o=this;n.HAS_TOUCHEVENTS||n.HAS_POINTEREVENTS?this.bindDom(e,n.EVENT_TYPES[t],a):this.bindDom(e,n.EVENT_TYPES[t],function(e){1===e.which&&(r=!0,a.apply(this,arguments)),"mouseup"==e.type&&(r=!1)})},determineEventTypes:function(){var e;e=n.HAS_POINTEREVENTS?["MSPointerDown","MSPointerMove","MSPointerUp MSPointerCancel"]:n.HAS_TOUCHEVENTS?["touchstart","touchmove","touchend touchcancel"]:["mousedown","mousemove","mouseup"],n.EVENT_TYPES[n.EVENT_START]=e[0],n.EVENT_TYPES[n.EVENT_MOVE]=e[1],n.EVENT_TYPES[n.EVENT_END]=e[2]},getTouchList:function(e){return n.HAS_POINTEREVENTS?n.PointerEvent.getPointers():n.HAS_TOUCHEVENTS?e.touches:[{identifier:1,pageX:e.pageX,pageY:e.pageY,target:e.target}]},collectEventData:function(e,t,i){var r=this.getTouchList(i,t);return{center:n.utils.getCenter(r),time:(new Date).getTime(),target:i.target,touches:r,eventType:t,srcEvent:i,preventDefault:function(){return this.srcEvent.preventDefault()}}}};var s=Math.PI;n.utils={extend:function(e,t,n){n=n||0;for(var i in t)t.hasOwnProperty(i)&&(e[i]=n&&"object"==typeof t[i]?this.extend({},t[i],n-1):t[i]);return e},fastAbs:function(e){return(e^e>>31)-(e>>31)},getCenter:function(e){for(var t=[],n=[],i=0,r=e.length;r>i;i++)t.push(e[i].pageX),n.push(e[i].pageY);return{pageX:(Math.min.apply(Math,t)+Math.max.apply(Math,t))/2,pageY:(Math.min.apply(Math,n)+Math.max.apply(Math,n))/2}},getSimpleDistance:function(e,t){return this.fastAbs(t-e)},getAngle:function(e,t){var n=t.pageY-e.pageY,i=t.pageX-e.pageX;return 180*Math.atan2(n,i)/s},getDirection:function(e,t){var i=this.fastAbs(e.pageX-t.pageX),r=this.fastAbs(e.pageY-t.pageY);return i>=r?e.pageX-t.pageX>0?n.DIRECTION_LEFT:n.DIRECTION_RIGHT:e.pageY-t.pageY>0?n.DIRECTION_UP:n.DIRECTION_DOWN},getDistance:function(e,t){var n=t.pageX-e.pageX,i=t.pageY-e.pageY;return Math.sqrt(n*n+i*i)},getScale:function(e,t){return e.length>=2&&t.length>=2?this.getDistance(t[0],t[1])/this.getDistance(e[0],e[1]):1},getRotation:function(e,t){return e.length>=2&&t.length>=2?this.getAngle(t[1],t[0])-this.getAngle(e[1],e[0]):0},stopDefaultBrowserBehavior:function(e){var t,n=["webkit","khtml","moz","ms","o",""],i=e.options.stop_browser_behavior;if(i){for(var r=0;n.length>r;r++)for(var s in i)i.hasOwnProperty(s)&&(t=s,n[r]&&(t=n[r]+t.substring(0,1).toUpperCase()+t.substring(1)),e.element.style[t]=i[s]);"none"==i.userSelect&&(e.element.onselectstart=function(){return!1})}}},n.gesture={gestures:[],current:null,previous:null,startDetect:function(e,t){return this.current?undefined:(this.current={inst:e,startEvent:n.utils.extend({},t),lastEvent:!1,name:""},this.detect(t))},detect:function(e){if(this.current){for(var t=this.extendEventData(e),n=this.current.inst.options,i=0,r=this.gestures.length;r>i;i++){var s=this.gestures[i];if(n[s.name]!==!1&&s.handler.call(s,t,this.current.inst)===!1){this.stop();break}}this.current.lastEvent=t}},endDetect:function(e){this.detect(e),this.stop()},stop:function(){this.previous=n.utils.extend({},this.current),this.current=null},extendEventData:function(e){var t=this.current.startEvent;return t&&e.touches.length!=t.touches.length&&(t.touches=n.utils.extend({},e.touches,1)),n.utils.extend(e,{touchTime:e.time-t.time,angle:n.utils.getAngle(t.center,e.center),direction:n.utils.getDirection(t.center,e.center),distance:n.utils.getDistance(t.center,e.center),distanceX:n.utils.getSimpleDistance(t.center.pageX,e.center.pageX),distanceY:n.utils.getSimpleDistance(t.center.pageY,e.center.pageY),scale:n.utils.getScale(t.touches,e.touches),rotation:n.utils.getRotation(t.touches,e.touches),startEvent:t}),e},register:function(e){var t=e.defaults||{};t[e.name]===undefined&&(t[e.name]=!0),n.utils.extend(n.defaults,t),e.index=e.index||1e3,this.gestures.push(e),this.gestures.sort(function(e,t){return e.indext.index?1:0})}},n.gestures=n.gestures||{},n.gestures.Hold={name:"hold",index:10,defaults:{hold_timeout:500,hold_threshold:2},timer:null,handler:function(e,t){switch(e.eventType){case n.EVENT_START:clearTimeout(this.timer),n.gesture.current.name=this.name,this.timer=setTimeout(function(){"hold"==n.gesture.current.name&&t.trigger("hold",e)},t.options.hold_timeout);break;case n.EVENT_MOVE:e.distance>t.options.hold_treshold&&clearTimeout(this.timer);break;case n.EVENT_END:clearTimeout(this.timer)}}},n.gestures.Tap={name:"tap",index:100,defaults:{tap_max_touchtime:250,tap_max_distance:10,doubletap_distance:20,doubletap_interval:300},handler:function(e,t){if(e.eventType==n.EVENT_END){var i=n.gesture.previous;if(e.touchTime>t.options.tap_max_touchtime||e.distance>t.options.tap_max_distance)return;n.gesture.current.name=i&&"tap"==i.name&&e.time-i.lastEvent.time0&&e.touches.length>t.options.drag_max_touches)&&e.eventType==n.EVENT_MOVE){if(e.distancet.options.swipe_min_time&&e.touchTimet.options.swipe_min_distance&&(t.trigger(this.name,e),t.trigger(this.name+e.direction,e))}},n.gestures.Transform={name:"transform",index:45,defaults:{transform_min_scale:.01,transform_min_rotation:1,transform_always_block:!1},handler:function(e,t){if(t.options.transform_always_block&&2==e.touches.length&&e.preventDefault(),e.eventType==n.EVENT_MOVE&&2==e.touches.length){var i=Math.abs(1-e.scale),r=Math.abs(e.rotation);if(t.options.transform_min_scale>i&&t.options.transform_min_rotation>r)return;n.gesture.current.name=this.name,t.trigger(this.name,e),r>t.options.transform_min_rotation&&t.trigger("rotate",e),i>t.options.transform_min_scale&&(t.trigger("pinch",e),t.trigger("pinch"+(1>e.scale?"in":"out"),e))}}},n.gestures.Touch={name:"touch",index:-1/0,handler:function(e,t){e.eventType==n.EVENT_START&&t.trigger(this.name,e)}},n.gestures.Release={name:"release",index:1/0,handler:function(e,t){e.eventType==n.EVENT_END&&t.trigger(this.name,e)}},e.Hammer=n})(window); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hammer.js v2 (release candidate) 2 | ### A javascript library for multi-touch gestures 3 | 4 | > I told you, homeboy / 5 | > You *CAN* touch this / 6 | > Yeah, that's how we living and you know / 7 | > You *CAN* touch this 8 | 9 | 10 | ## Demo 11 | [Watch the demo's](http://eightmedia.github.com/hammer.js/v2/). 12 | It always needs some testing with all kind of devices, please contribute! 13 | 14 | 15 | ## Why the rewrite 16 | - The previous Hammer.js became old, and too much of a hobby project: Inefficient code, bad documentation. 17 | - It wasn't possible to add custom gestures, or change anything about the (inner) working of the gestures. 18 | - It needed DOM events, to use with event event delegation. 19 | - Windows8 has touch AND mouse, so the Pointer Events API needs to be implemented. 20 | - Did I mentioned the code was inefficient? Now a Hammer instance is light, and only contains the methods it should. 21 | 22 | 23 | ## New features in v2 24 | - DOM Events 25 | - Debug plugins 26 | - Custom gestures api 27 | - jQuery plugin with events delegation (the on/off methods) available 28 | - Efficient code, lower memory usage 29 | - IE8 and older compatibility with jQuery plugin 30 | - More events for faster implementation 31 | 32 | 33 | ## How to use it 34 | Hammer became simpler to use, with an jQuery-like API. You dont need to add the new keyword, and the eventlisteners are chainable. 35 | 36 | var element = document.getElementById('test_el'); 37 | var hammertime = Hammer(element).on("tap", function(event) { 38 | alert('hello!'); 39 | }); 40 | 41 | You can change the default settings by adding an second argument with options 42 | 43 | var hammertime = Hammer(element, { 44 | drag: false, 45 | transform: false 46 | }); 47 | 48 | Events can be added/removed with the on and off methods, just like you would in jQuery. 49 | Event delegation is also possible when you use the jQuery plugin. 50 | 51 | $('#test_el').hammer().on("tap", ".nested_el", function(event) { 52 | console.log(this, event); 53 | }); 54 | 55 | The ````event```` argument in the callback contains the same properties for each gesture, making more sense for some then for others. 56 | The gesture that was triggered is found in ````event.type````. Following properties are available in ````event.gesture```` 57 | 58 | time {Number} time the event occurred 59 | target {HTMLElement} target element 60 | touches {Array} touches (fingers, pointers, mouse) on the screen 61 | center {Object} center position of the touches. contains pageX and pageY 62 | touchTime {Number} the total time of the touches in the screen 63 | angle {Number} the angle we are moving 64 | direction {String} the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT 65 | distance {Number} the distance we haved moved 66 | distanceX {Number} the distance on x axis we haved moved 67 | distanceY {Number} the distance on y axis we haved moved 68 | scale {Number} scaling of the touches, needs 2 touches 69 | rotation {Number} rotation of the touches, needs 2 touches * 70 | eventType {String} matches Hammer.EVENT_START|MOVE|END 71 | srcEvent {Object} the source event, like TouchStart or MouseDown * 72 | startEvent {Object} contains the same properties as above, 73 | but from the first touch. this is used to calculate 74 | distances, touchTime, scaling etc 75 | 76 | You can write your own gestures, you can find examples and documentation about this in gestures.js. 77 | The following gestures are available, you can find options for it in gestures.js 78 | 79 | - hold 80 | - tap 81 | - doubletap 82 | - drag, dragup, dragdown, dragleft, dragright 83 | - swipe, swipeup, swipedown, swipeleft, swiperight 84 | - transform 85 | - rotate 86 | - pinch, pinchin, pinchout 87 | - touch (gesture detection starts) 88 | - release (gesture detection ends) 89 | 90 | 91 | ## Compatibility 92 | | | Tap | Double Tap | Hold | Drag | Transform | 93 | |:----------------------------------|:----|:-----------|:-----|:-----|:----------| 94 | | **BlackBerry** | 95 | | Playbook | X | X | X | X | X | 96 | | BlackBerry 10 | X | X | X | X | X | 97 | | | 98 | | **iOS** | 99 | | iPhone/iPod iOS 6 | X | X | X | X | X | 100 | | iPad/iPhone iOS 5 | X | X | X | X | X | 101 | | iPhone iOS 4 | X | X | X | X | X | 102 | | | 103 | | **Android 4** | 104 | | Default browser | X | X | X | X | X | 105 | | Chrome | X | X | X | X | X | 106 | | Opera | X | X | X | X | X | 107 | | Firefox | X | X | X | X | X | 108 | | | 109 | | **Android 3** | 110 | | Default browser | X | X | X | X | X | 111 | | | 112 | | **Android 2** | 113 | | Default browser | X | X | X | X | | 114 | | Firefox | X | X | X | X | | 115 | | Opera Mobile | X | X | X | X | | 116 | | Opera Mini | X | | | | | 117 | | | 118 | | **Others** | 119 | | Kindle Fire | X | X | X | X | X | 120 | | Nokia N900 - Firefox 1.1 | X | | | | | 121 | | | 122 | | **Windows** | 123 | | Internet Explorer 7 | X | X | X | X | X* | 124 | | Internet Explorer 8 | X | X | X | X | X* | 125 | | Internet Explorer 9 | X | X | X | X | X* | 126 | | Internet Explorer 10 | X | X | X | X | X* | 127 | | | 128 | | **OSX** | 129 | | Firefox | X | X | X | X | X* | 130 | | Opera | X | X | X | X | X* | 131 | | Chrome | X | X | X | X | X* | 132 | | Safari | X | X | X | X | X* | 133 | 134 | 135 | On a desktop browser the mouse can be used to simulate touch events with one finger. 136 | On Android 2 (and 3?) doesn't support multi-touch events, so there's no transform callback on these Android versions. 137 | Firefox 1.1 (Nokia N900) and Windows Phone 7.5 doesnt support touch events, and mouse events are badly supported. 138 | 139 | Not all gestures are supported on every device. This matrix shows the support we have tested. This is ofcourse far from extensive. 140 | If you've tested hammer.js on a different device, please let us know. 141 | 142 | * Transform gesture is available on Windows and OSX with the hammer.fakemultitouch.js plugin. 143 | 144 | 145 | ## Todo 146 | - Update website in gh-pages 147 | - More demo's 148 | - Measure speed difference of V1 with V2 149 | 150 | 151 | ## Further notes 152 | Created by [Jorik Tangelder](http://twitter.com/jorikdelaporik) and developed further by everyone at [Eight Media](http://www.eight.nl/) in Arnhem, the Netherlands. 153 | 154 | Add your feature suggestions and bug reports on [Github](http://github.com/eightmedia/hammer.js/issues). 155 | 156 | We recommend listening to [this loop](http://soundcloud.com/eightmedia/hammerhammerhammer) while using hammer.js. 157 | -------------------------------------------------------------------------------- /examples/events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hammer.js 5 | 6 | 7 | 84 | 85 | 86 | 132 | 133 |
-
134 | 135 |
136 |

Hitarea

137 |

Press shift on your desktop for multitouch.

138 |

139 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent at erat felis. Donec ornare ligula non nibh vulputate sollicitudin. Cras sodales eros a velit pulvinar vehicula. In sed lorem lectus, vel dapibus nulla. Mauris lacus massa, volutpat vel suscipit at, lacinia condimentum libero. Praesent nec metus ligula. Morbi porttitor rhoncus mattis. 140 |

141 |

142 | Donec nisi ante, eleifend vitae luctus vel, auctor et nibh. Donec scelerisque urna id massa ultricies in facilisis purus rutrum. 143 | Proin tristique luctus leo vitae feugiat. Donec sit amet ipsum mi, nec bibendum sem. Nullam sodales aliquet venenatis. 144 |

145 |
146 |

147 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent at erat felis. Donec ornare ligula non nibh vulputate sollicitudin. Cras sodales eros a velit pulvinar vehicula. In sed lorem lectus, vel dapibus nulla. Mauris lacus massa, volutpat vel suscipit at, lacinia condimentum libero. Praesent nec metus ligula. Morbi porttitor rhoncus mattis. 148 |

149 |

150 | Donec adipiscing porttitor risus, vel commodo ante tincidunt vitae. Fusce lacinia augue non sapien volutpat facilisis. Integer varius faucibus metus, sit amet viverra justo venenatis id. Nam ornare rhoncus tempus. Nulla eleifend, mauris quis auctor bibendum, mi purus interdum sapien, id fringilla nunc mi ut tortor. Sed pretium egestas augue, eget volutpat ipsum egestas nec. Aliquam a elementum justo. Suspendisse tempus, nisi id tincidunt vulputate, nunc sem scelerisque risus, molestie facilisis lacus velit et nunc. Phasellus sed convallis libero. Phasellus sit amet neque non arcu pellentesque laoreet id sed ligula. Donec gravida laoreet condimentum. Ut ornare dignissim tempus. Mauris aliquet tincidunt turpis, quis pulvinar nisl pulvinar id. Sed in gravida ligula. 151 |

152 |

153 | Ut molestie, lectus vel pharetra pharetra, nunc libero interdum ligula, eget vulputate purus nulla sit amet turpis. Aliquam volutpat porttitor erat ac volutpat. Donec ligula elit, tincidunt non congue id, iaculis feugiat sem. Phasellus vestibulum mi id enim interdum imperdiet. Ut dolor ante, tempus sit amet ornare a, faucibus sed massa. Curabitur adipiscing, mauris eget vestibulum lacinia, nisl lorem viverra velit, vitae facilisis urna erat et est. Pellentesque fringilla metus libero, at accumsan nisl. Etiam nisl lorem, placerat ut tristique vel, luctus id nulla. Sed vel nunc ut justo volutpat eleifend ac nec risus. Praesent at viverra tellus. Maecenas semper pellentesque quam, et bibendum nisl eleifend sit amet. Donec sed elit eget magna dictum dignissim. 154 |

155 | 156 |

Event log

157 |

158 | 
159 | 
160 | 
161 | 
162 | 
163 | 
164 | 
165 | 
166 | 
167 | 
168 | 
175 | 
176 | 
177 | 
178 | 
249 | 
250 | 


--------------------------------------------------------------------------------
/src/gestures.js:
--------------------------------------------------------------------------------
  1 | Hammer.gestures = Hammer.gestures || {};
  2 | 
  3 | /**
  4 |  * Custom gestures
  5 |  * ==============================
  6 |  *
  7 |  * Gesture object
  8 |  * --------------------
  9 |  * The object structure of a gesture:
 10 |  *
 11 |  * { name: 'mygesture',
 12 |  *   index: 1337,
 13 |  *   defaults: {
 14 |  *     mygesture_option: true
 15 |  *   }
 16 |  *   handler: function(type, ev, inst) {
 17 |  *     // trigger gesture event
 18 |  *     inst.trigger(this.name, ev);
 19 |  *   }
 20 |  * }
 21 | 
 22 |  * @param   {String}    name
 23 |  * this should be the name of the gesture, lowercase
 24 |  * it is also being used to disable/enable the gesture per instance config.
 25 |  *
 26 |  * @param   {Number}    [index=1000]
 27 |  * the index of the gesture, where it is going to be in the stack of gestures detection
 28 |  * like when you build an gesture that depends on the drag gesture, it is a good
 29 |  * idea to place it after the index of the drag gesture.
 30 |  *
 31 |  * @param   {Object}    [defaults={}]
 32 |  * the default settings of the gesture. these are added to the instance settings,
 33 |  * and can be overruled per instance. you can also add the name of the gesture,
 34 |  * but this is also added by default (and set to true).
 35 |  *
 36 |  * @param   {Function}  handler
 37 |  * this handles the gesture detection of your custom gesture and receives the
 38 |  * following arguments:
 39 |  *
 40 |  *      @param  {Object}    eventData
 41 |  *      event data containing the following properties:
 42 |  *          time        {Number}        time the event occurred
 43 |  *          target      {HTMLElement}   target element
 44 |  *          touches     {Array}         touches (fingers, pointers, mouse) on the screen
 45 |  *          center      {Object}        center position of the touches. contains pageX and pageY
 46 |  *          touchTime   {Number}        the total time of the touches in the screen
 47 |  *          angle       {Number}        the angle we are moving
 48 |  *          direction   {String}        the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT
 49 |  *          distance    {Number}        the distance we haved moved
 50 |  *          distanceX   {Number}        the distance on x axis we haved moved
 51 |  *          distanceY   {Number}        the distance on y axis we haved moved
 52 |  *          scale       {Number}        scaling of the touches, needs 2 touches
 53 |  *          rotation    {Number}        rotation of the touches, needs 2 touches *
 54 |  *          eventType   {String}        matches Hammer.EVENT_START|MOVE|END
 55 |  *          srcEvent    {Object}        the source event, like TouchStart or MouseDown *
 56 |  *          startEvent  {Object}        contains the same properties as above,
 57 |  *                                      but from the first touch. this is used to calculate
 58 |  *                                      distances, touchTime, scaling etc
 59 |  *
 60 |  *      @param  {Hammer.Instance}    inst
 61 |  *      the instance we are doing the detection for. you can get the options from
 62 |  *      the inst.options object and trigger the gesture event by calling inst.trigger
 63 |  *
 64 |  *
 65 |  * Handle gestures
 66 |  * --------------------
 67 |  * inside the handler you can get/set Hammer.gesture.current. This is the current
 68 |  * detection session. It has the following properties
 69 |  *      @param  {String}    name
 70 |  *      contains the name of the gesture we have detected. it has not a real function,
 71 |  *      only to check in other gestures if something is detected.
 72 |  *      like in the drag gesture we set it to 'drag' and in the swipe gesture we can
 73 |  *      check if the current gesture is 'drag' by accessing Hammer.gesture.current.name
 74 |  *
 75 |  *      @readonly
 76 |  *      @param  {Hammer.Instance}    inst
 77 |  *      the instance we do the detection for
 78 |  *
 79 |  *      @readonly
 80 |  *      @param  {Object}    startEvent
 81 |  *      contains the properties of the first gesture detection in this session.
 82 |  *      Used for calculations about timing, distance, etc.
 83 |  *
 84 |  *      @readonly
 85 |  *      @param  {Object}    lastEvent
 86 |  *      contains all the properties of the last gesture detect in this session.
 87 |  *
 88 |  * after the gesture detection session has been completed (user has released the screen)
 89 |  * the Hammer.gesture.current object is copied into Hammer.gesture.previous,
 90 |  * this is usefull for gestures like doubletap, where you need to know if the
 91 |  * previous gesture was a tap
 92 |  *
 93 |  * options that have been set by the instance can be received by calling inst.options
 94 |  *
 95 |  * You can trigger a gesture event by calling inst.trigger("mygesture", event).
 96 |  * The first param is the name of your gesture, the second the event argument
 97 |  *
 98 |  *
 99 |  * Register gestures
100 |  * --------------------
101 |  * When an gesture is added to the Hammer.gestures object, it is auto registered
102 |  * at the setup of the first Hammer instance. You can also call Hammer.gesture.register
103 |  * manually and pass your gesture object as a param
104 |  *
105 |  */
106 | 
107 | /**
108 |  * Hold
109 |  * Touch stays at the same place for x time
110 |  * @events  hold
111 |  */
112 | Hammer.gestures.Hold = {
113 |     name: 'hold',
114 |     index: 10,
115 |     defaults: {
116 |         hold_timeout: 500,
117 |         hold_threshold: 2
118 |     },
119 |     timer: null,
120 |     handler: function holdGesture(ev, inst) {
121 |         var self = this;
122 |         switch(ev.eventType) {
123 |             case Hammer.EVENT_START:
124 |                 // clear any running timers
125 |                 clearTimeout(this.timer);
126 | 
127 |                 // set the gesture so we can check in the timeout if it still is
128 |                 Hammer.gesture.current.name = this.name;
129 | 
130 |                 // set timer and if after the timeout it still is hold,
131 |                 // we trigger the hold event
132 |                 this.timer = setTimeout(function() {
133 |                     if(Hammer.gesture.current.name == 'hold') {
134 |                         inst.trigger('hold', ev);
135 |                     }
136 |                 }, inst.options.hold_timeout);
137 |                 break;
138 | 
139 |             // when you move or end we clear the timer
140 |             case Hammer.EVENT_MOVE:
141 |                 if(ev.distance > inst.options.hold_treshold) {
142 |                     clearTimeout(this.timer);
143 |                 }
144 |                 break;
145 | 
146 |             case Hammer.EVENT_END:
147 |                 clearTimeout(this.timer);
148 |                 break;
149 |         }
150 |     }
151 | };
152 | 
153 | 
154 | /**
155 |  * Tap/DoubleTap
156 |  * Quick touch at a place or double at the same place
157 |  * @events  tap, doubletap
158 |  */
159 | Hammer.gestures.Tap = {
160 |     name: 'tap',
161 |     index: 100,
162 |     defaults: {
163 |         tap_max_touchtime  : 250,
164 |         tap_max_distance   : 10,
165 |         doubletap_distance : 20,
166 |         doubletap_interval : 300
167 |     },
168 |     handler: function tapGesture(ev, inst) {
169 |         if(ev.eventType == Hammer.EVENT_END) {
170 |             // previous gesture, for the double tap since these are two different gesture detections
171 |             var prev = Hammer.gesture.previous;
172 | 
173 |             // when the touchtime is higher then the max touch time
174 |             // or when the moving distance is too much
175 |             if(ev.touchTime > inst.options.tap_max_touchtime ||
176 |                 ev.distance > inst.options.tap_max_distance) {
177 |                 return;
178 |             }
179 | 
180 |             // check if double tap
181 |             if(prev && prev.name == 'tap' &&
182 |                 (ev.time - prev.lastEvent.time) < inst.options.doubletap_interval &&
183 |                 ev.distance < inst.options.doubletap_distance) {
184 |                 Hammer.gesture.current.name = 'doubletap';
185 |             }
186 |             else {
187 |                 Hammer.gesture.current.name = 'tap';
188 |             }
189 | 
190 |             inst.trigger(Hammer.gesture.current.name, ev);
191 |         }
192 |     }
193 | };
194 | 
195 | 
196 | /**
197 |  * Drag
198 |  * Move with x fingers (default 1) around on the page. Blocking the scrolling when
199 |  * moving left and right is a good practice. When all the drag events are blocking
200 |  * you disable scrolling on that area.
201 |  * @events  drag, drapleft, dragright, dragup, dragdown
202 |  */
203 | Hammer.gestures.Drag = {
204 |     name: 'drag',
205 |     index: 50,
206 |     defaults: {
207 |         drag_min_distance : 10,
208 |         // set 0 for unlimited, but this can conflict with transform
209 |         drag_max_touches  : 1,
210 |         // prevent default browser behavior when dragging occurs
211 |         // be careful with it, it makes the element a blocking element
212 |         // when you are using the drag gesture, it is a good practice to set this true
213 |         drag_block_horizontal   : false,
214 |         drag_block_vertical     : false
215 |     },
216 |     handler: function dragGesture(ev, inst) {
217 |         // max touches
218 |         if(inst.options.drag_max_touches > 0 &&
219 |             ev.touches.length > inst.options.drag_max_touches) {
220 |             return;
221 |         }
222 | 
223 |         if(ev.eventType == Hammer.EVENT_MOVE){
224 |             // when the distance we moved is too small we skip this gesture
225 |             // or we can be already in dragging
226 |             if(ev.distance < inst.options.drag_min_distance &&
227 |                 Hammer.gesture.current.name != this.name) {
228 |                 return;
229 |             }
230 | 
231 |             Hammer.gesture.current.name = this.name;
232 |             inst.trigger(this.name, ev); // basic drag event
233 |             inst.trigger(this.name + ev.direction, ev);  // direction event, like dragdown
234 | 
235 |             // block the browser events
236 |             if( (inst.options.drag_block_vertical && (
237 |                     ev.direction == Hammer.DIRECTION_UP ||
238 |                     ev.direction == Hammer.DIRECTION_DOWN)) ||
239 |                 (inst.options.drag_block_horizontal && (
240 |                     ev.direction == Hammer.DIRECTION_LEFT ||
241 |                     ev.direction == Hammer.DIRECTION_RIGHT))) {
242 |                 ev.preventDefault();
243 |             }
244 |         }
245 |     }
246 | };
247 | 
248 | 
249 | /**
250 |  * Swipe
251 |  * called after dragging ends and the user moved for x ms a small distance
252 |  * @events  swipe, swipeleft, swiperight, swipeup, swipedown
253 |  */
254 | Hammer.gestures.Swipe = {
255 |     name: 'swipe',
256 |     index: 51,
257 |     defaults: {
258 |         swipe_min_time     : 150,
259 |         swipe_max_time     : 500,
260 |         swipe_min_distance : 30
261 |     },
262 |     handler: function swipeGesture(ev, inst) {
263 |         if(ev.eventType == Hammer.EVENT_END) {
264 |             // when the distance we moved is too small we skip this gesture
265 |             // or we can be already in dragging
266 |             if(Hammer.gesture.current.name == 'drag' &&
267 |                 ev.touchTime > inst.options.swipe_min_time &&
268 |                 ev.touchTime < inst.options.swipe_max_time &&
269 |                 ev.distance > inst.options.swipe_min_distance) {
270 |                 // trigger swipe events
271 |                 inst.trigger(this.name, ev);
272 |                 inst.trigger(this.name + ev.direction, ev);
273 |             }
274 |         }
275 |     }
276 | };
277 | 
278 | 
279 | /**
280 |  * Transform
281 |  * User want to scale or rotate with 2 fingers
282 |  * @events  transform, pinch, pinchin, pinchout, rotate
283 |  */
284 | Hammer.gestures.Transform = {
285 |     name: 'transform',
286 |     index: 45,
287 |     defaults: {
288 |         // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
289 |         transform_min_scale     : 0.01,
290 |         // rotation in degrees
291 |         transform_min_rotation  : 1,
292 |         // prevent default browser behavior when two touches are on the screen
293 |         // but it makes the element a blocking element
294 |         // when you are using the transform gesture, it is a good practice to set this true
295 |         transform_always_block  : false
296 |     },
297 |     handler: function transformGesture(ev, inst) {
298 |         // prevent default when two fingers are on the screen
299 |         if(inst.options.transform_always_block && ev.touches.length == 2) {
300 |             ev.preventDefault();
301 |         }
302 | 
303 |         // at least multitouch
304 |         if(ev.eventType == Hammer.EVENT_MOVE && ev.touches.length == 2) {
305 |             var scale_threshold = Math.abs(1-ev.scale);
306 |             var rotation_threshold = Math.abs(ev.rotation);
307 | 
308 |             // when the distance we moved is too small we skip this gesture
309 |             // or we can be already in dragging
310 |             if(scale_threshold < inst.options.transform_min_scale &&
311 |                 rotation_threshold < inst.options.transform_min_rotation) {
312 |                 return;
313 |             }
314 | 
315 |             Hammer.gesture.current.name = this.name;
316 |             inst.trigger(this.name, ev); // basic drag event
317 | 
318 |             // trigger rotate event
319 |             if(rotation_threshold > inst.options.transform_min_rotation) {
320 |                 inst.trigger('rotate', ev);
321 |             }
322 | 
323 |             // trigger pinch event
324 |             if(scale_threshold > inst.options.transform_min_scale) {
325 |                 inst.trigger('pinch', ev);
326 |                 inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev);  // direction event, like pinchin
327 |             }
328 |         }
329 |     }
330 | };
331 | 
332 | 
333 | /**
334 |  * Touch
335 |  * Called as first, tells the user has touched the screen
336 |  * @events  touch
337 |  */
338 | Hammer.gestures.Touch = {
339 |     name: 'touch',
340 |     index: -Infinity,
341 |     handler: function touchGesture(ev, inst) {
342 |         if(ev.eventType ==  Hammer.EVENT_START) {
343 |             inst.trigger(this.name, ev);
344 |         }
345 |     }
346 | };
347 | 
348 | 
349 | /**
350 |  * Release
351 |  * Called as last, tells the user has released the screen
352 |  * @events  release
353 |  */
354 | Hammer.gestures.Release = {
355 |     name: 'release',
356 |     index: Infinity,
357 |     handler: function releaseGesture(ev, inst) {
358 |         if(ev.eventType ==  Hammer.EVENT_END) {
359 |             inst.trigger(this.name, ev);
360 |         }
361 |     }
362 | };


--------------------------------------------------------------------------------
/examples/pull-to-refresh.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 |     Hammer.js
  5 |     
  6 |     
  7 | 
  8 |     
120 | 
121 | 
122 | 
123 | 
124 | 
125 | 126 |
127 |
128 |
129 | 130 |
131 |
132 | 133 |
134 |

Pull-to-Refresh random images

135 |

136 | 137 |

138 |

139 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent at erat felis. Donec ornare ligula non nibh vulputate sollicitudin. Cras sodales eros a velit pulvinar vehicula. In sed lorem lectus, vel dapibus nulla. Mauris lacus massa, volutpat vel suscipit at, lacinia condimentum libero. Praesent nec metus ligula. Morbi porttitor rhoncus mattis. 140 |

141 |

142 | Donec adipiscing porttitor risus, vel commodo ante tincidunt vitae. Fusce lacinia augue non sapien volutpat facilisis. Integer varius faucibus metus, sit amet viverra justo venenatis id. Nam ornare rhoncus tempus. Nulla eleifend, mauris quis auctor bibendum, mi purus interdum sapien, id fringilla nunc mi ut tortor. Sed pretium egestas augue, eget volutpat ipsum egestas nec. Aliquam a elementum justo. Suspendisse tempus, nisi id tincidunt vulputate, nunc sem scelerisque risus, molestie facilisis lacus velit et nunc. Phasellus sed convallis libero. Phasellus sit amet neque non arcu pellentesque laoreet id sed ligula. Donec gravida laoreet condimentum. Ut ornare dignissim tempus. Mauris aliquet tincidunt turpis, quis pulvinar nisl pulvinar id. Sed in gravida ligula. 143 |

144 |

145 | Ut molestie, lectus vel pharetra pharetra, nunc libero interdum ligula, eget vulputate purus nulla sit amet turpis. Aliquam volutpat porttitor erat ac volutpat. Donec ligula elit, tincidunt non congue id, iaculis feugiat sem. Phasellus vestibulum mi id enim interdum imperdiet. Ut dolor ante, tempus sit amet ornare a, faucibus sed massa. Curabitur adipiscing, mauris eget vestibulum lacinia, nisl lorem viverra velit, vitae facilisis urna erat et est. Pellentesque fringilla metus libero, at accumsan nisl. Etiam nisl lorem, placerat ut tristique vel, luctus id nulla. Sed vel nunc ut justo volutpat eleifend ac nec risus. Praesent at viverra tellus. Maecenas semper pellentesque quam, et bibendum nisl eleifend sit amet. Donec sed elit eget magna dictum dignissim. 146 |

147 |

148 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent at erat felis. Donec ornare ligula non nibh vulputate sollicitudin. Cras sodales eros a velit pulvinar vehicula. In sed lorem lectus, vel dapibus nulla. Mauris lacus massa, volutpat vel suscipit at, lacinia condimentum libero. Praesent nec metus ligula. Morbi porttitor rhoncus mattis. 149 |

150 |

151 | Donec adipiscing porttitor risus, vel commodo ante tincidunt vitae. Fusce lacinia augue non sapien volutpat facilisis. Integer varius faucibus metus, sit amet viverra justo venenatis id. Nam ornare rhoncus tempus. Nulla eleifend, mauris quis auctor bibendum, mi purus interdum sapien, id fringilla nunc mi ut tortor. Sed pretium egestas augue, eget volutpat ipsum egestas nec. Aliquam a elementum justo. Suspendisse tempus, nisi id tincidunt vulputate, nunc sem scelerisque risus, molestie facilisis lacus velit et nunc. Phasellus sed convallis libero. Phasellus sit amet neque non arcu pellentesque laoreet id sed ligula. Donec gravida laoreet condimentum. Ut ornare dignissim tempus. Mauris aliquet tincidunt turpis, quis pulvinar nisl pulvinar id. Sed in gravida ligula. 152 |

153 |

154 | Ut molestie, lectus vel pharetra pharetra, nunc libero interdum ligula, eget vulputate purus nulla sit amet turpis. Aliquam volutpat porttitor erat ac volutpat. Donec ligula elit, tincidunt non congue id, iaculis feugiat sem. Phasellus vestibulum mi id enim interdum imperdiet. Ut dolor ante, tempus sit amet ornare a, faucibus sed massa. Curabitur adipiscing, mauris eget vestibulum lacinia, nisl lorem viverra velit, vitae facilisis urna erat et est. Pellentesque fringilla metus libero, at accumsan nisl. Etiam nisl lorem, placerat ut tristique vel, luctus id nulla. Sed vel nunc ut justo volutpat eleifend ac nec risus. Praesent at viverra tellus. Maecenas semper pellentesque quam, et bibendum nisl eleifend sit amet. Donec sed elit eget magna dictum dignissim. 155 |

156 |
157 |
158 | 159 | 160 | 161 | 162 | 163 | 395 | 396 | 397 | -------------------------------------------------------------------------------- /examples/assets/js/modernizr.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load 3 | */ 4 | ; 5 | 6 | 7 | 8 | window.Modernizr = (function( window, document, undefined ) { 9 | 10 | var version = '2.6.2', 11 | 12 | Modernizr = {}, 13 | 14 | enableClasses = true, 15 | 16 | docElement = document.documentElement, 17 | 18 | mod = 'modernizr', 19 | modElem = document.createElement(mod), 20 | mStyle = modElem.style, 21 | 22 | inputElem = document.createElement('input') , 23 | 24 | smile = ':)', 25 | 26 | toString = {}.toString, 27 | 28 | prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), 29 | 30 | 31 | 32 | omPrefixes = 'Webkit Moz O ms', 33 | 34 | cssomPrefixes = omPrefixes.split(' '), 35 | 36 | domPrefixes = omPrefixes.toLowerCase().split(' '), 37 | 38 | ns = {'svg': 'http://www.w3.org/2000/svg'}, 39 | 40 | tests = {}, 41 | inputs = {}, 42 | attrs = {}, 43 | 44 | classes = [], 45 | 46 | slice = classes.slice, 47 | 48 | featureName, 49 | 50 | 51 | injectElementWithStyles = function( rule, callback, nodes, testnames ) { 52 | 53 | var style, ret, node, docOverflow, 54 | div = document.createElement('div'), 55 | body = document.body, 56 | fakeBody = body || document.createElement('body'); 57 | 58 | if ( parseInt(nodes, 10) ) { 59 | while ( nodes-- ) { 60 | node = document.createElement('div'); 61 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 62 | div.appendChild(node); 63 | } 64 | } 65 | 66 | style = ['­',''].join(''); 67 | div.id = mod; 68 | (body ? div : fakeBody).innerHTML += style; 69 | fakeBody.appendChild(div); 70 | if ( !body ) { 71 | fakeBody.style.background = ''; 72 | fakeBody.style.overflow = 'hidden'; 73 | docOverflow = docElement.style.overflow; 74 | docElement.style.overflow = 'hidden'; 75 | docElement.appendChild(fakeBody); 76 | } 77 | 78 | ret = callback(div, rule); 79 | if ( !body ) { 80 | fakeBody.parentNode.removeChild(fakeBody); 81 | docElement.style.overflow = docOverflow; 82 | } else { 83 | div.parentNode.removeChild(div); 84 | } 85 | 86 | return !!ret; 87 | 88 | }, 89 | 90 | 91 | 92 | isEventSupported = (function() { 93 | 94 | var TAGNAMES = { 95 | 'select': 'input', 'change': 'input', 96 | 'submit': 'form', 'reset': 'form', 97 | 'error': 'img', 'load': 'img', 'abort': 'img' 98 | }; 99 | 100 | function isEventSupported( eventName, element ) { 101 | 102 | element = element || document.createElement(TAGNAMES[eventName] || 'div'); 103 | eventName = 'on' + eventName; 104 | 105 | var isSupported = eventName in element; 106 | 107 | if ( !isSupported ) { 108 | if ( !element.setAttribute ) { 109 | element = document.createElement('div'); 110 | } 111 | if ( element.setAttribute && element.removeAttribute ) { 112 | element.setAttribute(eventName, ''); 113 | isSupported = is(element[eventName], 'function'); 114 | 115 | if ( !is(element[eventName], 'undefined') ) { 116 | element[eventName] = undefined; 117 | } 118 | element.removeAttribute(eventName); 119 | } 120 | } 121 | 122 | element = null; 123 | return isSupported; 124 | } 125 | return isEventSupported; 126 | })(), 127 | 128 | 129 | _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; 130 | 131 | if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { 132 | hasOwnProp = function (object, property) { 133 | return _hasOwnProperty.call(object, property); 134 | }; 135 | } 136 | else { 137 | hasOwnProp = function (object, property) { 138 | return ((property in object) && is(object.constructor.prototype[property], 'undefined')); 139 | }; 140 | } 141 | 142 | 143 | if (!Function.prototype.bind) { 144 | Function.prototype.bind = function bind(that) { 145 | 146 | var target = this; 147 | 148 | if (typeof target != "function") { 149 | throw new TypeError(); 150 | } 151 | 152 | var args = slice.call(arguments, 1), 153 | bound = function () { 154 | 155 | if (this instanceof bound) { 156 | 157 | var F = function(){}; 158 | F.prototype = target.prototype; 159 | var self = new F(); 160 | 161 | var result = target.apply( 162 | self, 163 | args.concat(slice.call(arguments)) 164 | ); 165 | if (Object(result) === result) { 166 | return result; 167 | } 168 | return self; 169 | 170 | } else { 171 | 172 | return target.apply( 173 | that, 174 | args.concat(slice.call(arguments)) 175 | ); 176 | 177 | } 178 | 179 | }; 180 | 181 | return bound; 182 | }; 183 | } 184 | 185 | function setCss( str ) { 186 | mStyle.cssText = str; 187 | } 188 | 189 | function setCssAll( str1, str2 ) { 190 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); 191 | } 192 | 193 | function is( obj, type ) { 194 | return typeof obj === type; 195 | } 196 | 197 | function contains( str, substr ) { 198 | return !!~('' + str).indexOf(substr); 199 | } 200 | 201 | function testProps( props, prefixed ) { 202 | for ( var i in props ) { 203 | var prop = props[i]; 204 | if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { 205 | return prefixed == 'pfx' ? prop : true; 206 | } 207 | } 208 | return false; 209 | } 210 | 211 | function testDOMProps( props, obj, elem ) { 212 | for ( var i in props ) { 213 | var item = obj[props[i]]; 214 | if ( item !== undefined) { 215 | 216 | if (elem === false) return props[i]; 217 | 218 | if (is(item, 'function')){ 219 | return item.bind(elem || obj); 220 | } 221 | 222 | return item; 223 | } 224 | } 225 | return false; 226 | } 227 | 228 | function testPropsAll( prop, prefixed, elem ) { 229 | 230 | var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), 231 | props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); 232 | 233 | if(is(prefixed, "string") || is(prefixed, "undefined")) { 234 | return testProps(props, prefixed); 235 | 236 | } else { 237 | props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); 238 | return testDOMProps(props, prefixed, elem); 239 | } 240 | } tests['flexbox'] = function() { 241 | return testPropsAll('flexWrap'); 242 | }; tests['canvas'] = function() { 243 | var elem = document.createElement('canvas'); 244 | return !!(elem.getContext && elem.getContext('2d')); 245 | }; 246 | 247 | tests['canvastext'] = function() { 248 | return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); 249 | }; 250 | 251 | 252 | 253 | tests['webgl'] = function() { 254 | return !!window.WebGLRenderingContext; 255 | }; 256 | 257 | 258 | tests['touch'] = function() { 259 | var bool; 260 | 261 | if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { 262 | bool = true; 263 | } else { 264 | injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { 265 | bool = node.offsetTop === 9; 266 | }); 267 | } 268 | 269 | return bool; 270 | }; 271 | 272 | 273 | 274 | tests['geolocation'] = function() { 275 | return 'geolocation' in navigator; 276 | }; 277 | 278 | 279 | tests['postmessage'] = function() { 280 | return !!window.postMessage; 281 | }; 282 | 283 | 284 | tests['websqldatabase'] = function() { 285 | return !!window.openDatabase; 286 | }; 287 | 288 | tests['indexedDB'] = function() { 289 | return !!testPropsAll("indexedDB", window); 290 | }; 291 | 292 | tests['hashchange'] = function() { 293 | return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); 294 | }; 295 | 296 | tests['history'] = function() { 297 | return !!(window.history && history.pushState); 298 | }; 299 | 300 | tests['draganddrop'] = function() { 301 | var div = document.createElement('div'); 302 | return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); 303 | }; 304 | 305 | tests['websockets'] = function() { 306 | return 'WebSocket' in window || 'MozWebSocket' in window; 307 | }; 308 | 309 | 310 | tests['rgba'] = function() { 311 | setCss('background-color:rgba(150,255,150,.5)'); 312 | 313 | return contains(mStyle.backgroundColor, 'rgba'); 314 | }; 315 | 316 | tests['hsla'] = function() { 317 | setCss('background-color:hsla(120,40%,100%,.5)'); 318 | 319 | return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); 320 | }; 321 | 322 | tests['multiplebgs'] = function() { 323 | setCss('background:url(https://),url(https://),red url(https://)'); 324 | 325 | return (/(url\s*\(.*?){3}/).test(mStyle.background); 326 | }; tests['backgroundsize'] = function() { 327 | return testPropsAll('backgroundSize'); 328 | }; 329 | 330 | tests['borderimage'] = function() { 331 | return testPropsAll('borderImage'); 332 | }; 333 | 334 | 335 | 336 | tests['borderradius'] = function() { 337 | return testPropsAll('borderRadius'); 338 | }; 339 | 340 | tests['boxshadow'] = function() { 341 | return testPropsAll('boxShadow'); 342 | }; 343 | 344 | tests['textshadow'] = function() { 345 | return document.createElement('div').style.textShadow === ''; 346 | }; 347 | 348 | 349 | tests['opacity'] = function() { 350 | setCssAll('opacity:.55'); 351 | 352 | return (/^0.55$/).test(mStyle.opacity); 353 | }; 354 | 355 | 356 | tests['cssanimations'] = function() { 357 | return testPropsAll('animationName'); 358 | }; 359 | 360 | 361 | tests['csscolumns'] = function() { 362 | return testPropsAll('columnCount'); 363 | }; 364 | 365 | 366 | tests['cssgradients'] = function() { 367 | var str1 = 'background-image:', 368 | str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', 369 | str3 = 'linear-gradient(left top,#9f9, white);'; 370 | 371 | setCss( 372 | (str1 + '-webkit- '.split(' ').join(str2 + str1) + 373 | prefixes.join(str3 + str1)).slice(0, -str1.length) 374 | ); 375 | 376 | return contains(mStyle.backgroundImage, 'gradient'); 377 | }; 378 | 379 | 380 | tests['cssreflections'] = function() { 381 | return testPropsAll('boxReflect'); 382 | }; 383 | 384 | 385 | tests['csstransforms'] = function() { 386 | return !!testPropsAll('transform'); 387 | }; 388 | 389 | 390 | tests['csstransforms3d'] = function() { 391 | 392 | var ret = !!testPropsAll('perspective'); 393 | 394 | if ( ret && 'webkitPerspective' in docElement.style ) { 395 | 396 | injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { 397 | ret = node.offsetLeft === 9 && node.offsetHeight === 3; 398 | }); 399 | } 400 | return ret; 401 | }; 402 | 403 | 404 | tests['csstransitions'] = function() { 405 | return testPropsAll('transition'); 406 | }; 407 | 408 | 409 | 410 | tests['fontface'] = function() { 411 | var bool; 412 | 413 | injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { 414 | var style = document.getElementById('smodernizr'), 415 | sheet = style.sheet || style.styleSheet, 416 | cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; 417 | 418 | bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; 419 | }); 420 | 421 | return bool; 422 | }; 423 | 424 | tests['generatedcontent'] = function() { 425 | var bool; 426 | 427 | injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { 428 | bool = node.offsetHeight >= 3; 429 | }); 430 | 431 | return bool; 432 | }; 433 | tests['video'] = function() { 434 | var elem = document.createElement('video'), 435 | bool = false; 436 | 437 | try { 438 | if ( bool = !!elem.canPlayType ) { 439 | bool = new Boolean(bool); 440 | bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); 441 | 442 | bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); 443 | 444 | bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); 445 | } 446 | 447 | } catch(e) { } 448 | 449 | return bool; 450 | }; 451 | 452 | tests['audio'] = function() { 453 | var elem = document.createElement('audio'), 454 | bool = false; 455 | 456 | try { 457 | if ( bool = !!elem.canPlayType ) { 458 | bool = new Boolean(bool); 459 | bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); 460 | bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); 461 | 462 | bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); 463 | bool.m4a = ( elem.canPlayType('audio/x-m4a;') || 464 | elem.canPlayType('audio/aac;')) .replace(/^no$/,''); 465 | } 466 | } catch(e) { } 467 | 468 | return bool; 469 | }; 470 | 471 | 472 | tests['localstorage'] = function() { 473 | try { 474 | localStorage.setItem(mod, mod); 475 | localStorage.removeItem(mod); 476 | return true; 477 | } catch(e) { 478 | return false; 479 | } 480 | }; 481 | 482 | tests['sessionstorage'] = function() { 483 | try { 484 | sessionStorage.setItem(mod, mod); 485 | sessionStorage.removeItem(mod); 486 | return true; 487 | } catch(e) { 488 | return false; 489 | } 490 | }; 491 | 492 | 493 | tests['webworkers'] = function() { 494 | return !!window.Worker; 495 | }; 496 | 497 | 498 | tests['applicationcache'] = function() { 499 | return !!window.applicationCache; 500 | }; 501 | 502 | 503 | tests['svg'] = function() { 504 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; 505 | }; 506 | 507 | tests['inlinesvg'] = function() { 508 | var div = document.createElement('div'); 509 | div.innerHTML = ''; 510 | return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; 511 | }; 512 | 513 | tests['smil'] = function() { 514 | return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); 515 | }; 516 | 517 | 518 | tests['svgclippaths'] = function() { 519 | return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); 520 | }; 521 | 522 | function webforms() { 523 | Modernizr['input'] = (function( props ) { 524 | for ( var i = 0, len = props.length; i < len; i++ ) { 525 | attrs[ props[i] ] = !!(props[i] in inputElem); 526 | } 527 | if (attrs.list){ 528 | attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); 529 | } 530 | return attrs; 531 | })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); 532 | Modernizr['inputtypes'] = (function(props) { 533 | 534 | for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { 535 | 536 | inputElem.setAttribute('type', inputElemType = props[i]); 537 | bool = inputElem.type !== 'text'; 538 | 539 | if ( bool ) { 540 | 541 | inputElem.value = smile; 542 | inputElem.style.cssText = 'position:absolute;visibility:hidden;'; 543 | 544 | if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { 545 | 546 | docElement.appendChild(inputElem); 547 | defaultView = document.defaultView; 548 | 549 | bool = defaultView.getComputedStyle && 550 | defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && 551 | (inputElem.offsetHeight !== 0); 552 | 553 | docElement.removeChild(inputElem); 554 | 555 | } else if ( /^(search|tel)$/.test(inputElemType) ){ 556 | } else if ( /^(url|email)$/.test(inputElemType) ) { 557 | bool = inputElem.checkValidity && inputElem.checkValidity() === false; 558 | 559 | } else { 560 | bool = inputElem.value != smile; 561 | } 562 | } 563 | 564 | inputs[ props[i] ] = !!bool; 565 | } 566 | return inputs; 567 | })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); 568 | } 569 | for ( var feature in tests ) { 570 | if ( hasOwnProp(tests, feature) ) { 571 | featureName = feature.toLowerCase(); 572 | Modernizr[featureName] = tests[feature](); 573 | 574 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); 575 | } 576 | } 577 | 578 | Modernizr.input || webforms(); 579 | 580 | 581 | Modernizr.addTest = function ( feature, test ) { 582 | if ( typeof feature == 'object' ) { 583 | for ( var key in feature ) { 584 | if ( hasOwnProp( feature, key ) ) { 585 | Modernizr.addTest( key, feature[ key ] ); 586 | } 587 | } 588 | } else { 589 | 590 | feature = feature.toLowerCase(); 591 | 592 | if ( Modernizr[feature] !== undefined ) { 593 | return Modernizr; 594 | } 595 | 596 | test = typeof test == 'function' ? test() : test; 597 | 598 | if (typeof enableClasses !== "undefined" && enableClasses) { 599 | docElement.className += ' ' + (test ? '' : 'no-') + feature; 600 | } 601 | Modernizr[feature] = test; 602 | 603 | } 604 | 605 | return Modernizr; 606 | }; 607 | 608 | 609 | setCss(''); 610 | modElem = inputElem = null; 611 | 612 | ;(function(window, document) { 613 | var options = window.html5 || {}; 614 | 615 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 616 | 617 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 618 | 619 | var supportsHtml5Styles; 620 | 621 | var expando = '_html5shiv'; 622 | 623 | var expanID = 0; 624 | 625 | var expandoData = {}; 626 | 627 | var supportsUnknownElements; 628 | 629 | (function() { 630 | try { 631 | var a = document.createElement('a'); 632 | a.innerHTML = ''; 633 | supportsHtml5Styles = ('hidden' in a); 634 | 635 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 636 | (document.createElement)('a'); 637 | var frag = document.createDocumentFragment(); 638 | return ( 639 | typeof frag.cloneNode == 'undefined' || 640 | typeof frag.createDocumentFragment == 'undefined' || 641 | typeof frag.createElement == 'undefined' 642 | ); 643 | }()); 644 | } catch(e) { 645 | supportsHtml5Styles = true; 646 | supportsUnknownElements = true; 647 | } 648 | 649 | }()); function addStyleSheet(ownerDocument, cssText) { 650 | var p = ownerDocument.createElement('p'), 651 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 652 | 653 | p.innerHTML = 'x'; 654 | return parent.insertBefore(p.lastChild, parent.firstChild); 655 | } 656 | 657 | function getElements() { 658 | var elements = html5.elements; 659 | return typeof elements == 'string' ? elements.split(' ') : elements; 660 | } 661 | 662 | function getExpandoData(ownerDocument) { 663 | var data = expandoData[ownerDocument[expando]]; 664 | if (!data) { 665 | data = {}; 666 | expanID++; 667 | ownerDocument[expando] = expanID; 668 | expandoData[expanID] = data; 669 | } 670 | return data; 671 | } 672 | 673 | function createElement(nodeName, ownerDocument, data){ 674 | if (!ownerDocument) { 675 | ownerDocument = document; 676 | } 677 | if(supportsUnknownElements){ 678 | return ownerDocument.createElement(nodeName); 679 | } 680 | if (!data) { 681 | data = getExpandoData(ownerDocument); 682 | } 683 | var node; 684 | 685 | if (data.cache[nodeName]) { 686 | node = data.cache[nodeName].cloneNode(); 687 | } else if (saveClones.test(nodeName)) { 688 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 689 | } else { 690 | node = data.createElem(nodeName); 691 | } 692 | 693 | return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node; 694 | } 695 | 696 | function createDocumentFragment(ownerDocument, data){ 697 | if (!ownerDocument) { 698 | ownerDocument = document; 699 | } 700 | if(supportsUnknownElements){ 701 | return ownerDocument.createDocumentFragment(); 702 | } 703 | data = data || getExpandoData(ownerDocument); 704 | var clone = data.frag.cloneNode(), 705 | i = 0, 706 | elems = getElements(), 707 | l = elems.length; 708 | for(;i; 5 | * Licensed under the MIT license */ 6 | 7 | (function( window, undefined ) { 8 | "use strict"; 9 | 10 | var Hammer = function(element, options) { 11 | return new Hammer.Instance(element, options || {}); 12 | }; 13 | 14 | // default settings 15 | Hammer.defaults = { 16 | stop_browser_behavior: { // set to false to disable this 17 | userSelect: "none", // this also triggers onselectstart=false for IE 18 | touchCallout: "none", 19 | touchAction: "none", 20 | contentZooming: "none", 21 | userDrag: "none", 22 | tapHighlightColor: "rgba(0,0,0,0)" 23 | } 24 | 25 | // more settings are defined at gestures.js 26 | }; 27 | 28 | // detect touchevents 29 | Hammer.HAS_POINTEREVENTS = window.navigator.msPointerEnabled; 30 | Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window); 31 | 32 | // eventtypes per touchevent (start, move, end) 33 | // are filled by Hammer.event.determineEventTypes on setup 34 | Hammer.EVENT_TYPES = {}; 35 | 36 | // direction defines 37 | Hammer.DIRECTION_DOWN = 'down'; 38 | Hammer.DIRECTION_LEFT = 'left'; 39 | Hammer.DIRECTION_UP = 'up'; 40 | Hammer.DIRECTION_RIGHT = 'right'; 41 | 42 | // touch event defines 43 | Hammer.EVENT_START = 'start'; 44 | Hammer.EVENT_MOVE = 'move'; 45 | Hammer.EVENT_END = 'end'; 46 | 47 | // plugins namespace 48 | Hammer.plugins = {}; 49 | 50 | // if the window events are set... 51 | Hammer.READY = false; 52 | 53 | /** 54 | * setup events to detect gestures on the document 55 | * @return 56 | */ 57 | function setup() { 58 | if(Hammer.READY) { 59 | return; 60 | } 61 | 62 | // find what eventtypes we add listeners to 63 | Hammer.event.determineEventTypes(); 64 | 65 | // Register all gestures inside Hammer.gestures 66 | for(var name in Hammer.gestures) { 67 | if(Hammer.gestures.hasOwnProperty(name)) { 68 | Hammer.gesture.register(Hammer.gestures[name]); 69 | } 70 | } 71 | 72 | // Add touch events on the window 73 | Hammer.event.onTouch(document, Hammer.EVENT_MOVE, Hammer.gesture.detect); 74 | Hammer.event.onTouch(document, Hammer.EVENT_END, Hammer.gesture.endDetect); 75 | 76 | // Hammer is ready...! 77 | Hammer.READY = true; 78 | } 79 | 80 | /** 81 | * create new hammer instance 82 | * all methods should return the instance itself, so it is chainable. 83 | * @param {HTMLElement} element 84 | * @param {Object} [options={}] 85 | * @return {Object} instance 86 | */ 87 | Hammer.Instance = function(element, options) { 88 | var self = this; 89 | 90 | // setup HammerJS window events and register all gestures 91 | // this also sets up the default options 92 | setup(); 93 | 94 | this.element = element; 95 | 96 | // merge options 97 | this.options = Hammer.utils.extend( 98 | Hammer.utils.extend({}, Hammer.defaults), 99 | options || {}); 100 | 101 | // add some css to the element to prevent the browser from doing its native behavoir 102 | if(this.options.stop_browser_behavior) { 103 | Hammer.utils.stopDefaultBrowserBehavior(this); 104 | } 105 | 106 | // start detection on touchstart 107 | Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) { 108 | return Hammer.gesture.startDetect(self, ev); 109 | }); 110 | 111 | // return instance 112 | return this; 113 | }; 114 | 115 | 116 | Hammer.Instance.prototype = { 117 | /** 118 | * bind events to the instance 119 | * @param string gestures 120 | * @param callback callback 121 | * @return {*} 122 | */ 123 | on: function onEvent(gestures, handler){ 124 | gestures = gestures.split(" "); 125 | for(var t=0; t> 31)) - (value >> 31); 349 | }, 350 | 351 | 352 | /** 353 | * get the center of all the touches 354 | * @param {TouchList} touches 355 | * @return {Object} center 356 | */ 357 | getCenter: function getCenter(touches) { 358 | var valuesX = [], valuesY = []; 359 | 360 | for(var t= 0,len=touches.length; t= y) { 405 | return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT; 406 | } 407 | else { 408 | return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN; 409 | } 410 | }, 411 | 412 | 413 | /** 414 | * calculate the distance between two touches 415 | * @param Touch touch1 416 | * @param Touch touch2 417 | */ 418 | getDistance: function getDistance(touch1, touch2) { 419 | var x = touch2.pageX - touch1.pageX, 420 | y = touch2.pageY - touch1.pageY; 421 | return Math.sqrt((x*x) + (y*y)); 422 | }, 423 | 424 | 425 | /** 426 | * calculate the scale factor between two touchLists (fingers) 427 | * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out 428 | * @param TouchList start 429 | * @param TouchList end 430 | * @return float scale 431 | */ 432 | getScale: function getScale(start, end) { 433 | // need two fingers... 434 | if(start.length >= 2 && end.length >= 2) { 435 | return this.getDistance(end[0], end[1]) / 436 | this.getDistance(start[0], start[1]); 437 | } 438 | return 1; 439 | }, 440 | 441 | 442 | /** 443 | * calculate the rotation degrees between two touchLists (fingers) 444 | * @param TouchList start 445 | * @param TouchList end 446 | * @return float rotation 447 | */ 448 | getRotation: function getRotation(start, end) { 449 | // need two fingers 450 | if(start.length >= 2 && end.length >= 2) { 451 | return this.getAngle(end[1], end[0]) - 452 | this.getAngle(start[1], start[0]); 453 | } 454 | return 0; 455 | }, 456 | 457 | 458 | /** 459 | * stop browser default behavior with css props 460 | * @param Hammer.Instance inst 461 | * @return {*} 462 | */ 463 | stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(inst) { 464 | var prop, 465 | vendors = ['webkit','khtml','moz','ms','o',''], 466 | css_props = inst.options.stop_browser_behavior; 467 | 468 | if(!css_props) { 469 | return; 470 | } 471 | 472 | // with css properties for modern browsers 473 | for(var i = 0; i < vendors.length; i++) { 474 | for(var p in css_props) { 475 | if(css_props.hasOwnProperty(p)) { 476 | prop = p; 477 | if(vendors[i]) { 478 | prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); 479 | } 480 | inst.element.style[prop] = css_props[p]; 481 | } 482 | } 483 | } 484 | 485 | // also the disable onselectstart 486 | if(css_props.userSelect == 'none') { 487 | inst.element.onselectstart = function() { 488 | return false; 489 | }; 490 | } 491 | } 492 | }; 493 | 494 | Hammer.gesture = { 495 | // contains all registred Hammer.gestures in the correct order 496 | gestures: [], 497 | 498 | // data of the current Hammer.gesture detection session 499 | current: null, 500 | 501 | // the previous Hammer.gesture session data 502 | // is a full clone of the previous gesture.current object 503 | previous: null, 504 | 505 | 506 | /** 507 | * start Hammer.gesture detection 508 | * @param HammerInstane inst 509 | * @param Event ev 510 | */ 511 | startDetect: function startDetect(inst, ev) { 512 | // already busy with an Hammer.gesture detection on a element 513 | if(this.current) { 514 | return; 515 | } 516 | 517 | this.current = { 518 | inst : inst, // reference to HammerInstance we're working for 519 | startEvent : Hammer.utils.extend({}, ev), // start eventData for distances, timing etc 520 | lastEvent : false, // last eventData 521 | name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc 522 | }; 523 | 524 | return this.detect(ev); 525 | }, 526 | 527 | 528 | /** 529 | * Hammer.gesture detection 530 | * @param Event ev 531 | */ 532 | detect: function detect(ev) { 533 | if(!this.current) { 534 | return; 535 | } 536 | 537 | // extend event data with calculations about scale, distance etc 538 | var eventData = this.extendEventData(ev); 539 | 540 | // instance options 541 | var inst_options = this.current.inst.options; 542 | 543 | // call Hammer.gesture handles 544 | for(var g=0,len=this.gestures.length; g b.index) 649 | return 1; 650 | return 0; 651 | }); 652 | } 653 | }; 654 | 655 | Hammer.gestures = Hammer.gestures || {}; 656 | 657 | /** 658 | * Custom gestures 659 | * ============================== 660 | * 661 | * Gesture object 662 | * -------------------- 663 | * The object structure of a gesture: 664 | * 665 | * { name: 'mygesture', 666 | * index: 1337, 667 | * defaults: { 668 | * mygesture_option: true 669 | * } 670 | * handler: function(type, ev, inst) { 671 | * // trigger gesture event 672 | * inst.trigger(this.name, ev); 673 | * } 674 | * } 675 | 676 | * @param {String} name 677 | * this should be the name of the gesture, lowercase 678 | * it is also being used to disable/enable the gesture per instance config. 679 | * 680 | * @param {Number} [index=1000] 681 | * the index of the gesture, where it is going to be in the stack of gestures detection 682 | * like when you build an gesture that depends on the drag gesture, it is a good 683 | * idea to place it after the index of the drag gesture. 684 | * 685 | * @param {Object} [defaults={}] 686 | * the default settings of the gesture. these are added to the instance settings, 687 | * and can be overruled per instance. you can also add the name of the gesture, 688 | * but this is also added by default (and set to true). 689 | * 690 | * @param {Function} handler 691 | * this handles the gesture detection of your custom gesture and receives the 692 | * following arguments: 693 | * 694 | * @param {Object} eventData 695 | * event data containing the following properties: 696 | * time {Number} time the event occurred 697 | * target {HTMLElement} target element 698 | * touches {Array} touches (fingers, pointers, mouse) on the screen 699 | * center {Object} center position of the touches. contains pageX and pageY 700 | * touchTime {Number} the total time of the touches in the screen 701 | * angle {Number} the angle we are moving 702 | * direction {String} the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT 703 | * distance {Number} the distance we haved moved 704 | * distanceX {Number} the distance on x axis we haved moved 705 | * distanceY {Number} the distance on y axis we haved moved 706 | * scale {Number} scaling of the touches, needs 2 touches 707 | * rotation {Number} rotation of the touches, needs 2 touches * 708 | * eventType {String} matches Hammer.EVENT_START|MOVE|END 709 | * srcEvent {Object} the source event, like TouchStart or MouseDown * 710 | * startEvent {Object} contains the same properties as above, 711 | * but from the first touch. this is used to calculate 712 | * distances, touchTime, scaling etc 713 | * 714 | * @param {Hammer.Instance} inst 715 | * the instance we are doing the detection for. you can get the options from 716 | * the inst.options object and trigger the gesture event by calling inst.trigger 717 | * 718 | * 719 | * Handle gestures 720 | * -------------------- 721 | * inside the handler you can get/set Hammer.gesture.current. This is the current 722 | * detection session. It has the following properties 723 | * @param {String} name 724 | * contains the name of the gesture we have detected. it has not a real function, 725 | * only to check in other gestures if something is detected. 726 | * like in the drag gesture we set it to 'drag' and in the swipe gesture we can 727 | * check if the current gesture is 'drag' by accessing Hammer.gesture.current.name 728 | * 729 | * @readonly 730 | * @param {Hammer.Instance} inst 731 | * the instance we do the detection for 732 | * 733 | * @readonly 734 | * @param {Object} startEvent 735 | * contains the properties of the first gesture detection in this session. 736 | * Used for calculations about timing, distance, etc. 737 | * 738 | * @readonly 739 | * @param {Object} lastEvent 740 | * contains all the properties of the last gesture detect in this session. 741 | * 742 | * after the gesture detection session has been completed (user has released the screen) 743 | * the Hammer.gesture.current object is copied into Hammer.gesture.previous, 744 | * this is usefull for gestures like doubletap, where you need to know if the 745 | * previous gesture was a tap 746 | * 747 | * options that have been set by the instance can be received by calling inst.options 748 | * 749 | * You can trigger a gesture event by calling inst.trigger("mygesture", event). 750 | * The first param is the name of your gesture, the second the event argument 751 | * 752 | * 753 | * Register gestures 754 | * -------------------- 755 | * When an gesture is added to the Hammer.gestures object, it is auto registered 756 | * at the setup of the first Hammer instance. You can also call Hammer.gesture.register 757 | * manually and pass your gesture object as a param 758 | * 759 | */ 760 | 761 | /** 762 | * Hold 763 | * Touch stays at the same place for x time 764 | * @events hold 765 | */ 766 | Hammer.gestures.Hold = { 767 | name: 'hold', 768 | index: 10, 769 | defaults: { 770 | hold_timeout: 500, 771 | hold_threshold: 2 772 | }, 773 | timer: null, 774 | handler: function holdGesture(ev, inst) { 775 | var self = this; 776 | switch(ev.eventType) { 777 | case Hammer.EVENT_START: 778 | // clear any running timers 779 | clearTimeout(this.timer); 780 | 781 | // set the gesture so we can check in the timeout if it still is 782 | Hammer.gesture.current.name = this.name; 783 | 784 | // set timer and if after the timeout it still is hold, 785 | // we trigger the hold event 786 | this.timer = setTimeout(function() { 787 | if(Hammer.gesture.current.name == 'hold') { 788 | inst.trigger('hold', ev); 789 | } 790 | }, inst.options.hold_timeout); 791 | break; 792 | 793 | // when you move or end we clear the timer 794 | case Hammer.EVENT_MOVE: 795 | if(ev.distance > inst.options.hold_treshold) { 796 | clearTimeout(this.timer); 797 | } 798 | break; 799 | 800 | case Hammer.EVENT_END: 801 | clearTimeout(this.timer); 802 | break; 803 | } 804 | } 805 | }; 806 | 807 | 808 | /** 809 | * Tap/DoubleTap 810 | * Quick touch at a place or double at the same place 811 | * @events tap, doubletap 812 | */ 813 | Hammer.gestures.Tap = { 814 | name: 'tap', 815 | index: 100, 816 | defaults: { 817 | tap_max_touchtime : 250, 818 | tap_max_distance : 10, 819 | doubletap_distance : 20, 820 | doubletap_interval : 300 821 | }, 822 | handler: function tapGesture(ev, inst) { 823 | if(ev.eventType == Hammer.EVENT_END) { 824 | // previous gesture, for the double tap since these are two different gesture detections 825 | var prev = Hammer.gesture.previous; 826 | 827 | // when the touchtime is higher then the max touch time 828 | // or when the moving distance is too much 829 | if(ev.touchTime > inst.options.tap_max_touchtime || 830 | ev.distance > inst.options.tap_max_distance) { 831 | return; 832 | } 833 | 834 | // check if double tap 835 | if(prev && prev.name == 'tap' && 836 | (ev.time - prev.lastEvent.time) < inst.options.doubletap_interval && 837 | ev.distance < inst.options.doubletap_distance) { 838 | Hammer.gesture.current.name = 'doubletap'; 839 | } 840 | else { 841 | Hammer.gesture.current.name = 'tap'; 842 | } 843 | 844 | inst.trigger(Hammer.gesture.current.name, ev); 845 | } 846 | } 847 | }; 848 | 849 | 850 | /** 851 | * Drag 852 | * Move with x fingers (default 1) around on the page. Blocking the scrolling when 853 | * moving left and right is a good practice. When all the drag events are blocking 854 | * you disable scrolling on that area. 855 | * @events drag, drapleft, dragright, dragup, dragdown 856 | */ 857 | Hammer.gestures.Drag = { 858 | name: 'drag', 859 | index: 50, 860 | defaults: { 861 | drag_min_distance : 10, 862 | // set 0 for unlimited, but this can conflict with transform 863 | drag_max_touches : 1, 864 | // prevent default browser behavior when dragging occurs 865 | // be careful with it, it makes the element a blocking element 866 | // when you are using the drag gesture, it is a good practice to set this true 867 | drag_block_horizontal : false, 868 | drag_block_vertical : false 869 | }, 870 | handler: function dragGesture(ev, inst) { 871 | // max touches 872 | if(inst.options.drag_max_touches > 0 && 873 | ev.touches.length > inst.options.drag_max_touches) { 874 | return; 875 | } 876 | 877 | if(ev.eventType == Hammer.EVENT_MOVE){ 878 | // when the distance we moved is too small we skip this gesture 879 | // or we can be already in dragging 880 | if(ev.distance < inst.options.drag_min_distance && 881 | Hammer.gesture.current.name != this.name) { 882 | return; 883 | } 884 | 885 | Hammer.gesture.current.name = this.name; 886 | inst.trigger(this.name, ev); // basic drag event 887 | inst.trigger(this.name + ev.direction, ev); // direction event, like dragdown 888 | 889 | // block the browser events 890 | if( (inst.options.drag_block_vertical && ( 891 | ev.direction == Hammer.DIRECTION_UP || 892 | ev.direction == Hammer.DIRECTION_DOWN)) || 893 | (inst.options.drag_block_horizontal && ( 894 | ev.direction == Hammer.DIRECTION_LEFT || 895 | ev.direction == Hammer.DIRECTION_RIGHT))) { 896 | ev.preventDefault(); 897 | } 898 | } 899 | } 900 | }; 901 | 902 | 903 | /** 904 | * Swipe 905 | * called after dragging ends and the user moved for x ms a small distance 906 | * @events swipe, swipeleft, swiperight, swipeup, swipedown 907 | */ 908 | Hammer.gestures.Swipe = { 909 | name: 'swipe', 910 | index: 51, 911 | defaults: { 912 | swipe_min_time : 150, 913 | swipe_max_time : 500, 914 | swipe_min_distance : 30 915 | }, 916 | handler: function swipeGesture(ev, inst) { 917 | if(ev.eventType == Hammer.EVENT_END) { 918 | // when the distance we moved is too small we skip this gesture 919 | // or we can be already in dragging 920 | if(Hammer.gesture.current.name == 'drag' && 921 | ev.touchTime > inst.options.swipe_min_time && 922 | ev.touchTime < inst.options.swipe_max_time && 923 | ev.distance > inst.options.swipe_min_distance) { 924 | // trigger swipe events 925 | inst.trigger(this.name, ev); 926 | inst.trigger(this.name + ev.direction, ev); 927 | } 928 | } 929 | } 930 | }; 931 | 932 | 933 | /** 934 | * Transform 935 | * User want to scale or rotate with 2 fingers 936 | * @events transform, pinch, pinchin, pinchout, rotate 937 | */ 938 | Hammer.gestures.Transform = { 939 | name: 'transform', 940 | index: 45, 941 | defaults: { 942 | // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 943 | transform_min_scale : 0.01, 944 | // rotation in degrees 945 | transform_min_rotation : 1, 946 | // prevent default browser behavior when two touches are on the screen 947 | // but it makes the element a blocking element 948 | // when you are using the transform gesture, it is a good practice to set this true 949 | transform_always_block : false 950 | }, 951 | handler: function transformGesture(ev, inst) { 952 | // prevent default when two fingers are on the screen 953 | if(inst.options.transform_always_block && ev.touches.length == 2) { 954 | ev.preventDefault(); 955 | } 956 | 957 | // at least multitouch 958 | if(ev.eventType == Hammer.EVENT_MOVE && ev.touches.length == 2) { 959 | var scale_threshold = Math.abs(1-ev.scale); 960 | var rotation_threshold = Math.abs(ev.rotation); 961 | 962 | // when the distance we moved is too small we skip this gesture 963 | // or we can be already in dragging 964 | if(scale_threshold < inst.options.transform_min_scale && 965 | rotation_threshold < inst.options.transform_min_rotation) { 966 | return; 967 | } 968 | 969 | Hammer.gesture.current.name = this.name; 970 | inst.trigger(this.name, ev); // basic drag event 971 | 972 | // trigger rotate event 973 | if(rotation_threshold > inst.options.transform_min_rotation) { 974 | inst.trigger('rotate', ev); 975 | } 976 | 977 | // trigger pinch event 978 | if(scale_threshold > inst.options.transform_min_scale) { 979 | inst.trigger('pinch', ev); 980 | inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev); // direction event, like pinchin 981 | } 982 | } 983 | } 984 | }; 985 | 986 | 987 | /** 988 | * Touch 989 | * Called as first, tells the user has touched the screen 990 | * @events touch 991 | */ 992 | Hammer.gestures.Touch = { 993 | name: 'touch', 994 | index: -Infinity, 995 | handler: function touchGesture(ev, inst) { 996 | if(ev.eventType == Hammer.EVENT_START) { 997 | inst.trigger(this.name, ev); 998 | } 999 | } 1000 | }; 1001 | 1002 | 1003 | /** 1004 | * Release 1005 | * Called as last, tells the user has released the screen 1006 | * @events release 1007 | */ 1008 | Hammer.gestures.Release = { 1009 | name: 'release', 1010 | index: Infinity, 1011 | handler: function releaseGesture(ev, inst) { 1012 | if(ev.eventType == Hammer.EVENT_END) { 1013 | inst.trigger(this.name, ev); 1014 | } 1015 | } 1016 | }; 1017 | 1018 | // Expose Hammer to the global object 1019 | window.Hammer = Hammer; 1020 | 1021 | })(window); -------------------------------------------------------------------------------- /dist/hammer-latest.js: -------------------------------------------------------------------------------- 1 | /*! Hammer.JS - v1.0.0rc1 - 2013-02-05 2 | * http://eightmedia.github.com/hammer.js 3 | * 4 | * Copyright (c) 2013 Jorik Tangelder ; 5 | * Licensed under the MIT license */ 6 | 7 | (function( window, undefined ) { 8 | "use strict"; 9 | 10 | var Hammer = function(element, options) { 11 | return new Hammer.Instance(element, options || {}); 12 | }; 13 | 14 | // default settings 15 | Hammer.defaults = { 16 | stop_browser_behavior: { // set to false to disable this 17 | userSelect: "none", // this also triggers onselectstart=false for IE 18 | touchCallout: "none", 19 | touchAction: "none", 20 | contentZooming: "none", 21 | userDrag: "none", 22 | tapHighlightColor: "rgba(0,0,0,0)" 23 | } 24 | 25 | // more settings are defined at gestures.js 26 | }; 27 | 28 | // detect touchevents 29 | Hammer.HAS_POINTEREVENTS = window.navigator.msPointerEnabled; 30 | Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window); 31 | 32 | // eventtypes per touchevent (start, move, end) 33 | // are filled by Hammer.event.determineEventTypes on setup 34 | Hammer.EVENT_TYPES = {}; 35 | 36 | // direction defines 37 | Hammer.DIRECTION_DOWN = 'down'; 38 | Hammer.DIRECTION_LEFT = 'left'; 39 | Hammer.DIRECTION_UP = 'up'; 40 | Hammer.DIRECTION_RIGHT = 'right'; 41 | 42 | // touch event defines 43 | Hammer.EVENT_START = 'start'; 44 | Hammer.EVENT_MOVE = 'move'; 45 | Hammer.EVENT_END = 'end'; 46 | 47 | // plugins namespace 48 | Hammer.plugins = {}; 49 | 50 | // if the window events are set... 51 | Hammer.READY = false; 52 | 53 | /** 54 | * setup events to detect gestures on the document 55 | * @return 56 | */ 57 | function setup() { 58 | if(Hammer.READY) { 59 | return; 60 | } 61 | 62 | // find what eventtypes we add listeners to 63 | Hammer.event.determineEventTypes(); 64 | 65 | // Register all gestures inside Hammer.gestures 66 | for(var name in Hammer.gestures) { 67 | if(Hammer.gestures.hasOwnProperty(name)) { 68 | Hammer.gesture.register(Hammer.gestures[name]); 69 | } 70 | } 71 | 72 | // Add touch events on the window 73 | Hammer.event.onTouch(document, Hammer.EVENT_MOVE, Hammer.gesture.detect); 74 | Hammer.event.onTouch(document, Hammer.EVENT_END, Hammer.gesture.endDetect); 75 | 76 | // Hammer is ready...! 77 | Hammer.READY = true; 78 | } 79 | 80 | /** 81 | * create new hammer instance 82 | * all methods should return the instance itself, so it is chainable. 83 | * @param {HTMLElement} element 84 | * @param {Object} [options={}] 85 | * @return {Object} instance 86 | */ 87 | Hammer.Instance = function(element, options) { 88 | var self = this; 89 | 90 | // setup HammerJS window events and register all gestures 91 | // this also sets up the default options 92 | setup(); 93 | 94 | this.element = element; 95 | 96 | // merge options 97 | this.options = Hammer.utils.extend( 98 | Hammer.utils.extend({}, Hammer.defaults), 99 | options || {}); 100 | 101 | // add some css to the element to prevent the browser from doing its native behavoir 102 | if(this.options.stop_browser_behavior) { 103 | Hammer.utils.stopDefaultBrowserBehavior(this); 104 | } 105 | 106 | // start detection on touchstart 107 | Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) { 108 | return Hammer.gesture.startDetect(self, ev); 109 | }); 110 | 111 | // return instance 112 | return this; 113 | }; 114 | 115 | 116 | Hammer.Instance.prototype = { 117 | /** 118 | * bind events to the instance 119 | * @param string gestures 120 | * @param callback callback 121 | * @return {*} 122 | */ 123 | on: function onEvent(gestures, handler){ 124 | gestures = gestures.split(" "); 125 | for(var t=0; t> 31)) - (value >> 31); 349 | }, 350 | 351 | 352 | /** 353 | * get the center of all the touches 354 | * @param {TouchList} touches 355 | * @return {Object} center 356 | */ 357 | getCenter: function getCenter(touches) { 358 | var valuesX = [], valuesY = []; 359 | 360 | for(var t= 0,len=touches.length; t= y) { 405 | return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT; 406 | } 407 | else { 408 | return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN; 409 | } 410 | }, 411 | 412 | 413 | /** 414 | * calculate the distance between two touches 415 | * @param Touch touch1 416 | * @param Touch touch2 417 | */ 418 | getDistance: function getDistance(touch1, touch2) { 419 | var x = touch2.pageX - touch1.pageX, 420 | y = touch2.pageY - touch1.pageY; 421 | return Math.sqrt((x*x) + (y*y)); 422 | }, 423 | 424 | 425 | /** 426 | * calculate the scale factor between two touchLists (fingers) 427 | * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out 428 | * @param TouchList start 429 | * @param TouchList end 430 | * @return float scale 431 | */ 432 | getScale: function getScale(start, end) { 433 | // need two fingers... 434 | if(start.length >= 2 && end.length >= 2) { 435 | return this.getDistance(end[0], end[1]) / 436 | this.getDistance(start[0], start[1]); 437 | } 438 | return 1; 439 | }, 440 | 441 | 442 | /** 443 | * calculate the rotation degrees between two touchLists (fingers) 444 | * @param TouchList start 445 | * @param TouchList end 446 | * @return float rotation 447 | */ 448 | getRotation: function getRotation(start, end) { 449 | // need two fingers 450 | if(start.length >= 2 && end.length >= 2) { 451 | return this.getAngle(end[1], end[0]) - 452 | this.getAngle(start[1], start[0]); 453 | } 454 | return 0; 455 | }, 456 | 457 | 458 | /** 459 | * stop browser default behavior with css props 460 | * @param Hammer.Instance inst 461 | * @return {*} 462 | */ 463 | stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(inst) { 464 | var prop, 465 | vendors = ['webkit','khtml','moz','ms','o',''], 466 | css_props = inst.options.stop_browser_behavior; 467 | 468 | if(!css_props) { 469 | return; 470 | } 471 | 472 | // with css properties for modern browsers 473 | for(var i = 0; i < vendors.length; i++) { 474 | for(var p in css_props) { 475 | if(css_props.hasOwnProperty(p)) { 476 | prop = p; 477 | if(vendors[i]) { 478 | prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); 479 | } 480 | inst.element.style[prop] = css_props[p]; 481 | } 482 | } 483 | } 484 | 485 | // also the disable onselectstart 486 | if(css_props.userSelect == 'none') { 487 | inst.element.onselectstart = function() { 488 | return false; 489 | }; 490 | } 491 | } 492 | }; 493 | 494 | Hammer.gesture = { 495 | // contains all registred Hammer.gestures in the correct order 496 | gestures: [], 497 | 498 | // data of the current Hammer.gesture detection session 499 | current: null, 500 | 501 | // the previous Hammer.gesture session data 502 | // is a full clone of the previous gesture.current object 503 | previous: null, 504 | 505 | 506 | /** 507 | * start Hammer.gesture detection 508 | * @param HammerInstane inst 509 | * @param Event ev 510 | */ 511 | startDetect: function startDetect(inst, ev) { 512 | // already busy with an Hammer.gesture detection on a element 513 | if(this.current) { 514 | return; 515 | } 516 | 517 | this.current = { 518 | inst : inst, // reference to HammerInstance we're working for 519 | startEvent : Hammer.utils.extend({}, ev), // start eventData for distances, timing etc 520 | lastEvent : false, // last eventData 521 | name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc 522 | }; 523 | 524 | return this.detect(ev); 525 | }, 526 | 527 | 528 | /** 529 | * Hammer.gesture detection 530 | * @param Event ev 531 | */ 532 | detect: function detect(ev) { 533 | if(!this.current) { 534 | return; 535 | } 536 | 537 | // extend event data with calculations about scale, distance etc 538 | var eventData = this.extendEventData(ev); 539 | 540 | // instance options 541 | var inst_options = this.current.inst.options; 542 | 543 | // call Hammer.gesture handles 544 | for(var g=0,len=this.gestures.length; g b.index) 649 | return 1; 650 | return 0; 651 | }); 652 | } 653 | }; 654 | 655 | Hammer.gestures = Hammer.gestures || {}; 656 | 657 | /** 658 | * Custom gestures 659 | * ============================== 660 | * 661 | * Gesture object 662 | * -------------------- 663 | * The object structure of a gesture: 664 | * 665 | * { name: 'mygesture', 666 | * index: 1337, 667 | * defaults: { 668 | * mygesture_option: true 669 | * } 670 | * handler: function(type, ev, inst) { 671 | * // trigger gesture event 672 | * inst.trigger(this.name, ev); 673 | * } 674 | * } 675 | 676 | * @param {String} name 677 | * this should be the name of the gesture, lowercase 678 | * it is also being used to disable/enable the gesture per instance config. 679 | * 680 | * @param {Number} [index=1000] 681 | * the index of the gesture, where it is going to be in the stack of gestures detection 682 | * like when you build an gesture that depends on the drag gesture, it is a good 683 | * idea to place it after the index of the drag gesture. 684 | * 685 | * @param {Object} [defaults={}] 686 | * the default settings of the gesture. these are added to the instance settings, 687 | * and can be overruled per instance. you can also add the name of the gesture, 688 | * but this is also added by default (and set to true). 689 | * 690 | * @param {Function} handler 691 | * this handles the gesture detection of your custom gesture and receives the 692 | * following arguments: 693 | * 694 | * @param {Object} eventData 695 | * event data containing the following properties: 696 | * time {Number} time the event occurred 697 | * target {HTMLElement} target element 698 | * touches {Array} touches (fingers, pointers, mouse) on the screen 699 | * center {Object} center position of the touches. contains pageX and pageY 700 | * touchTime {Number} the total time of the touches in the screen 701 | * angle {Number} the angle we are moving 702 | * direction {String} the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT 703 | * distance {Number} the distance we haved moved 704 | * distanceX {Number} the distance on x axis we haved moved 705 | * distanceY {Number} the distance on y axis we haved moved 706 | * scale {Number} scaling of the touches, needs 2 touches 707 | * rotation {Number} rotation of the touches, needs 2 touches * 708 | * eventType {String} matches Hammer.EVENT_START|MOVE|END 709 | * srcEvent {Object} the source event, like TouchStart or MouseDown * 710 | * startEvent {Object} contains the same properties as above, 711 | * but from the first touch. this is used to calculate 712 | * distances, touchTime, scaling etc 713 | * 714 | * @param {Hammer.Instance} inst 715 | * the instance we are doing the detection for. you can get the options from 716 | * the inst.options object and trigger the gesture event by calling inst.trigger 717 | * 718 | * 719 | * Handle gestures 720 | * -------------------- 721 | * inside the handler you can get/set Hammer.gesture.current. This is the current 722 | * detection session. It has the following properties 723 | * @param {String} name 724 | * contains the name of the gesture we have detected. it has not a real function, 725 | * only to check in other gestures if something is detected. 726 | * like in the drag gesture we set it to 'drag' and in the swipe gesture we can 727 | * check if the current gesture is 'drag' by accessing Hammer.gesture.current.name 728 | * 729 | * @readonly 730 | * @param {Hammer.Instance} inst 731 | * the instance we do the detection for 732 | * 733 | * @readonly 734 | * @param {Object} startEvent 735 | * contains the properties of the first gesture detection in this session. 736 | * Used for calculations about timing, distance, etc. 737 | * 738 | * @readonly 739 | * @param {Object} lastEvent 740 | * contains all the properties of the last gesture detect in this session. 741 | * 742 | * after the gesture detection session has been completed (user has released the screen) 743 | * the Hammer.gesture.current object is copied into Hammer.gesture.previous, 744 | * this is usefull for gestures like doubletap, where you need to know if the 745 | * previous gesture was a tap 746 | * 747 | * options that have been set by the instance can be received by calling inst.options 748 | * 749 | * You can trigger a gesture event by calling inst.trigger("mygesture", event). 750 | * The first param is the name of your gesture, the second the event argument 751 | * 752 | * 753 | * Register gestures 754 | * -------------------- 755 | * When an gesture is added to the Hammer.gestures object, it is auto registered 756 | * at the setup of the first Hammer instance. You can also call Hammer.gesture.register 757 | * manually and pass your gesture object as a param 758 | * 759 | */ 760 | 761 | /** 762 | * Hold 763 | * Touch stays at the same place for x time 764 | * @events hold 765 | */ 766 | Hammer.gestures.Hold = { 767 | name: 'hold', 768 | index: 10, 769 | defaults: { 770 | hold_timeout: 500, 771 | hold_threshold: 2 772 | }, 773 | timer: null, 774 | handler: function holdGesture(ev, inst) { 775 | var self = this; 776 | switch(ev.eventType) { 777 | case Hammer.EVENT_START: 778 | // clear any running timers 779 | clearTimeout(this.timer); 780 | 781 | // set the gesture so we can check in the timeout if it still is 782 | Hammer.gesture.current.name = this.name; 783 | 784 | // set timer and if after the timeout it still is hold, 785 | // we trigger the hold event 786 | this.timer = setTimeout(function() { 787 | if(Hammer.gesture.current.name == 'hold') { 788 | inst.trigger('hold', ev); 789 | } 790 | }, inst.options.hold_timeout); 791 | break; 792 | 793 | // when you move or end we clear the timer 794 | case Hammer.EVENT_MOVE: 795 | if(ev.distance > inst.options.hold_treshold) { 796 | clearTimeout(this.timer); 797 | } 798 | break; 799 | 800 | case Hammer.EVENT_END: 801 | clearTimeout(this.timer); 802 | break; 803 | } 804 | } 805 | }; 806 | 807 | 808 | /** 809 | * Tap/DoubleTap 810 | * Quick touch at a place or double at the same place 811 | * @events tap, doubletap 812 | */ 813 | Hammer.gestures.Tap = { 814 | name: 'tap', 815 | index: 100, 816 | defaults: { 817 | tap_max_touchtime : 250, 818 | tap_max_distance : 10, 819 | doubletap_distance : 20, 820 | doubletap_interval : 300 821 | }, 822 | handler: function tapGesture(ev, inst) { 823 | if(ev.eventType == Hammer.EVENT_END) { 824 | // previous gesture, for the double tap since these are two different gesture detections 825 | var prev = Hammer.gesture.previous; 826 | 827 | // when the touchtime is higher then the max touch time 828 | // or when the moving distance is too much 829 | if(ev.touchTime > inst.options.tap_max_touchtime || 830 | ev.distance > inst.options.tap_max_distance) { 831 | return; 832 | } 833 | 834 | // check if double tap 835 | if(prev && prev.name == 'tap' && 836 | (ev.time - prev.lastEvent.time) < inst.options.doubletap_interval && 837 | ev.distance < inst.options.doubletap_distance) { 838 | Hammer.gesture.current.name = 'doubletap'; 839 | } 840 | else { 841 | Hammer.gesture.current.name = 'tap'; 842 | } 843 | 844 | inst.trigger(Hammer.gesture.current.name, ev); 845 | } 846 | } 847 | }; 848 | 849 | 850 | /** 851 | * Drag 852 | * Move with x fingers (default 1) around on the page. Blocking the scrolling when 853 | * moving left and right is a good practice. When all the drag events are blocking 854 | * you disable scrolling on that area. 855 | * @events drag, drapleft, dragright, dragup, dragdown 856 | */ 857 | Hammer.gestures.Drag = { 858 | name: 'drag', 859 | index: 50, 860 | defaults: { 861 | drag_min_distance : 10, 862 | // set 0 for unlimited, but this can conflict with transform 863 | drag_max_touches : 1, 864 | // prevent default browser behavior when dragging occurs 865 | // be careful with it, it makes the element a blocking element 866 | // when you are using the drag gesture, it is a good practice to set this true 867 | drag_block_horizontal : false, 868 | drag_block_vertical : false 869 | }, 870 | handler: function dragGesture(ev, inst) { 871 | // max touches 872 | if(inst.options.drag_max_touches > 0 && 873 | ev.touches.length > inst.options.drag_max_touches) { 874 | return; 875 | } 876 | 877 | if(ev.eventType == Hammer.EVENT_MOVE){ 878 | // when the distance we moved is too small we skip this gesture 879 | // or we can be already in dragging 880 | if(ev.distance < inst.options.drag_min_distance && 881 | Hammer.gesture.current.name != this.name) { 882 | return; 883 | } 884 | 885 | Hammer.gesture.current.name = this.name; 886 | inst.trigger(this.name, ev); // basic drag event 887 | inst.trigger(this.name + ev.direction, ev); // direction event, like dragdown 888 | 889 | // block the browser events 890 | if( (inst.options.drag_block_vertical && ( 891 | ev.direction == Hammer.DIRECTION_UP || 892 | ev.direction == Hammer.DIRECTION_DOWN)) || 893 | (inst.options.drag_block_horizontal && ( 894 | ev.direction == Hammer.DIRECTION_LEFT || 895 | ev.direction == Hammer.DIRECTION_RIGHT))) { 896 | ev.preventDefault(); 897 | } 898 | } 899 | } 900 | }; 901 | 902 | 903 | /** 904 | * Swipe 905 | * called after dragging ends and the user moved for x ms a small distance 906 | * @events swipe, swipeleft, swiperight, swipeup, swipedown 907 | */ 908 | Hammer.gestures.Swipe = { 909 | name: 'swipe', 910 | index: 51, 911 | defaults: { 912 | swipe_min_time : 150, 913 | swipe_max_time : 500, 914 | swipe_min_distance : 30 915 | }, 916 | handler: function swipeGesture(ev, inst) { 917 | if(ev.eventType == Hammer.EVENT_END) { 918 | // when the distance we moved is too small we skip this gesture 919 | // or we can be already in dragging 920 | if(Hammer.gesture.current.name == 'drag' && 921 | ev.touchTime > inst.options.swipe_min_time && 922 | ev.touchTime < inst.options.swipe_max_time && 923 | ev.distance > inst.options.swipe_min_distance) { 924 | // trigger swipe events 925 | inst.trigger(this.name, ev); 926 | inst.trigger(this.name + ev.direction, ev); 927 | } 928 | } 929 | } 930 | }; 931 | 932 | 933 | /** 934 | * Transform 935 | * User want to scale or rotate with 2 fingers 936 | * @events transform, pinch, pinchin, pinchout, rotate 937 | */ 938 | Hammer.gestures.Transform = { 939 | name: 'transform', 940 | index: 45, 941 | defaults: { 942 | // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 943 | transform_min_scale : 0.01, 944 | // rotation in degrees 945 | transform_min_rotation : 1, 946 | // prevent default browser behavior when two touches are on the screen 947 | // but it makes the element a blocking element 948 | // when you are using the transform gesture, it is a good practice to set this true 949 | transform_always_block : false 950 | }, 951 | handler: function transformGesture(ev, inst) { 952 | // prevent default when two fingers are on the screen 953 | if(inst.options.transform_always_block && ev.touches.length == 2) { 954 | ev.preventDefault(); 955 | } 956 | 957 | // at least multitouch 958 | if(ev.eventType == Hammer.EVENT_MOVE && ev.touches.length == 2) { 959 | var scale_threshold = Math.abs(1-ev.scale); 960 | var rotation_threshold = Math.abs(ev.rotation); 961 | 962 | // when the distance we moved is too small we skip this gesture 963 | // or we can be already in dragging 964 | if(scale_threshold < inst.options.transform_min_scale && 965 | rotation_threshold < inst.options.transform_min_rotation) { 966 | return; 967 | } 968 | 969 | Hammer.gesture.current.name = this.name; 970 | inst.trigger(this.name, ev); // basic drag event 971 | 972 | // trigger rotate event 973 | if(rotation_threshold > inst.options.transform_min_rotation) { 974 | inst.trigger('rotate', ev); 975 | } 976 | 977 | // trigger pinch event 978 | if(scale_threshold > inst.options.transform_min_scale) { 979 | inst.trigger('pinch', ev); 980 | inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev); // direction event, like pinchin 981 | } 982 | } 983 | } 984 | }; 985 | 986 | 987 | /** 988 | * Touch 989 | * Called as first, tells the user has touched the screen 990 | * @events touch 991 | */ 992 | Hammer.gestures.Touch = { 993 | name: 'touch', 994 | index: -Infinity, 995 | handler: function touchGesture(ev, inst) { 996 | if(ev.eventType == Hammer.EVENT_START) { 997 | inst.trigger(this.name, ev); 998 | } 999 | } 1000 | }; 1001 | 1002 | 1003 | /** 1004 | * Release 1005 | * Called as last, tells the user has released the screen 1006 | * @events release 1007 | */ 1008 | Hammer.gestures.Release = { 1009 | name: 'release', 1010 | index: Infinity, 1011 | handler: function releaseGesture(ev, inst) { 1012 | if(ev.eventType == Hammer.EVENT_END) { 1013 | inst.trigger(this.name, ev); 1014 | } 1015 | } 1016 | }; 1017 | 1018 | // Expose Hammer to the global object 1019 | window.Hammer = Hammer; 1020 | 1021 | })(window); --------------------------------------------------------------------------------