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