├── README.md ├── example ├── icons │ ├── arrow_down.png │ ├── arrow_left.png │ ├── arrow_out.png │ ├── arrow_right.png │ ├── arrow_up.png │ ├── cross.png │ ├── tick.png │ ├── zoom_in.png │ └── zoom_out.png ├── index.html ├── sample.jpg ├── sample2.jpg ├── sample3.jpg └── style.css ├── jquery-panzoom.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Summary # 2 | 3 | Pan and zoom an image within a parent div using any combination of buttons (anything you can bind a click to), mousewheel and drag/drop. 4 | 5 | Plugin can also read/write the co-ordinates of the image within the parent div to/from a (probably hidden) form field for easy integration into your own projects. 6 | 7 | Live example available at: [http://benlumley.co.uk/dev/panzoom/example/index.html](http://benlumley.co.uk/dev/panzoom/example/index.html) 8 | 9 | # Licence # 10 | 11 | Copyright (c) 2011 Ben Lumley 12 | Examples and documentation at: [https://github.com/benlumley/jQuery-PanZoom](https://github.com/benlumley/jQuery-PanZoom]) 13 | 14 | Dual licensed under the MIT and GPL licenses: 15 | [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) 16 | [http://www.gnu.org/licenses/gpl.html](http://www.gnu.org/licenses/gpl.html) 17 | 18 | # Usage # 19 | 20 | In addition to this documentation, a working example showing all options is available from the github repository linked above. 21 | 22 | Include jquery and the plugin's js files. 23 | 24 | 25 | 26 | 27 | If you want to enable the mousewheel setting you need jQuery tools' mousewheel tool, and draggable needs jQuery UI: 28 | 29 | 30 | 31 | 32 | First, you need a div with a width and height specified, and inside that place the image. This becomes the area within which your image can be panned and zoomed. Create html thats something like this: 33 | 34 |
35 | 36 |
37 | 38 | Then initialise the plugin by calling (best to use $(document).ready() for this, as in the eg): 39 | 40 | $(document).ready(function () { 41 | $('#pan img').panZoom(options); 42 | }); 43 | 44 | You need to pass some options for the plugin to do anything - see below. So a working example might be (see below for an explanation of options): 45 | 46 | $(document).ready(function () { 47 | $('#pan img').panZoom({ 48 | 'zoomIn' : $('#zoomin'), 49 | 'zoomOut' : $('#zoomout'), 50 | 'panUp' : $('#panup'), 51 | 'panDown' : $('#pandown'), 52 | 'panLeft' : $('#panleft'), 53 | 'panRight' : $('#panright'), 54 | 'fit' : $('#fit') 55 | }); 56 | }); 57 | 58 | # A Note On How PanZoom Handles Co Ordinates # 59 | 60 | The plugin stores and outputs the position of the top left corner of the image and the bottom right corner. The top left is x1,y1, the bottom right is x2,y2 and is equivalent to x1+width, y2+height. 61 | 62 | # Options # 63 | 64 | Where appropriate, defaults are given in brackets. 65 | 66 | ### zoomIn, zoomOut, panUp, panDown, panLeft, panRight, 67 | 68 | Pass a (different) jQuery dom node in to each of these options, the relevant action will be bound to the click event of that element. EG: 69 | 70 | { zoomIn: $('#zoomIn') }. 71 | 72 | If you don't pass in at least one of these options, the plugin won't be able to do anything. 73 | 74 | ### fit 75 | 76 | Pass in a jQuery dom node to bind the fit action to. This can be thought of as a reset button, it will center the image within the div and size it as large as possible within the div. 77 | 78 | ### destroy 79 | 80 | Pass in a jQuery dom node to bind the destroy method to - this is documented below, but in short it de-initialises panZoom. 81 | 82 | ### out\_x1, out\_y1, out\_x2, out\_y2 83 | 84 | Pass in jQuery dom nodes for the form elements to read/write the position of the image from. The plugin will initialise using the values in these fields if valid. 85 | 86 | ### zoom\_step, pan\_step (3, 3) 87 | 88 | Percentage of the image's dimensions to zoom/pan at once when the controls are clicked. In short, increase to make the plugin zoom/pan in larger steps, and vice versa 89 | 90 | ### min\_width, min\_height (20, 20) 91 | 92 | The width/height of the image (according to the output co-ordinates) can't be less than the respective value here. 93 | 94 | ### debug 95 | 96 | The plugin will console.log the current co-ordinates. Beware, will break things if console.log isn't available in your browser. 97 | 98 | ### directedit (false) 99 | 100 | Boolean. If true, the form will monitor the form fields passed in to out_x1 etc for changes and will update the image accordingly. Probably best used with form fields that aren't hidden :) 101 | 102 | ### aspect (true) 103 | 104 | boolean, whether the plugin should keep the original aspect ratio (length/width) of the source image. 105 | 106 | ### factor (1) 107 | 108 | Pass a number in here if you wish to have the plugin scale up/down the values used in the form fields relative to the actual pixel offsets of the image within the div. 109 | 110 | ### animate (true) 111 | 112 | Boolean - whether to animate the zoom in/out, pan actions, or just change them. (jQuery's css method or animate method). For animate, see following two options. 113 | 114 | NB: Animate doesn't start until the plugin has initialised the first image properly, as doing so lets the user see the initial image shrinking etc, rather than it appearing instant. 115 | 116 | ### animate\_duration (200) 117 | 118 | If animate\_enabled is true, duration for the animations - passed directly into jQuery's animate method. 119 | 120 | ### animate\_easing (linear) 121 | 122 | If animate\_enabled is true, easing method for the animations - passed directly into jQuery's animate method. 123 | 124 | ### double_click (true) 125 | 126 | Boolean - whether double clicking the image should trigger zoom in. 127 | 128 | ### mousewheel 129 | 130 | Boolean - whether to enable mousewheel to zoom or not. Requires mousewheel from jquery tools - http://flowplayer.org/tools/toolbox/mousewheel.html 131 | 132 | ### mousewheel_delta (1) 133 | 134 | Amount of mousewheel movement thats equivalent to one click of the zoom button. Be warned that this seems to be influenced by browser choice as well as people's settings for their mousewheel in their operating system. 135 | 136 | ### draggable (true) 137 | 138 | Should the image be made "draggable"? Requires jQuery UI - http://jqueryui.com/ 139 | 140 | ### clickandhold (true) 141 | 142 | Should the zoom/pan controls contine to zoom/pan for as long as you click on the control? If false, each click causes one pan/zoom step. 143 | 144 | # Public Methods # 145 | 146 | Methods can all be called via 147 | 148 | $(selector).panZoom('methodname', arg1, arg2) 149 | 150 | Which would call 151 | 152 | methodname(arg1, arg2) 153 | 154 | ### panLeft, panRight, panUp, panDown 155 | 156 | These are the methods used by the event handlers for the relevant controls. You can make manual calls to these or bind them to extra elements to integrate panZoom into more complicated interfaces. 157 | 158 | ### zoomIn( [steps] ), zoomOut( [steps] ) 159 | 160 | Similar to the above, these are the methods used by the event handlers for zoom in/out and can be called directly. 161 | 162 | They take an optional steps argument, which dictates how far will be zoomed in and out. If not provided, a default is used based on the size of the image panZoom is working with and the zoom\_step config value passed when you initialised panZoom. 163 | 164 | Steps should be an object like look like: 165 | 166 | { 167 | zoom: { 168 | x: 10, 169 | y: 10 170 | } 171 | } 172 | 173 | The units represent the amount to increment/decrement the x1,y1,x2,y2 values by. 174 | 175 | ### mouseWheel(delta) 176 | 177 | This is the handler for mousewheel events. It takes one argument, delta, which represents the amount and direction of mouse movement. 178 | 179 | ### fit 180 | 181 | This is used for the reset handler. It will re-center the image in the containing div and will size it as large as possible, respecting the aspect if appropriate (according to the settings). 182 | 183 | ### readPosition 184 | 185 | Forces panZoom to reload co-ordinates from the output form fields. Useful to call after manually setting position, and is bound to the change and blur events if directedit is enabled. 186 | 187 | ### dragComplete 188 | 189 | This is called after a drag event, hence it's name. It reads the co-ordinates of the image from css top, left, width and height and applies them to the output fields. 190 | 191 | ### updatePosition 192 | 193 | Re-applies the internal co-ordinates in the data object to the output form fields and sets the image position accordingly. Useful if you have directly manipulated the current image co-ordinates in the data object to force panZoom to apply the new co-ordinates. 194 | 195 | ### loadImage 196 | 197 | Bound to the main image's change event - possibly of little use aside from this - causes panZoom to re-read the image size, and fit the image if it's changed. 198 | 199 | ### destroy 200 | 201 | Removes the panZoom instance - should restore everything to it's pre-panZoom state by removing bound events and stored data. It will not reset the position of the main image or remove co-ordinates written out to any elements passed in via the out_\* options. 202 | 203 | ### mouseDown(action, [arg1, arg2]) 204 | 205 | This is a wrapper method used to call other methods via setInterval to provide the clickandhold functionality. It takes a method (action) to call - eg: 'panUp' and optional arguments to pass to it. It calls this once and then uses javascript's setInterval to repeatedly call it if clickandhold is enabled. 206 | 207 | ### mouseUp 208 | 209 | Clears the setInterval from mouseDown. Bound to the mouseleave and mouseup events of relevant controls. 210 | 211 | # Tips # 212 | 213 | ## Swapping the image ## 214 | 215 | A load event is bound to the image when the plugin is initialised, so if you swap the image via something like 216 | 217 | $('#pan img').attr('src', '/someimage.png'); 218 | 219 | the plugin will automatically fit the new image to the box once it loads. 220 | 221 | ## Manually setting image position ## 222 | 223 | You can manually change the image position from javascript using something like: 224 | 225 | // retrieve the current position 226 | data = $('#pan img').data('panZoom'); 227 | 228 | // set the four co-ordinates 229 | data.position.x1 = 10; 230 | data.position.y1 = 10; 231 | data.position.x2 = 50; 232 | data.position.y2 = 50; 233 | 234 | // write out the position 235 | $('#pan img').panZoom('updatePosition'); 236 | 237 | (yes, a utility method for this would be nicer/cleaner. Its on the list!) 238 | 239 | You could also do this via the directedit setting and changing the form field values. 240 | 241 | ## Feature Wishlist 242 | 243 | Few extra utility methods 244 | Rotation (supported browsers only) 245 | Draggable snap 246 | Fix diving to corner as you zoom out past limits 247 | Zoom should zoom the image towards the point that is currently at center of viewport - currently it zooms to center of image wherever that may be, so if zoomed in, it effectively pans for you. 248 | Verify destroy 249 | 250 | -------------------------------------------------------------------------------- /example/icons/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/arrow_down.png -------------------------------------------------------------------------------- /example/icons/arrow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/arrow_left.png -------------------------------------------------------------------------------- /example/icons/arrow_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/arrow_out.png -------------------------------------------------------------------------------- /example/icons/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/arrow_right.png -------------------------------------------------------------------------------- /example/icons/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/arrow_up.png -------------------------------------------------------------------------------- /example/icons/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/cross.png -------------------------------------------------------------------------------- /example/icons/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/tick.png -------------------------------------------------------------------------------- /example/icons/zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/zoom_in.png -------------------------------------------------------------------------------- /example/icons/zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/icons/zoom_out.png -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jQuery PanZoom Example - by Ben Lumley 5 | 6 | 7 | 8 | 9 | 10 | 11 | 51 | 52 | 53 | 54 |
55 |

jQuery PanZoom Example

56 | 57 |
58 | benlumley 59 |

60 | jQuery PanZoom by Ben Lumley
61 | @benlumley
62 | http://benlumley.co.uk/
63 | https://github.com/benlumley/jQuery-PanZoom 64 |

65 |
66 | 67 | 68 |
69 | 70 |
71 | 72 |
73 |

Switch Images

74 |

Click an image below to switch the image above to it.

75 | 76 | 77 | 78 |
79 | 80 |
81 |

Controls

82 |

Zoom

83 | 84 |
85 | Zoom In 86 |
87 | 88 |
89 | Zoom Out 90 |
91 | 92 |

Pan

93 | 94 |
95 | Pan Up 96 |
97 | 98 |
99 | Pan Down 100 |
101 | 102 |
103 | Pan Left 104 |
105 | 106 |
107 | Pan Right 108 |
109 | 110 |

Fit/Destroy

111 | 112 |
113 | fit/reset 114 |
115 | 116 |
117 | destroy 118 |
119 | 120 |
121 | re-initialise 122 |
123 | 124 |

Other Controls

125 |

You can also drag the image around or zoom with your mousewheel when hovering. Double click will zoom too.

126 |
127 | 128 |
129 |

Output

130 |

View the current co-ordinates live. You will probably want hidden fields, but whilst they are visible, try changing the numbers.

131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |
x1: x2:
y1: y2:
139 |
140 | 141 |
142 |

Attribution

143 |

Example images from flickr user kevindooley here, here and here, licensed under Creative Commons Attribution 2.0 Generic

144 |

Icons from http://www.famfamfam.com/lab/icons/silk/ 145 |

146 |
147 | Fork me on GitHub 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /example/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/sample.jpg -------------------------------------------------------------------------------- /example/sample2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/sample2.jpg -------------------------------------------------------------------------------- /example/sample3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlumley/jQuery-PanZoom/36c8e6a938049977b27e5a3deb35a38a552e529b/example/sample3.jpg -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 20px; 3 | margin-left: 30px; 4 | color: #222222; 5 | font-size: 14px; 6 | } 7 | 8 | div.wrapper { 9 | width: 820px; 10 | } 11 | 12 | h1 { 13 | font-size: 24px; 14 | } 15 | 16 | h2 { 17 | font-size: 18px; 18 | margin-top: 0; 19 | } 20 | 21 | h3 { 22 | font-size: 16px; 23 | margin-top: 0; 24 | margin-bottom: 5px; 25 | } 26 | 27 | /* if you are reading this, the below is the start of the css for the image and wrapper div for the panner - thats probably what you are interested in */ 28 | 29 | div#pan { 30 | border: #dddddd 1px solid; 31 | width: 400px; 32 | height: 310px; 33 | overflow: hidden; 34 | } 35 | 36 | /* and this is the end */ 37 | 38 | /* this is extra stuff for #pan to get the layout right for the eg - not necessary */ 39 | div#pan { 40 | float: right; 41 | margin-bottom: 20px; 42 | } 43 | 44 | div.me { 45 | font-size: 12px; 46 | width: 400px; 47 | background-color: #eeeeee; 48 | border: #dddddd 1px solid; 49 | margin-bottom: 20px; 50 | } 51 | 52 | div.me img { 53 | float: left; 54 | margin-right: 20px; 55 | } 56 | 57 | div#controls, div#output, div#images { 58 | width: 380px; 59 | border: #dddddd 1px solid; 60 | background-color: #eeeeee; 61 | padding: 10px; 62 | margin-right: 10px; 63 | margin-bottom: 20px; 64 | } 65 | 66 | div#controls { 67 | height: 290px; 68 | } 69 | 70 | div#controls a { 71 | display: inline-block; 72 | text-align: center; 73 | width: 90px; 74 | height: 40px; 75 | font-size: 12px; 76 | color: #0000CC; 77 | text-decoration: none; 78 | } 79 | 80 | div#controls a img { 81 | border: none; 82 | margin-bottom: 5px; 83 | } 84 | 85 | div#controls a:hover { 86 | text-decoration: underline; 87 | } 88 | 89 | div#output, div#images { 90 | height: 170px; 91 | } 92 | 93 | div#images { 94 | float: right; 95 | clear: right; 96 | margin-right: 0; 97 | } 98 | 99 | div#attribution { 100 | 101 | } 102 | 103 | div.clear { 104 | clear: both; 105 | } 106 | -------------------------------------------------------------------------------- /jquery-panzoom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery PanZoom Plugin 3 | * Pan and zoom an image within a parent div. 4 | * 5 | * version: 0.10.0 6 | * @requires jQuery v1.4.2 or later (earlier probably work, but untested so far) 7 | * 8 | * Copyright (c) 2011 Ben Lumley 9 | * Examples and documentation at: https://github.com/benlumley/jQuery-PanZoom 10 | * 11 | * Dual licensed under the MIT and GPL licenses: 12 | * http://www.opensource.org/licenses/mit-license.php 13 | * http://www.gnu.org/licenses/gpl.html 14 | */ 15 | 16 | (function( $ ){ 17 | 18 | $.fn.panZoom = function(method) { 19 | 20 | if ( methods[method] ) { 21 | return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); 22 | } else if ( typeof method === 'object' || ! method ) { 23 | return methods.init.apply( this, arguments ); 24 | } else { 25 | $.error( 'Method ' + method + ' does not exist' ); 26 | } 27 | 28 | }; 29 | 30 | $.fn.panZoom.defaults = { 31 | zoomIn : false, 32 | zoomOut : false, 33 | panUp : false, 34 | panDown : false, 35 | panLeft : false, 36 | panRight : false, 37 | fit : false, 38 | destroy : false, 39 | out_x1 : false, 40 | out_y1 : false, 41 | out_x2 : false, 42 | out_y2 : false, 43 | min_width : 20, 44 | min_height : 20, 45 | zoom_step : 3, 46 | pan_step : 3, 47 | debug : false, 48 | directedit : false, 49 | aspect : true, 50 | factor : 1, 51 | animate : true, 52 | animate_duration : 200, 53 | animate_easing : 'linear', 54 | double_click : true, 55 | mousewheel : true, 56 | mousewheel_delta : 1, 57 | draggable : true, 58 | clickandhold : true 59 | }; 60 | 61 | var settings = {} 62 | 63 | var methods = { 64 | 'init': function (options) { 65 | $.extend(settings, $.fn.panZoom.defaults, options); 66 | setupCSS.apply(this); 67 | setupData.apply(this); 68 | setupBindings.apply(this); 69 | methods.readPosition.apply(this); 70 | }, 71 | 72 | 'destroy': function () { 73 | var data = this.data('panZoom'); 74 | data.bound_elements.unbind('.panZoom'); 75 | if (settings.draggable && typeof(this.draggable) == 'function') { 76 | this.draggable('destroy'); 77 | } 78 | this.removeData('panZoom'); 79 | }, 80 | 81 | 'loadImage': function () { 82 | var data = this.data('panZoom'); 83 | loadTargetDimensions.apply(this); 84 | methods.updatePosition.apply(this); 85 | if (data.last_image != null && data.last_image != this.attr('src')) { 86 | methods.fit.apply(this); 87 | } 88 | data.last_image = this.attr('src'); 89 | data.loaded = true; 90 | }, 91 | 92 | 'readPosition': function () { 93 | var data = this.data('panZoom'); 94 | if (settings.out_x1) { data.position.x1 = settings.out_x1.val()*settings.factor } 95 | if (settings.out_y1) { data.position.y1 = settings.out_y1.val()*settings.factor } 96 | if (settings.out_x2) { data.position.x2 = settings.out_x2.val()*settings.factor } 97 | if (settings.out_y2) { data.position.y2 = settings.out_y2.val()*settings.factor } 98 | methods.updatePosition.apply(this); 99 | }, 100 | 101 | 'updatePosition': function() { 102 | validatePosition.apply(this); 103 | writePosition.apply(this); 104 | applyPosition.apply(this); 105 | }, 106 | 107 | 'fit': function () { 108 | var data = this.data('panZoom'); 109 | data.position.x1 = 0; 110 | data.position.y1 = 0; 111 | data.position.x2 = data.viewport_dimensions.x; 112 | data.position.y2 = data.viewport_dimensions.y; 113 | methods.updatePosition.apply(this); 114 | }, 115 | 116 | 'zoomIn': function (steps) { 117 | var data = this.data('panZoom'); 118 | if (typeof(steps) == 'undefined') { 119 | var steps = getStepDimensions.apply(this); 120 | } 121 | data.position.x1 = data.position.x1*1 - steps.zoom.x; 122 | data.position.x2 = data.position.x2*1 + steps.zoom.x; 123 | data.position.y1 = data.position.y1*1 - steps.zoom.y; 124 | data.position.y2 = data.position.y2*1 + steps.zoom.y; 125 | methods.updatePosition.apply(this); 126 | }, 127 | 128 | 'zoomOut': function (steps) { 129 | var data = this.data('panZoom'); 130 | if (typeof(steps) == 'undefined') { 131 | var steps = getStepDimensions.apply(this); 132 | } 133 | data.position.x1 = data.position.x1*1 + steps.zoom.x; 134 | data.position.x2 = data.position.x2*1 - steps.zoom.x; 135 | data.position.y1 = data.position.y1*1 + steps.zoom.y; 136 | data.position.y2 = data.position.y2*1 - steps.zoom.y; 137 | methods.updatePosition.apply(this); 138 | }, 139 | 140 | 'panUp': function () { 141 | var data = this.data('panZoom'); 142 | var steps = getStepDimensions.apply(this); 143 | data.position.y1 -= steps.pan.y; 144 | data.position.y2 -= steps.pan.y; 145 | methods.updatePosition.apply(this); 146 | }, 147 | 148 | 'panDown': function () { 149 | var data = this.data('panZoom'); 150 | var steps = getStepDimensions.apply(this); 151 | data.position.y1 = data.position.y1*1 + steps.pan.y; 152 | data.position.y2 = data.position.y2*1 + steps.pan.y; 153 | methods.updatePosition.apply(this); 154 | }, 155 | 156 | 'panLeft': function () { 157 | var data = this.data('panZoom'); 158 | var steps = getStepDimensions.apply(this); 159 | data.position.x1 -= steps.pan.x; 160 | data.position.x2 -= steps.pan.x; 161 | methods.updatePosition.apply(this); 162 | }, 163 | 164 | 'panRight': function () { 165 | var data = this.data('panZoom'); 166 | var steps = getStepDimensions.apply(this); 167 | data.position.x1 = data.position.x1*1 + steps.pan.x; 168 | data.position.x2 = data.position.x2*1 + steps.pan.x; 169 | methods.updatePosition.apply(this); 170 | }, 171 | 172 | 'mouseWheel': function (delta) { 173 | // first calculate how much to zoom in/out 174 | var steps = getStepDimensions.apply(this); 175 | steps.zoom.x = steps.zoom.x * (Math.abs(delta) / settings.mousewheel_delta); 176 | steps.zoom.y = steps.zoom.y * (Math.abs(delta) / settings.mousewheel_delta); 177 | 178 | // then do it 179 | if (delta > 0) { 180 | methods.zoomIn.apply(this, [steps]); 181 | } else if (delta < 0) { 182 | methods.zoomOut.apply(this, [steps]); 183 | } 184 | }, 185 | 186 | 'dragComplete': function() { 187 | var data = this.data('panZoom'); 188 | data.position.x1 = this.position().left; 189 | data.position.y1 = this.position().top; 190 | data.position.x2 = this.position().left*1 + this.width(); 191 | data.position.y2 = this.position().top*1 + this.height(); 192 | methods.updatePosition.apply(this); 193 | }, 194 | 195 | 'mouseDown': function (action) { 196 | methods[action].apply(this); 197 | 198 | if (settings.clickandhold) { 199 | var data = this.data('panZoom'); 200 | methods.mouseUp.apply(this); 201 | data.mousedown_interval = window.setInterval(function (that, action) { 202 | that.panZoom(action); 203 | }, settings.animate_duration, this, action); 204 | } 205 | }, 206 | 207 | 'mouseUp': function() { 208 | var data = this.data('panZoom'); 209 | window.clearInterval(data.mousedown_interval); 210 | } 211 | 212 | } 213 | 214 | function setupBindings() { 215 | 216 | eventData = { target: this } 217 | var data = this.data('panZoom'); 218 | 219 | // bind up controls 220 | if (settings.zoomIn) { 221 | settings.zoomIn.bind('mousedown.panZoom', eventData, function(event) { 222 | event.preventDefault(); event.data.target.panZoom('mouseDown', 'zoomIn'); 223 | }).bind('mouseleave.panZoom mouseup.panZoom', eventData, function(event) { 224 | event.preventDefault(); event.data.target.panZoom('mouseUp'); 225 | }).bind('click.panZoom', function (event) { event.preventDefault() } ); 226 | data.bound_elements = data.bound_elements.add(settings.zoomIn); 227 | } 228 | 229 | if (settings.zoomOut) { 230 | settings.zoomOut.bind('mousedown.panZoom', eventData, function(event) { 231 | event.preventDefault(); event.data.target.panZoom('mouseDown', 'zoomOut'); 232 | }).bind('mouseleave.panZoom mouseup.panZoom', eventData, function(event) { 233 | event.preventDefault(); event.data.target.panZoom('mouseUp'); 234 | }).bind('click.panZoom', function (event) { event.preventDefault() } ); 235 | data.bound_elements = data.bound_elements.add(settings.zoomOut); 236 | } 237 | 238 | if (settings.panUp) { 239 | settings.panUp.bind('mousedown.panZoom', eventData, function(event) { 240 | event.preventDefault(); event.data.target.panZoom('mouseDown', 'panUp'); 241 | }).bind('mouseleave.panZoom mouseup.panZoom', eventData, function(event) { 242 | event.preventDefault(); event.data.target.panZoom('mouseUp'); 243 | }).bind('click.panZoom', function (event) { event.preventDefault() } ); 244 | data.bound_elements = data.bound_elements.add(settings.panUp); 245 | } 246 | 247 | if (settings.panDown) { 248 | settings.panDown.bind('mousedown.panZoom', eventData, function(event) { 249 | event.preventDefault(); event.data.target.panZoom('mouseDown', 'panDown'); 250 | }).bind('mouseleave.panZoom mouseup.panZoom', eventData, function(event) { 251 | event.preventDefault(); event.data.target.panZoom('mouseUp'); 252 | }).bind('click.panZoom', function (event) { event.preventDefault() } ); 253 | data.bound_elements = data.bound_elements.add(settings.panDown); 254 | } 255 | 256 | if (settings.panLeft) { 257 | settings.panLeft.bind('mousedown.panZoom', eventData, function(event) { 258 | event.preventDefault(); event.data.target.panZoom('mouseDown', 'panLeft'); 259 | }).bind('mouseleave.panZoom mouseup.panZoom', eventData, function(event) { 260 | event.preventDefault(); event.data.target.panZoom('mouseUp'); 261 | }).bind('click.panZoom', function (event) { event.preventDefault() } ); 262 | data.bound_elements = data.bound_elements.add(settings.panLeft); 263 | } 264 | 265 | if (settings.panRight) { 266 | settings.panRight.bind('mousedown.panZoom', eventData, function(event) { 267 | event.preventDefault(); event.data.target.panZoom('mouseDown', 'panRight'); 268 | }).bind('mouseleave.panZoom mouseup.panZoom', eventData, function(event) { 269 | event.preventDefault(); event.data.target.panZoom('mouseUp'); 270 | }).bind('click.panZoom', function (event) { event.preventDefault() } ); 271 | data.bound_elements = data.bound_elements.add(settings.panRight); 272 | } 273 | 274 | if (settings.fit) { 275 | settings.fit.bind('click.panZoom', eventData, function(event) { event.preventDefault(); event.data.target.panZoom('fit'); } ); 276 | data.bound_elements = data.bound_elements.add(settings.fit); 277 | } 278 | 279 | if (settings.destroy) { 280 | settings.destroy.bind('click.panZoom', eventData, function(event) { event.preventDefault(); event.data.target.panZoom('destroy'); } ); 281 | data.bound_elements = data.bound_elements.add(settings.destroy); 282 | } 283 | 284 | // double click 285 | if (settings.double_click) { 286 | this.bind('dblclick.panZoom', eventData, function(event) { event.data.target.panZoom('zoomIn') } ); 287 | // no need to record in bound elements array - gets done anyway when imageload is bound 288 | } 289 | 290 | // mousewheel 291 | if (settings.mousewheel && typeof(this.mousewheel) == 'function') { 292 | this.parent().bind('mousewheel.panZoom', function(event, delta) { event.preventDefault(); $(this).find('img').panZoom('mouseWheel', delta) } ); 293 | data.bound_elements = data.bound_elements.add(this.parent()); 294 | } else if (settings.mousewheel) { 295 | alert('Mousewheel requires jquery-mousewheel by Brandon Aaron (https://github.com/brandonaaron/jquery-mousewheel) - please include it or disable mousewheel to remove this warning.') 296 | } 297 | 298 | // direct form input 299 | if (settings.directedit) { 300 | $(settings.out_x1).add(settings.out_y1).add(settings.out_x2).add(settings.out_y2).bind('change.panZoom blur.panZoom', eventData, function(event) { event.data.target.panZoom('readPosition') } ); 301 | data.bound_elements = data.bound_elements.add($(settings.out_x1).add(settings.out_y1).add(settings.out_x2).add(settings.out_y2)); 302 | } 303 | 304 | if (settings.draggable && typeof(this.draggable) == 'function') { 305 | this.draggable({ 306 | stop: function () { $(this).panZoom('dragComplete'); } 307 | }); 308 | } else if (settings.draggable) { 309 | alert('Draggable requires jQuery UI - please include jQuery UI or disable draggable to remove this warning.') 310 | } 311 | 312 | // image load 313 | $(this).bind('load.panZoom', eventData, function (event) { event.data.target.panZoom('loadImage') }) 314 | data.bound_elements = data.bound_elements.add(this); 315 | 316 | } 317 | 318 | function setupData() { 319 | this.data('panZoom', { 320 | target_element: this, 321 | target_dimensions: { x: null, y: null }, 322 | viewport_element: this.parent(), 323 | viewport_dimensions: { x: this.parent().width(), y: this.parent().height() }, 324 | position: { x1: null, y1: null, x2: null, y2: null }, 325 | last_image: null, 326 | loaded: false, 327 | mousewheel_delta: 0, 328 | mousedown_interval: false, 329 | bound_elements: $() 330 | }); 331 | if (settings.debug) { 332 | console.log(this.data('panZoom')); 333 | } 334 | } 335 | 336 | function setupCSS() { 337 | if (this.parent().css('position') == 'static') { 338 | this.parent().css('position', 'relative'); 339 | } 340 | this.css({ 341 | 'position': 'absolute', 342 | 'top': 0, 343 | 'left': 0 344 | }); 345 | if (settings.draggable) { 346 | this.css({ 347 | 'cursor': 'move' 348 | }); 349 | } 350 | } 351 | 352 | function validatePosition() { 353 | var data = this.data('panZoom'); 354 | // if dimensions are too small... 355 | if ( data.position.x2 - data.position.x1 < settings.min_width/settings.factor || data.position.y2 - data.position.y1 < settings.min_height/settings.factor ) { 356 | // and second co-ords are zero (IE: no dims set), fit image 357 | if (data.position.x2 == 0 || data.position.y2 == 0) { 358 | methods.fit.apply(this); 359 | } 360 | // otherwise, backout a bit 361 | else { 362 | if (data.position.x2 - data.position.x1 < settings.min_width/settings.factor) { 363 | data.position.x2 = data.position.x1*1+settings.min_width/settings.factor; 364 | } 365 | if (data.position.y2 - data.position.y1 < settings.min_height/settings.factor) { 366 | data.position.y2 = data.position.y1*1+settings.min_height/settings.factor; 367 | } 368 | } 369 | } 370 | 371 | if (settings.aspect) { 372 | target = data.target_dimensions.ratio; 373 | current = getCurrentAspectRatio.apply(this) 374 | if (current > target) { 375 | new_width = getHeight.apply(this) * target; 376 | diff = getWidth.apply(this) - new_width; 377 | data.position.x1 = data.position.x1*1 + (diff/2); 378 | data.position.x2 = data.position.x2*1 - (diff/2); 379 | } else if (current < target) { 380 | new_height = getWidth.apply(this) / target; 381 | diff = getHeight.apply(this) - new_height; 382 | data.position.y1 = data.position.y1*1 + (diff/2); 383 | data.position.y2 = data.position.y2*1 - (diff/2); 384 | } 385 | } 386 | 387 | 388 | } 389 | 390 | function applyPosition() { 391 | var data = this.data('panZoom'); 392 | 393 | width = getWidth.apply(this); 394 | height = getHeight.apply(this); 395 | left_offset = getLeftOffset.apply(this); 396 | top_offset = getTopOffset.apply(this); 397 | 398 | properties = { 399 | 'top': Math.round(top_offset), 400 | 'left': Math.round(left_offset), 401 | 'width': Math.round(width), 402 | 'height': Math.round(height) 403 | } 404 | 405 | if (data.loaded && settings.animate) { 406 | applyAnimate.apply(this, [ properties ]); 407 | } else { 408 | applyCSS.apply(this, [ properties ]); 409 | } 410 | 411 | if (settings.debug) { 412 | console.log('--'); 413 | console.log('width:' + width); 414 | console.log('height:' + height); 415 | console.log('left:' + left_offset); 416 | console.log('top:' + top_offset); 417 | } 418 | } 419 | 420 | function applyCSS() { 421 | this.css( properties ); 422 | } 423 | 424 | function applyAnimate() { 425 | this.stop().animate( properties , settings.animate_duration, settings.animate_easing); 426 | } 427 | 428 | function getWidth() { 429 | var data = this.data('panZoom'); 430 | width = (data.position.x2 - data.position.x1); 431 | return width; 432 | } 433 | 434 | function getLeftOffset() { 435 | var data = this.data('panZoom'); 436 | return data.position.x1; 437 | } 438 | 439 | function getHeight() { 440 | var data = this.data('panZoom'); 441 | height = (data.position.y2 - data.position.y1); 442 | return height; 443 | } 444 | 445 | function getTopOffset() { 446 | var data = this.data('panZoom'); 447 | top_offset = data.position.y1; 448 | return top_offset; 449 | } 450 | 451 | function getCurrentAspectRatio() { 452 | return (getWidth.apply(this) / getHeight.apply(this)); 453 | } 454 | 455 | function writePosition() { 456 | var data = this.data('panZoom'); 457 | if (settings.out_x1) { settings.out_x1.val(Math.round(data.position.x1 / settings.factor)) } 458 | if (settings.out_y1) { settings.out_y1.val(Math.round(data.position.y1 / settings.factor)) } 459 | if (settings.out_x2) { settings.out_x2.val(Math.round(data.position.x2 / settings.factor)) } 460 | if (settings.out_y2) { settings.out_y2.val(Math.round(data.position.y2 / settings.factor)) } 461 | } 462 | 463 | function getStepDimensions() { 464 | var data = this.data('panZoom'); 465 | ret = { 466 | zoom: { 467 | x: (settings.zoom_step/100 * data.viewport_dimensions.x), 468 | y: (settings.zoom_step/100 * data.viewport_dimensions.y) 469 | }, 470 | pan: { 471 | x: (settings.pan_step/100 * data.viewport_dimensions.x), 472 | y: (settings.pan_step/100 * data.viewport_dimensions.y) 473 | } 474 | } 475 | return ret; 476 | } 477 | 478 | function loadTargetDimensions() { 479 | var data = this.data('panZoom'); 480 | var img = document.createElement('img'); 481 | img.src = this.attr('src'); 482 | img.id = "jqpz-temp"; 483 | $('body').append(img); 484 | data.target_dimensions.x = $('#jqpz-temp').width(); 485 | data.target_dimensions.y = $('#jqpz-temp').height(); 486 | $('#jqpz-temp').remove(); 487 | data.target_dimensions.ratio = data.target_dimensions.x / data.target_dimensions.y; 488 | } 489 | 490 | })( jQuery ); 491 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PanZoom", 3 | "version": "0.10.0", 4 | "title": "jQuery.Color()", 5 | "author": { 6 | "name": "Ben Lumley", 7 | "url": "http://benlumley.co.uk" 8 | }, 9 | "licenses": [ 10 | { 11 | "type": "MIT", 12 | "url": "http://www.opensource.org/licenses/mit-license.php" 13 | }, 14 | { 15 | "type": "GPL", 16 | "url": "http://www.gnu.org/licenses/gpl.html" 17 | } 18 | ], 19 | "dependencies": { 20 | "jquery": ">1.4" 21 | }, 22 | "description": "Pan and zoom an image within a parent div using any combination of buttons (anything you can bind a click to), mousewheel and drag/drop. 23 | 24 | Plugin can also read/write the co-ordinates of the image within the parent div to/from a (probably hidden) form field for easy integration into your own projects. 25 | 26 | Live example available at: http://benlumley.co.uk/dev/panzoom/example/index.html", 27 | "keywords": [ 28 | "pan", 29 | "zoom", 30 | "photo", 31 | "picture", 32 | "panzoom", 33 | "mouse", 34 | "drag", 35 | "widget" 36 | ], 37 | "homepage": "http://benlumley.co.uk/dev/panzoom/example/index.html", 38 | "contributors": [ 39 | { 40 | "name": "Ben Lumley", 41 | "url": "https://github.com/gnarf37" 42 | } 43 | ], 44 | "files": [ 45 | "jquery.panzoom.js" 46 | ] 47 | } 48 | --------------------------------------------------------------------------------