1// jquery.event.move 2// 3// 1.3.6 4// 5// Stephen Band 6// 7// Triggers 'movestart', 'move' and 'moveend' events after 8// mousemoves following a mousedown cross a distance threshold, 9// similar to the native 'dragstart', 'drag' and 'dragend' events. 10// Move events are throttled to animation frames. Move event objects 11// have the properties: 12// 13// pageX: 14// pageY: Page coordinates of pointer. 15// startX: 16// startY: Page coordinates of pointer at movestart. 17// distX: 18// distY: Distance the pointer has moved since movestart. 19// deltaX: 20// deltaY: Distance the finger has moved since last event. 21// velocityX: 22// velocityY: Average velocity over last few events. 23 24 25(function (thisModule) { 26 if (typeof define === 'function' && define.amd) { 27 // AMD. Register as an anonymous module. 28 define(['jquery'], thisModule); 29 } else if ((typeof module !== "undefined" && module !== null) && module.exports) { 30 module.exports = thisModule; 31 } else { 32 // Browser globals 33 thisModule(jQuery); 34 } 35})(function(jQuery, undefined){ 36 37 var // Number of pixels a pressed pointer travels before movestart 38 // event is fired. 39 threshold = 6, 40 41 add = jQuery.event.add, 42 43 remove = jQuery.event.remove, 44 45 // Just sugar, so we can have arguments in the same order as 46 // add and remove. 47 trigger = function(node, type, data) { 48 jQuery.event.trigger(type, data, node); 49 }, 50 51 // Shim for requestAnimationFrame, falling back to timer. See: 52 // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 53 requestFrame = (function(){ 54 return ( 55 window.requestAnimationFrame || 56 window.webkitRequestAnimationFrame || 57 window.mozRequestAnimationFrame || 58 window.oRequestAnimationFrame || 59 window.msRequestAnimationFrame || 60 function(fn, element){ 61 return window.setTimeout(function(){ 62 fn(); 63 }, 25); 64 } 65 ); 66 })(), 67 68 ignoreTags = { 69 textarea: true, 70 input: true, 71 select: true, 72 button: true 73 }, 74 75 mouseevents = { 76 move: 'mousemove', 77 cancel: 'mouseup dragstart', 78 end: 'mouseup' 79 }, 80 81 touchevents = { 82 move: 'touchmove', 83 cancel: 'touchend', 84 end: 'touchend' 85 }; 86 87 88 // Constructors 89 90 function Timer(fn){ 91 var callback = fn, 92 active = false, 93 running = false; 94 95 function trigger(time) { 96 if (active){ 97 callback(); 98 requestFrame(trigger); 99 running = true; 100 active = false; 101 } 102 else { 103 running = false; 104 } 105 } 106 107 this.kick = function(fn) { 108 active = true; 109 if (!running) { trigger(); } 110 }; 111 112 this.end = function(fn) { 113 var cb = callback; 114 115 if (!fn) { return; } 116 117 // If the timer is not running, simply call the end callback. 118 if (!running) { 119 fn(); 120 } 121 // If the timer is running, and has been kicked lately, then 122 // queue up the current callback and the end callback, otherwise 123 // just the end callback. 124 else { 125 callback = active ? 126 function(){ cb(); fn(); } : 127 fn ; 128 129 active = true; 130 } 131 }; 132 } 133 134 135 // Functions 136 137 function returnTrue() { 138 return true; 139 } 140 141 function returnFalse() { 142 return false; 143 } 144 145 function preventDefault(e) { 146 e.preventDefault(); 147 } 148 149 function preventIgnoreTags(e) { 150 // Don't prevent interaction with form elements. 151 if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; } 152 153 e.preventDefault(); 154 } 155 156 function isLeftButton(e) { 157 // Ignore mousedowns on any button other than the left (or primary) 158 // mouse button, or when a modifier key is pressed. 159 return (e.which === 1 && !e.ctrlKey && !e.altKey); 160 } 161 162 function identifiedTouch(touchList, id) { 163 var i, l; 164 165 if (touchList.identifiedTouch) { 166 return touchList.identifiedTouch(id); 167 } 168 169 // touchList.identifiedTouch() does not exist in 170 // webkit yet⦠we must do the search ourselves... 171 172 i = -1; 173 l = touchList.length; 174 175 while (++i < l) { 176 if (touchList[i].identifier === id) { 177 return touchList[i]; 178 } 179 } 180 } 181 182 function changedTouch(e, event) { 183 var touch = identifiedTouch(e.changedTouches, event.identifier); 184 185 // This isn't the touch you're looking for. 186 if (!touch) { return; } 187 188 // Chrome Android (at least) includes touches that have not 189 // changed in e.changedTouches. That's a bit annoying. Check 190 // that this touch has changed. 191 if (touch.pageX === event.pageX && touch.pageY === event.pageY) { return; } 192 193 return touch; 194 } 195 196 197 // Handlers that decide when the first movestart is triggered 198 199 function mousedown(e){ 200 var data; 201 202 if (!isLeftButton(e)) { return; } 203 204 data = { 205 target: e.target, 206 startX: e.pageX, 207 startY: e.pageY, 208 timeStamp: e.timeStamp 209 }; 210 211 add(document, mouseevents.move, mousemove, data); 212 add(document, mouseevents.cancel, mouseend, data); 213 } 214 215 function mousemove(e){ 216 var data = e.data; 217 218 checkThreshold(e, data, e, removeMouse); 219 } 220 221 function mouseend(e) { 222 removeMouse(); 223 } 224 225 function removeMouse() { 226 remove(document, mouseevents.move, mousemove); 227 remove(document, mouseevents.cancel, mouseend); 228 } 229 230 function touchstart(e) { 231 var touch, template; 232 233 // Don't get in the way of interaction with form elements. 234 if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; } 235 236 touch = e.changedTouches[0]; 237
238 // iOS live updates the touch objects whereas Android gives us copies. 239 // That means we can't trust the touchstart object to stay the same, 240 // so we must copy the data. This object acts as a template for 241 // movestart, move and moveend event objects. 242 template = { 243 target: touch.target, 244 startX: touch.pageX, 245 startY: touch.pageY, 246 timeStamp: e.timeStamp, 247 identifier: touch.identifier 248 }; 249 250 // Use the touch identifier as a namespace, so that we can later 251 // remove handlers pertaining only to this touch. 252 add(document, touchevents.move + '.' + touch.identifier, touchmove, template); 253 add(document, touchevents.cancel + '.' + touch.identifier, touchend, template); 254 } 255 256 function touchmove(e){ 257 var data = e.data, 258 touch = changedTouch(e, data); 259 260 if (!touch) { return; } 261 262 checkThreshold(e, data, touch, removeTouch); 263 } 264 265 function touchend(e) { 266 var template = e.data, 267 touch = identifiedTouch(e.changedTouches, template.identifier); 268 269 if (!touch) { return; } 270 271 removeTouch(template.identifier); 272 } 273 274 function removeTouch(identifier) { 275 remove(document, '.' + identifier, touchmove); 276 remove(document, '.' + identifier, touchend); 277 } 278 279 280 // Logic for deciding when to trigger a movestart. 281 282 function checkThreshold(e, template, touch, fn) { 283 var distX = touch.pageX - template.startX, 284 distY = touch.pageY - template.startY; 285 286 // Do nothing if the threshold has not been crossed. 287 if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; } 288 289 triggerStart(e, template, touch, distX, distY, fn); 290 } 291 292 function handled() { 293 // this._handled should return false once, and after return true. 294 this._handled = returnTrue; 295 return false; 296 } 297 298 function flagAsHandled(e) { 299 e._handled(); 300 } 301 302 function triggerStart(e, template, touch, distX, distY, fn) { 303 var node = template.target, 304 touches, time; 305 306 touches = e.targetTouches; 307 time = e.timeStamp - template.timeStamp; 308 309 // Create a movestart object with some special properties that 310 // are passed only to the movestart handlers. 311 template.type = 'movestart'; 312 template.distX = distX; 313 template.distY = distY; 314 template.deltaX = distX; 315 template.deltaY = distY; 316 template.pageX = touch.pageX; 317 template.pageY = touch.pageY; 318 template.velocityX = distX / time; 319 template.velocityY = distY / time; 320 template.targetTouches = touches; 321 template.finger = touches ? 322 touches.length : 323 1 ; 324 325 // The _handled method is fired to tell the default movestart 326 // handler that one of the move events is bound. 327 template._handled = handled; 328 329 // Pass the touchmove event so it can be prevented if or when 330 // movestart is handled. 331 template._preventTouchmoveDefault = function() { 332 e.preventDefault(); 333 }; 334 335 // Trigger the movestart event. 336 trigger(template.target, template); 337 338 // Unbind handlers that tracked the touch or mouse up till now. 339 fn(template.identifier); 340 } 341 342 343 // Handlers that control what happens following a movestart 344 345 function activeMousemove(e) { 346 var timer = e.data.timer; 347 348 e.data.touch = e; 349 e.data.timeStamp = e.timeStamp; 350 timer.kick(); 351 } 352 353 function activeMouseend(e) { 354 var event = e.data.event, 355 timer = e.data.timer; 356 357 removeActiveMouse(); 358 359 endEvent(event, timer, function() { 360 // Unbind the click suppressor, waiting until after mouseup 361 // has been handled. 362 setTimeout(function(){ 363 remove(event.target, 'click', returnFalse); 364 }, 0); 365 }); 366 } 367 368 function removeActiveMouse(event) { 369 remove(document, mouseevents.move, activeMousemove); 370 remove(document, mouseevents.end, activeMouseend); 371 } 372 373 function activeTouchmove(e) { 374 var event = e.data.event, 375 timer = e.data.timer, 376 touch = changedTouch(e, event); 377 378 if (!touch) { return; } 379 380 // Stop the interface from gesturing 381 e.preventDefault(); 382 383 event.targetTouches = e.targetTouches; 384 e.data.touch = touch; 385 e.data.timeStamp = e.timeStamp; 386 timer.kick(); 387 } 388 389 function activeTouchend(e) { 390 var event = e.data.event, 391 timer = e.data.timer, 392 touch = identifiedTouch(e.changedTouches, event.identifier); 393 394 // This isn't the touch you're looking for. 395 if (!touch) { return; } 396 397 removeActiveTouch(event); 398 endEvent(event, timer); 399 } 400 401 function removeActiveTouch(event) {
402 remove(document, '.' + event.identifier, activeTouchmove); 403 remove(document, '.' + event.identifier, activeTouchend); 404 } 405 406 407 // Logic for triggering move and moveend events 408 409 function updateEvent(event, touch, timeStamp, timer) { 410 var time = timeStamp - event.timeStamp; 411 412 event.type = 'move'; 413 event.distX = touch.pageX - event.startX; 414 event.distY = touch.pageY - event.startY; 415 event.deltaX = touch.pageX - event.pageX; 416 event.deltaY = touch.pageY - event.pageY; 417 418 // Average the velocity of the last few events using a decay 419 // curve to even out spurious jumps in values. 420 event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time; 421 event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time; 422 event.pageX = touch.pageX; 423 event.pageY = touch.pageY; 424 } 425 426 function endEvent(event, timer, fn) { 427 timer.end(function(){ 428 event.type = 'moveend'; 429 430 trigger(event.target, event); 431 432 return fn && fn(); 433 }); 434 } 435 436 437 // jQuery special event definition 438 439 function setup(data, namespaces, eventHandle) { 440 // Stop the node from being dragged 441 //add(this, 'dragstart.move drag.move', preventDefault); 442 443 // Prevent text selection and touch interface scrolling 444 //add(this, 'mousedown.move', preventIgnoreTags); 445 446 // Tell movestart default handler that we've handled this 447 add(this, 'movestart.move', flagAsHandled); 448 449 // Don't bind to the DOM. For speed. 450 return true; 451 } 452 453 function teardown(namespaces) { 454 remove(this, 'dragstart drag', preventDefault); 455 remove(this, 'mousedown touchstart', preventIgnoreTags); 456 remove(this, 'movestart', flagAsHandled); 457 458 // Don't bind to the DOM. For speed. 459 return true; 460 } 461 462 function addMethod(handleObj) { 463 // We're not interested in preventing defaults for handlers that 464 // come from internal move or moveend bindings 465 if (handleObj.namespace === "move" || handleObj.namespace === "moveend") { 466 return; 467 } 468 469 // Stop the node from being dragged 470 add(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid, preventDefault, undefined, handleObj.selector); 471 472 // Prevent text selection and touch interface scrolling 473 add(this, 'mousedown.' + handleObj.guid, preventIgnoreTags, undefined, handleObj.selector); 474 } 475 476 function removeMethod(handleObj) { 477 if (handleObj.namespace === "move" || handleObj.namespace === "moveend") { 478 return; 479 } 480 481 remove(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid); 482 remove(this, 'mousedown.' + handleObj.guid); 483 } 484 485 jQuery.event.special.movestart = { 486 setup: setup, 487 teardown: teardown, 488 add: addMethod, 489 remove: removeMethod, 490 491 _default: function(e) { 492 var event, data; 493 494 // If no move events were bound to any ancestors of this 495 // target, high tail it out of here. 496 if (!e._handled()) { return; } 497 498 function update(time) { 499 updateEvent(event, data.touch, data.timeStamp); 500 trigger(e.target, event); 501 } 502 503 event = { 504 target: e.target, 505 startX: e.startX, 506 startY: e.startY, 507 pageX: e.pageX, 508 pageY: e.pageY, 509 distX: e.distX, 510 distY: e.distY, 511 deltaX: e.deltaX, 512 deltaY: e.deltaY, 513 velocityX: e.velocityX, 514 velocityY: e.velocityY, 515 timeStamp: e.timeStamp, 516 identifier: e.identifier, 517 targetTouches: e.targetTouches, 518 finger: e.finger 519 }; 520 521 data = { 522 event: event, 523 timer: new Timer(update), 524 touch: undefined, 525 timeStamp: undefined 526 }; 527 528 if (e.identifier === undefined) { 529 // We're dealing with a mouse 530 // Stop clicks from propagating during a move 531 add(e.target, 'click', returnFalse); 532 add(document, mouseevents.move, activeMousemove, data); 533 add(document, mouseevents.end, activeMouseend, data); 534 } 535 else { 536 // We're dealing with a touch. Stop touchmove doing 537 // anything defaulty. 538 e._preventTouchmoveDefault(); 539 add(document, touchevents.move + '.' + e.identifier, activeTouchmove, data); 540 add(document, touchevents.end + '.' + e.identifier, activeTouchend, data); 541 } 542 } 543 }; 544 545 jQuery.event.special.move = { 546 setup: function() { 547 // Bind a noop to movestart. Why? It's the movestart 548 // setup that decides whether other move events are fired. 549 add(this, 'movestart.move', jQuery.noop); 550 }, 551 552 teardown: function() { 553 remove(this, 'movestart.move', jQuery.noop); 554 } 555 }; 556 557 jQuery.event.special.moveend = { 558 setup: function() { 559 // Bind a noop to movestart. Why? It's the movestart 560 // setup that decides whether other move events are fired. 561 add(this, 'movestart.moveend', jQuery.noop); 562 }, 563 564 teardown: function() { 565 remove(this, 'movestart.moveend', jQuery.noop); 566 } 567 }; 568 569 add(document, 'mousedown.move', mousedown); 570 add(document, 'touchstart.move', touchstart); 571 572 // Make jQuery copy touch event properties over to the jQuery event 573 // object, if they are not already listed. But only do the ones we 574 // really need. IE7/8 do not have Array#indexOf(), but nor do they 575 // have touch events, so let's assume we can ignore them. 576 if (typeof Array.prototype.indexOf === 'function') { 577 (function(jQuery, undefined){ 578 var props = ["changedTouches", "targetTouches"], 579 l = props.length; 580 581 while (l--) { 582 if (jQuery.event.props.indexOf(props[l]) === -1) { 583 jQuery.event.props.push(props[l]); 584 } 585 } 586 })(jQuery); 587 }; 588});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.