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