1/*DOM.event.move 2 32.0.0 4 5Stephen Band 6 7Triggers 'movestart', 'move' and 'moveend' events after 8mousemoves following a mousedown cross a distance threshold, 9similar to the native 'dragstart', 'drag' and 'dragend' events. 10Move events are throttled to animation frames. Move event objects 11have the properties: 12 13pageX: 14pageY: Page coordinates of pointer. 15startX: 16startY: Page coordinates of pointer at movestart. 17distX: 18distY: Distance the pointer has moved since movestart. 19deltaX: 20deltaY: Distance the finger has moved since last event. 21velocityX: 22velocityY: 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 /* 526 * jQuery special events 527 * jQuery event objects are copies of DOM event objects. They need a little help copying the move properties across. 528 */ 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}); 600 601(function($){ 602 'use strict'; 603 $.fn.twentytwenty = function(options) { 604 var options = $.extend({ 605 default_offset_pct: 0.5, 606 orientation: 'horizontal', 607 before_label: 'Before', 608 after_label: 'After', 609 no_overlay: false, 610 move_slider_on_hover: false, 611 move_with_handle_only: false, 612 click_to_move: false 613 }, options); 614 615 return this.each(function() { 616 617 var sliderPct = options.default_offset_pct; 618 var container = $(this); 619 var sliderOrientation = options.orientation; 620 var beforeDirection = (sliderOrientation === 'vertical') ? 'down' : 'left'; 621 var afterDirection = (sliderOrientation === 'vertical') ? 'up' : 'right'; 622 623 624 container.wrap("<div class='twentytwenty-wrapper twentytwenty-" + sliderOrientation + "'></div>"); 625 if(!options.no_overlay) { 626 container.append("<div class='twentytwenty-overlay'></div>"); 627 } 628 var beforeImg = container.find("img:first"); 629 var afterImg = container.find("img:last"); 630 container.append("<div class='twentytwenty-handle'></div>"); 631 var slider = container.find(".twentytwenty-handle"); 632 slider.append("<span class='twentytwenty-" + beforeDirection + "-arrow'></span>"); 633 slider.append("<span class='twentytwenty-" + afterDirection + "-arrow'></span>"); 634 container.addClass("twentytwenty-container"); 635 beforeImg.addClass("twentytwenty-before"); 636 afterImg.addClass("twentytwenty-after"); 637 638 var overlay = container.find(".twentytwenty-overlay"); 639 overlay.append("<div class='twentytwenty-before-label' data-content='"+options.before_label+"'></div>"); 640 overlay.append("<div class='twentytwenty-after-label' data-content='"+options.after_label+"'></div>"); 641 642 var calcOffset = function(dimensionPct) { 643 var w = beforeImg.width(); 644 var h = beforeImg.height(); 645 return { 646 w: w+"px", 647 h: h+"px", 648 cw: (dimensionPct*w)+"px", 649 ch: (dimensionPct*h)+"px" 650 }; 651 }; 652 653 var adjustContainer = function(offset) { 654 if (sliderOrientation === 'vertical') { 655 beforeImg.css("clip", "rect(0,"+offset.w+","+offset.ch+",0)"); 656 afterImg.css("clip", "rect("+offset.ch+","+offset.w+","+offset.h+",0)"); 657 } 658 else { 659 beforeImg.css("clip", "rect(0,"+offset.cw+","+offset.h+",0)"); 660 afterImg.css("clip", "rect(0,"+offset.w+","+offset.h+","+offset.cw+")"); 661 } 662 container.css("height", offset.h); 663 }; 664 665 var adjustSlider = function(pct) { 666 var offset = calcOffset(pct); 667 slider.css((sliderOrientation==="vertical") ? "top" : "left", (sliderOrientation==="vertical") ? offset.ch : offset.cw); 668 adjustContainer(offset); 669 }; 670 671 var minMaxNumber = function(num, min, max) { 672 return Math.max(min, Math.min(max, num)); 673 }; 674 675 var getSliderPercentage = function(positionX, positionY) { 676 var sliderPercentage = (sliderOrientation === 'vertical') ? 677 (positionY-offsetY)/imgHeight :
678 (positionX-offsetX)/imgWidth; 679 680 return minMaxNumber(sliderPercentage, 0, 1); 681 }; 682 683 684 $(window).on("resize.twentytwenty", function(e) { 685 adjustSlider(sliderPct); 686 }); 687 688 var offsetX = 0; 689 var offsetY = 0; 690 var imgWidth = 0; 691 var imgHeight = 0; 692 var onMoveStart = function(e) { 693 if (((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) && sliderOrientation !== 'vertical') { 694 e.preventDefault(); 695 } 696 else if (((e.distX < e.distY && e.distX < -e.distY) || (e.distX > e.distY && e.distX > -e.distY)) && sliderOrientation === 'vertical') { 697 e.preventDefault(); 698 } 699 container.addClass("active"); 700 offsetX = container.offset().left; 701 offsetY = container.offset().top; 702 imgWidth = beforeImg.width(); 703 imgHeight = beforeImg.height(); 704 }; 705 var onMove = function(e) { 706 if (container.hasClass("active")) { 707 sliderPct = getSliderPercentage(e.pageX, e.pageY); 708 adjustSlider(sliderPct); 709 } 710 }; 711 var onMoveEnd = function() { 712 container.removeClass("active"); 713 }; 714 715 var moveTarget = options.move_with_handle_only ? slider : container; 716 moveTarget.on("movestart",onMoveStart); 717 moveTarget.on("move",onMove); 718 moveTarget.on("moveend",onMoveEnd); 719 720 if (options.move_slider_on_hover) { 721 container.on("mouseenter", onMoveStart); 722 container.on("mousemove", onMove); 723 container.on("mouseleave", onMoveEnd); 724 } 725 726 slider.on("touchmove", function(e) { 727 e.preventDefault(); 728 }); 729 730 container.find("img").on("mousedown", function(event) { 731 event.preventDefault(); 732 }); 733 734 if (options.click_to_move) { 735 container.on('click', function(e) { 736 offsetX = container.offset().left; 737 offsetY = container.offset().top; 738 imgWidth = beforeImg.width(); 739 imgHeight = beforeImg.height(); 740 741 sliderPct = getSliderPercentage(e.pageX, e.pageY); 742 adjustSlider(sliderPct); 743 }); 744 } 745 746 $(window).trigger("resize.twentytwenty"); 747 }); 748 }; 749 750})(jQuery);
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.