vendor: 18,937 bytes, lines 1-605
1/** 2 * @license jquery.panzoom.js v@VERSION 3 * Updated: @DATE 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(); 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 * Given a matrix object, quickly set the current matrix of the element 517 * @param {Array|String} matrix 518 * @param {Object} [options] 519 * @param {Boolean|String} [options.animate] Whether to animate the transform change, or 'skip' indicating that it is unnecessary to set 520 * @param {Boolean} [options.contain] Override the global contain option 521 * @param {Boolean} [options.range] If true, $zoomRange's value will be updated. 522 * @param {Boolean} [options.silent] If true, the change event will not be triggered 523 * @returns {Array} Returns the newly-set matrix 524 */ 525 setMatrix: function(matrix, options) { 526 if (this.disabled) { return; } 527 if (!options) { options = {}; } 528 // Convert to array 529 if (typeof matrix === 'string') { 530 matrix = this.getMatrix(matrix); 531 } 532 var scale = +matrix[0]; 533 var contain = typeof options.contain !== 'undefined' ? options.contain : this.options.contain; 534 535 // Apply containment 536 if (contain) { 537 var dims = options.dims; 538 if (!dims) { 539 this.resetDimensions(); 540 dims = this.dimensions; 541 } 542 var spaceWLeft, spaceWRight, scaleDiff; 543 var container = this.container; 544 var width = dims.width; 545 var height = dims.height; 546 var conWidth = container.width; 547 var conHeight = container.height; 548 var zoomAspectW = conWidth / width; 549 var zoomAspectH = conHeight / height; 550 551 // If the element is not naturally centered, 552 // assume full space right 553 if (this.$parent.css('textAlign') !== 'center' || $.css(this.elem, 'display') !== 'inline') { 554 // offsetWidth gets us the width without the transform 555 scaleDiff = (width - this.elem.offsetWidth) / 2; 556 spaceWLeft = scaleDiff - dims.border.left; 557 spaceWRight = width - conWidth - scaleDiff + dims.border.right; 558 } else { 559 spaceWLeft = spaceWRight = ((width - conWidth) / 2); 560 } 561 var spaceHTop = ((height - conHeight) / 2) + dims.border.top; 562 var spaceHBottom = ((height - conHeight) / 2) - dims.border.top - dims.border.bottom; 563 564 if (contain === 'invert' || contain === 'automatic' && zoomAspectW < 1.01) { 565 matrix[4] = Math.max(Math.min(matrix[4], spaceWLeft - dims.border.left), -spaceWRight); 566 } else { 567 matrix[4] = Math.min(Math.max(matrix[4], spaceWLeft), -spaceWRight); 568 } 569 570 if (contain === 'invert' || (contain === 'automatic' && zoomAspectH < 1.01)) { 571 matrix[5] = Math.max(Math.min(matrix[5], spaceHTop - dims.border.top), -spaceHBottom); 572 } else { 573 matrix[5] = Math.min(Math.max(matrix[5], spaceHTop), -spaceHBottom); 574 } 575 } 576 577 // Animate 578 if (options.animate !== 'skip') { 579 // Set transition 580 this.transition(!options.animate); 581 } 582 583 // Update range element 584 if (options.range) { 585 this.$zoomRange.val(scale); 586 } 587 588 // Set the matrix on this.$set 589 if (this.options.disableXAxis || this.options.disableYAxis) { 590 var originalMatrix = this.getMatrix(); 591 if (this.options.disableXAxis) { 592 matrix[4] = originalMatrix[4]; 593 } 594 if (this.options.disableYAxis) { 595 matrix[5] = originalMatrix[5]; 596 } 597 } 598 this.setTransform('matrix(' + matrix.join(',') + ')'); 599 600 this.scale = scale; 601 602 // Disable/enable panning if zooming is at minimum and panOnlyWhenZoomed is true 603 this._checkPanWhenZoomed(scale); 604 605 if (!options.silent) {
606 this._trigger('change', matrix); 607 } 608 609 return matrix; 610 }, 611 612 /** 613 * @returns {Boolean} Returns whether the panzoom element is currently being dragged 614 */ 615 isPanning: function() { 616 return this.panning; 617 }, 618 619 /** 620 * Apply the current transition to the element, if allowed 621 * @param {Boolean} [off] Indicates that the transition should be turned off 622 */ 623 transition: function(off) { 624 if (!this._transition) { return; } 625 var transition = off || !this.options.transition ? 'none' : this._transition; 626 var $set = this.$set; 627 var i = $set.length; 628 while(i--) { 629 // Avoid reflows when zooming 630 if ($.style($set[i], 'transition') !== transition) { 631 $.style($set[i], 'transition', transition); 632 } 633 } 634 }, 635 636 /** 637 * Pan the element to the specified translation X and Y 638 * Note: this is not the same as setting jQuery#offset() or jQuery#position() 639 * @param {Number} x 640 * @param {Number} y 641 * @param {Object} [options] These options are passed along to setMatrix 642 * @param {Array} [options.matrix] The matrix being manipulated (if already known so it doesn't have to be retrieved again) 643 * @param {Boolean} [options.silent] Silence the pan event. Note that this will also silence the setMatrix change event. 644 * @param {Boolean} [options.relative] Make the x and y values relative to the existing matrix 645 */ 646 pan: function(x, y, options) { 647 if (this.options.disablePan) { return; } 648 if (!options) { options = {}; } 649 var matrix = options.matrix; 650 if (!matrix) { 651 matrix = this.getMatrix(); 652 } 653 // Cast existing matrix values to numbers 654 if (options.relative) { 655 x += +matrix[4]; 656 y += +matrix[5]; 657 } 658 matrix[4] = x; 659 matrix[5] = y; 660 this.setMatrix(matrix, options); 661 if (!options.silent) { 662 this._trigger('pan', matrix[4], matrix[5]); 663 } 664 }, 665 666 /** 667 * Zoom in/out the element using the scale properties of a transform matrix 668 * @param {Number|Boolean} [scale] The scale to which to zoom or a boolean indicating to transition a zoom out 669 * @param {Object} [opts] All global options can be overwritten by this options object. For example, override the default increment. 670 * @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) 671 * @param {jQuery.Event|Object} [opts.focal] A focal point on the panzoom element on which to zoom. 672 * If an object, set the clientX and clientY properties to the position relative to the parent 673 * @param {Boolean} [opts.animate] Whether to animate the zoom (defaults to true if scale is not a number, false otherwise) 674 * @param {Boolean} [opts.silent] Silence the zoom event 675 * @param {Array} [opts.matrix] Optionally pass the current matrix so it doesn't need to be retrieved 676 * @param {Number} [opts.dValue] Think of a transform matrix as four values a, b, c, d 677 * where a/d are the horizontal/vertical scale values and b/c are the skew values 678 * (5 and 6 of matrix array are the tx/ty transform values). 679 * Normally, the scale is set to both the a and d values of the matrix. 680 * This option allows you to specify a different d value for the zoom. 681 * For instance, to flip vertically, you could set -1 as the dValue. 682 */ 683 zoom: function(scale, opts) { 684 // Shuffle arguments 685 if (typeof scale === 'object') { 686 opts = scale; 687 scale = null; 688 } else if (!opts) { 689 opts = {}; 690 } 691 var options = $.extend({}, this.options, opts); 692 // Check if disabled 693 if (options.disableZoom) { return; } 694 var animate = false; 695 var matrix = options.matrix || this.getMatrix(); 696 var startScale = +matrix[0]; 697 698 // Calculate zoom based on increment 699 if (typeof scale !== 'number') { 700 if (options.linearZoom) { 701 scale = startScale + (options.increment * (scale ? -1 : 1)); 702 } else { 703 scale = scale ? (startScale / (1 + options.increment)) : (startScale * (1 + options.increment)); 704 } 705 animate = true; 706 } 707 708 // Constrain scale 709 scale = Math.max(Math.min(scale, options.maxScale), options.minScale); 710 711 // Calculate focal point based on scale 712 var focal = options.focal; 713 if (focal && !options.disablePan) { 714 // Adapted from code by Florian Günther 715 // https://github.com/florianguenther/zui53 716 this.resetDimensions(); 717 var dims = options.dims = this.dimensions; 718 var clientX = focal.clientX; 719 var clientY = focal.clientY; 720 721 // Adjust the focal point for transform-origin 50% 50% 722 // SVG elements have a transform origin of 0 0 723 if (!this.isSVG) { 724 clientX -= (dims.width / startScale) / 2; 725 clientY -= (dims.height / startScale) / 2; 726 } 727 728 var clientV = new Vector(clientX, clientY, 1); 729 var surfaceM = new Matrix(matrix); 730 // Supply an offset manually if necessary 731 var o = this.parentOffset || this.$parent.offset(); 732 var offsetM = new Matrix(1, 0, o.left - this.$doc.scrollLeft(), 0, 1, o.top - this.$doc.scrollTop()); 733 var surfaceV = surfaceM.inverse().x(offsetM.inverse().x(clientV)); 734 var scaleBy = scale / matrix[0]; 735 surfaceM = surfaceM.x(new Matrix([scaleBy, 0, 0, scaleBy, 0, 0])); 736 clientV = offsetM.x(surfaceM.x(surfaceV));
737 matrix[4] = +matrix[4] + (clientX - clientV.e(0)); 738 matrix[5] = +matrix[5] + (clientY - clientV.e(1)); 739 } 740 741 // Set the scale 742 matrix[0] = scale; 743 matrix[3] = typeof options.dValue === 'number' ? options.dValue : scale; 744 745 // Calling zoom may still pan the element 746 this.setMatrix(matrix, { 747 animate: typeof options.animate !== 'undefined' ? options.animate : animate, 748 // Set the zoomRange value 749 range: !options.noSetRange 750 }); 751 752 // Trigger zoom event 753 if (!options.silent) { 754 this._trigger('zoom', matrix[0], options); 755 } 756 }, 757 758 /** 759 * Get/set option on an existing instance 760 * @returns {Array|undefined} If getting, returns an array of all values 761 * on each instance for a given key. If setting, continue chaining by returning undefined. 762 */ 763 option: function(key, value) { 764 var options; 765 if (!key) { 766 // Avoids returning direct reference 767 return $.extend({}, this.options); 768 } 769 770 if (typeof key === 'string') { 771 if (arguments.length === 1) { 772 return this.options[ key ] !== undefined ? 773 this.options[ key ] : 774 null; 775 } 776 options = {}; 777 options[ key ] = value; 778 } else { 779 options = key; 780 } 781 782 this._setOptions(options); 783 }, 784 785 /** 786 * Internally sets options 787 * @param {Object} options - An object literal of options to set 788 * @private 789 */ 790 _setOptions: function(options) { 791 $.each(options, $.proxy(function(key, value) { 792 switch(key) { 793 case 'disablePan': 794 this._resetStyle(); 795 /* falls through */ 796 case '$zoomIn': 797 case '$zoomOut': 798 case '$zoomRange': 799 case '$reset': 800 case 'disableZoom': 801 case 'onStart': 802 case 'onChange': 803 case 'onZoom': 804 case 'onPan': 805 case 'onEnd': 806 case 'onReset': 807 case 'eventNamespace': 808 this._unbind(); 809 } 810 this.options[ key ] = value; 811 switch(key) { 812 case 'disablePan': 813 this._initStyle(); 814 /* falls through */ 815 case '$zoomIn': 816 case '$zoomOut': 817 case '$zoomRange': 818 case '$reset': 819 // Set these on the instance 820 this[ key ] = value; 821 /* falls through */ 822 case 'disableZoom': 823 case 'onStart': 824 case 'onChange': 825 case 'onZoom': 826 case 'onPan': 827 case 'onEnd': 828 case 'onReset': 829 case 'eventNamespace': 830 this._bind(); 831 break; 832 case 'cursor':
vendor: 12,728 bytes, lines 833-1302
833 $.style(this.elem, 'cursor', value); 834 break; 835 case 'minScale': 836 this.$zoomRange.attr('min', value); 837 break; 838 case 'maxScale': 839 this.$zoomRange.attr('max', value); 840 break; 841 case 'rangeStep': 842 this.$zoomRange.attr('step', value); 843 break; 844 case 'startTransform': 845 this._buildTransform(); 846 break; 847 case 'duration': 848 case 'easing': 849 this._buildTransition(); 850 /* falls through */ 851 case 'transition': 852 this.transition(); 853 break; 854 case 'panOnlyWhenZoomed': 855 this._checkPanWhenZoomed(); 856 break; 857 case '$set': 858 if (value instanceof $ && value.length) { 859 this.$set = value; 860 // Reset styles 861 this._initStyle(); 862 this._buildTransform(); 863 } 864 } 865 }, this)); 866 }, 867 868 /** 869 * Disable/enable panning depending on whether the current scale 870 * matches the minimum 871 * @param {Number} [scale] 872 * @private 873 */ 874 _checkPanWhenZoomed: function(scale) { 875 var options = this.options; 876 if (options.panOnlyWhenZoomed) { 877 if (!scale) { 878 scale = this.getMatrix()[0]; 879 } 880 var toDisable = scale <= options.minScale; 881 if (options.disablePan !== toDisable) { 882 this.option('disablePan', toDisable); 883 } 884 } 885 }, 886 887 /** 888 * Initialize base styles for the element and its parent 889 * @private 890 */ 891 _initStyle: function() { 892 var styles = { 893 // Set the same default whether SVG or HTML 894 // transform-origin cannot be changed to 50% 50% in IE9-11 or Edge 13-14+ 895 'transform-origin': this.isSVG ? '0 0' : '50% 50%' 896 }; 897 // Set elem styles 898 if (!this.options.disablePan) { 899 styles.cursor = this.options.cursor; 900 } 901 this.$set.css(styles); 902 903 // Set parent to relative if set to static 904 var $parent = this.$parent; 905 // No need to add styles to the body 906 if ($parent.length && !$.nodeName(this.parent, 'body')) { 907 styles = { 908 overflow: 'hidden' 909 }; 910 if ($parent.css('position') === 'static') { 911 styles.position = 'relative'; 912 } 913 $parent.css(styles); 914 } 915 }, 916 917 /** 918 * Undo any styles attached in this plugin 919 * @private 920 */ 921 _resetStyle: function() { 922 this.$elem.css({ 923 'cursor': '', 924 'transition': '' 925 }); 926 this.$parent.css({ 927 'overflow': '', 928 'position': '' 929 }); 930 }, 931 932 /** 933 * Binds all necessary events 934 * @private 935 */ 936 _bind: function() { 937 var self = this; 938 var options = this.options; 939 var ns = options.eventNamespace; 940 var str_down = 'mousedown' + ns + ' pointerdown' + ns + ' MSPointerDown' + ns; 941 var str_start = 'touchstart' + ns + ' ' + str_down; 942 var str_click = 'touchend' + ns + ' click' + ns + ' pointerup' + ns + ' MSPointerUp' + ns; 943 var events = {}; 944 var $reset = this.$reset; 945 var $zoomRange = this.$zoomRange; 946 947 // Bind panzoom events from options 948 $.each([ 'Start', 'Change', 'Zoom', 'Pan', 'End', 'Reset' ], function() { 949 var m = options[ 'on' + this ]; 950 if ($.isFunction(m)) { 951 events[ 'panzoom' + this.toLowerCase() + ns ] = m; 952 } 953 }); 954 955 // Bind $elem drag and click/touchdown events 956 // Bind touchstart if either panning or zooming is enabled 957 if (!options.disablePan || !options.disableZoom) { 958 events[ str_start ] = function(e) { 959 var touches; 960 if (e.type === 'touchstart' ? 961 // Touch 962 (touches = e.touches || e.originalEvent.touches) && 963 ((touches.length === 1 && !options.disablePan) || touches.length === 2) : 964 // Mouse/Pointer: Ignore unexpected click types 965 // Support: IE10 only 966 // IE10 does not support e.button for MSPointerDown, but does have e.which 967 !options.disablePan && (e.which || e.originalEvent.which) === options.which) { 968 969 e.preventDefault(); 970 e.stopPropagation(); 971 self._startMove(e, touches); 972 } 973 }; 974 // Prevent the contextmenu event 975 // if we're binding to right-click 976 if (options.which === 3) { 977 events.contextmenu = false; 978 } 979 } 980 this.$elem.on(events); 981 982 // Bind reset 983 if ($reset.length) { 984 $reset.on(str_click, function(e) { 985 e.preventDefault(); 986 self.reset(); 987 }); 988 } 989 990 // Set default attributes for the range input 991 if ($zoomRange.length) { 992 $zoomRange.attr({ 993 // Only set the range step if explicit or 994 // set the default if there is no attribute present 995 step: options.rangeStep === Panzoom.defaults.rangeStep && 996 $zoomRange.attr('step') || 997 options.rangeStep, 998 min: options.minScale, 999 max: options.maxScale 1000 }).prop({ 1001 value: this.getMatrix()[0] 1002 }); 1003 } 1004 1005 // No bindings if zooming is disabled 1006 if (options.disableZoom) { 1007 return; 1008 } 1009 1010 var $zoomIn = this.$zoomIn; 1011 var $zoomOut = this.$zoomOut; 1012 1013 // Bind zoom in/out 1014 // Don't bind one without the other 1015 if ($zoomIn.length && $zoomOut.length) { 1016 // preventDefault cancels future mouse events on touch events 1017 $zoomIn.on(str_click, function(e) { 1018 e.preventDefault(); 1019 self.zoom(); 1020 }); 1021 $zoomOut.on(str_click, function(e) { 1022 e.preventDefault(); 1023 self.zoom(true); 1024 }); 1025 } 1026 1027 if ($zoomRange.length) { 1028 events = {}; 1029 // Cannot prevent default action here 1030 events[ str_down ] = function() { 1031 self.transition(true); 1032 }; 1033 // Zoom on input events if available and change events 1034 // See https://github.com/timmywil/jquery.panzoom/issues/90 1035 events[ (supportsInputEvent ? 'input' : 'change') + ns ] = function() { 1036 self.zoom(+this.value, { noSetRange: true }); 1037 }; 1038 $zoomRange.on(events); 1039 } 1040 }, 1041 1042 /** 1043 * Unbind all events 1044 * @private 1045 */ 1046 _unbind: function() { 1047 this.$elem 1048 .add(this.$zoomIn) 1049 .add(this.$zoomOut) 1050 .add(this.$reset) 1051 .off(this.options.eventNamespace); 1052 }, 1053 1054 /** 1055 * Builds the original transform value 1056 * @private 1057 */ 1058 _buildTransform: function() { 1059 // Save the original transform 1060 // Retrieving this also adds the correct prefixed style name 1061 // to jQuery's internal $.cssProps 1062 return this._origTransform = this.getTransform(this.options.startTransform); 1063 }, 1064 1065 /** 1066 * Set transition property for later use when zooming 1067 * @private 1068 */ 1069 _buildTransition: function() { 1070 if (this._transform) { 1071 var options = this.options; 1072 this._transition = this._transform + ' ' + options.duration + 'ms ' + options.easing; 1073 } 1074 }, 1075 1076 /** 1077 * Calculates the distance between two touch points 1078 * Remember pythagorean? 1079 * @param {Array} touches 1080 * @returns {Number} Returns the distance 1081 * @private 1082 */ 1083 _getDistance: function(touches) { 1084 var touch1 = touches[0]; 1085 var touch2 = touches[1]; 1086 return Math.sqrt(Math.pow(Math.abs(touch2.clientX - touch1.clientX), 2) + Math.pow(Math.abs(touch2.clientY - touch1.clientY), 2)); 1087 }, 1088 1089 /** 1090 * Constructs an approximated point in the middle of two touch points 1091 * @returns {Object} Returns an object containing pageX and pageY 1092 * @private 1093 */ 1094 _getMiddle: function(touches) { 1095 var touch1 = touches[0]; 1096 var touch2 = touches[1]; 1097 return { 1098 clientX: ((touch2.clientX - touch1.clientX) / 2) + touch1.clientX, 1099 clientY: ((touch2.clientY - touch1.clientY) / 2) + touch1.clientY 1100 }; 1101 }, 1102 1103 /** 1104 * Trigger a panzoom event on our element 1105 * The event is passed the Panzoom instance 1106 * @param {String|jQuery.Event} event 1107 * @param {Mixed} arg1[, arg2, arg3, ...] Arguments to append to the trigger 1108 * @private 1109 */ 1110 _trigger: function (event) { 1111 if (typeof event === 'string') { 1112 event = 'panzoom' + event; 1113 } 1114 this.$elem.triggerHandler(event, [this].concat(slice.call(arguments, 1))); 1115 }, 1116 1117 /** 1118 * Starts the pan 1119 * This is bound to mouse/touchmove on the element 1120 * @param {jQuery.Event} event An event with pageX, pageY, and possibly the touches list 1121 * @param {TouchList} [touches] The touches list if present 1122 * @private 1123 */ 1124 _startMove: function(event, touches) { 1125 if (this.panning) { 1126 return; 1127 } 1128 var moveEvent, endEvent, 1129 startDistance, startScale, startMiddle, 1130 startPageX, startPageY, touch; 1131 var self = this; 1132 var options = this.options; 1133 var ns = options.eventNamespace; 1134 var matrix = this.getMatrix(); 1135 var original = matrix.slice(0); 1136 var origPageX = +original[4]; 1137 var origPageY = +original[5]; 1138 var panOptions = { matrix: matrix, animate: 'skip' }; 1139 var type = event.type; 1140 1141 // Use proper events 1142 if (type === 'pointerdown') { 1143 moveEvent = 'pointermove'; 1144 endEvent = 'pointerup'; 1145 } else if (type === 'touchstart') { 1146 moveEvent = 'touchmove'; 1147 endEvent = 'touchend'; 1148 } else if (type === 'MSPointerDown') { 1149 moveEvent = 'MSPointerMove'; 1150 endEvent = 'MSPointerUp'; 1151 } else { 1152 moveEvent = 'mousemove'; 1153 endEvent = 'mouseup'; 1154 } 1155 1156 // Add namespace 1157 moveEvent += ns; 1158 endEvent += ns; 1159 1160 // Remove any transitions happening 1161 this.transition(true); 1162 1163 // Indicate that we are currently panning 1164 this.panning = true; 1165 1166 // Trigger start event 1167 this._trigger('start', event, touches); 1168 1169 var setStart = function(event, touches) { 1170 if (touches) { 1171 if (touches.length === 2) { 1172 if (startDistance != null) { 1173 return; 1174 } 1175 startDistance = self._getDistance(touches); 1176 startScale = +matrix[0]; 1177 startMiddle = self._getMiddle(touches); 1178 return; 1179 } 1180 if (startPageX != null) { 1181 return; 1182 } 1183 if ((touch = touches[0])) { 1184 startPageX = touch.pageX; 1185 startPageY = touch.pageY; 1186 } 1187 } 1188 if (startPageX != null) { 1189 return; 1190 } 1191 startPageX = event.pageX; 1192 startPageY = event.pageY; 1193 }; 1194 1195 setStart(event, touches); 1196 1197 var move = function(e) { 1198 var coords; 1199 e.preventDefault(); 1200 touches = e.touches || e.originalEvent.touches; 1201 setStart(e, touches); 1202 1203 if (touches) { 1204 if (touches.length === 2) { 1205 1206 // Calculate move on middle point 1207 var middle = self._getMiddle(touches); 1208 var diff = self._getDistance(touches) - startDistance; 1209 1210 // Set zoom 1211 self.zoom(diff * (options.increment / 100) + startScale, { 1212 focal: middle, 1213 matrix: matrix, 1214 animate: 'skip' 1215 }); 1216 1217 // Set pan 1218 self.pan( 1219 +matrix[4] + middle.clientX - startMiddle.clientX, 1220 +matrix[5] + middle.clientY - startMiddle.clientY, 1221 panOptions 1222 ); 1223 startMiddle = middle; 1224 return; 1225 } 1226 coords = touches[0] || { pageX: 0, pageY: 0 }; 1227 } 1228 1229 if (!coords) { 1230 coords = e; 1231 } 1232 1233 self.pan( 1234 origPageX + coords.pageX - startPageX, 1235 origPageY + coords.pageY - startPageY, 1236 panOptions 1237 ); 1238 }; 1239 1240 // Bind the handlers 1241 $(document) 1242 .off(ns) 1243 .on(moveEvent, move) 1244 .on(endEvent, function(e) { 1245 e.preventDefault(); 1246 // Unbind all document events 1247 $(this).off(ns); 1248 self.panning = false; 1249 // Trigger our end event 1250 // Simply set the type to "panzoomend" to pass through all end properties 1251 // jQuery's `not` is used here to compare Array equality 1252 e.type = 'panzoomend'; 1253 self._trigger(e, matrix, !matrixEquals(matrix, original)); 1254 }); 1255 } 1256 }; 1257 1258 // Add Panzoom as a static property 1259 $.Panzoom = Panzoom; 1260 1261 /** 1262 * Extend jQuery 1263 * @param {Object|String} options - The name of a method to call on the prototype 1264 * or an object literal of options 1265 * @returns {jQuery|Mixed} jQuery instance for regular chaining or the return value(s) of a panzoom method call 1266 */ 1267 $.fn.panzoom = function(options) { 1268 var instance, args, m, ret; 1269 1270 // Call methods widget-style 1271 if (typeof options === 'string') { 1272 ret = []; 1273 args = slice.call(arguments, 1); 1274 this.each(function() { 1275 instance = $.data(this, datakey); 1276 1277 if (!instance) { 1278 ret.push(undefined); 1279 1280 // Ignore methods beginning with `_` 1281 } else if (options.charAt(0) !== '_' && 1282 typeof (m = instance[ options ]) === 'function' && 1283 // If nothing is returned, do not add to return values 1284 (m = m.apply(instance, args)) !== undefined) { 1285 1286 ret.push(m); 1287 } 1288 }); 1289 1290 // Return an array of values for the jQuery instances 1291 // Or the value itself if there is only one 1292 // Or keep chaining 1293 return ret.length ? 1294 (ret.length === 1 ? ret[0] : ret) : 1295 this; 1296 } 1297 1298 return this.each(function() { new Panzoom(this, options); }); 1299 }; 1300 1301 return Panzoom; 1302}));
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.