��diff --git a/.gitignore b/.gitignore index 88f6afd..460e5a3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules/ **/*.orig .DS_Store npm-debug.log +dist/*.* +dist/svg-pan-zoom.js diff --git a/demo/inline.html b/demo/inline.html index 190a40a..63446b4 100644 --- a/demo/inline.html +++ b/demo/inline.html @@ -735,7 +735,9 @@ </div> <button id="enable">enable</button> <button id="disable">disable</button> - + <button id="rotate_left">rotate -10</button> + <button id="rotate_right">rotate +5</button> + <button id="rotate_reset">reset rotation</button> <script> // Don't use window.onLoad like this in production, because it can only listen to one function. window.onload = function() { @@ -754,6 +756,17 @@ document.getElementById('disable').addEventListener('click', function() { window.zoomTiger.disableControlIcons(); }) + document.getElementById('rotate_left').addEventListener('click', function() { + var currentAngle = window.zoomTiger.getCenterRotationAngle() + window.zoomTiger.setCenterRotationAngle(currentAngle - 10); + }) + document.getElementById('rotate_right').addEventListener('click', function() { + var currentAngle = window.zoomTiger.getCenterRotationAngle() + window.zoomTiger.setCenterRotationAngle(currentAngle + 5); + }) + document.getElementById('rotate_reset').addEventListener('click', function() { + window.zoomTiger.resetRotation(); + }) }; </script> diff --git a/demo/simple-animation.html b/demo/simple-animation.html index 7e9d69d..073356c 100644 --- a/demo/simple-animation.html +++ b/demo/simple-animation.html @@ -35,6 +35,7 @@ fit: true, center: true, minZoom: 0.1 + }); // Zoom out @@ -52,7 +53,7 @@ intervalID = setInterval(function(){ if (animationStep++ < animationSteps) { panZoomInstance.panBy({x: stepX, y: stepY}); - panZoomInstance.rotate(panZoomInstance.getRotate()+5); + panZoomInstance.setCenterRotationAngle(panZoomInstance.getCenterRotationAngle()+5); } else { // Cancel interval diff --git a/dist/svg-pan-zoom.js b/dist/svg-pan-zoom.js index bf6c982..54005bc 100644 --- a/dist/svg-pan-zoom.js +++ b/dist/svg-pan-zoom.js @@ -221,8 +221,8 @@ ShadowViewport.prototype.init = function(viewport, options) { this.options = options; // State cache - this.originalState = { zoom: 1, x: 0, y: 0, rotate:0 }; - this.activeState = { zoom: 1, x: 0, y: 0, rotate:0 }; + this.originalState = { zoom: 1, x: 0, y: 0, angle: 0 }; + this.activeState = { zoom: 1, x: 0, y: 0, angle: 0 }; this.updateCTMCached = Utils.proxy(this.updateCTM, this); @@ -411,36 +411,42 @@ ShadowViewport.prototype.getPan = function() { return { x: this.activeState.x, y: this.activeState.y }; }; /** - * Get rotate + * Get rotation angle * * @return {Float} angle */ -ShadowViewport.prototype.getRotate = function() { - return this.activeState.rotate -} +ShadowViewport.prototype.getAngle = function() { + return this.activeState.angle; +}; /** - * Get rotate transformation + * Get rotation * * @return {Object} angle and point of rotation */ -ShadowViewport.prototype.getRotateTransform = function() { +ShadowViewport.prototype.getRotation = function() { + // console.log("a", this.getAngle()); + if (this.getAngle() === 0) { + return null; + } + return { - angle: this.getRotate(), + angle: this.getAngle(), x: this.getViewBox().width / 2, y: this.getViewBox().height / 2 - } -} + }; +}; /** - * Set rotate + * Set angle * * @param {Float} angle */ -ShadowViewport.prototype.rotate = function(angle) { - this.activeState.rotate = angle; - this.updateCTMOnNextFrame() -} +ShadowViewport.prototype.setAngle = function(angle) { + this.activeState.angle = angle; + this.updateCTMOnNextFrame(); + // console.log("new angle", angle, this.activeState); +}; /** * Return cached viewport CTM value that can be safely modified @@ -583,9 +589,11 @@ ShadowViewport.prototype.updateCTMOnNextFrame = function() { */ ShadowViewport.prototype.updateCTM = function() { var ctm = this.getCTM(); + var rotation = this.getRotation(); + // console.log("updateCTM", ctm, rotation); // Updates SVG element - SvgUtils.setCTM(this.viewport, ctm, this.defs, this.getRotateTransform()); + SvgUtils.setCTM(this.viewport, ctm, this.defs, rotation); // Free the lock this.pendingUpdate = false; @@ -651,7 +659,7 @@ var optionsDefaults = { beforePan: null, onPan: null, beforeRotate: null, - onRotate:null, + onRotate: null, customEventsHandler: null, eventsListenerElement: null, onUpdatedCTM: null @@ -1025,27 +1033,18 @@ SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) { * * @param {Float} angle */ -SvgPanZoom.prototype.rotate = function(angle) { - this.viewport.rotate(angle) -} - -/** - * Rotate relative - * - * @param {Float} relative angle - */ -SvgPanZoom.prototype.rotateRelative = function(angle) { - this.rotate(this.getRotate() + angle) -} +SvgPanZoom.prototype.setAngle = function(angle) { + this.viewport.setAngle(angle); +}; /** * Get rotate for public usage * * @return {Float} rotate */ -SvgPanZoom.prototype.getRotate = function() { - return this.viewport.getRotate() -} +SvgPanZoom.prototype.getAngle = function() { + return this.viewport.getAngle(); +}; /** * Get zoom scale @@ -1091,18 +1090,18 @@ SvgPanZoom.prototype.resetPan = function() { this.pan(this.viewport.getOriginalState()); }; /** - * Set rotate to initial state + * Set rotation to initial state */ -SvgPanZoom.prototype.resetRotate = function() { - this.rotate(this.viewport.getOriginalState().rotate); -} +SvgPanZoom.prototype.resetRotation = function() { + this.setAngle(this.viewport.getOriginalState().angle); +}; /** * Set pan and zoom to initial state */ SvgPanZoom.prototype.reset = function() { this.resetZoom(); this.resetPan(); - this.resetRotate(); + this.resetRotation(); }; /** @@ -1550,16 +1549,14 @@ SvgPanZoom.prototype.getPublicInstance = function() { }, getZoom: function() { return that.getRelativeZoom(); - }, - rotate: function(angle) { - that.rotate(angle); return that.pi - }, - rotateRelative: function(angle) { - that.rotateRelative(angle); return that.pi - }, - getRotate: function() { - return that.getRotate() - }, + }, + setCenterRotationAngle: function(angle) { + that.setAngle(angle); + return that.pi; + }, + getCenterRotationAngle: function() { + return that.getAngle(); + }, // CTM update setOnUpdatedCTM: function(fn) { that.options.onUpdatedCTM = @@ -1575,8 +1572,9 @@ SvgPanZoom.prototype.getPublicInstance = function() { that.resetPan(); return that.pi; }, - resetRotate: function() { - that.resetPan(); return that.pi + resetRotation: function() { + that.resetRotation(); + return that.pi; }, reset: function() { that.reset(); @@ -1609,8 +1607,8 @@ SvgPanZoom.prototype.getPublicInstance = function() { width: that.width, height: that.height, realZoom: that.getZoom(), - viewBox: that.viewport.getViewBox(), - rotate: that.getRotate() + angle: that.getAngle(), + viewBox: that.viewport.getViewBox() }; }, // Destroy @@ -1810,6 +1808,19 @@ module.exports = { this ? this.internetExplorerRedisplayInterval : null ), + getMatrix: function(m, rotation){ + if(rotation == null) + return m; + var deg2radians = Math.PI * 2 / 360; + return{ + a : m.a * Math.cos(rotation.angle * deg2radians), + b : m.a * Math.sin(rotation.angle * deg2radians), + c : m.d * Math.sin(-rotation.angle * deg2radians), + d : m.d * Math.cos(-rotation.angle * deg2radians), + e : m.e, + f : m.f + }; + }, /** * Sets the current transform matrix of an element * @@ -1817,7 +1828,10 @@ module.exports = { * @param {SVGMatrix} matrix CTM * @param {SVGElement} defs */ - setCTM: function(element, matrix, defs, rotate) { + setCTM: function(element, matrix, defs, rotation) { + + + var rotatedMatrix = this.getMatrix(matrix, rotation); var that = this, s = "matrix(" + @@ -1832,20 +1846,25 @@ module.exports = { matrix.e + "," + matrix.f + - ")"; - if(rotate != 0){ - s += ' rotate(' + rotate.angle + ',' + rotate.x + ',' + rotate.y + ')'; - } - - element.setAttributeNS(null, "transform", s); + ")", + cssTransform = s; + var svgString ="scale("+matrix.a+" "+matrix.d+") translate("+matrix.e+" "+matrix.f+")"; + cssTransform = "scaleX("+matrix.a+") scaleY("+matrix.d+") translate("+matrix.e+"px,"+matrix.f+"px)"; + if(rotation != null){ + svgString += " rotate("+rotation.angle+" "+rotation.x+" "+rotation.y+")"; + cssTransform += " rotate("+rotation.angle+"deg)"; + } + // var composedString = "scale("+1+","+1+") translateX("+matrix.e+") translateY("+matrix.f+");"; + element.setAttributeNS(null, "transform", svgString); + if ("transform" in element.style) { - element.style.transform = s; + element.style.transform = cssTransform; } else if ("-ms-transform" in element.style) { - element.style["-ms-transform"] = s; + element.style["-ms-transform"] = cssTransform; } else if ("-webkit-transform" in element.style) { - element.style["-webkit-transform"] = s; + element.style["-webkit-transform"] = cssTransform; } - + // IE has a bug that makes markers disappear on zoom (when the matrix "a" and/or "d" elements change) // see http://stackoverflow.com/questions/17654578/svg-marker-does-not-work-in-ie9-10 // and http://srndolha.wordpress.com/2013/11/25/svg-line-markers-may-disappear-in-internet-explorer-11/ @@ -1860,7 +1879,6 @@ module.exports = { }, that.internetExplorerRedisplayInterval); } }, - /** * Instantiate an SVGPoint object with given event coordinates * diff --git a/gulpfile.js b/gulpfile.js index c0e3321..53551ef 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -27,27 +27,33 @@ function isFixed(file) { * Build script */ function compile() { - return browserify({ entries: "./src/stand-alone.js" }) - .bundle() - .on("error", function(err) { - console.log(err.toString()); - this.emit("end"); - }) - .pipe(source("svg-pan-zoom.js")) - .pipe(buffer()) - .pipe(header(banner, { pkg: pkg })) - .pipe(gulp.dest("./dist/")) - .pipe(rename("svg-pan-zoom.min.js")) - .pipe(uglify()) - .pipe(header(banner, { pkg: pkg })) - .pipe(gulp.dest("./dist/")); + return ( + browserify({ entries: "./src/stand-alone.js" }) + .bundle() + .on("error", function(err) { + console.log(err.toString()); + this.emit("end"); + }) + .pipe(source("svg-pan-zoom.js")) + .pipe(buffer()) + .pipe(header(banner, { pkg: pkg })) + .pipe(gulp.dest("./dist/")) + .pipe(rename("svg-pan-zoom.min.js")) + .pipe(uglify()) + // Catch minification errors + // .on("error", function(err) { + // console.log(err.toString()); + // this.emit("end"); + // }) + .pipe(header(banner, { pkg: pkg })) + ); } /** * Watch script */ function watch() { - return gulp.watch("./src/**/*.js", gulp.series("compile")); + return gulp.watch("./src/**/*.js", gulp.series([compile])); } /** diff --git a/src/shadow-viewport.js b/src/shadow-viewport.js index c723a8f..0a1db2d 100644 --- a/src/shadow-viewport.js +++ b/src/shadow-viewport.js @@ -17,8 +17,8 @@ ShadowViewport.prototype.init = function(viewport, options) { this.options = options; // State cache - this.originalState = { zoom: 1, x: 0, y: 0, rotate:0 }; - this.activeState = { zoom: 1, x: 0, y: 0, rotate:0 }; + this.originalState = { zoom: 1, x: 0, y: 0, angle: 0 }; + this.activeState = { zoom: 1, x: 0, y: 0, angle: 0 }; this.updateCTMCached = Utils.proxy(this.updateCTM, this); @@ -207,36 +207,42 @@ ShadowViewport.prototype.getPan = function() { return { x: this.activeState.x, y: this.activeState.y }; }; /** - * Get rotate + * Get rotation angle * * @return {Float} angle */ -ShadowViewport.prototype.getRotate = function() { - return this.activeState.rotate -} +ShadowViewport.prototype.getAngle = function() { + return this.activeState.angle; +}; /** - * Get rotate transformation + * Get rotation * * @return {Object} angle and point of rotation */ -ShadowViewport.prototype.getRotateTransform = function() { +ShadowViewport.prototype.getRotation = function() { + // console.log("a", this.getAngle()); + if (this.getAngle() === 0) { + return null; + } + return { - angle: this.getRotate(), + angle: this.getAngle(), x: this.getViewBox().width / 2, y: this.getViewBox().height / 2 - } -} + }; +}; /** - * Set rotate + * Set angle * * @param {Float} angle */ -ShadowViewport.prototype.rotate = function(angle) { - this.activeState.rotate = angle; - this.updateCTMOnNextFrame() -} +ShadowViewport.prototype.setAngle = function(angle) { + this.activeState.angle = angle; + this.updateCTMOnNextFrame(); + // console.log("new angle", angle, this.activeState); +}; /** * Return cached viewport CTM value that can be safely modified @@ -379,9 +385,11 @@ ShadowViewport.prototype.updateCTMOnNextFrame = function() { */ ShadowViewport.prototype.updateCTM = function() { var ctm = this.getCTM(); + var rotation = this.getRotation(); + // console.log("updateCTM", ctm, rotation); // Updates SVG element - SvgUtils.setCTM(this.viewport, ctm, this.defs, this.getRotateTransform()); + SvgUtils.setCTM(this.viewport, ctm, this.defs, rotation); // Free the lock this.pendingUpdate = false; diff --git a/src/svg-pan-zoom.js b/src/svg-pan-zoom.js index a846e45..7bfa3bb 100644 --- a/src/svg-pan-zoom.js +++ b/src/svg-pan-zoom.js @@ -28,7 +28,7 @@ var optionsDefaults = { beforePan: null, onPan: null, beforeRotate: null, - onRotate:null, + onRotate: null, customEventsHandler: null, eventsListenerElement: null, onUpdatedCTM: null @@ -402,27 +402,18 @@ SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) { * * @param {Float} angle */ -SvgPanZoom.prototype.rotate = function(angle) { - this.viewport.rotate(angle) -} - -/** - * Rotate relative - * - * @param {Float} relative angle - */ -SvgPanZoom.prototype.rotateRelative = function(angle) { - this.rotate(this.getRotate() + angle) -} +SvgPanZoom.prototype.setAngle = function(angle) { + this.viewport.setAngle(angle); +}; /** * Get rotate for public usage * * @return {Float} rotate */ -SvgPanZoom.prototype.getRotate = function() { - return this.viewport.getRotate() -} +SvgPanZoom.prototype.getAngle = function() { + return this.viewport.getAngle(); +}; /** * Get zoom scale @@ -468,18 +459,18 @@ SvgPanZoom.prototype.resetPan = function() { this.pan(this.viewport.getOriginalState()); }; /** - * Set rotate to initial state + * Set rotation to initial state */ -SvgPanZoom.prototype.resetRotate = function() { - this.rotate(this.viewport.getOriginalState().rotate); -} +SvgPanZoom.prototype.resetRotation = function() { + this.setAngle(this.viewport.getOriginalState().angle); +}; /** * Set pan and zoom to initial state */ SvgPanZoom.prototype.reset = function() { this.resetZoom(); this.resetPan(); - this.resetRotate(); + this.resetRotation(); }; /** @@ -927,16 +918,14 @@ SvgPanZoom.prototype.getPublicInstance = function() { }, getZoom: function() { return that.getRelativeZoom(); - }, - rotate: function(angle) { - that.rotate(angle); return that.pi - }, - rotateRelative: function(angle) { - that.rotateRelative(angle); return that.pi - }, - getRotate: function() { - return that.getRotate() - }, + }, + setCenterRotationAngle: function(angle) { + that.setAngle(angle); + return that.pi; + }, + getCenterRotationAngle: function() { + return that.getAngle(); + }, // CTM update setOnUpdatedCTM: function(fn) { that.options.onUpdatedCTM = @@ -952,8 +941,9 @@ SvgPanZoom.prototype.getPublicInstance = function() { that.resetPan(); return that.pi; }, - resetRotate: function() { - that.resetPan(); return that.pi + resetRotation: function() { + that.resetRotation(); + return that.pi; }, reset: function() { that.reset(); @@ -986,8 +976,8 @@ SvgPanZoom.prototype.getPublicInstance = function() { width: that.width, height: that.height, realZoom: that.getZoom(), - viewBox: that.viewport.getViewBox(), - rotate: that.getRotate() + angle: that.getAngle(), + viewBox: that.viewport.getViewBox() }; }, // Destroy diff --git a/src/svg-utilities.js b/src/svg-utilities.js index 8a1daf8..506ddde 100644 --- a/src/svg-utilities.js +++ b/src/svg-utilities.js @@ -147,6 +147,19 @@ module.exports = { this ? this.internetExplorerRedisplayInterval : null ), + getMatrix: function(m, rotation){ + if(rotation == null) + return m; + var deg2radians = Math.PI * 2 / 360; + return{ + a : m.a * Math.cos(rotation.angle * deg2radians), + b : m.a * Math.sin(rotation.angle * deg2radians), + c : m.d * Math.sin(-rotation.angle * deg2radians), + d : m.d * Math.cos(-rotation.angle * deg2radians), + e : m.e, + f : m.f + }; + }, /** * Sets the current transform matrix of an element * @@ -154,7 +167,10 @@ module.exports = { * @param {SVGMatrix} matrix CTM * @param {SVGElement} defs */ - setCTM: function(element, matrix, defs, rotate) { + setCTM: function(element, matrix, defs, rotation) { + + + var rotatedMatrix = this.getMatrix(matrix, rotation); var that = this, s = "matrix(" + @@ -169,20 +185,25 @@ module.exports = { matrix.e + "," + matrix.f + - ")"; - if(rotate != 0){ - s += ' rotate(' + rotate.angle + ',' + rotate.x + ',' + rotate.y + ')'; - } - - element.setAttributeNS(null, "transform", s); + ")", + cssTransform = s; + var svgString ="scale("+matrix.a+" "+matrix.d+") translate("+matrix.e+" "+matrix.f+")"; + cssTransform = "scaleX("+matrix.a+") scaleY("+matrix.d+") translate("+matrix.e+"px,"+matrix.f+"px)"; + if(rotation != null){ + svgString += " rotate("+rotation.angle+" "+rotation.x+" "+rotation.y+")"; + cssTransform += " rotate("+rotation.angle+"deg)"; + } + // var composedString = "scale("+1+","+1+") translateX("+matrix.e+") translateY("+matrix.f+");"; + element.setAttributeNS(null, "transform", svgString); + if ("transform" in element.style) { - element.style.transform = s; + element.style.transform = cssTransform; } else if ("-ms-transform" in element.style) { - element.style["-ms-transform"] = s; + element.style["-ms-transform"] = cssTransform; } else if ("-webkit-transform" in element.style) { - element.style["-webkit-transform"] = s; + element.style["-webkit-transform"] = cssTransform; } - + // IE has a bug that makes markers disappear on zoom (when the matrix "a" and/or "d" elements change) // see http://stackoverflow.com/questions/17654578/svg-marker-does-not-work-in-ie9-10 // and http://srndolha.wordpress.com/2013/11/25/svg-line-markers-may-disappear-in-internet-explorer-11/ @@ -197,7 +218,6 @@ module.exports = { }, that.internetExplorerRedisplayInterval); } }, - /** * Instantiate an SVGPoint object with given event coordinates * diff --git a/tests/test_api.js b/tests/test_api.js index 446424a..666129d 100644 --- a/tests/test_api.js +++ b/tests/test_api.js @@ -622,43 +622,42 @@ test("reset (zoom and pan)", function() { }); /** - * Rotate + * Rotate */ -test("rotate", function(){ +test("rotate", function() { expect(1); instance = initSvgPanZoom(); - instance.rotate(45); - deepEqual(instance.getRotate(), 45); + instance.setCenterRotationAngle(45); + deepEqual(instance.getCenterRotationAngle(), 45); }); -test("rotateRelative", function(){ +test("rotateRelative", function() { expect(2); instance = initSvgPanZoom(); - instance.rotate(45); - deepEqual(instance.getRotate(), 45); - instance.rotateRelative(45); - deepEqual(instance.getRotate(), 90); - + instance.setCenterRotationAngle(45); + deepEqual(instance.getCenterRotationAngle(), 45); + instance.setCenterRotationAngle(90); + deepEqual(instance.getCenterRotationAngle(), 90); }); -test("resetRotate", function(){ +test("resetRotation", function() { expect(2); instance = initSvgPanZoom(); - var initialRotate = instance.getRotate(); - instance.rotate(45); - deepEqual(instance.getRotate(), 45); - instance.resetRotate(); - deepEqual(instance.getRotate(), 45);//need test where resetRotate is tested before original object is adjusted... it still weird that the normal reset does work... + var initialRotate = instance.getCenterRotationAngle(); + instance.setCenterRotationAngle(45); + deepEqual(instance.getCenterRotationAngle(), 45); + instance.resetRotation(); + deepEqual(instance.getCenterRotationAngle(), 0); }); -test("global reset", function(){ +test("global reset", function() { expect(1); instance = initSvgPanZoom(); - var initialRotate = instance.getRotate(); - instance.rotate(45); + var initialRotate = instance.getCenterRotationAngle(); + instance.setCenterRotationAngle(45); instance.reset(); - deepEqual(instance.getRotate(), initialRotate); + deepEqual(instance.getCenterRotationAngle(), initialRotate); }); /**