Skip to content

Commit

Permalink
Allow canceling pan and zoom from beforePan and beforeZoom. Closes #37
Browse files Browse the repository at this point in the history
  • Loading branch information
bumbu committed Nov 25, 2014
1 parent caac7be commit b1aa594
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 13 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,24 @@ If any arguments are specified, they must have the following value types:
* 'onZoom' must be a callback function to be called when zoom changes.
* 'beforePan' must be a callback function to be called before pan changes.
* 'onPan' must be a callback function to be called when pan changes.
* 'customEventsHandler' must be a object with init and destroy arguments as functions.
* 'customEventsHandler' must be a object with `init` and `destroy` arguments as functions.

`beforeZoom` and `onZoom` callbacks will be called with a float attribute. The attribute will be equal to current zoom scale of the viewport.

`beforePan` and `onPan` callbacks will be called with an object attribute. The object will have two attributes (x and y) each representing current pan (on X and Y axes) of the viewport.
If `beforeZoom` will return `false` then zooming will be halted.

`beforePan` will be called with 2 attributes:
* `oldPan`
* `newPan`

Each of this objects has two attributes (x and y) representing current pan (on X and Y axes).

If `beforePan` will return `false` or an object `{x: true, y: true}` then panning will be halted.
If you want to prevent panning only on one axis then return a object of type `{x: true, y: false}`.

`onPan` callback will be called with one attribute: `newPan`.

> *Caution!* Calling zoom or pan API methods form inside of `beforeZoom`, `onZoom`, `beforePan` and `onPan` callbacks may lead to infinite loop.
`panEnabled` and `zoomEnabled` are related only to user interaction. If any of this options are disabled - you still can zoom and pan via API.

Expand Down
88 changes: 88 additions & 0 deletions demo/limit-pan.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<script src="../dist/svg-pan-zoom.js"></script>
</head>

<body>
<div id="limit-div" style="width: 602px; height: 420px; border:1px solid black; ">
<svg id="limit-svg" xmlns="http://www.w3.org/2000/svg" style="display: inline; width: inherit; min-width: inherit; max-width: inherit; height: inherit; min-height: inherit; max-height: inherit;" version="1.1">
<defs>
<linearGradient id="linear-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:rgb(56,121,217);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(138,192,7);stop-opacity:1" />
</linearGradient>
</defs>
<g fill="none">
<g stroke="#000" fill="#FFF">
<rect x="5" y="5" width="240" height="240" fill="url(#linear-gradient)"/>
<path d="M 5 5 L 245 245 Z"/>
</g>
</g>
</svg>
</div>

<script>
// Don't use window.onLoad like this in production, because it can only listen to one function.
window.onload = function() {
var beforePan

beforePan = function(oldPan, newPan){
var stopHorizontal = false
, stopVertical = false
, gutterWidth = 100
, gutterHeight = 100
// Computed variables
, sizes = this.getSizes()
, leftLimit = -((sizes.viewBox.x + sizes.viewBox.width) * sizes.realZoom) + gutterWidth
, rightLimit = sizes.width - gutterWidth - (sizes.viewBox.x * sizes.realZoom)
, topLimit = -((sizes.viewBox.y + sizes.viewBox.height) * sizes.realZoom) + gutterHeight
, bottomLimit = sizes.height - gutterHeight - (sizes.viewBox.y * sizes.realZoom)

// If over left limit and panning to the left
if (newPan.x < leftLimit && newPan.x <= oldPan.x) {
stopHorizontal = true
}

// If over right limit and panning to the right
if (newPan.x > rightLimit && newPan.x >= oldPan.x) {
stopHorizontal = true
}

// If over top limit and panning to the top
if (newPan.y < topLimit && newPan.y <= oldPan.y) {
stopVertical = true
}

// If over bottom limit and panning to the bottom
if (newPan.y > bottomLimit && newPan.y >= oldPan.y) {
stopVertical = true
}


if (stopHorizontal && stopVertical) {
// Fully prevent panning
return false
} else if (stopHorizontal || stopVertical) {
// Prevent panning only on X or Y axys
return {x: !stopHorizontal, y: !stopVertical}
}
}

// Expose to window namespace for testing purposes
window.panZoom = svgPanZoom('#limit-svg', {
zoomEnabled: true
, controlIconsEnabled: true
, fit: 1
, center: 1
, beforePan: beforePan
});

// panZoom.setBeforePan(beforePan)
};
</script>

</body>

</html>
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ <h1>Demos for svg-pan-zoom</h1>
<li>
<a href="./demo/mobile.html">Touch events support: pan, double tap, pinch</a>
</li>
<li>
<a href="./demo/limit-pan.html">Limit pan</a>
</li>
</ul>

</body>
Expand Down
58 changes: 47 additions & 11 deletions src/shadow-viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,17 +203,53 @@ ShadowViewport.prototype.setCTM = function(newCTM) {
, willPan = this.isPanDifferent(newCTM)

if (willZoom || willPan) {
// Before callbacks
if (willZoom) {this.options.beforeZoom(this.getRelativeZoom())}
if (willPan) {this.options.beforePan(this.getPan())}

this.updateCache(newCTM)

this.updateCTMOnNextFrame()

// After callbacks
if (willZoom) {this.options.onZoom(this.getRelativeZoom())}
if (willPan) {this.options.onPan(this.getPan())}
// Before zoom
if (willZoom) {
// If returns false then cancel zooming
if (this.options.beforeZoom(this.getRelativeZoom()) === false) {
newCTM.a = newCTM.d = this.activeState.zoom
willZoom = false
}
}

// Before pan
if (willPan) {
var preventPan = this.options.beforePan(this.getPan(), {x: newCTM.e, y: newCTM.f})
// If prevent pan is an object
, preventPanX = Utils.isObject(preventPan) && preventPan.x === false ? true : false
, preventPanY = Utils.isObject(preventPan) && preventPan.y === false ? true : false

// If prevent pan is boolean false
if (preventPan === false) {
preventPanX = preventPanY = true
}

// Prevent panning on X axis
if (preventPanX) {
newCTM.e = this.activeState.x
}

// Prevent panning on Y axis
if (preventPanY) {
newCTM.f = this.activeState.y
}

// Update willPan flag
if (preventPanX && preventPanY) {
willPan = false
}
}

// Check again if should zoom or pan
if (willZoom || willPan) {
this.updateCache(newCTM)

this.updateCTMOnNextFrame()

// After callbacks
if (willZoom) {this.options.onZoom(this.getRelativeZoom())}
if (willPan) {this.options.onPan(this.getPan())}
}
}
}

Expand Down

0 comments on commit b1aa594

Please sign in to comment.