├── .gitignore
├── bower.json
├── package.json
├── README.md
├── svgpan.js
└── tiger.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svgpan",
3 | "description": "Tiny javascript library to add pan, zoom & drag capabilities to SVG",
4 | "main": "svgpan.js",
5 | "authors": [
6 | "Andrea Leofreddi"
7 | ],
8 | "license": "BSD-3-Clause",
9 | "keywords": [
10 | "svg",
11 | "pan",
12 | "zoom",
13 | "drag"
14 | ],
15 | "homepage": "https://github.com/aleofreddi/svgpan",
16 | "ignore": [
17 | "**/.*",
18 | "node_modules",
19 | "bower_components",
20 | "test",
21 | "tests"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svgpan",
3 | "version": "1.2.2",
4 | "description": "Tiny javascript library to add pan, zoom & drag capabilities to SVG",
5 | "main": "svgpan.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/aleofreddi/svgpan.git"
9 | },
10 | "keywords": [
11 | "svg",
12 | "pan",
13 | "zoom",
14 | "drag"
15 | ],
16 | "author": "Andrea Leofreddi",
17 | "license": "BSD-3-Clause",
18 | "bugs": {
19 | "url": "https://github.com/aleofreddi/svgpan/issues"
20 | },
21 | "homepage": "https://github.com/aleofreddi/svgpan#readme",
22 | "dependencies": {
23 | "bower": "^1.8.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # svgpan
2 | Tiny Javascript to add pan, zoom & drag capabilities to SVG.
3 |
4 | Some time ago I had a need to display some vectorial drawings on the web so I've found out that SVG is decently supported by modern browsers (I won’t say anything about Internet Explorer – it’s just unsupported there). The language itself is great, but the interactivity provided by the browsers is definitely low: can display your SVG file on a browser and that's it - you can't navigate it, zoom it or pan :(
5 |
6 | Since I couldn't find anything around, I've decided to write a minimalistic javascript library to provide such interactions, so SVGPan was born!
7 |
8 | Features:
9 |
10 | - Panning (pan à la Google maps) (click on the white background and pan)
11 | - Zooming (using the mouse wheel)
12 | - Element dragging (click on a drawing element and drag it somewhere else)
13 | - Combinations of the above like zooming while dragging
14 |
15 | The library itself is very small and easy to use; and it’s licensed under the BSD license. You can try a [demo](http://www.vleo.net/docs/projects/SVGPan/tiger.svg).
16 |
17 | ## Quickstart
18 | You can install svgpan via [bower](http://bower.io):
19 |
20 | ```
21 | bower install svgpan
22 | ```
23 |
24 | Alternatively just download svgpan.js into your application.
25 |
26 | The library requires a root group to be identified by the id `viewport`, which is where the svg/pan/zoom will be applied. Below an example:
27 |
28 | ```html
29 |
30 |
31 | ...
32 | ```
33 |
--------------------------------------------------------------------------------
/svgpan.js:
--------------------------------------------------------------------------------
1 | /**
2 | * SVGPan library 1.2.2
3 | * ======================
4 | *
5 | * Given an unique existing element with id "viewport" (or when missing, the
6 | * first g-element), including the the library into any SVG adds the following
7 | * capabilities:
8 | *
9 | * - Mouse panning
10 | * - Mouse zooming (using the wheel)
11 | * - Object dragging
12 | *
13 | * You can configure the behaviour of the pan/zoom/drag with the variables
14 | * listed in the CONFIGURATION section of this file.
15 | *
16 | * This code is licensed under the following BSD license:
17 | *
18 | * Copyright 2009-2019 Andrea Leofreddi . All rights reserved.
19 | *
20 | * Redistribution and use in source and binary forms, with or without modification, are
21 | * permitted provided that the following conditions are met:
22 | *
23 | * 1. Redistributions of source code must retain the above copyright
24 | * notice, this list of conditions and the following disclaimer.
25 | * 2. Redistributions in binary form must reproduce the above copyright
26 | * notice, this list of conditions and the following disclaimer in the
27 | * documentation and/or other materials provided with the distribution.
28 | * 3. Neither the name of the copyright holder nor the names of its
29 | * contributors may be used to endorse or promote products derived from
30 | * this software without specific prior written permission.
31 | *
32 | * THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
33 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
34 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR
35 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
36 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
37 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
38 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
39 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
40 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 | *
42 | * The views and conclusions contained in the software and documentation are those of the
43 | * authors and should not be interpreted as representing official policies, either expressed
44 | * or implied, of Andrea Leofreddi.
45 | */
46 |
47 | "use strict";
48 |
49 | /// CONFIGURATION
50 | /// ====>
51 |
52 | var enablePan = 1; // 1 or 0: enable or disable panning (default enabled)
53 | var enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled)
54 | var enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled)
55 | var zoomScale = 0.2; // Zoom sensitivity
56 |
57 | /// <====
58 | /// END OF CONFIGURATION
59 |
60 | var root = document.documentElement;
61 | var state = 'none', svgRoot = null, stateTarget, stateOrigin, stateTf;
62 |
63 | setupHandlers(root);
64 |
65 | /**
66 | * Register handlers
67 | */
68 | function setupHandlers(root){
69 | setAttributes(root, {
70 | "onmouseup" : "handleMouseUp(evt)",
71 | "onmousedown" : "handleMouseDown(evt)",
72 | "onmousemove" : "handleMouseMove(evt)",
73 | //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
74 | });
75 |
76 | if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
77 | window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
78 | else
79 | window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
80 | }
81 |
82 | /**
83 | * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable.
84 | */
85 | function getRoot(root) {
86 | if(svgRoot == null) {
87 | var r = root.getElementById("viewport") ? root.getElementById("viewport") : root.documentElement, t = r;
88 |
89 | while(t != root) {
90 | if(t.getAttribute("viewBox")) {
91 | setCTM(r, t.getCTM());
92 |
93 | t.removeAttribute("viewBox");
94 | }
95 |
96 | t = t.parentNode;
97 | }
98 |
99 | svgRoot = r;
100 | }
101 |
102 | return svgRoot;
103 | }
104 |
105 | /**
106 | * Instance an SVGPoint object with given event coordinates.
107 | */
108 | function getEventPoint(evt) {
109 | var p = root.createSVGPoint();
110 |
111 | p.x = evt.clientX;
112 | p.y = evt.clientY;
113 |
114 | return p;
115 | }
116 |
117 | /**
118 | * Sets the current transform matrix of an element.
119 | */
120 | function setCTM(element, matrix) {
121 | var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
122 |
123 | element.setAttribute("transform", s);
124 | }
125 |
126 | /**
127 | * Dumps a matrix to a string (useful for debug).
128 | */
129 | function dumpMatrix(matrix) {
130 | var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n 0, 0, 1 ]";
131 |
132 | return s;
133 | }
134 |
135 | /**
136 | * Sets attributes of an element.
137 | */
138 | function setAttributes(element, attributes){
139 | for (var i in attributes)
140 | element.setAttributeNS(null, i, attributes[i]);
141 | }
142 |
143 | /**
144 | * Handle mouse wheel event.
145 | */
146 | function handleMouseWheel(evt) {
147 | if(!enableZoom)
148 | return;
149 |
150 | if(evt.preventDefault)
151 | evt.preventDefault();
152 |
153 | evt.returnValue = false;
154 |
155 | var svgDoc = evt.target.ownerDocument;
156 |
157 | var delta;
158 |
159 | if(evt.wheelDelta)
160 | delta = evt.wheelDelta / 360; // Chrome/Safari
161 | else
162 | delta = evt.detail / -9; // Mozilla
163 |
164 | var z = Math.pow(1 + zoomScale, delta);
165 |
166 | var g = getRoot(svgDoc);
167 |
168 | var p = getEventPoint(evt);
169 |
170 | p = p.matrixTransform(g.getCTM().inverse());
171 |
172 | // Compute new scale matrix in current mouse position
173 | var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
174 |
175 | setCTM(g, g.getCTM().multiply(k));
176 |
177 | if(typeof(stateTf) == "undefined")
178 | stateTf = g.getCTM().inverse();
179 |
180 | stateTf = stateTf.multiply(k.inverse());
181 | }
182 |
183 | /**
184 | * Handle mouse move event.
185 | */
186 | function handleMouseMove(evt) {
187 | if(evt.preventDefault)
188 | evt.preventDefault();
189 |
190 | evt.returnValue = false;
191 |
192 | var svgDoc = evt.target.ownerDocument;
193 |
194 | var g = getRoot(svgDoc);
195 |
196 | if(state == 'pan' && enablePan) {
197 | // Pan mode
198 | var p = getEventPoint(evt).matrixTransform(stateTf);
199 |
200 | setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
201 | } else if(state == 'drag' && enableDrag) {
202 | // Drag mode
203 | var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
204 |
205 | setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
206 |
207 | stateOrigin = p;
208 | }
209 | }
210 |
211 | /**
212 | * Handle click event.
213 | */
214 | function handleMouseDown(evt) {
215 | if(evt.preventDefault)
216 | evt.preventDefault();
217 |
218 | evt.returnValue = false;
219 |
220 | var svgDoc = evt.target.ownerDocument;
221 |
222 | var g = getRoot(svgDoc);
223 |
224 | if(
225 | evt.target.tagName == "svg"
226 | || !enableDrag // Pan anyway when drag is disabled and the user clicked on an element
227 | ) {
228 | // Pan mode
229 | state = 'pan';
230 |
231 | stateTf = g.getCTM().inverse();
232 |
233 | stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
234 | } else {
235 | // Drag mode
236 | state = 'drag';
237 |
238 | stateTarget = evt.target;
239 |
240 | stateTf = g.getCTM().inverse();
241 |
242 | stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
243 | }
244 | }
245 |
246 | /**
247 | * Handle mouse button release event.
248 | */
249 | function handleMouseUp(evt) {
250 | if(evt.preventDefault)
251 | evt.preventDefault();
252 |
253 | evt.returnValue = false;
254 |
255 | var svgDoc = evt.target.ownerDocument;
256 |
257 | if(state == 'pan' || state == 'drag') {
258 | // Quit pan mode
259 | state = '';
260 | }
261 | }
262 |
263 |
--------------------------------------------------------------------------------
/tiger.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
728 |
--------------------------------------------------------------------------------