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