vendor: 6,895 bytes, lines 1-239
1/** 2 * @license jquery.panzoom.js v3.2.2 3 * Updated: Wed Jun 21 2017 4 * Add pan and zoom functionality to any element 5 * Copyright (c) timmy willison 6 * Released under the MIT license 7 * https://github.com/timmywil/jquery.panzoom/blob/master/MIT-License.txt 8 */ 9 10(function(global, factory) { 11 // AMD 12 if (typeof define === 'function' && define.amd) { 13 define([ 'jquery' ], function(jQuery) { 14 return factory(global, jQuery); 15 }); 16 // CommonJS/Browserify 17 } else if (typeof exports === 'object') { 18 factory(global, require('jquery')); 19 // Global 20 } else { 21 factory(global, global.jQuery); 22 } 23}(typeof window !== 'undefined' ? window : this, function(window, $) { 24 'use strict'; 25 26 var document = window.document; 27 var datakey = '__pz__'; 28 var slice = Array.prototype.slice; 29 var rIE11 = /trident\/7./i; 30 var supportsInputEvent = (function() { 31 // IE11 returns a false positive 32 if (rIE11.test(navigator.userAgent)) { 33 return false; 34 } 35 var input = document.createElement('input'); 36 input.setAttribute('oninput', 'return'); 37 return typeof input.oninput === 'function'; 38 })(); 39 40 // Regex 41 var rupper = /([A-Z])/g; 42 var rsvg = /^http:[\w\.\/]+svg$/; 43 44 var floating = '(\\-?\\d[\\d\\.e-]*)'; 45 var commaSpace = '\\,?\\s*'; 46 var rmatrix = new RegExp( 47 '^matrix\\(' + 48 floating + commaSpace + 49 floating + commaSpace + 50 floating + commaSpace + 51 floating + commaSpace + 52 floating + commaSpace + 53 floating + '\\)$' 54 ); 55 56 /** 57 * Utility for determining transform matrix equality 58 * Checks backwards to test translation first 59 * @param {Array} first 60 * @param {Array} second 61 */ 62 function matrixEquals(first, second) { 63 var i = first.length; 64 while(--i) { 65 if (Math.round(+first[i]) !== Math.round(+second[i])) { 66 return false; 67 } 68 } 69 return true; 70 } 71 72 /** 73 * Creates the options object for reset functions 74 * @param {Boolean|Object} opts See reset methods 75 * @returns {Object} Returns the newly-created options object 76 */ 77 function createResetOptions(opts) { 78 var options = { range: true, animate: true }; 79 if (typeof opts === 'boolean') { 80 options.animate = opts; 81 } else { 82 $.extend(options, opts); 83 } 84 return options; 85 } 86 87 /** 88 * Represent a transformation matrix with a 3x3 matrix for calculations 89 * Matrix functions adapted from Louis Remi's jQuery.transform (https://github.com/louisremi/jquery.transform.js) 90 * @param {Array|Number} a An array of six values representing a 2d transformation matrix 91 */ 92 function Matrix(a, b, c, d, e, f, g, h, i) { 93 if ($.type(a) === 'array') { 94 this.elements = [ 95 +a[0], +a[2], +a[4], 96 +a[1], +a[3], +a[5], 97 0, 0, 1 98 ]; 99 } else { 100 this.elements = [ 101 a, b, c, 102 d, e, f, 103 g || 0, h || 0, i || 1 104 ]; 105 } 106 } 107 108 Matrix.prototype = { 109 /** 110 * Multiply a 3x3 matrix by a similar matrix or a vector 111 * @param {Matrix|Vector} matrix 112 * @return {Matrix|Vector} Returns a vector if multiplying by a vector 113 */ 114 x: function(matrix) { 115 var isVector = matrix instanceof Vector; 116 117 var a = this.elements, 118 b = matrix.elements; 119 120 if (isVector && b.length === 3) { 121 // b is actually a vector 122 return new Vector( 123 a[0] * b[0] + a[1] * b[1] + a[2] * b[2], 124 a[3] * b[0] + a[4] * b[1] + a[5] * b[2], 125 a[6] * b[0] + a[7] * b[1] + a[8] * b[2] 126 ); 127 } else if (b.length === a.length) { 128 // b is a 3x3 matrix 129 return new Matrix( 130 a[0] * b[0] + a[1] * b[3] + a[2] * b[6], 131 a[0] * b[1] + a[1] * b[4] + a[2] * b[7], 132 a[0] * b[2] + a[1] * b[5] + a[2] * b[8], 133 134 a[3] * b[0] + a[4] * b[3] + a[5] * b[6], 135 a[3] * b[1] + a[4] * b[4] + a[5] * b[7], 136 a[3] * b[2] + a[4] * b[5] + a[5] * b[8], 137 138 a[6] * b[0] + a[7] * b[3] + a[8] * b[6], 139 a[6] * b[1] + a[7] * b[4] + a[8] * b[7], 140 a[6] * b[2] + a[7] * b[5] + a[8] * b[8] 141 ); 142 } 143 return false; // fail 144 }, 145 /** 146 * Generates an inverse of the current matrix 147 * @returns {Matrix} 148 */ 149 inverse: function() { 150 var d = 1 / this.determinant(), 151 a = this.elements; 152 return new Matrix( 153 d * ( a[8] * a[4] - a[7] * a[5]), 154 d * (-(a[8] * a[1] - a[7] * a[2])), 155 d * ( a[5] * a[1] - a[4] * a[2]), 156 157 d * (-(a[8] * a[3] - a[6] * a[5])), 158 d * ( a[8] * a[0] - a[6] * a[2]), 159 d * (-(a[5] * a[0] - a[3] * a[2])), 160 161 d * ( a[7] * a[3] - a[6] * a[4]), 162 d * (-(a[7] * a[0] - a[6] * a[1])), 163 d * ( a[4] * a[0] - a[3] * a[1]) 164 ); 165 }, 166 /** 167 * Calculates the determinant of the current matrix 168 * @returns {Number} 169 */ 170 determinant: function() { 171 var a = this.elements; 172 return a[0] * (a[8] * a[4] - a[7] * a[5]) - a[3] * (a[8] * a[1] - a[7] * a[2]) + a[6] * (a[5] * a[1] - a[4] * a[2]); 173 } 174 }; 175 176 /** 177 * Create a vector containing three values 178 */ 179 function Vector(x, y, z) { 180 this.elements = [ x, y, z ]; 181 } 182 183 /** 184 * Get the element at zero-indexed index i 185 * @param {Number} i 186 */ 187 Vector.prototype.e = Matrix.prototype.e = function(i) { 188 return this.elements[ i ]; 189 }; 190 191 /** 192 * Create a Panzoom object for a given element 193 * @constructor 194 * @param {Element} elem - Element to use pan and zoom 195 * @param {Object} [options] - An object literal containing options to override default options 196 * (See Panzoom.defaults for ones not listed below) 197 * @param {jQuery} [options.$zoomIn] - zoom in buttons/links collection (you can also bind these yourself 198 * e.g. $button.on('click', function(e) { e.preventDefault(); $elem.panzoom('zoomIn'); });) 199 * @param {jQuery} [options.$zoomOut] - zoom out buttons/links collection on which to bind zoomOut 200 * @param {jQuery} [options.$zoomRange] - zoom in/out with this range control 201 * @param {jQuery} [options.$reset] - Reset buttons/links collection on which to bind the reset method 202 * @param {Function} [options.on[Start|Change|Zoom|Pan|End|Reset] - Optional callbacks for panzoom events 203 */ 204 function Panzoom(elem, options) { 205 206 // Allow instantiation without `new` keyword 207 if (!(this instanceof Panzoom)) { 208 return new Panzoom(elem, options); 209 } 210 211 // Sanity checks 212 if (elem.nodeType !== 1) { 213 $.error('Panzoom called on non-Element node'); 214 } 215 if (!$.contains(document, elem)) { 216 $.error('Panzoom element must be attached to the document'); 217 } 218 219 // Don't remake 220 var d = $.data(elem, datakey); 221 if (d) { 222 return d; 223 } 224 225 // Extend default with given object literal 226 // Each instance gets its own options 227 this.options = options = $.extend({}, Panzoom.defaults, options); 228 this.elem = elem; 229 var $elem = this.$elem = $(elem); 230 this.$set = options.$set && options.$set.length ? options.$set : $elem; 231 this.$doc = $(elem.ownerDocument || document); 232 this.$parent = $elem.parent(); 233 this.parent = this.$parent[0]; 234 235 // This is SVG if the namespace is SVG 236 // However, while <svg> elements are SVG, we want to treat those like other elements 237 this.isSVG = rsvg.test(elem.namespaceURI) && elem.nodeName.toLowerCase() !== 'svg'; 238 239 this.panning = false;
240 241 // Save the original transform value 242 // Save the prefixed transform style key 243 // Set the starting transform 244 this._buildTransform(); 245 246 // Build the appropriately-prefixed transform style property name 247 // De-camelcase 248 this._transform = $.cssProps.transform.replace(rupper, '-$1').toLowerCase()
vendor: 31,398 bytes, lines 248-1315
248; 249 250 // Build the transition value 251 this._buildTransition(); 252 253 // Build containment dimensions 254 this.resetDimensions(); 255 256 // Add zoom and reset buttons to `this` 257 var $empty = $(); 258 var self = this; 259 $.each([ '$zoomIn', '$zoomOut', '$zoomRange', '$reset' ], function(i, name) { 260 self[ name ] = options[ name ] || $empty; 261 }); 262 263 this.enable(); 264 265 this.scale = this.getMatrix()[0]; 266 this._checkPanWhenZoomed(); 267 268 // Save the instance 269 $.data(elem, datakey, this); 270 } 271 272 // Attach regex for possible use (immutable) 273 Panzoom.rmatrix = rmatrix; 274 275 Panzoom.defaults = { 276 // Should always be non-empty 277 // Used to bind jQuery events without collisions 278 // A guid is not added here as different instantiations/versions of panzoom 279 // on the same element is not supported, so don't do it. 280 eventNamespace: '.panzoom', 281 282 // Whether or not to transition the scale 283 transition: true, 284 285 // Default cursor style for the element 286 cursor: 'move', 287 288 // There may be some use cases for zooming without panning or vice versa 289 disablePan: false, 290 disableZoom: false, 291 292 // Pan only on the X or Y axes 293 disableXAxis: false, 294 disableYAxis: false, 295 296 // Set whether you'd like to pan on left (1), middle (2), or right click (3) 297 which: 1, 298 299 // The increment at which to zoom 300 // Should be a number greater than 0 301 increment: 0.3, 302 303 // When no scale is passed, this option tells 304 // the `zoom` method to increment 305 // the scale *linearly* based on the increment option. 306 // This often ends up looking like very little happened at larger zoom levels. 307 // The default is to multiply/divide the scale based on the increment. 308 linearZoom: false, 309 310 // Pan only when the scale is greater than minScale 311 panOnlyWhenZoomed: false, 312 313 // min and max zoom scales 314 minScale: 0.3, 315 maxScale: 6, 316 317 // The default step for the range input 318 // Precendence: default < HTML attribute < option setting 319 rangeStep: 0.05, 320 321 // Animation duration (ms) 322 duration: 200, 323 // CSS easing used for scale transition 324 easing: 'ease-in-out', 325 326 // Indicate that the element should be contained within it's parent when panning 327 // Note: this does not affect zooming outside of the parent 328 // Set this value to 'invert' to only allow panning outside of the parent element (basically the opposite of the normal use of contain) 329 // 'invert' is useful for a large panzoom element where you don't want to show anything behind it 330 contain: false 331 }; 332 333 Panzoom.prototype = { 334 constructor: Panzoom, 335 336 /** 337 * @returns {Panzoom} Returns the instance 338 */ 339 instance: function() { 340 return this; 341 }, 342 343 /** 344 * Enable or re-enable the panzoom instance 345 */ 346 enable: function() { 347 // Unbind first 348 this._initStyle(); 349 this._bind(); 350 this.disabled = false; 351 }, 352 353 /** 354 * Disable panzoom 355 */ 356 disable: function() { 357 this.disabled = true; 358 this._resetStyle(); 359 this._unbind(); 360 }, 361 362 /** 363 * @returns {Boolean} Returns whether the current panzoom instance is disabled 364 */ 365 isDisabled: function() { 366 return this.disabled; 367 }, 368 369 /** 370 * Destroy the panzoom instance 371 */ 372 destroy: function() { 373 this.disable(); 374 $.removeData(this.elem, datakey); 375 }, 376 377 /** 378 * Builds the restricing dimensions from the containment element 379 * Also used with focal points 380 * Call this method whenever the dimensions of the element or parent are changed 381 */ 382 resetDimensions: function() { 383 // Reset container properties 384 this.container = this.parent.getBoundingClientRect(); 385 386 // Set element properties 387 var elem = this.elem; 388 // getBoundingClientRect() works with SVG, offsetWidth does not 389 var dims = elem.getBoundingClientRect(); 390 var absScale = Math.abs(this.scale); 391 this.dimensions = { 392 width: dims.width, 393 height: dims.height, 394 left: $.css(elem, 'left', true) || 0, 395 top: $.css(elem, 'top', true) || 0, 396 // Borders and margins are scaled 397 border: { 398 top: $.css(elem, 'borderTopWidth', true) * absScale || 0, 399 bottom: $.css(elem, 'borderBottomWidth', true) * absScale || 0, 400 left: $.css(elem, 'borderLeftWidth', true) * absScale || 0, 401 right: $.css(elem, 'borderRightWidth', true) * absScale || 0 402 }, 403 margin: { 404 top: $.css(elem, 'marginTop', true) * absScale || 0, 405 left: $.css(elem, 'marginLeft', true) * absScale || 0 406 } 407 }; 408 }, 409 410 /** 411 * Return the element to it's original transform matrix 412 * @param {Boolean} [options] If a boolean is passed, animate the reset (default: true). If an options object is passed, simply pass that along to setMatrix. 413 * @param {Boolean} [options.silent] Silence the reset event 414 */ 415 reset: function(options) { 416 options = createResetOptions(options); 417 // Reset the transform to its original value 418 var matrix = this.setMatrix(this._origTransform, options); 419 if (!options.silent) { 420 this._trigger('reset', matrix); 421 } 422 }, 423 424 /** 425 * Only resets zoom level 426 * @param {Boolean|Object} [options] Whether to animate the reset (default: true) or an object of options to pass to zoom() 427 */ 428 resetZoom: function(options) { 429 options = createResetOptions(options); 430 var origMatrix = this.getMatrix(this._origTransform); 431 options.dValue = origMatrix[ 3 ]; 432 this.zoom(origMatrix[0], options); 433 }, 434 435 /** 436 * Only reset panning 437 * @param {Boolean|Object} [options] Whether to animate the reset (default: true) or an object of options to pass to pan() 438 */ 439 resetPan: function(options) { 440 var origMatrix = this.getMatrix(this._origTransform); 441 this.pan(origMatrix[4], origMatrix[5], createResetOptions(options)); 442 }, 443 444 /** 445 * Sets a transform on the $set 446 * For SVG, the style attribute takes precedence 447 * and allows us to animate 448 * @param {String} transform 449 */ 450 setTransform: function(transform) { 451 var $set = this.$set; 452 var i = $set.length; 453 while(i--) { 454 $.style($set[i], 'transform', transform); 455 456 // Support IE9-11, Edge 13-14+ 457 // Set attribute alongside style attribute 458 // since IE and Edge do not respect style settings on SVG 459 // See https://css-tricks.com/transforms-on-svg-elements/ 460 if (this.isSVG) { 461 $set[i].setAttribute('transform', transform); 462 } 463 } 464 }, 465 466 /** 467 * Retrieving the transform is different for SVG 468 * (unless a style transform is already present) 469 * Uses the $set collection for retrieving the transform 470 * @param {String} [transform] Pass in an transform value (like 'scale(1.1)') 471 * to have it formatted into matrix format for use by Panzoom 472 * @returns {String} Returns the current transform value of the element 473 */ 474 getTransform: function(transform) { 475 var $set = this.$set; 476 var transformElem = $set[0]; 477 if (transform) { 478 this.setTransform(transform); 479 } else { 480 481 // IE and Edge still set the transform style properly 482 // They just don't render it on SVG 483 // So we get a correct value here 484 transform = $.style(transformElem, 'transform'); 485 486 if (this.isSVG && (!transform || transform === 'none')) { 487 transform = $.attr(transformElem, 'transform') || 'none'; 488 } 489 } 490 491 // Convert any transforms set by the user to matrix format 492 // by setting to computed 493 if (transform !== 'none' && !rmatrix.test(transform)) { 494 495 // Get computed and set for next time 496 this.setTransform(transform = $.css(transformElem, 'transform')); 497 } 498 499 return transform || 'none'; 500 }, 501 502 /** 503 * Retrieve the current transform matrix for $elem (or turn a transform into it's array values) 504 * @param {String} [transform] matrix-formatted transform value 505 * @returns {Array} Returns the current transform matrix split up into it's parts, or a default matrix 506 */ 507 getMatrix: function(transform) { 508 var matrix = rmatrix.exec(transform || this.getTransform()); 509 if (matrix) { 510 matrix.shift(); 511 } 512 return matrix || [ 1, 0, 0, 1, 0, 0 ]; 513 }, 514 515 /** 516 * Get the current scale. 517 * @param {String} [transform] matrix-formatted transform value 518 * @returns {Number} Current scale relative to the initial scale (height / width = 1) 519 */ 520 getScale: function(matrix) { 521 return Math.sqrt(Math.pow(matrix[0], 2) + Math.pow(matrix[1], 2)); 522 }, 523 524 /** 525 * Given a matrix object, quickly set the current matrix of the element 526 * @param {Array|String} matrix 527 * @param {Object} [options] 528 * @param {Boolean|String} [options.animate] Whether to animate the transform change, or 'skip' indicating that it is unnecessary to set 529 * @param {Boolean} [options.contain] Override the global contain option 530 * @param {Boolean} [options.range] If true, $zoomRange's value will be updated. 531 * @param {Boolean} [options.silent] If true, the change event will not be triggered 532 * @returns {Array} Returns the newly-set matrix 533 */ 534 setMatrix: function(matrix, options) { 535 if (this.disabled) { return; } 536 if (!options) { options = {}; } 537 // Convert to array 538 if (typeof matrix === 'string') { 539 matrix = this.getMatrix(matrix); 540 } 541 var scale = this.getScale(matrix); 542 var contain = typeof options.contain !== 'undefined' ? options.contain : this.options.contain; 543 544 // Apply containment 545 if (contain) { 546 var dims = options.dims; 547 if (!dims) { 548 this.resetDimensions(); 549 dims = this.dimensions; 550 } 551 var spaceWLeft, spaceWRight, scaleDiff; 552 var container = this.container; 553 var width = dims.width; 554 var height = dims.height; 555 var conWidth = container.width; 556 var conHeight = container.height; 557 var zoomAspectW = conWidth / width; 558 var zoomAspectH = conHeight / height; 559 560 // If the element is not naturally centered, 561 // assume full space right 562 if (this.$parent.css('textAlign') !== 'center' || $.css(this.elem, 'display') !== 'inline') { 563 // offsetWidth gets us the width without the transform 564 scaleDiff = (width - this.elem.offsetWidth) / 2; 565 spaceWLeft = scaleDiff - dims.border.left; 566 spaceWRight = width - conWidth - scaleDiff + dims.border.right; 567 } else { 568 spaceWLeft = spaceWRight = ((width - conWidth) / 2); 569 } 570 var spaceHTop = ((height - conHeight) / 2) + dims.border.top; 571 var spaceHBottom = ((height - conHeight) / 2) - dims.border.top - dims.border.bottom; 572 573 if (contain === 'invert' || contain === 'automatic' && zoomAspectW < 1.01) { 574 matrix[4] = Math.max(Math.min(matrix[4], spaceWLeft - dims.border.left), -spaceWRight); 575 } else { 576 matrix[4] = Math.min(Math.max(matrix[4], spaceWLeft), -spaceWRight); 577 } 578 579 if (contain === 'invert' || (contain === 'automatic' && zoomAspectH < 1.01)) { 580 matrix[5] = Math.max(Math.min(matrix[5], spaceHTop - dims.border.top), -spaceHBottom); 581 } else { 582 matrix[5] = Math.min(Math.max(matrix[5], spaceHTop), -spaceHBottom); 583 } 584 } 585 586 // Animate 587 if (options.animate !== 'skip') { 588 // Set transition 589 this.transition(!options.animate); 590 } 591 592 // Update range element 593 if (options.range) { 594 this.$zoomRange.val(scale); 595 } 596 597 // Set the matrix on this.$set 598 if (this.options.disableXAxis || this.options.disableYAxis) { 599 var originalMatrix = this.getMatrix(); 600 if (this.options.disableXAxis) { 601 matrix[4] = originalMatrix[4]; 602 } 603 if (this.options.disableYAxis) { 604 matrix[5] = originalMatrix[5]; 605 } 606 } 607 this.setTransform('matrix(' + matrix.join(',') + ')'); 608 609 this.scale = scale; 610 611 // Disable/enable panning if zooming is at minimum and panOnlyWhenZoomed is true 612 this._checkPanWhenZoomed(scale); 613 614 if (!options.silent) { 615 this._trigger('change', matrix); 616 } 617 618 return matrix; 619 }, 620 621 /** 622 * @returns {Boolean} Returns whether the panzoom element is currently being dragged 623 */ 624 isPanning: function() { 625 return this.panning; 626 }, 627 628 /** 629 * Apply the current transition to the element, if allowed 630 * @param {Boolean} [off] Indicates that the transition should be turned off 631 */ 632 transition: function(off) { 633 if (!this._transition) { return; } 634 var transition = off || !this.options.transition ? 'none' : this._transition; 635 var $set = this.$set; 636 var i = $set.length; 637 while(i--) { 638 // Avoid reflows when zooming 639 if ($.style($set[i], 'transition') !== transition) { 640 $.style($set[i], 'transition', transition); 641 } 642 } 643 }, 644 645 /** 646 * Pan the element to the specified translation X and Y 647 * Note: this is not the same as setting jQuery#offset() or jQuery#position() 648 * @param {Number} x 649 * @param {Number} y 650 * @param {Object} [options] These options are passed along to setMatrix 651 * @param {Array} [options.matrix] The matrix being manipulated (if already known so it doesn't have to be retrieved again) 652 * @param {Boolean} [options.silent] Silence the pan event. Note that this will also silence the setMatrix change event. 653 * @param {Boolean} [options.relative] Make the x and y values relative to the existing matrix 654 */ 655 pan: function(x, y, options) { 656 if (this.options.disablePan) { return; } 657 if (!options) { options = {}; } 658 var matrix = options.matrix; 659 if (!matrix) { 660 matrix = this.getMatrix(); 661 } 662 // Cast existing matrix values to numbers 663 if (options.relative) { 664 x += +matrix[4]; 665 y += +matrix[5]; 666 } 667 matrix[4] = x; 668 matrix[5] = y; 669 this.setMatrix(matrix, options); 670 if (!options.silent) { 671 this._trigger('pan', matrix[4], matrix[5]); 672 } 673 }, 674 675 /** 676 * Zoom in/out the element using the scale properties of a transform matrix 677 * @param {Number|Boolean} [scale] The scale to which to zoom or a boolean indicating to transition a zoom out 678 * @param {Object} [opts] All global options can be overwritten by this options object. For example, override the default increment. 679 * @param {Boolean} [opts.noSetRange] Specify that the method should not set the $zoomRange value (as is the case when $zoomRange is calling zoom on change) 680 * @param {jQuery.Event|Object} [opts.focal] A focal point on the panzoom element on which to zoom. 681 * If an object, set the clientX and clientY properties to the position relative to the parent 682 * @param {Boolean} [opts.animate] Whether to animate the zoom (defaults to true if scale is not a number, false otherwise) 683 * @param {Boolean} [opts.silent] Silence the zoom event 684 * @param {Array} [opts.matrix] Optionally pass the current matrix so it doesn't need to be retrieved 685 * @param {Number} [opts.dValue] Think of a transform matrix as four values a, b, c, d 686 * where a/d are the horizontal/vertical scale values and b/c are the skew values 687 * (5 and 6 of matrix array are the tx/ty transform values). 688 * Normally, the scale is set to both the a and d values of the matrix. 689 * This option allows you to specify a different d value for the zoom. 690 * For instance, to flip vertically, you could set -1 as the dValue. 691 */ 692 zoom: function(scale, opts) { 693 // Shuffle arguments 694 if (typeof scale === 'object') { 695 opts = scale; 696 scale = null; 697 } else if (!opts) { 698 opts = {}; 699 } 700 var options = $.extend({}, this.options, opts); 701 // Check if disabled 702 if (options.disableZoom) { return; } 703 var animate = false; 704 var matrix = options.matrix || this.getMatrix(); 705 var surfaceM = new Matrix(matrix); 706 var startScale = this.getScale(matrix); 707 708 // Calculate zoom based on increment 709 if (typeof scale !== 'number') { 710 if (options.linearZoom) { 711 scale = 1 + (options.increment * (scale ? -1 : 1)) / startScale; 712 } else { 713 scale = scale ? (1 / (1 + options.increment)) : (1 + options.increment); 714 } 715 animate = true; 716 } else { 717 scale = 1 / startScale; 718 } 719 720 // Constrain scale 721 scale = Math.max(Math.min(scale, options.maxScale / startScale), options.minScale / startScale); 722 var m = surfaceM.x(new Matrix(scale, 0, 0, 0, (typeof options.dValue === 'number' ? options.dValue / startScale : scale), 0)); 723 724 // Calculate focal point based on scale 725 var focal = options.focal; 726 if (focal && !options.disablePan) { 727 // Adapted from code by Florian Günther 728 // https://github.com/florianguenther/zui53 729 this.resetDimensions(); 730 var dims = options.dims = this.dimensions; 731 var clientX = focal.clientX; 732 var clientY = focal.clientY; 733 734 // Adjust the focal point for transform-origin 50% 50% 735 // SVG elements have a transform origin of 0 0 736 if (!this.isSVG) { 737 clientX -= (dims.width / startScale) / 2; 738 clientY -= (dims.height / startScale) / 2; 739 } 740 741 var clientV = new Vector(clientX, clientY, 1); 742 // Supply an offset manually if necessary 743 var o = this.parentOffset || this.$parent.offset(); 744 var offsetM = new Matrix(1, 0, o.left - this.$doc.scrollLeft(), 0, 1, o.top - this.$doc.scrollTop()); 745 var surfaceV = surfaceM.inverse().x(offsetM.inverse().x(clientV)); 746 surfaceM = surfaceM.x(new Matrix([scale, 0, 0, scale, 0, 0])); 747 clientV = offsetM.x(surfaceM.x(surfaceV)); 748 matrix[4] = +matrix[4] + (clientX - clientV.e(0)); 749 matrix[5] = +matrix[5] + (clientY - clientV.e(1)); 750 } 751 752 // Set the scale 753 matrix[0] = m.e(0); 754 matrix[1] = m.e(3); 755 matrix[2] = m.e(1); 756 matrix[3] = m.e(4); 757 758 // Calling zoom may still pan the element 759 this.setMatrix(matrix, { 760 animate: typeof options.animate !== 'undefined' ? options.animate : animate, 761 // Set the zoomRange value 762 range: !options.noSetRange 763 }); 764 765 // Trigger zoom event 766 if (!options.silent) { 767 this._trigger('zoom', scale, options); 768 } 769 }, 770 771 /** 772 * Get/set option on an existing instance 773 * @returns {Array|undefined} If getting, returns an array of all values 774 * on each instance for a given key. If setting, continue chaining by returning undefined. 775 */ 776 option: function(key, value) { 777 var options; 778 if (!key) { 779 // Avoids returning direct reference 780 return $.extend({}, this.options); 781 } 782 783 if (typeof key === 'string') { 784 if (arguments.length === 1) { 785 return this.options[ key ] !== undefined ? 786 this.options[ key ] : 787 null; 788 } 789 options = {}; 790 options[ key ] = value; 791 } else { 792 options = key; 793 } 794 795 this._setOptions(options); 796 }, 797 798 /** 799 * Internally sets options 800 * @param {Object} options - An object literal of options to set 801 * @private 802 */ 803 _setOptions: function(options) { 804 $.each(options, $.proxy(function(key, value) { 805 switch(key) { 806 case 'disablePan': 807 this._resetStyle(); 808 /* falls through */ 809 case '$zoomIn': 810 case '$zoomOut': 811 case '$zoomRange': 812 case '$reset': 813 case 'disableZoom': 814 case 'onStart': 815 case 'onChange': 816 case 'onZoom': 817 case 'onPan': 818 case 'onEnd': 819 case 'onReset': 820 case 'eventNamespace': 821 this._unbind(); 822 } 823 this.options[ key ] = value; 824 switch(key) { 825 case 'disablePan': 826 this._initStyle(); 827 /* falls through */ 828 case '$zoomIn': 829 case '$zoomOut': 830 case '$zoomRange': 831 case '$reset': 832 // Set these on the instance 833 this[ key ] = value; 834 /* falls through */ 835 case 'disableZoom': 836 case 'onStart': 837 case 'onChange': 838 case 'onZoom': 839 case 'onPan': 840 case 'onEnd': 841 case 'onReset': 842 case 'eventNamespace': 843 this._bind(); 844 break; 845 case 'cursor': 846 $.style(this.elem, 'cursor', value); 847 break; 848 case 'minScale': 849 this.$zoomRange.attr('min', value); 850 break; 851 case 'maxScale': 852 this.$zoomRange.attr('max', value); 853 break; 854 case 'rangeStep': 855 this.$zoomRange.attr('step', value); 856 break; 857 case 'startTransform': 858 this._buildTransform(); 859 break; 860 case 'duration': 861 case 'easing': 862 this._buildTransition(); 863 /* falls through */ 864 case 'transition': 865 this.transition(); 866 break; 867 case 'panOnlyWhenZoomed': 868 this._checkPanWhenZoomed(); 869 break; 870 case '$set': 871 if (value instanceof $ && value.length) { 872 this.$set = value; 873 // Reset styles 874 this._initStyle(); 875 this._buildTransform(); 876 } 877 } 878 }, this)); 879 }, 880 881 /** 882 * Disable/enable panning depending on whether the current scale 883 * matches the minimum 884 * @param {Number} [scale] 885 * @private 886 */ 887 _checkPanWhenZoomed: function(scale) { 888 var options = this.options; 889 if (options.panOnlyWhenZoomed) { 890 if (!scale) { 891 scale = this.getMatrix()[0]; 892 } 893 var toDisable = scale <= options.minScale; 894 if (options.disablePan !== toDisable) { 895 this.option('disablePan', toDisable); 896 } 897 } 898 }, 899 900 /** 901 * Initialize base styles for the element and its parent 902 * @private 903 */ 904 _initStyle: function() { 905 var styles = { 906 // Set the same default whether SVG or HTML 907 // transform-origin cannot be changed to 50% 50% in IE9-11 or Edge 13-14+ 908 'transform-origin': this.isSVG ? '0 0' : '50% 50%' 909 }; 910 // Set elem styles 911 if (!this.options.disablePan) { 912 styles.cursor = this.options.cursor; 913 } 914 this.$set.css(styles); 915 916 // Set parent to relative if set to static 917 var $parent = this.$parent; 918 // No need to add styles to the body 919 if ($parent.length && !$.nodeName(this.parent, 'body')) { 920 styles = { 921 overflow: 'hidden' 922 }; 923 if ($parent.css('position') === 'static') { 924 styles.position = 'relative'; 925 } 926 $parent.css(styles); 927 } 928 }, 929 930 /** 931 * Undo any styles attached in this plugin 932 * @private 933 */ 934 _resetStyle: function() { 935 this.$elem.css({ 936 'cursor': '', 937 'transition': '' 938 }); 939 this.$parent.css({ 940 'overflow': '', 941 'position': '' 942 }); 943 }, 944 945 /** 946 * Binds all necessary events 947 * @private 948 */ 949 _bind: function() { 950 var self = this; 951 var options = this.options; 952 var ns = options.eventNamespace; 953 var str_down = 'mousedown' + ns + ' pointerdown' + ns + ' MSPointerDown' + ns; 954 var str_start = 'touchstart' + ns + ' ' + str_down; 955 var str_click = 'touchend' + ns + ' click' + ns + ' pointerup' + ns + ' MSPointerUp' + ns; 956 var events = {}; 957 var $reset = this.$reset; 958 var $zoomRange = this.$zoomRange; 959 960 // Bind panzoom events from options 961 $.each([ 'Start', 'Change', 'Zoom', 'Pan', 'End', 'Reset' ], function() { 962 var m = options[ 'on' + this ]; 963 if ($.isFunction(m)) { 964 events[ 'panzoom' + this.toLowerCase() + ns ] = m; 965 } 966 }); 967 968 // Bind $elem drag and click/touchdown events 969 // Bind touchstart if either panning or zooming is enabled 970 if (!options.disablePan || !options.disableZoom) { 971 events[ str_start ] = function(e) { 972 var touches; 973 if (e.type === 'touchstart' ? 974 // Touch 975 (touches = e.touches || e.originalEvent.touches) && 976 ((touches.length === 1 && !options.disablePan) || touches.length === 2) : 977 // Mouse/Pointer: Ignore unexpected click types 978 // Support: IE10 only 979 // IE10 does not support e.button for MSPointerDown, but does have e.which 980 !options.disablePan && (e.which || e.originalEvent.which) === options.which) { 981 982 e.preventDefault(); 983 e.stopPropagation(); 984 self._startMove(e, touches); 985 } 986 }; 987 // Prevent the contextmenu event 988 // if we're binding to right-click 989 if (options.which === 3) { 990 events.contextmenu = false; 991 } 992 } 993 this.$elem.on(events); 994 995 // Bind reset 996 if ($reset.length) { 997 $reset.on(str_click, function(e) { 998 e.preventDefault(); 999 self.reset(); 1000 }); 1001 } 1002 1003 // Set default attributes for the range input 1004 if ($zoomRange.length) { 1005 $zoomRange.attr({ 1006 // Only set the range step if explicit or 1007 // set the default if there is no attribute present 1008 step: options.rangeStep === Panzoom.defaults.rangeStep && 1009 $zoomRange.attr('step') || 1010 options.rangeStep, 1011 min: options.minScale, 1012 max: options.maxScale 1013 }).prop({ 1014 value: this.getMatrix()[0] 1015 }); 1016 } 1017 1018 // No bindings if zooming is disabled 1019 if (options.disableZoom) { 1020 return; 1021 } 1022 1023 var $zoomIn = this.$zoomIn; 1024 var $zoomOut = this.$zoomOut; 1025 1026 // Bind zoom in/out 1027 // Don't bind one without the other 1028 if ($zoomIn.length && $zoomOut.length) { 1029 // preventDefault cancels future mouse events on touch events 1030 $zoomIn.on(str_click, function(e) { 1031 e.preventDefault(); 1032 self.zoom(); 1033 }); 1034 $zoomOut.on(str_click, function(e) { 1035 e.preventDefault(); 1036 self.zoom(true); 1037 }); 1038 } 1039 1040 if ($zoomRange.length) { 1041 events = {}; 1042 // Cannot prevent default action here 1043 events[ str_down ] = function() { 1044 self.transition(true); 1045 }; 1046 // Zoom on input events if available and change events 1047 // See https://github.com/timmywil/jquery.panzoom/issues/90 1048 events[ (supportsInputEvent ? 'input' : 'change') + ns ] = function() { 1049 self.zoom(+this.value, { noSetRange: true }); 1050 }; 1051 $zoomRange.on(events); 1052 } 1053 }, 1054 1055 /** 1056 * Unbind all events 1057 * @private 1058 */ 1059 _unbind: function() { 1060 this.$elem 1061 .add(this.$zoomIn) 1062 .add(this.$zoomOut) 1063 .add(this.$reset) 1064 .off(this.options.eventNamespace); 1065 }, 1066 1067 /** 1068 * Builds the original transform value 1069 * @private 1070 */ 1071 _buildTransform: function() { 1072 // Save the original transform 1073 // Retrieving this also adds the correct prefixed style name 1074 // to jQuery's internal $.cssProps 1075 return this._origTransform = this.getTransform(this.options.startTransform); 1076 }, 1077 1078 /** 1079 * Set transition property for later use when zooming 1080 * @private 1081 */ 1082 _buildTransition: function() { 1083 if (this._transform) { 1084 var options = this.options; 1085 this._transition = this._transform + ' ' + options.duration + 'ms ' + options.easing; 1086 } 1087 }, 1088 1089 /** 1090 * Calculates the distance between two touch points 1091 * Remember pythagorean? 1092 * @param {Array} touches 1093 * @returns {Number} Returns the distance 1094 * @private 1095 */ 1096 _getDistance: function(touches) { 1097 var touch1 = touches[0]; 1098 var touch2 = touches[1]; 1099 return Math.sqrt(Math.pow(Math.abs(touch2.clientX - touch1.clientX), 2) + Math.pow(Math.abs(touch2.clientY - touch1.clientY), 2)); 1100 }, 1101 1102 /** 1103 * Constructs an approximated point in the middle of two touch points 1104 * @returns {Object} Returns an object containing pageX and pageY 1105 * @private 1106 */ 1107 _getMiddle: function(touches) { 1108 var touch1 = touches[0]; 1109 var touch2 = touches[1]; 1110 return { 1111 clientX: ((touch2.clientX - touch1.clientX) / 2) + touch1.clientX, 1112 clientY: ((touch2.clientY - touch1.clientY) / 2) + touch1.clientY 1113 }; 1114 }, 1115 1116 /** 1117 * Trigger a panzoom event on our element 1118 * The event is passed the Panzoom instance 1119 * @param {String|jQuery.Event} event 1120 * @param {Mixed} arg1[, arg2, arg3, ...] Arguments to append to the trigger 1121 * @private 1122 */ 1123 _trigger: function (event) { 1124 if (typeof event === 'string') { 1125 event = 'panzoom' + event; 1126 } 1127 this.$elem.triggerHandler(event, [this].concat(slice.call(arguments, 1))); 1128 }, 1129 1130 /** 1131 * Starts the pan 1132 * This is bound to mouse/touchmove on the element 1133 * @param {jQuery.Event} event An event with pageX, pageY, and possibly the touches list 1134 * @param {TouchList} [touches] The touches list if present 1135 * @private 1136 */ 1137 _startMove: function(event, touches) { 1138 if (this.panning) { 1139 return; 1140 } 1141 var moveEvent, endEvent, 1142 startDistance, startScale, startMiddle, 1143 startPageX, startPageY, touch; 1144 var self = this; 1145 var options = this.options; 1146 var ns = options.eventNamespace; 1147 var matrix = this.getMatrix(); 1148 var original = matrix.slice(0); 1149 var origPageX = +original[4]; 1150 var origPageY = +original[5]; 1151 var panOptions = { matrix: matrix, animate: 'skip' }; 1152 var type = event.type; 1153 1154 // Use proper events 1155 if (type === 'pointerdown') { 1156 moveEvent = 'pointermove'; 1157 endEvent = 'pointerup'; 1158 } else if (type === 'touchstart') { 1159 moveEvent = 'touchmove'; 1160 endEvent = 'touchend'; 1161 } else if (type === 'MSPointerDown') { 1162 moveEvent = 'MSPointerMove'; 1163 endEvent = 'MSPointerUp'; 1164 } else { 1165 moveEvent = 'mousemove'; 1166 endEvent = 'mouseup'; 1167 } 1168 1169 // Add namespace 1170 moveEvent += ns; 1171 endEvent += ns; 1172 1173 // Remove any transitions happening 1174 this.transition(true); 1175 1176 // Indicate that we are currently panning 1177 this.panning = true; 1178 1179 // Trigger start event 1180 this._trigger('start', event, touches); 1181 1182 var setStart = function(event, touches) { 1183 if (touches) { 1184 if (touches.length === 2) { 1185 if (startDistance != null) { 1186 return; 1187 } 1188 startDistance = self._getDistance(touches); 1189 startScale = self.getScale(matrix); 1190 startMiddle = self._getMiddle(touches); 1191 return; 1192 } 1193 if (startPageX != null) { 1194 return; 1195 } 1196 if ((touch = touches[0])) { 1197 startPageX = touch.pageX; 1198 startPageY = touch.pageY; 1199 } 1200 } 1201 if (startPageX != null) { 1202 return; 1203 } 1204 startPageX = event.pageX; 1205 startPageY = event.pageY; 1206 }; 1207 1208 setStart(event, touches); 1209 1210 var move = function(e) { 1211 var coords; 1212 e.preventDefault(); 1213 touches = e.touches || e.originalEvent.touches; 1214 setStart(e, touches); 1215 1216 if (touches) { 1217 if (touches.length === 2) { 1218 1219 // Calculate move on middle point 1220 var middle = self._getMiddle(touches); 1221 var diff = self._getDistance(touches) - startDistance; 1222 1223 // Set zoom 1224 self.zoom(diff * (options.increment / 100) + startScale, { 1225 focal: middle, 1226 matrix: matrix, 1227 animate: 'skip' 1228 }); 1229 1230 // Set pan 1231 self.pan( 1232 +matrix[4] + middle.clientX - startMiddle.clientX, 1233 +matrix[5] + middle.clientY - startMiddle.clientY, 1234 panOptions 1235 ); 1236 startMiddle = middle; 1237 return; 1238 } 1239 coords = touches[0] || { pageX: 0, pageY: 0 }; 1240 } 1241 1242 if (!coords) { 1243 coords = e; 1244 } 1245 1246 self.pan( 1247 origPageX + coords.pageX - startPageX, 1248 origPageY + coords.pageY - startPageY, 1249 panOptions 1250 ); 1251 }; 1252 1253 // Bind the handlers 1254 $(document) 1255 .off(ns) 1256 .on(moveEvent, move) 1257 .on(endEvent, function(e) { 1258 e.preventDefault(); 1259 // Unbind all document events 1260 $(this).off(ns); 1261 self.panning = false; 1262 // Trigger our end event 1263 // Simply set the type to "panzoomend" to pass through all end properties 1264 // jQuery's `not` is used here to compare Array equality 1265 e.type = 'panzoomend'; 1266 self._trigger(e, matrix, !matrixEquals(matrix, original)); 1267 }); 1268 } 1269 }; 1270 1271 // Add Panzoom as a static property 1272 $.Panzoom = Panzoom; 1273 1274 /** 1275 * Extend jQuery 1276 * @param {Object|String} options - The name of a method to call on the prototype 1277 * or an object literal of options 1278 * @returns {jQuery|Mixed} jQuery instance for regular chaining or the return value(s) of a panzoom method call 1279 */ 1280 $.fn.panzoom = function(options) { 1281 var instance, args, m, ret; 1282 1283 // Call methods widget-style 1284 if (typeof options === 'string') { 1285 ret = []; 1286 args = slice.call(arguments, 1); 1287 this.each(function() { 1288 instance = $.data(this, datakey); 1289 1290 if (!instance) { 1291 ret.push(undefined); 1292 1293 // Ignore methods beginning with `_` 1294 } else if (options.charAt(0) !== '_' && 1295 typeof (m = instance[ options ]) === 'function' && 1296 // If nothing is returned, do not add to return values 1297 (m = m.apply(instance, args)) !== undefined) { 1298 1299 ret.push(m); 1300 } 1301 }); 1302 1303 // Return an array of values for the jQuery instances 1304 // Or the value itself if there is only one 1305 // Or keep chaining 1306 return ret.length ? 1307 (ret.length === 1 ? ret[0] : ret) : 1308 this; 1309 } 1310 1311 return this.each(function() { new Panzoom(this, options); }); 1312 }; 1313 1314 return Panzoom; 1315}));
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.