diff --git a/README.md b/README.md index afc2ec03..12e7dbff 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,11 @@ iOS and Android are supported. ## SVG support -Panzoom supports panning and zooming SVG elements directly, in browsers that support SVG. Note that animations do not work on SVG elements, -but one could implement transitions manually by overriding the `setTransform()` method and integrating a tweening library for javascript animations (such as [tween.js](http://www.createjs.com/#!/TweenJS)). +Panzoom supports panning and zooming SVG elements directly, in browsers that support SVG. + +In IE9-11 and Edge 13-14+, CSS animations/transitions do not work on SVG elements, at least for the transform style. They do work in other browsers. + +One could implement transitions manually in those browsers by overriding the `setTransform()` method and integrating a tweening library for javascript animations (such as [tween.js](http://www.createjs.com/#!/TweenJS)). **Compatibility note:** *There is a [known issue with Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=530985) and using the `focal` option. Firefox does not correctly maintain the dimensions of SVG parent elements, which throws off offsets. If using the `focal` option with SVG, the workaround is to set the correct offset on the Panzoom instance manually using `Panzoom.prototype.parentOffset` ([example](http://jsfiddle.net/timmywil/Vu8nA/)).* diff --git a/dist/jquery.panzoom.js b/dist/jquery.panzoom.js index 1516116a..c71f036a 100644 --- a/dist/jquery.panzoom.js +++ b/dist/jquery.panzoom.js @@ -1,6 +1,6 @@ /** * @license jquery.panzoom.js v3.1.1 - * Updated: Tue Jul 19 2016 + * Updated: Tue Jul 26 2016 * Add pan and zoom functionality to any element * Copyright (c) timmy willison * Released under the MIT license @@ -244,7 +244,7 @@ // Build the appropriately-prefixed transform style property name // De-camelcase - this._transform = !this.isSVG && $.cssProps.transform.replace(rupper, '-$1').toLowerCase(); + this._transform = $.cssProps.transform.replace(rupper, '-$1').toLowerCase(); // Build the transition value this._buildTransition(); @@ -439,14 +439,23 @@ /** * Sets a transform on the $set + * For SVG, the style attribute takes precedence + * and allows us to animate * @param {String} transform */ setTransform: function(transform) { - var method = this.isSVG ? (transform === 'none' ? 'removeAttr' : 'attr') : 'style'; var $set = this.$set; var i = $set.length; while(i--) { - $[method]($set[i], 'transform', transform); + $.style($set[i], 'transform', transform); + + // Support IE9-11, Edge 13-14+ + // Set attribute alongside style attribute + // since IE and Edge do not respect style settings on SVG + // See https://css-tricks.com/transforms-on-svg-elements/ + if (this.isSVG) { + $set[i].setAttribute('transform', transform); + } } }, @@ -464,13 +473,21 @@ if (transform) { this.setTransform(transform); } else { - // Retrieve the transform - transform = $[this.isSVG ? 'attr' : 'style'](transformElem, 'transform'); + + // IE and Edge still set the transform style properly + // They just don't render it on SVG + // So we get a correct value here + transform = $.style(transformElem, 'transform'); + + if (this.isSVG && (!transform || transform === 'none')) { + transform = $.attr(transformElem, 'transform') || 'none'; + } } // Convert any transforms set by the user to matrix format // by setting to computed if (transform !== 'none' && !rmatrix.test(transform)) { + // Get computed and set for next time this.setTransform(transform = $.css(transformElem, 'transform')); } @@ -692,11 +709,14 @@ var dims = options.dims = this.dimensions; var clientX = focal.clientX; var clientY = focal.clientY; - // Adjust the focal point for default transform-origin => 50% 50% + + // Adjust the focal point for transform-origin 50% 50% + // SVG elements have a transform origin of 0 0 if (!this.isSVG) { clientX -= (dims.width / startScale) / 2; clientY -= (dims.height / startScale) / 2; } + var clientV = new Vector(clientX, clientY, 1); var surfaceM = new Matrix(matrix); // Supply an offset manually if necessary @@ -860,7 +880,8 @@ */ _initStyle: function() { var styles = { - // Set to defaults for the namespace + // Set the same default whether SVG or HTML + // transform-origin cannot be changed to 50% 50% in IE9-11 or Edge 13-14+ 'transform-origin': this.isSVG ? '0 0' : '50% 50%' }; // Set elem styles @@ -1026,7 +1047,6 @@ /** * Set transition property for later use when zooming - * If SVG, create necessary animations elements for translations and scaling */ _buildTransition: function() { if (this._transform) { diff --git a/dist/jquery.panzoom.min.js b/dist/jquery.panzoom.min.js index 4f002106..09ca5df1 100644 --- a/dist/jquery.panzoom.min.js +++ b/dist/jquery.panzoom.min.js @@ -1,2 +1,2 @@ /* jquery.panzoom.min.js 3.1.1 (c) Timmy Willison - MIT License */ -!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(c){return b(a,c)}):"object"==typeof exports?b(a,require("jquery")):b(a,a.jQuery)}("undefined"!=typeof window?window:this,function(a,b){"use strict";function c(a,b){for(var c=a.length;--c;)if(Math.round(+a[c])!==Math.round(+b[c]))return!1;return!0}function d(a){var c={range:!0,animate:!0};return"boolean"==typeof a?c.animate=a:b.extend(c,a),c}function e(a,c,d,e,f,g,h,i,j){"array"===b.type(a)?this.elements=[+a[0],+a[2],+a[4],+a[1],+a[3],+a[5],0,0,1]:this.elements=[a,c,d,e,f,g,h||0,i||0,j||1]}function f(a,b,c){this.elements=[a,b,c]}function g(a,c){if(!(this instanceof g))return new g(a,c);1!==a.nodeType&&b.error("Panzoom called on non-Element node"),b.contains(h,a)||b.error("Panzoom element must be attached to the document");var d=b.data(a,i);if(d)return d;this.options=c=b.extend({},g.defaults,c),this.elem=a;var e=this.$elem=b(a);this.$set=c.$set&&c.$set.length?c.$set:e,this.$doc=b(a.ownerDocument||h),this.$parent=e.parent(),this.isSVG=n.test(a.namespaceURI)&&"svg"!==a.nodeName.toLowerCase(),this.panning=!1,this._buildTransform(),this._transform=!this.isSVG&&b.cssProps.transform.replace(m,"-$1").toLowerCase(),this._buildTransition(),this.resetDimensions();var f=b(),j=this;b.each(["$zoomIn","$zoomOut","$zoomRange","$reset"],function(a,b){j[b]=c[b]||f}),this.enable(),this.scale=this.getMatrix()[0],this._checkPanWhenZoomed(),b.data(a,i,this)}var h=a.document,i="__pz__",j=Array.prototype.slice,k=/trident\/7./i,l=function(){if(k.test(navigator.userAgent))return!1;var a=h.createElement("input");return a.setAttribute("oninput","return"),"function"==typeof a.oninput}(),m=/([A-Z])/g,n=/^http:[\w\.\/]+svg$/,o="(\\-?\\d[\\d\\.e-]*)",p="\\,?\\s*",q=new RegExp("^matrix\\("+o+p+o+p+o+p+o+p+o+p+o+"\\)$");return e.prototype={x:function(a){var b=a instanceof f,c=this.elements,d=a.elements;return b&&3===d.length?new f(c[0]*d[0]+c[1]*d[1]+c[2]*d[2],c[3]*d[0]+c[4]*d[1]+c[5]*d[2],c[6]*d[0]+c[7]*d[1]+c[8]*d[2]):d.length===c.length&&new e(c[0]*d[0]+c[1]*d[3]+c[2]*d[6],c[0]*d[1]+c[1]*d[4]+c[2]*d[7],c[0]*d[2]+c[1]*d[5]+c[2]*d[8],c[3]*d[0]+c[4]*d[3]+c[5]*d[6],c[3]*d[1]+c[4]*d[4]+c[5]*d[7],c[3]*d[2]+c[4]*d[5]+c[5]*d[8],c[6]*d[0]+c[7]*d[3]+c[8]*d[6],c[6]*d[1]+c[7]*d[4]+c[8]*d[7],c[6]*d[2]+c[7]*d[5]+c[8]*d[8])},inverse:function(){var a=1/this.determinant(),b=this.elements;return new e(a*(b[8]*b[4]-b[7]*b[5]),a*-(b[8]*b[1]-b[7]*b[2]),a*(b[5]*b[1]-b[4]*b[2]),a*-(b[8]*b[3]-b[6]*b[5]),a*(b[8]*b[0]-b[6]*b[2]),a*-(b[5]*b[0]-b[3]*b[2]),a*(b[7]*b[3]-b[6]*b[4]),a*-(b[7]*b[0]-b[6]*b[1]),a*(b[4]*b[0]-b[3]*b[1]))},determinant:function(){var a=this.elements;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])}},f.prototype.e=e.prototype.e=function(a){return this.elements[a]},g.rmatrix=q,g.defaults={eventNamespace:".panzoom",transition:!0,cursor:"move",disablePan:!1,disableZoom:!1,disableXAxis:!1,disableYAxis:!1,which:1,increment:.3,exponential:!0,panOnlyWhenZoomed:!1,minScale:.3,maxScale:6,rangeStep:.05,duration:200,easing:"ease-in-out",contain:!1},g.prototype={constructor:g,instance:function(){return this},enable:function(){this._initStyle(),this._bind(),this.disabled=!1},disable:function(){this.disabled=!0,this._resetStyle(),this._unbind()},isDisabled:function(){return this.disabled},destroy:function(){this.disable(),b.removeData(this.elem,i)},resetDimensions:function(){this.container=this.$parent[0].getBoundingClientRect();var a=this.elem,c=a.getBoundingClientRect(),d=Math.abs(this.scale);this.dimensions={width:c.width,height:c.height,left:b.css(a,"left",!0)||0,top:b.css(a,"top",!0)||0,border:{top:b.css(a,"borderTopWidth",!0)*d||0,bottom:b.css(a,"borderBottomWidth",!0)*d||0,left:b.css(a,"borderLeftWidth",!0)*d||0,right:b.css(a,"borderRightWidth",!0)*d||0},margin:{top:b.css(a,"marginTop",!0)*d||0,left:b.css(a,"marginLeft",!0)*d||0}}},reset:function(a){a=d(a);var b=this.setMatrix(this._origTransform,a);a.silent||this._trigger("reset",b)},resetZoom:function(a){a=d(a);var b=this.getMatrix(this._origTransform);a.dValue=b[3],this.zoom(b[0],a)},resetPan:function(a){var b=this.getMatrix(this._origTransform);this.pan(b[4],b[5],d(a))},setTransform:function(a){for(var c=this.isSVG?"none"===a?"removeAttr":"attr":"style",d=this.$set,e=d.length;e--;)b[c](d[e],"transform",a)},getTransform:function(a){var c=this.$set,d=c[0];return a?this.setTransform(a):a=b[this.isSVG?"attr":"style"](d,"transform"),"none"===a||q.test(a)||this.setTransform(a=b.css(d,"transform")),a||"none"},getMatrix:function(a){var b=q.exec(a||this.getTransform());return b&&b.shift(),b||[1,0,0,1,0,0]},setMatrix:function(a,b){if(!this.disabled){b||(b={}),"string"==typeof a&&(a=this.getMatrix(a));var c=+a[0],d="undefined"!=typeof b.contain?b.contain:this.options.contain;if(d){var e=b.dims;e||(this.resetDimensions(),e=this.dimensions);var f=this.container,g=e.width,h=e.height,i=f.width,j=f.height,k=i/g,l=j/h,m=(g-i)/2,n=(h-j)/2;"invert"===d||"automatic"===d&&k<1.01?a[4]=Math.max(Math.min(a[4],m),-m):a[4]=Math.min(Math.max(a[4],m),-m),"invert"===d||"automatic"===d&&l<1.01?a[5]=Math.max(Math.min(a[5],n),-n):a[5]=Math.min(Math.max(a[5],n),-n)}if("skip"!==b.animate&&this.transition(!b.animate),b.range&&this.$zoomRange.val(c),this.options.disableXAxis||this.options.disableYAxis){var o=this.getMatrix();this.options.disableXAxis&&(a[4]=o[4]),this.options.disableYAxis&&(a[5]=o[5])}return this.setTransform("matrix("+a.join(",")+")"),this.scale=c,this._checkPanWhenZoomed(c),b.silent||this._trigger("change",a),a}},isPanning:function(){return this.panning},transition:function(a){if(this._transition)for(var c=a||!this.options.transition?"none":this._transition,d=this.$set,e=d.length;e--;)b.style(d[e],"transition")!==c&&b.style(d[e],"transition",c)},pan:function(a,b,c){if(!this.options.disablePan){c||(c={});var d=c.matrix;d||(d=this.getMatrix()),c.relative&&(a+=+d[4],b+=+d[5]),d[4]=a,d[5]=b,this.setMatrix(d,c),c.silent||this._trigger("pan",d[4],d[5])}},zoom:function(a,c){"object"==typeof a?(c=a,a=null):c||(c={});var d=b.extend({},this.options,c);if(!d.disableZoom){var g=!1,h=d.matrix||this.getMatrix(),i=+h[0];"number"!=typeof a&&(a=d.exponential&&i-d.increment>=1?Math[a?"sqrt":"pow"](i,2):i+d.increment*(a?-1:1),g=!0),a>d.maxScale?a=d.maxScale:a=1?Math[a?"sqrt":"pow"](i,2):i+d.increment*(a?-1:1),g=!0),a>d.maxScale?a=d.maxScale:a 50% 50% + + // Adjust the focal point for transform-origin 50% 50% + // SVG elements have a transform origin of 0 0 if (!this.isSVG) { clientX -= (dims.width / startScale) / 2; clientY -= (dims.height / startScale) / 2; } + var clientV = new Vector(clientX, clientY, 1); var surfaceM = new Matrix(matrix); // Supply an offset manually if necessary @@ -860,7 +880,8 @@ */ _initStyle: function() { var styles = { - // Set to defaults for the namespace + // Set the same default whether SVG or HTML + // transform-origin cannot be changed to 50% 50% in IE9-11 or Edge 13-14+ 'transform-origin': this.isSVG ? '0 0' : '50% 50%' }; // Set elem styles @@ -1026,7 +1047,6 @@ /** * Set transition property for later use when zooming - * If SVG, create necessary animations elements for translations and scaling */ _buildTransition: function() { if (this._transform) { diff --git a/test/bdd/test.js b/test/bdd/test.js index a01bc2fc..f9d65e42 100644 --- a/test/bdd/test.js +++ b/test/bdd/test.js @@ -478,7 +478,7 @@ describe('Panzoom', function() { var _matrix = panzoom.getMatrix(); panzoom.setMatrix('none'); expect( panzoom.getTransform() ).to.match( rnoneMatrix ); - panzoom.setMatrix( _matrix ); + panzoom.setMatrix(_matrix); expect( panzoom.getMatrix() ).to.eql( _matrix ); }); it('should respect the $set option when getting and setting', function() { @@ -747,7 +747,7 @@ describe('Panzoom', function() { it('should create an SVG panzoom with buttons', function() { var panzoom = $svgElem.panzoom().panzoom('instance'); // isSVG should be false on nodeName svg - expect( panzoom.isSVG ).to.be.false; + expect(panzoom.isSVG).to.be.false; panzoom.destroy(); }); it('should create an SVG panzoom on a rect', function() { @@ -767,11 +767,12 @@ describe('Panzoom', function() { // IE10 will ignore a 'none' setting startTransform: 'matrix(1,0,0,-1,0,0)' }); - var transform = $.attr($rect[0], 'transform').replace(rcommaSpace, ' '); + var transform = $.style($rect[0], 'transform').replace(rcommaSpace, ' '); expect(transform).to.equal('matrix(1 0 0 -1 0 0)'); }); it('should retrieve the transform attribute and use that as the start', function() { - var panzoom = $rect.panzoom('destroy').attr('transform', 'matrix(2,0,0,2,0,0)').panzoom().panzoom('instance'); + $rect.css('transform', '').panzoom('destroy'); + var panzoom = $rect.attr('transform', 'matrix(2,0,0,2,0,0)').panzoom().panzoom('instance'); expect(panzoom._origTransform.replace(rcommaSpace, ' ')).to.equal('matrix(2 0 0 2 0 0)'); }); });