vendor: 32,783 bytes, lines 1-850
1(function (global, factory) { 2 if (typeof define === "function" && define.amd) { 3 define(['exports'], factory); 4 } else if (typeof exports !== "undefined") { 5 factory(exports); 6 } else { 7 var mod = { 8 exports: {} 9 }; 10 factory(mod.exports); 11 global.PinchZoom = mod.exports; 12 } 13})(this, function (exports) { 14 'use strict'; 15 16 Object.defineProperty(exports, "__esModule", { 17 value: true 18 }); 19 /* 20 21 PinchZoom.js 22 Copyright (c) Manuel Stofer 2013 - today 23 24 Author: Manuel Stofer ([email protected]) 25 Version: 2.2.0 26 27 Permission is hereby granted, free of charge, to any person obtaining a copy 28 of this software and associated documentation files (the "Software"), to deal 29 in the Software without restriction, including without limitation the rights 30 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 31 copies of the Software, and to permit persons to whom the Software is 32 furnished to do so, subject to the following conditions: 33 34 The above copyright notice and this permission notice shall be included in 35 all copies or substantial portions of the Software. 36 37 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 39 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 43 THE SOFTWARE. 44 45 */ 46 47 // polyfills 48 if (typeof Object.assign != 'function') { 49 // Must be writable: true, enumerable: false, configurable: true 50 Object.defineProperty(Object, "assign", { 51 value: function assign(target, varArgs) { 52 // .length of function is 2 53 if (target == null) { 54 // TypeError if undefined or null 55 throw new TypeError('Cannot convert undefined or null to object'); 56 } 57 58 var to = Object(target); 59 60 for (var index = 1; index < arguments.length; index++) { 61 var nextSource = arguments[index]; 62 63 if (nextSource != null) { 64 // Skip over if undefined or null 65 for (var nextKey in nextSource) { 66 // Avoid bugs when hasOwnProperty is shadowed 67 if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { 68 to[nextKey] = nextSource[nextKey]; 69 } 70 } 71 } 72 } 73 return to; 74 }, 75 writable: true, 76 configurable: true 77 }); 78 } 79 80 if (typeof Array.from != 'function') { 81 Array.from = function (object) { 82 return [].slice.call(object); 83 }; 84 } 85 86 // utils 87 var buildElement = function buildElement(str) { 88 // empty string as title argument required by IE and Edge 89 var tmp = document.implementation.createHTMLDocument(''); 90 tmp.body.innerHTML = str; 91 return Array.from(tmp.body.children)[0]; 92 }; 93 94 var triggerEvent = function triggerEvent(el, name) { 95 var event = document.createEvent('HTMLEvents'); 96 event.initEvent(name, true, false); 97 el.dispatchEvent(event); 98 }; 99 100 var definePinchZoom = function definePinchZoom() { 101 102 /** 103 * Pinch zoom 104 * @param el 105 * @param options 106 * @constructor 107 */ 108 var PinchZoom = function PinchZoom(el, options) { 109 this.el = el; 110 this.zoomFactor = 1; 111 this.lastScale = 1; 112 this.offset = { 113 x: 0, 114 y: 0 115 }; 116 this.initialOffset = { 117 x: 0, 118 y: 0 119 }; 120 this.options = Object.assign({}, this.defaults, options); 121 this.setupMarkup(); 122 this.bindEvents(); 123 this.update(); 124 125 // The image may already be loaded when PinchZoom is initialized, 126 // and then the load event (which trigger update) will never fire. 127 if (this.isImageLoaded(this.el)) { 128 this.updateAspectRatio(); 129 this.setupInitialOffset(); 130 } 131 132 this.enable(); 133 }, 134 sum = function sum(a, b) { 135 return a + b; 136 }, 137 isCloseTo = function isCloseTo(value, expected) { 138 return value > expected - 0.01 && value < expected + 0.01; 139 }; 140 141 PinchZoom.prototype = { 142 143 defaults: { 144 tapZoomFactor: 2, 145 zoomOutFactor: 1.3, 146 animationDuration: 300, 147 maxZoom: 4, 148 minZoom: 0.5, 149 draggableUnzoomed: true, 150 lockDragAxis: false, 151 use2d: true, 152 zoomStartEventName: 'pz_zoomstart', 153 zoomUpdateEventName: 'pz_zoomupdate', 154 zoomEndEventName: 'pz_zoomend', 155 dragStartEventName: 'pz_dragstart', 156 dragUpdateEventName: 'pz_dragupdate', 157 dragEndEventName: 'pz_dragend', 158 doubleTapEventName: 'pz_doubletap', 159 verticalPadding: 0, 160 horizontalPadding: 0 161 }, 162 163 /** 164 * Event handler for 'dragstart' 165 * @param event 166 */ 167 handleDragStart: function handleDragStart(event) { 168 triggerEvent(this.el, this.options.dragStartEventName); 169 this.stopAnimation(); 170 this.lastDragPosition = false; 171 this.hasInteraction = true; 172 this.handleDrag(event); 173 }, 174 175 /** 176 * Event handler for 'drag' 177 * @param event 178 */ 179 handleDrag: function handleDrag(event) { 180 var touch = this.getTouches(event)[0]; 181 this.drag(touch, this.lastDragPosition); 182 this.offset = this.sanitizeOffset(this.offset); 183 this.lastDragPosition = touch; 184 }, 185 186 handleDragEnd: function handleDragEnd() { 187 triggerEvent(this.el, this.options.dragEndEventName); 188 this.end(); 189 }, 190 191 /** 192 * Event handler for 'zoomstart' 193 * @param event 194 */ 195 handleZoomStart: function handleZoomStart(event) { 196 triggerEvent(this.el, this.options.zoomStartEventName); 197 this.stopAnimation(); 198 this.lastScale = 1; 199 this.nthZoom = 0; 200 this.lastZoomCenter = false; 201 this.hasInteraction = true; 202 }, 203 204 /** 205 * Event handler for 'zoom' 206 * @param event 207 */ 208 handleZoom: function handleZoom(event, newScale) { 209 // a relative scale factor is used 210 var touchCenter = this.getTouchCenter(this.getTouches(event)), 211 scale = newScale / this.lastScale; 212 this.lastScale = newScale; 213 214 // the first touch events are thrown away since they are not precise 215 this.nthZoom += 1; 216 if (this.nthZoom > 3) { 217 218 this.scale(scale, touchCenter); 219 this.drag(touchCenter, this.lastZoomCenter); 220 } 221 this.lastZoomCenter = touchCenter; 222 }, 223 224 handleZoomEnd: function handleZoomEnd() { 225 triggerEvent(this.el, this.options.zoomEndEventName); 226 this.end(); 227 }, 228 229 /** 230 * Event handler for 'doubletap' 231 * @param event 232 */ 233 handleDoubleTap: function handleDoubleTap(event) { 234 var center = this.getTouches(event)[0], 235 zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor, 236 startZoomFactor = this.zoomFactor, 237 updateProgress = function (progress) { 238 this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); 239 }.bind(this); 240 241 if (this.hasInteraction) { 242 return; 243 } 244 245 this.isDoubleTap = true; 246 247 if (startZoomFactor > zoomFactor) { 248 center = this.getCurrentZoomCenter(); 249 } 250 251 this.animate(this.options.animationDuration, updateProgress, this.swing); 252 triggerEvent(this.el, this.options.doubleTapEventName); 253 }, 254 255 /** 256 * Compute the initial offset 257 * 258 * the element should be centered in the container upon initialization 259 */ 260 computeInitialOffset: function computeInitialOffset() { 261 this.initialOffset = { 262 x: -Math.abs(this.el.offsetWidth * this.getInitialZoomFactor() - this.container.offsetWidth) / 2, 263 y: -Math.abs(this.el.offsetHeight * this.getInitialZoomFactor() - this.container.offsetHeight) / 2 264 }; 265 }, 266 267 /** 268 * Determine if image is loaded 269 */ 270 isImageLoaded: function isImageLoaded(el) { 271 if (el.nodeName === 'IMG') { 272 return el.complete && el.naturalHeight !== 0; 273 } else { 274 return Array.from(el.querySelectorAll('img')).every(this.isImageLoaded); 275 } 276 }, 277 278 setupInitialOffset: function setupInitialOffset() { 279 if (this._initialOffsetSetup) { 280 return; 281 } 282 283 this._initialOffsetSetup = true; 284 285 this.computeInitialOffset(); 286 this.offset.x = this.initialOffset.x; 287 this.offset.y = this.initialOffset.y; 288 }, 289 290 /** 291 * Max / min values for the offset 292 * @param offset 293 * @return {Object} the sanitized offset 294 */ 295 sanitizeOffset: function sanitizeOffset(offset) { 296 var elWidth = this.el.offsetWidth * this.getInitialZoomFactor() * this.zoomFactor; 297 var elHeight = this.el.offsetHeight * this.getInitialZoomFactor() * this.zoomFactor; 298 var maxX = elWidth - this.getContainerX() + this.options.horizontalPadding, 299 maxY = elHeight - this.getContainerY() + this.options.verticalPadding, 300 maxOffsetX = Math.max(maxX, 0), 301 maxOffsetY = Math.max(maxY, 0), 302 minOffsetX = Math.min(maxX, 0) - this.options.horizontalPadding, 303 minOffsetY = Math.min(maxY, 0) - this.options.verticalPadding; 304 305 return { 306 x: Math.min(Math.max(offset.x, minOffsetX), maxOffsetX), 307 y: Math.min(Math.max(offset.y, minOffsetY), maxOffsetY) 308 }; 309 }, 310 311 /** 312 * Scale to a specific zoom factor (not relative) 313 * @param zoomFactor 314 * @param center 315 */ 316 scaleTo: function scaleTo(zoomFactor, center) { 317 this.scale(zoomFactor / this.zoomFactor, center); 318 }, 319 320 /** 321 * Scales the element from specified center 322 * @param scale 323 * @param center 324 */ 325 scale: function scale(_scale, center) { 326 _scale = this.scaleZoomFactor(_scale); 327 this.addOffset({ 328 x: (_scale - 1) * (center.x + this.offset.x), 329 y: (_scale - 1) * (center.y + this.offset.y) 330 }); 331 triggerEvent(this.el, this.options.zoomUpdateEventName); 332 }, 333 334 /** 335 * Scales the zoom factor relative to current state 336 * @param scale 337 * @return the actual scale (can differ because of max min zoom factor) 338 */ 339 scaleZoomFactor: function scaleZoomFactor(scale) { 340 var originalZoomFactor = this.zoomFactor; 341 this.zoomFactor *= scale; 342 this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options.minZoom)); 343 return this.zoomFactor / originalZoomFactor; 344 }, 345 346 /** 347 * Determine if the image is in a draggable state 348 * 349 * When the image can be dragged, the drag event is acted upon and cancelled. 350 * When not draggable, the drag event bubbles through this component. 351 * 352 * @return {Boolean} 353 */ 354 canDrag: function canDrag() { 355 return this.options.draggableUnzoomed || !isCloseTo(this.zoomFactor, 1); 356 }, 357 358 /** 359 * Drags the element 360 * @param center 361 * @param lastCenter 362 */ 363 drag: function drag(center, lastCenter) { 364 if (lastCenter) { 365 if (this.options.lockDragAxis) { 366 // lock scroll to position that was changed the most 367 if (Math.abs(center.x - lastCenter.x) > Math.abs(center.y - lastCenter.y)) { 368 this.addOffset({ 369 x: -(center.x - lastCenter.x), 370 y: 0 371 }); 372 } else { 373 this.addOffset({ 374 y: -(center.y - lastCenter.y), 375 x: 0 376 }); 377 } 378 } else { 379 this.addOffset({ 380 y: -(center.y - lastCenter.y), 381 x: -(center.x - lastCenter.x) 382 }); 383 } 384 triggerEvent(this.el, this.options.dragUpdateEventName); 385 } 386 }, 387 388 /** 389 * Calculates the touch center of multiple touches 390 * @param touches 391 * @return {Object} 392 */ 393 getTouchCenter: function getTouchCenter(touches) { 394 return this.getVectorAvg(touches); 395 }, 396 397 /** 398 * Calculates the average of multiple vectors (x, y values) 399 */ 400 getVectorAvg: function getVectorAvg(vectors) { 401 return { 402 x: vectors.map(function (v) { 403 return v.x; 404 }).reduce(sum) / vectors.length, 405 y: vectors.map(function (v) { 406 return v.y; 407 }).reduce(sum) / vectors.length 408 }; 409 }, 410 411 /** 412 * Adds an offset 413 * @param offset the offset to add 414 * @return return true when the offset change was accepted 415 */ 416 addOffset: function addOffset(offset) { 417 this.offset = { 418 x: this.offset.x + offset.x, 419 y: this.offset.y + offset.y 420 }; 421 }, 422 423 sanitize: function sanitize() { 424 if (this.zoomFactor < this.options.zoomOutFactor) { 425 this.zoomOutAnimation(); 426 } else if (this.isInsaneOffset(this.offset)) { 427 this.sanitizeOffsetAnimation(); 428 } 429 }, 430 431 /** 432 * Checks if the offset is ok with the current zoom factor 433 * @param offset 434 * @return {Boolean} 435 */ 436 isInsaneOffset: function isInsaneOffset(offset) { 437 var sanitizedOffset = this.sanitizeOffset(offset); 438 return sanitizedOffset.x !== offset.x || sanitizedOffset.y !== offset.y; 439 }, 440 441 /** 442 * Creates an animation moving to a sane offset 443 */ 444 sanitizeOffsetAnimation: function sanitizeOffsetAnimation() { 445 var targetOffset = this.sanitizeOffset(this.offset), 446 startOffset = { 447 x: this.offset.x, 448 y: this.offset.y 449 }, 450 updateProgress = function (progress) { 451 this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x); 452 this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y); 453 this.update(); 454 }.bind(this); 455 456 this.animate(this.options.animationDuration, updateProgress, this.swing); 457 }, 458 459 /** 460 * Zooms back to the original position, 461 * (no offset and zoom factor 1) 462 */ 463 zoomOutAnimation: function zoomOutAnimation() { 464 if (this.zoomFactor === 1) { 465 return; 466 } 467 468 var startZoomFactor = this.zoomFactor, 469 zoomFactor = 1, 470 center = this.getCurrentZoomCenter(), 471 updateProgress = function (progress) { 472 this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); 473 }.bind(this); 474 475 this.animate(this.options.animationDuration, updateProgress, this.swing); 476 }, 477 478 /** 479 * Updates the aspect ratio 480 */ 481 updateAspectRatio: function updateAspectRatio() { 482 this.setContainerY(this.container.parentElement.offsetHeight); 483 }, 484 485 /** 486 * Calculates the initial zoom factor (for the element to fit into the container) 487 * @return {number} the initial zoom factor 488 */ 489 getInitialZoomFactor: function getInitialZoomFactor() { 490 var xZoomFactor = this.container.offsetWidth / this.el.offsetWidth; 491 var yZoomFactor = this.container.offsetHeight / this.el.offsetHeight; 492 493 return Math.min(xZoomFactor, yZoomFactor); 494 }, 495 496 /** 497 * Calculates the aspect ratio of the element 498 * @return the aspect ratio 499 */ 500 getAspectRatio: function getAspectRatio() { 501 return this.el.offsetWidth / this.el.offsetHeight; 502 }, 503 504 /** 505 * Calculates the virtual zoom center for the current offset and zoom factor 506 * (used for reverse zoom) 507 * @return {Object} the current zoom center 508 */ 509 getCurrentZoomCenter: function getCurrentZoomCenter() { 510 var offsetLeft = this.offset.x - this.initialOffset.x; 511 var centerX = -1 * this.offset.x - offsetLeft / (1 / this.zoomFactor - 1); 512 513 var offsetTop = this.offset.y - this.initialOffset.y; 514 var centerY = -1 * this.offset.y - offsetTop / (1 / this.zoomFactor - 1); 515 516 return { 517 x: centerX, 518 y: centerY 519 }; 520 }, 521 522 /** 523 * Returns the touches of an event relative to the container offset 524 * @param event 525 * @return array touches 526 */ 527 getTouches: function getTouches(event) { 528 var rect = this.container.getBoundingClientRect(); 529 var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; 530 var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; 531 var posTop = rect.top + scrollTop; 532 var posLeft = rect.left + scrollLeft; 533 534 return Array.prototype.slice.call(event.touches).map(function (touch) { 535 return { 536 x: touch.pageX - posLeft, 537 y: touch.pageY - posTop 538 }; 539 }); 540 }, 541 542 /** 543 * Animation loop 544 * does not support simultaneous animations 545 * @param duration 546 * @param framefn 547 * @param timefn 548 * @param callback 549 */ 550 animate: function animate(duration, framefn, timefn, callback) { 551 var startTime = new Date().getTime(), 552 renderFrame = function () { 553 if (!this.inAnimation) { 554 return; 555 } 556 var frameTime = new Date().getTime() - startTime, 557 progress = frameTime / duration; 558 if (frameTime >= duration) { 559 framefn(1); 560 if (callback) { 561 callback(); 562 } 563 this.update(); 564 this.stopAnimation(); 565 this.update(); 566 } else { 567 if (timefn) { 568 progress = timefn(progress); 569 } 570 framefn(progress); 571 this.update(); 572 requestAnimationFrame(renderFrame); 573 } 574 }.bind(this); 575 this.inAnimation = true; 576 requestAnimationFrame(renderFrame); 577 }, 578 579 /** 580 * Stops the animation 581 */ 582 stopAnimation: function stopAnimation() { 583 this.inAnimation = false; 584 }, 585 586 /** 587 * Swing timing function for animations 588 * @param p 589 * @return {Number} 590 */ 591 swing: function swing(p) { 592 return -Math.cos(p * Math.PI) / 2 + 0.5; 593 }, 594 595 getContainerX: function getContainerX() { 596 return this.container.offsetWidth; 597 }, 598 599 getContainerY: function getContainerY() { 600 return this.container.offsetHeight; 601 }, 602 603 setContainerY: function setContainerY(y) { 604 return this.container.style.height = y + 'px'; 605 }, 606 607 /** 608 * Creates the expected html structure 609 */ 610 setupMarkup: function setupMarkup() { 611 this.container = buildElement('<div class="pinch-zoom-container"></div>'); 612 this.el.parentNode.insertBefore(this.container, this.el); 613 this.container.appendChild(this.el); 614 615 this.container.style.overflow = 'hidden'; 616 this.container.style.position = 'relative'; 617 618 this.el.style.webkitTransformOrigin = '0% 0%'; 619 this.el.style.mozTransformOrigin = '0% 0%'; 620 this.el.style.msTransformOrigin = '0% 0%'; 621 this.el.style.oTransformOrigin = '0% 0%'; 622 this.el.style.transformOrigin = '0% 0%'; 623 624 this.el.style.position = 'absolute'; 625 }, 626 627 end: function end() { 628 this.hasInteraction = false; 629 this.sanitize(); 630 this.update(); 631 }, 632 633 /** 634 * Binds all required event listeners 635 */ 636 bindEvents: function bindEvents() { 637 var self = this; 638 detectGestures(this.container, this); 639 640 window.addEventListener('resize', this.update.bind(this)); 641 Array.from(this.el.querySelectorAll('img')).forEach(function (imgEl) { 642 imgEl.addEventListener('load', self.update.bind(self)); 643 }); 644 645 if (this.el.nodeName === 'IMG') { 646 this.el.addEventListener('load', this.update.bind(this)); 647 } 648 }, 649 650 /** 651 * Updates the css values according to the current zoom factor and offset 652 */ 653 update: function update(event) { 654 if (this.updatePlaned) { 655 return; 656 } 657 this.updatePlaned = true; 658 659 window.setTimeout(function () { 660 this.updatePlaned = false; 661 this.updateAspectRatio(); 662 663 if (event && event.type === 'resize') { 664 this.computeInitialOffset(); 665 } 666 667 if (event && event.type === 'load') { 668 this.setupInitialOffset(); 669 } 670 671 var zoomFactor = this.getInitialZoomFactor() * this.zoomFactor, 672 offsetX = -this.offset.x / zoomFactor, 673 offsetY = -this.offset.y / zoomFactor, 674 transform3d = 'scale3d(' + zoomFactor + ', ' + zoomFactor + ',1) ' + 'translate3d(' + offsetX + 'px,' + offsetY + 'px,0px)', 675 transform2d = 'scale(' + zoomFactor + ', ' + zoomFactor + ') ' + 'translate(' + offsetX + 'px,' + offsetY + 'px)', 676 removeClone = function () { 677 if (this.clone) { 678 this.clone.parentNode.removeChild(this.clone); 679 delete this.clone; 680 } 681 }.bind(this); 682 683 // Scale 3d and translate3d are faster (at least on ios) 684 // but they also reduce the quality. 685 // PinchZoom uses the 3d transformations during interactions 686 // after interactions it falls back to 2d transformations 687 if (!this.options.use2d || this.hasInteraction || this.inAnimation) { 688 this.is3d = true; 689 removeClone(); 690 691 this.el.style.webkitTransform = transform3d; 692 this.el.style.mozTransform = transform2d; 693 this.el.style.msTransform = transform2d; 694 this.el.style.oTransform = transform2d; 695 this.el.style.transform = transform3d; 696 } else { 697 // When changing from 3d to 2d transform webkit has some glitches. 698 // To avoid this, a copy of the 3d transformed element is displayed in the 699 // foreground while the element is converted from 3d to 2d transform 700 if (this.is3d) { 701 this.clone = this.el.cloneNode(true); 702 this.clone.style.pointerEvents = 'none'; 703 this.container.appendChild(this.clone); 704 window.setTimeout(removeClone, 200); 705 } 706 707 this.el.style.webkitTransform = transform2d; 708 this.el.style.mozTransform = transform2d; 709 this.el.style.msTransform = transform2d; 710 this.el.style.oTransform = transform2d; 711 this.el.style.transform = transform2d; 712 713 this.is3d = false; 714 } 715 }.bind(this), 0); 716 }, 717 718 /** 719 * Enables event handling for gestures 720 */ 721 enable: function enable() { 722 this.enabled = true; 723 }, 724 725 /** 726 * Disables event handling for gestures 727 */ 728 disable: function disable() { 729 this.enabled = false; 730 } 731 }; 732 733 var detectGestures = function detectGestures(el, target) { 734 var interaction = null, 735 fingers = 0, 736 lastTouchStart = null, 737 startTouches = null, 738 setInteraction = function setInteraction(newInteraction, event) { 739 if (interaction !== newInteraction) { 740 741 if (interaction && !newInteraction) { 742 switch (interaction) { 743 case "zoom": 744 target.handleZoomEnd(event); 745 break; 746 case 'drag': 747 target.handleDragEnd(event); 748 break; 749 } 750 } 751 752 switch (newInteraction) { 753 case 'zoom': 754 target.handleZoomStart(event); 755 break; 756 case 'drag': 757 target.handleDragStart(event); 758 break; 759 } 760 } 761 interaction = newInteraction; 762 }, 763 updateInteraction = function updateInteraction(event) { 764 if (fingers === 2) { 765 setInteraction('zoom'); 766 } else if (fingers === 1 && target.canDrag()) { 767 setInteraction('drag', event); 768 } else { 769 setInteraction(null, event); 770 } 771 }, 772 targetTouches = function targetTouches(touches) { 773 return Array.from(touches).map(function (touch) { 774 return { 775 x: touch.pageX, 776 y: touch.pageY 777 }; 778 }); 779 }, 780 getDistance = function getDistance(a, b) { 781 var x, y; 782 x = a.x - b.x; 783 y = a.y - b.y; 784 return Math.sqrt(x * x + y * y); 785 }, 786 calculateScale = function calculateScale(startTouches, endTouches) { 787 var startDistance = getDistance(startTouches[0], startTouches[1]), 788 endDistance = getDistance(endTouches[0], endTouches[1]); 789 return endDistance / startDistance; 790 }, 791 cancelEvent = function cancelEvent(event) { 792 event.stopPropagation(); 793 event.preventDefault(); 794 }, 795 detectDoubleTap = function detectDoubleTap(event) { 796 var time = new Date().getTime(); 797 798 if (fingers > 1) { 799 lastTouchStart = null; 800 } 801 802 if (time - lastTouchStart < 300) { 803 cancelEvent(event); 804 805 target.handleDoubleTap(event); 806 switch (interaction) { 807 case "zoom": 808 target.handleZoomEnd(event); 809 break; 810 case 'drag': 811 target.handleDragEnd(event); 812 break; 813 } 814 } else { 815 target.isDoubleTap = false; 816 } 817 818 if (fingers === 1) { 819 lastTouchStart = time; 820 } 821 }, 822 firstMove = true; 823 824 el.addEventListener('touchstart', function (event) { 825 if (target.enabled) { 826 firstMove = true; 827 fingers = event.touches.length; 828 detectDoubleTap(event); 829 } 830 }); 831 832 el.addEventListener('touchmove', function (event) { 833 if (target.enabled && !target.isDoubleTap) { 834 if (firstMove) { 835 updateInteraction(event); 836 if (interaction) { 837 cancelEvent(event); 838 } 839 startTouches = targetTouches(event.touches); 840 } else { 841 switch (interaction) { 842 case 'zoom': 843 target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches))); 844 break; 845 case 'drag': 846 target.handleDrag(event); 847 break; 848 } 849 if (interaction) { 850 cancelEvent(event);
851 target.update(); 852 } 853 } 854 855 firstMove = false; 856 } 857 }); 858 859 el.addEventListener('touchend', function (event) { 860 if (target.enabled) { 861 fingers = event.touches.length; 862 updateInteraction(event); 863 } 864 }); 865 }; 866 867 return PinchZoom; 868 }; 869 870 var PinchZoom = definePinchZoom(); 871 872 exports.default = PinchZoom; 873});
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.