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 } 363 }; 364 365 // Trigger the movestart event. 366 trigger(data.target, 'movestart', template); 367 368 // Unbind handlers that tracked the touch or mouse up till now. 369 fn(data); 370 } 371 372 373 // Handlers that control what happens following a movestart 374 375 function activeMousemove(e, data) { 376 var timer = data.timer; 377 378 data.touch = e; 379 data.timeStamp = e.timeStamp; 380 timer.kick(); 381 } 382 383 function activeMouseend(e, data) { 384 var target = data.target; 385 var event = data.event; 386 var timer = data.timer; 387 388 removeActiveMouse(); 389 390 endEvent(target, event, timer, function() { 391 // Unbind the click suppressor, waiting until after mouseup 392 // has been handled. 393 setTimeout(function(){ 394 off(target, 'click', preventDefault); 395 }, 0); 396 }); 397 } 398 399 function removeActiveMouse() { 400 off(document, mouseevents.move, activeMousemove); 401 off(document, mouseevents.end, activeMouseend); 402 } 403 404 function activeTouchmove(e, data) { 405 var event = data.event; 406 var timer = data.timer; 407 var touch = changedTouch(e, event); 408 409 if (!touch) { return; } 410 411 event.targetTouches = e.targetTouches; 412 data.touch = touch; 413 data.timeStamp = e.timeStamp; 414 415 timer.kick(); 416 } 417 418 function activeTouchend(e, data) { 419 var target = data.target; 420 var event = data.event; 421 var timer = data.timer; 422 var touch = identifiedTouch(e.changedTouches, event.identifier); 423 424 // This isn't the touch you're looking for. 425 if (!touch) { return; } 426 427 removeActiveTouch(data); 428 endEvent(target, event, timer); 429 } 430 431 function removeActiveTouch(data) { 432 off(document, touchevents.move, data.activeTouchmove); 433 off(document, touchevents.end, data.activeTouchend); 434 } 435 436 437 // Logic for triggering move and moveend events 438 439 function updateEvent(event, touch, timeStamp) { 440 var time = timeStamp - event.timeStamp; 441 442 event.distX = touch.pageX - event.startX; 443 event.distY = touch.pageY - event.startY; 444 event.deltaX = touch.pageX - event.pageX; 445 event.deltaY = touch.pageY - event.pageY; 446 447 // Average the velocity of the last few events using a decay 448 // curve to even out spurious jumps in values. 449 event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time; 450 event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time; 451 event.pageX = touch.pageX; 452 event.pageY = touch.pageY; 453 } 454 455 function endEvent(target, event, timer, fn) { 456 timer.end(function(){ 457 trigger(target, 'moveend', event); 458 return fn && fn(); 459 }); 460 } 461 462 463 // Set up the DOM 464 465 function movestart(e) { 466 if (e.defaultPrevented) { return; } 467 if (!e.moveEnabled) { return; } 468 469 var event = { 470 startX: e.startX, 471 startY: e.startY, 472 pageX: e.pageX, 473 pageY: e.pageY, 474 distX: e.distX, 475 distY: e.distY, 476 deltaX: e.deltaX, 477 deltaY: e.deltaY, 478 velocityX: e.velocityX, 479 velocityY: e.velocityY, 480 identifier: e.identifier, 481 targetTouches: e.targetTouches, 482 finger: e.finger 483 }; 484 485 var data = { 486 target: e.target, 487 event: event, 488 timer: new Timer(update), 489 touch: undefined, 490 timeStamp: e.timeStamp 491 }; 492 493 function update(time) { 494 updateEvent(event, data.touch, data.timeStamp); 495 trigger(data.target, 'move', event); 496 } 497 498 if (e.identifier === undefined) { 499 // We're dealing with a mouse event. 500 // Stop clicks from propagating during a move 501 on(e.target, 'click', preventDefault); 502 on(document, mouseevents.move, activeMousemove, data); 503 on(document, mouseevents.end, activeMouseend, data); 504 } 505 else { 506 // In order to unbind correct handlers they have to be unique 507 data.activeTouchmove = function(e, data) { activeTouchmove(e, data); }; 508 data.activeTouchend = function(e, data) { activeTouchend(e, data); }; 509 510 // We're dealing with a touch. 511 on(document, touchevents.move, data.activeTouchmove, data); 512 on(document, touchevents.end, data.activeTouchend, data); 513 } 514 } 515 516 on(document, 'mousedown', mousedown); 517 on(document, 'touchstart', touchstart); 518 on(document, 'movestart', movestart); 519 520 521 // jQuery special events 522 // 523 // jQuery event objects are copies of DOM event objects. They need 524 // a little help copying the move properties across. 525 526 if (!window.jQuery) { return; } 527 528 var properties = ("startX startY pageX pageY distX distY deltaX deltaY velocityX velocityY").split(' '); 529 530 function enableMove1(e) { e.enableMove(); } 531 function enableMove2(e) { e.enableMove(); } 532 function enableMove3(e) { e.enableMove(); } 533 534 function add(handleObj) { 535 var handler = handleObj.handler; 536 537 handleObj.handler = function(e) { 538 // Copy move properties across from originalEvent 539 var i = properties.length; 540 var property; 541 542 while(i--) { 543 property = properties[i]; 544 e[property] = e.originalEvent[property]; 545 } 546 547 handler.apply(this, arguments); 548 }; 549 } 550 551 jQuery.event.special.movestart = { 552 setup: function() { 553 // Movestart must be enabled to allow other move events 554 on(this, 'movestart', enableMove1); 555 556 // Do listen to DOM events 557 return false;
558 }, 559 560 teardown: function() { 561 off(this, 'movestart', enableMove1); 562 return false; 563 }, 564 565 add: add 566 }; 567 568 jQuery.event.special.move = { 569 setup: function() { 570 on(this, 'movestart', enableMove2); 571 return false; 572 }, 573 574 teardown: function() { 575 off(this, 'movestart', enableMove2); 576 return false; 577 }, 578 579 add: add 580 }; 581 582 jQuery.event.special.moveend = { 583 setup: function() { 584 on(this, 'movestart', enableMove3); 585 return false; 586 }, 587 588 teardown: function() { 589 off(this, 'movestart', enableMove3); 590 return false; 591 }, 592 593 add: add 594 }; 595});
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.