1// DOM.event.move 2// 3// 2.0.0 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(fn) { 26 if (typeof define === 'function' && define.amd) { 27 define([], fn); 28 } else if ((typeof module !== "undefined" && module !== null) && module.exports) { 29 module.exports = fn; 30 } else { 31 fn(); 32 } 33})(function(){ 34 var assign = Object.assign || window.jQuery && jQuery.extend; 35 36 // Number of pixels a pressed pointer travels before movestart 37 // event is fired. 38 var threshold = 8; 39 40 // Shim for requestAnimationFrame, falling back to timer. See: 41 // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 42 var requestFrame = (function(){ 43 return ( 44 window.requestAnimationFrame || 45 window.webkitRequestAnimationFrame || 46 window.mozRequestAnimationFrame || 47 window.oRequestAnimationFrame || 48 window.msRequestAnimationFrame || 49 function(fn, element){ 50 return window.setTimeout(function(){ 51 fn(); 52 }, 25); 53 } 54 ); 55 })(); 56 57 // Shim for customEvent 58 // see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill 59 (function () { 60 if ( typeof window.CustomEvent === "function" ) return false; 61 function CustomEvent ( event, params ) { 62 params = params || { bubbles: false, cancelable: false, detail: undefined }; 63 var evt = document.createEvent( 'CustomEvent' ); 64 evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); 65 return evt; 66 } 67 68 CustomEvent.prototype = window.Event.prototype; 69 window.CustomEvent = CustomEvent; 70 })(); 71 72 var ignoreTags = { 73 textarea: true, 74 input: true, 75 select: true, 76 button: true 77 }; 78 79 var mouseevents = { 80 move: 'mousemove', 81 cancel: 'mouseup dragstart', 82 end: 'mouseup' 83 }; 84 85 var touchevents = { 86 move: 'touchmove', 87 cancel: 'touchend', 88 end: 'touchend' 89 }; 90 91 var rspaces = /\s+/; 92 93 94 // DOM Events 95 96 var eventOptions = { bubbles: true, cancelable: true }; 97 98 var eventsSymbol = typeof Symbol === "function" ? Symbol('events') : {}; 99 100 function createEvent(type) { 101 return new CustomEvent(type, eventOptions); 102 } 103 104 function getEvents(node) { 105 return node[eventsSymbol] || (node[eventsSymbol] = {}); 106 } 107 108 function on(node, types, fn, data, selector) { 109 types = types.split(rspaces); 110 111 var events = getEvents(node); 112 var i = types.length; 113 var handlers, type; 114 115 function handler(e) { fn(e, data); } 116 117 while (i--) { 118 type = types[i]; 119 handlers = events[type] || (events[type] = []); 120 handlers.push([fn, handler]); 121 node.addEventListener(type, handler); 122 } 123 } 124 125 function off(node, types, fn, selector) { 126 types = types.split(rspaces); 127 128 var events = getEvents(node); 129 var i = types.length;
130 var type, handlers, k; 131 132 if (!events) { return; } 133 134 while (i--) { 135 type = types[i]; 136 handlers = events[type]; 137 if (!handlers) { continue; } 138 k = handlers.length; 139 while (k--) { 140 if (handlers[k][0] === fn) { 141 node.removeEventListener(type, handlers[k][1]); 142 handlers.splice(k, 1); 143 } 144 } 145 } 146 } 147 148 function trigger(node, type, properties) { 149 // Don't cache events. It prevents you from triggering an event of a 150 // given type from inside the handler of another event of that type. 151 var event = createEvent(type); 152 if (properties) { assign(event, properties); } 153 node.dispatchEvent(event); 154 } 155 156 157 // Constructors 158 159 function Timer(fn){ 160 var callback = fn, 161 active = false, 162 running = false; 163 164 function trigger(time) { 165 if (active){ 166 callback(); 167 requestFrame(trigger); 168 running = true; 169 active = false; 170 } 171 else { 172 running = false; 173 } 174 } 175 176 this.kick = function(fn) { 177 active = true; 178 if (!running) { trigger(); } 179 }; 180 181 this.end = function(fn) { 182 var cb = callback; 183 184 if (!fn) { return; } 185 186 // If the timer is not running, simply call the end callback. 187 if (!running) { 188 fn(); 189 } 190 // If the timer is running, and has been kicked lately, then 191 // queue up the current callback and the end callback, otherwise 192 // just the end callback. 193 else { 194 callback = active ? 195 function(){ cb(); fn(); } : 196 fn ; 197 198 active = true; 199 } 200 }; 201 } 202 203 204 // Functions 205 206 function noop() {} 207 208 function preventDefault(e) { 209 e.preventDefault(); 210 } 211 212 function isIgnoreTag(e) { 213 return !!ignoreTags[e.target.tagName.toLowerCase()]; 214 } 215 216 function isPrimaryButton(e) { 217 // Ignore mousedowns on any button other than the left (or primary) 218 // mouse button, or when a modifier key is pressed. 219 return (e.which === 1 && !e.ctrlKey && !e.altKey); 220 } 221 222 function identifiedTouch(touchList, id) { 223 var i, l; 224 225 if (touchList.identifiedTouch) { 226 return touchList.identifiedTouch(id); 227 } 228 229 // touchList.identifiedTouch() does not exist in 230 // webkit yet⦠we must do the search ourselves... 231 232 i = -1; 233 l = touchList.length; 234 235 while (++i < l) { 236 if (touchList[i].identifier === id) { 237 return touchList[i]; 238 } 239 } 240 } 241 242 function changedTouch(e, data) { 243 var touch = identifiedTouch(e.changedTouches, data.identifier); 244 245 // This isn't the touch you're looking for. 246 if (!touch) { return; } 247 248 // Chrome Android (at least) includes touches that have not 249 // changed in e.changedTouches. That's a bit annoying. Check 250 // that this touch has changed. 251 if (touch.pageX === data.pageX && touch.pageY === data.pageY) { return; } 252 253 return touch; 254 } 255 256 257 // Handlers that decide when the first movestart is triggered 258 259 function mousedown(e){ 260 // Ignore non-primary buttons 261 if (!isPrimaryButton(e)) { return; } 262 263 // Ignore form and interactive elements 264 if (isIgnoreTag(e)) { return; } 265 266 on(document, mouseevents.move, mousemove, e); 267 on(document, mouseevents.cancel, mouseend, e); 268 } 269 270 function mousemove(e, data){ 271 checkThreshold(e, data, e, removeMouse); 272 } 273 274 function mouseend(e, data) { 275 removeMouse(); 276 } 277 278 function removeMouse() { 279 off(document, mouseevents.move, mousemove); 280 off(document, mouseevents.cancel, mouseend); 281 } 282 283 function touchstart(e) { 284 // Don't get in the way of interaction with form elements 285 if (ignoreTags[e.target.tagName.toLowerCase()]) { return; } 286 287 var touch = e.changedTouches[0]; 288 289 // iOS live updates the touch objects whereas Android gives us copies. 290 // That means we can't trust the touchstart object to stay the same, 291 // so we must copy the data. This object acts as a template for 292 // movestart, move and moveend event objects. 293 var data = { 294 target: touch.target, 295 pageX: touch.pageX, 296 pageY: touch.pageY, 297 identifier: touch.identifier, 298 299 // The only way to make handlers individually unbindable is by 300 // making them unique. 301 touchmove: function(e, data) { touchmove(e, data); }, 302 touchend: function(e, data) { touchend(e, data); } 303 }; 304 305 on(document, touchevents.move, data.touchmove, data); 306 on(document, touchevents.cancel, data.touchend, data); 307 } 308 309 function touchmove(e, data) { 310 var touch = changedTouch(e, data); 311 if (!touch) { return; } 312 checkThreshold(e, data, touch, removeTouch); 313 } 314 315 function touchend(e, data) { 316 var touch = identifiedTouch(e.changedTouches, data.identifier); 317 if (!touch) { return; } 318 removeTouch(data); 319 } 320 321 function removeTouch(data) { 322 off(document, touchevents.move, data.touchmove); 323 off(document, touchevents.cancel, data.touchend); 324 } 325 326 function checkThreshold(e, data, touch, fn) { 327 var distX = touch.pageX - data.pageX; 328 var distY = touch.pageY - data.pageY; 329
330 // Do nothing if the threshold has not been crossed. 331 if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; } 332 333 triggerStart(e, data, touch, distX, distY, fn); 334 } 335 336 function triggerStart(e, data, touch, distX, distY, fn) { 337 var touches = e.targetTouches; 338 var time = e.timeStamp - data.timeStamp; 339 340 // Create a movestart object with some special properties that 341 // are passed only to the movestart handlers. 342 var template = { 343 altKey: e.altKey, 344 ctrlKey: e.ctrlKey, 345 shiftKey: e.shiftKey, 346 startX: data.pageX, 347 startY: data.pageY, 348 distX: distX, 349 distY: distY, 350 deltaX: distX, 351 deltaY: distY, 352 pageX: touch.pageX, 353 pageY: touch.pageY, 354 velocityX: distX / time, 355 velocityY: distY / time, 356 identifier: data.identifier, 357 targetTouches: touches, 358 finger: touches ? touches.length : 1, 359 enableMove: function() { 360 this.moveEnabled = true; 361 this.enableMove = noop; 362 //e.preventDefault(); 363 } 364 }; 365 366 // Trigger the movestart event. 367 trigger(data.target, 'movestart', template); 368 369 // Unbind handlers that tracked the touch or mouse up till now. 370 fn(data); 371 } 372 373 374 // Handlers that control what happens following a movestart 375 376 function activeMousemove(e, data) { 377 var timer = data.timer; 378 379 data.touch = e; 380 data.timeStamp = e.timeStamp; 381 timer.kick(); 382 } 383 384 function activeMouseend(e, data) { 385 var target = data.target; 386 var event = data.event; 387 var timer = data.timer; 388 389 removeActiveMouse(); 390 391 endEvent(target, event, timer, function() { 392 // Unbind the click suppressor, waiting until after mouseup 393 // has been handled. 394 setTimeout(function(){ 395 off(target, 'click', preventDefault); 396 }, 0); 397 }); 398 } 399 400 function removeActiveMouse() { 401 off(document, mouseevents.move, activeMousemove); 402 off(document, mouseevents.end, activeMouseend); 403 } 404 405 function activeTouchmove(e, data) { 406 var event = data.event; 407 var timer = data.timer; 408 var touch = changedTouch(e, event); 409 410 if (!touch) { return; } 411 412 // Stop the interface from gesturing 413 //e.preventDefault(); 414 415 event.targetTouches = e.targetTouches; 416 data.touch = touch; 417 data.timeStamp = e.timeStamp; 418 419 timer.kick(); 420 } 421 422 function activeTouchend(e, data) { 423 var target = data.target; 424 var event = data.event; 425 var timer = data.timer; 426 var touch = identifiedTouch(e.changedTouches, event.identifier); 427 428 // This isn't the touch you're looking for. 429 if (!touch) { return; } 430 431 removeActiveTouch(data); 432 endEvent(target, event, timer); 433 } 434 435 function removeActiveTouch(data) { 436 off(document, touchevents.move, data.activeTouchmove); 437 off(document, touchevents.end, data.activeTouchend); 438 } 439 440 441 // Logic for triggering move and moveend events 442 443 function updateEvent(event, touch, timeStamp) { 444 var time = timeStamp - event.timeStamp; 445 446 event.distX = touch.pageX - event.startX; 447 event.distY = touch.pageY - event.startY; 448 event.deltaX = touch.pageX - event.pageX; 449 event.deltaY = touch.pageY - event.pageY; 450 451 // Average the velocity of the last few events using a decay 452 // curve to even out spurious jumps in values. 453 event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time; 454 event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time; 455 event.pageX = touch.pageX; 456 event.pageY = touch.pageY; 457 } 458 459 function endEvent(target, event, timer, fn) { 460 timer.end(function(){ 461 trigger(target, 'moveend', event); 462 return fn && fn(); 463 }); 464 } 465 466 467 // Set up the DOM 468 469 function movestart(e) { 470 if (e.defaultPrevented) { return; } 471 if (!e.moveEnabled) { return; } 472 473 var event = { 474 startX: e.startX, 475 startY: e.startY, 476 pageX: e.pageX, 477 pageY: e.pageY, 478 distX: e.distX, 479 distY: e.distY, 480 deltaX: e.deltaX, 481 deltaY: e.deltaY, 482 velocityX: e.velocityX, 483 velocityY: e.velocityY, 484 identifier: e.identifier, 485 targetTouches: e.targetTouches, 486 finger: e.finger 487 }; 488 489 var data = { 490 target: e.target, 491 event: event, 492 timer: new Timer(update), 493 touch: undefined, 494 timeStamp: e.timeStamp 495 }; 496 497 function update(time) { 498 updateEvent(event, data.touch, data.timeStamp); 499 trigger(data.target, 'move', event); 500 } 501 502 if (e.identifier === undefined) { 503 // We're dealing with a mouse event. 504 // Stop clicks from propagating during a move 505 on(e.target, 'click', preventDefault); 506 on(document, mouseevents.move, activeMousemove, data); 507 on(document, mouseevents.end, activeMouseend, data); 508 } 509 else { 510 // In order to unbind correct handlers they have to be unique 511 data.activeTouchmove = function(e, data) { activeTouchmove(e, data); }; 512 data.activeTouchend = function(e, data) { activeTouchend(e, data); }; 513 514 // We're dealing with a touch. 515 on(document, touchevents.move, data.activeTouchmove, data); 516 on(document, touchevents.end, data.activeTouchend, data); 517 } 518 } 519 520 on(document, 'mousedown', mousedown); 521 on(document, 'touchstart', touchstart); 522 on(document, 'movestart', movestart); 523 524 525 // jQuery special events 526 // 527 // jQuery event objects are copies of DOM event objects. They need 528 // a little help copying the move properties across. 529 530 if (!window.jQuery) { return; } 531 532 var properties = ("startX startY pageX pageY distX distY deltaX deltaY velocityX velocityY").split(' '); 533 534 function enableMove1(e) { e.enableMove(); } 535 function enableMove2(e) { e.enableMove(); } 536 function enableMove3(e) { e.enableMove(); } 537 538 function add(handleObj) { 539 var handler = handleObj.handler; 540 541 handleObj.handler = function(e) { 542 // Copy move properties across from originalEvent 543 var i = properties.length; 544 var property; 545 546 while(i--) { 547 property = properties[i]; 548 e[property] = e.originalEvent[property]; 549 } 550 551 handler.apply(this, arguments); 552 }; 553 } 554 555 jQuery.event.special.movestart = { 556 setup: function() { 557 // Movestart must be enabled to allow other move events 558 on(this, 'movestart', enableMove1); 559 560 // Do listen to DOM events 561 return false;
562 }, 563 564 teardown: function() { 565 off(this, 'movestart', enableMove1); 566 return false; 567 }, 568 569 add: add 570 }; 571 572 jQuery.event.special.move = { 573 setup: function() { 574 on(this, 'movestart', enableMove2); 575 return false; 576 }, 577 578 teardown: function() { 579 off(this, 'movestart', enableMove2); 580 return false; 581 }, 582 583 add: add 584 }; 585 586 jQuery.event.special.moveend = { 587 setup: function() { 588 on(this, 'movestart', enableMove3); 589 return false; 590 }, 591 592 teardown: function() { 593 off(this, 'movestart', enableMove3); 594 return false; 595 }, 596 597 add: add 598 }; 599});
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.