The code below is hightlighted with the aids of only 6 lines of PHP code (thanks to regex)
 
1window.PanZoom = (function(window, undefined) {
2 // default values if not provided
3 var CURSOR = 'zoom-out';
4 var DIMMING = false;
5 var MARGIN_COLOR = '#ffffff';
6 var BORDER_RADIUS = 10;
7 var ZOOM_DURATION = 200;
8 var ZOOM_STEPS = 10;
9 var OPENED_POPUP_WINDOWS = [];
10 var SHADOW = { blur : 15,
11 spread : 5,
12 opacity : 0.5,
13 color : '#000000' };
14
15 /**
16 * Returns an unique random number
17 */
18 function s4 ( ) {
19 return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
20 }
21
22 // returns IE version
23 var ie = (function() {
24 var undef, v = 3, div = document.createElement('div'), all = div.getElementsByTagName('i');
25 while (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->', all[0])
26 ;
27 return v > 4 ? v : undef;
28 }());
29
30 /**
31 * Returns the the r,g,b values coresponding to the specified hex string
32 */
33 function hexToRgb ( hex ) {
34 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
35 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
36 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
37 return r + r + g + g + b + b;
38 });
39
40 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
41 return result ? { r : parseInt(result[1], 16),
42 g : parseInt(result[2], 16),
43 b : parseInt(result[3], 16) } : null;
44 }
45
46 /**
47 * Returns the object when is set, otherwise the provided default value
48 */
49 function isNull ( obj, value ) {
50 return (typeof obj === 'undefined' || obj === null) ? value : obj;
51 }
52 /**
53 * Cross-browser surogate for addEventListener
54 */
55 function _addEventListener ( object, type, listener, useCapture ) {
56 return 'function' == typeof object.addEventListener ? object.addEventListener(type, listener, useCapture) : object.attachEvent(type, listener);
57 }
58 /**
59 * Removes a popup from DOM and from internal stack
60 */
61 function removePopup ( popup ) {
62 if (popup) {
63 var id = popup.id;
64 document.body.removeChild(popup);
65 popup = null;
66 delete OPENED_POPUP_WINDOWS[id];
67 }
68 }
69 /**
70 * Listen ESC globaly and remove the top popup, if any
71 */
72 _addEventListener(document, (ie < 9 ? 'on' : '') + 'keydown', function(ev) {
73 ev = ev || window.event;
74 if (ev.keyCode == 27 && (divId = Object.keys(OPENED_POPUP_WINDOWS).pop()) && (top_div = document.getElementById(divId))) {
75 zoomOut(top_div);
76 if (ev.preventDefault)
77 ev.preventDefault();
78 else
79 ev.returnValue = false;
80 return false;
81 }
82 });
83
84 /**
85 * Calculates and adjust the div position
86 */
87 var popupCalcSize = function(id, hcentered, max_width, max_height) {
88 hcentered = isNull(hcentered, true);
89 var newdiv = document.getElementById(id);
90 if (!newdiv)
91 return false;
92 var WPadminBar = document.getElementById('wpadminbar'), WPadminMenu = document.getElementById('adminmenuback'), newdiv = document.getElementById(id);
93 var WPadminBarHeight = WPadminBar ? WPadminBar.clientHeight : 0, WPadminMenuWidth = WPadminMenu ? WPadminMenu.clientWidth : 0;
94 var style = 'width:100%;';
95 window_height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
96 window_width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
97 if (newdiv.clientHeight > window_height - 100 - WPadminBarHeight) {
98 var h = window_height - WPadminBarHeight - 50;
99 style += 'overflow-y:auto;max-height:' + (h - WPadminBarHeight - 24) + 'px;';
100 newdiv.style.maxHeight = h + 'px';
101 }
102 if (newdiv.clientWidth > window_width - 100 - WPadminMenuWidth) {
103 var w = window_width - 50;
104 style += 'overflow:auto;max-width:' + (w - 10 - WPadminMenuWidth) + 'px;';
105 newdiv.style.maxWidth = w + 'px';
106 }
107
108 if (hcentered) {
109 newdiv.style.left = (window_width - newdiv.clientWidth + WPadminMenuWidth) / 2 + 'px';
110 newdiv.style.top = (window_height - newdiv.clientHeight + WPadminBarHeight) / 2 + 'px';
111 }
112
113 if (style.length > 0)
114 newdiv.innerHTML = newdiv.innerHTML.replace(/title=(['"]*)@INNER-STYLE@\1/, 'style="' + style + '"');
115 if (null !== isNull(max_width, null))
116 newdiv.style.maxWidth = isNull(max_width + 'px', newdiv.style.maxWidth);
117 if (null !== isNull(max_height, null))
118 newdiv.style.maxHeight = isNull(max_height + 'px', newdiv.style.maxHeight);
119 };
120
121 /**
122 * Creates the zoom panel and the div/image container for the div/image
123 * content from url
124 */
125 function initPanel ( url, caption, callback, width, height, zindex ) {
126 var uuid = s4() + s4() + s4() + s4(), divIdName = 'panZoom_' + uuid, inner_style = '', shadow_color = hexToRgb(PanZoom.SHADOW.color);
127 newdiv = document.createElement('div');
128 newdiv.setAttribute('id', divIdName);
129 if (PanZoom.BORDER_RADIUS)
130 inner_style = 'border-radius:' + PanZoom.BORDER_RADIUS + 'px;';
131 var prefix = '<div style="background-color:' + PanZoom.MARGIN_COLOR + ';box-shadow:' + ('0px 0px ' + SHADOW.blur + 'px ' + SHADOW.spread + 'px ' + 'rgb' + (ie < 9 ? '' : 'a') + '(' + shadow_color.r + ',' + shadow_color.g + ',' + shadow_color.b + (ie < 9 ? '' : ',' + SHADOW.opacity) + ')') + ';' + inner_style + '">';
132 var sufix = '</div>';
133 if (caption)
134 sufix = '<div style="text-align:center"></div>' + sufix;
135 if (url)
136 newdiv.innerHTML = prefix + '<img id="panimg_' + uuid + '" style="' + (PanZoom.CURSOR ? 'cursor:' + PanZoom.CURSOR + ';' : '') + 'border:10px;width:100%;height:100%;' + inner_style + '" src="' + url + '" onclick="PanZoom.zoomOut(this.parentElement.parentElement)">' + sufix;
137 else
138 newdiv.innerHTML = prefix + '<div id="div_' + uuid + '" style="' + (PanZoom.CURSOR ? 'cursor:' + PanZoom.CURSOR + ';' : '') + 'border:10px;width:100%;height:100%;' + inner_style + '">' + sufix + '</div>';
139 newdiv.style.margin = 'auto';
140 newdiv.style.position = 'absolute';
141 newdiv.style.top = 0;
142 newdiv.style.left = 0;
143 newdiv.style.bottom = 0;
144 newdiv.style.right = 0;
145 newdiv.style.width = 0;
146 newdiv.style.height = 0;
147 newdiv.style.color = 'black';
148 newdiv.style.select = 'none';
149 newdiv.style.zIndex = zindex ? zindex : 1000;
150
151 document.body.appendChild(newdiv);
152
153 OPENED_POPUP_WINDOWS[divIdName] = false;
154
155 popupCalcSize(divIdName, false, width, height);
156
157 if (callback) {
158 btn_prev = document.createElement('div');
159 btn_prev.className = 'previmg';
160 btn_prev.onclick = function() {
161 shiftImage(this, callback);
162 };
163 newdiv.firstChild.appendChild(btn_prev);
164
165 btn_next = document.createElement('div');
166 btn_next.className = 'nextimg';
167 btn_next.onclick = function() {
168 shiftImage(this, callback);
169 };
170 newdiv.firstChild.appendChild(btn_next);
171
172 updateNavBtn(url, btn_prev, callback);
173 }
174
175 return newdiv;
176 }
177
178 /**
179 * Update the visibility of gallery's navigation buttons
180 */
181 function updateNavBtn ( src, sender, callback ) {
182 var bname = src.substr(src.lastIndexOf('/') + 1), prev = callback(bname, -1), next = callback(bname, 1);
183 var btn_prev = sender.parentNode.getElementsByClassName('previmg')[0];
184 var btn_next = sender.parentNode.getElementsByClassName('nextimg')[0];
185 btn_prev.style.visibility = prev ? 'visible' : 'hidden';
186 btn_next.style.visibility = next ? 'visible' : 'hidden';
187 }
188
189 /**
190 * Shifts to the left|right the current image in gallery
191 */
192 function shiftImage ( sender, callback ) {
193 if (!sender || !callback)
194 return false;
195
196 var img = sender.parentNode.getElementsByTagName('img')[0];
197 var caption = sender.parentNode.firstChild.nextSibling;
198 if (!img)
199 return false;
200
201 var obj = callback(img.src.substr(img.src.lastIndexOf('/') + 1), 'previmg' == sender.className ? -1 : 1);
202 if (obj && obj.img) {
203 img.src = obj.img;
204 caption.innerHTML = obj.caption;
205 updateNavBtn(obj.img, sender, callback);
206 }
207 return true;
208 }
209
210 /**
211 * Attach a background dimmed panel to the parent
212 */
213 function addBackWall ( parent ) {
214 var uuid = s4() + s4() + s4() + s4(), divIdName = 'bakwall_' + uuid;
215 var newdiv = document.createElement('div');
216
217 parent = isNull(parent, document.body);
218
219 newdiv.setAttribute('id', divIdName);
220 newdiv.style.margin = 0;
221 newdiv.style.padding = 0;
222 newdiv.style.padding = 0;
223 newdiv.style.border = 0;
224 newdiv.style.position = 'fixed';
225 newdiv.style.width = '100%';
226 newdiv.style.height = '100%';
227 newdiv.style.left = 0;
228 newdiv.style.top = 0;
229 newdiv.style.backgroundColor = '#000';
230 newdiv.style.zIndex = -1;
231 newdiv.style.opacity = 0.8;
232
233 parent.appendChild(newdiv);
234
235 return newdiv;
236 }
237 /**
238 * Set some style attributes for the specified style_el
239 */
240 function mangleStyle ( style_el, padding, position, top, left, transform ) {
241 if (!style_el)
242 return;
243 style_el.padding = padding;
244 style_el.position = position;
245 style_el.top = top;
246 style_el.left = left;
247 style_el.transform = transform;
248 }
249 /**
250 * Creates the zoom panel and loads the image from the url
251 *
252 * @param url
253 * The URL for the image
254 * @param caption
255 * The string to be displayed as a caption; specify null to hide
256 * the caption
257 * @param factor
258 * 1 when zoom-in;-1 when zoom out
259 * @param div
260 * The DOM div element to zoom-in/out (especially usefull when
261 * comes to zoom-out)
262 * @param dimming
263 * true when you want the picture to be shielded by a dimmed
264 * background panel, false otherwise
265 * @param callback
266 * A callback function with 2 arguments: pos, offset. Pos is the
267 * basename of the current image; offset is the navigation offset
268 * from the current position (eg. +2 or -1). The callback returns
269 * an object with the basename to the image specified by the
270 * offset and its caption (or null if no caption defined).
271 */
272 var zoomImage = function(url, caption, factor, div, dimming, callback) {
273 factor = isNull(factor, 1);
274 dimming = isNull(dimming, PanZoom.DIMMING);
275 var panzoom_div = div ? div : initPanel(url, caption, callback);
276 var img = document.getElementById(panzoom_div.id).firstChild.firstChild;
277 if (dimming)
278 var wall = addBackWall(panzoom_div);
279
280 var child_style = panzoom_div.firstChild.style;
281
282 // restore its default style
283 if (factor < 0)
284 mangleStyle(child_style, 0, 'absolute', 0, 0, '');
285
286 var img_obj = new Image();
287 img_obj.onload = function() {
288 if (this.complete || this.readyState === 4) {
289 if (caption)
290 panzoom_div.firstChild.firstChild.nextSibling.innerHTML = caption;
291 var steps = PanZoom.ZOOM_STEPS, inc_x = factor * (factor > 0 ? this.width - img.clientWidth : this.width) / steps, inc_y = factor * (factor > 0 ? this.height - img.clientHeight : this.height) / steps;
292 var timer = setInterval(function() {
293 panzoom_div.style.width = (panzoom_div.clientWidth + inc_x) + 'px';
294 panzoom_div.style.height = (panzoom_div.clientHeight + inc_y) + 'px';
295 steps--;
296 if (steps < 1) {
297 clearInterval(timer);
298 if (factor < 0)
299 removePopup(panzoom_div);
300 else
301 // mangle the default style
302 mangleStyle(child_style, '10px', 'fixed', '50%', '50%', 'translateX(-50%) translateY(-50%)');
303 }
304
305 }, PanZoom.ZOOM_DURATION / PanZoom.ZOOM_STEPS);
306 }
307 }
308 img_obj.src = img.src;
309
310 return panzoom_div;
311 }
312
313 /**
314 * Creates a logical image gallery and provides support for navigation
315 *
316 * @param base_url
317 * The absolute|relative path where the images are located.
318 * @param img_list
319 * A comma-delimited list of image files (when base_url is used
320 * only the file basename should be provided)
321 * @param caption_list
322 * A comma-delimited list of captions for each image specified in
323 * img_list
324 * @param factor
325 * 1 when zoom-in;-1 when zoom out
326 * @param div
327 * The DOM div element to zoom-in/out (especially usefull when
328 * comes to zoom-out)
329 * @param dimming
330 * true when you want the picture to be shielded by a dimmed
331 * background panel, false otherwise
332 */
333 var zoomGallery = function(base_url, img_list, caption_list, factor, div, dimming) {
334 if (!img_list || !img_list.length)
335 return;
336 base_url = base_url ? base_url + ('/' != base_url.substr(-1) ? '/' : '') : '';
337 var ilist = img_list.split(','), clist = caption_list ? caption_list.split(',') : [];
338
339 var callback = function(pos, offset) {
340 var i = ilist.indexOf(pos);
341 if ((-1 == i) || (i + offset >= ilist.length || i + offset < 0))
342 return null;
343 return { img : base_url + ilist[i + offset],
344 caption : (i + offset >= clist.length) ? null : clist[i + offset] }
345 };
346
347 pos = ilist[0];
348 caption = clist.length ? clist[0] : null;
349 var pz = zoomImage(base_url + pos, caption, factor, div, dimming, callback);
350
351 }
352
353 /**
354 * Creates the zoom panel and listen for the HTML content
355 *
356 * @param width
357 * The div width
358 * @param height
359 * The div height
360 * @param factor
361 * 1 when zoom-in;-1 when zoom out
362 * @param div
363 * The DOM div element to zoom-in/out (especially usefull when
364 * comes to zoom-out)
365 * @param dimming
366 * true when you want the picture to be shielded by a dimmed
367 * background panel, false otherwise
368 * @param callback
369 * A callback function with one string argument that is called
370 * tight before the div content is filled. It can be used for
371 * instance to sanitize the div HTML content.
372 */
373 var zoomDiv = function(width, height, factor, div, dimming, callback) {
374 factor = isNull(factor, 1);
375 dimming = isNull(dimming, PanZoom.DIMMING);
376 var panzoom_div = div ? div : initPanel();
377 var fc = panzoom_div.firstChild;
378 mangleStyle(fc.style, '10px', 'fixed', '50%', '50%', 'translateX(-50%) translateY(-50%)');
379 var div = document.getElementById(panzoom_div.id).firstChild.firstChild;
380 if (factor > 0) {
381 fc.style.width = width + 'px';
382 fc.style.height = height + 'px';
383 fc.style.transform = 'translateX(-50%) translateY(-50%) scale(0)';
384 if (dimming)
385 var wall = addBackWall(panzoom_div);
386 }
387
388 var zoom_callback = function(xmlhttp) {
389 if (factor < 0 || 'object' != typeof xmlhttp || xmlhttp.readyState === 4) {
390 if (xmlhttp) {
391 var t = 'object' == typeof xmlhttp ? xmlhttp.responseText : xmlhttp;
392 if (callback)
393 t = callback(t);
394 div.innerHTML = t;
395 }
396 var i = 0, scale;
397 var timer = setInterval(function() {
398 scale = (factor < 0 ? 1 : 0) + factor * (i / PanZoom.ZOOM_STEPS);
399 fc.style.transform = 'translateX(-50%) translateY(-50%) scale(' + scale + ')';
400 if (i > PanZoom.ZOOM_STEPS) {
401 clearInterval(timer);
402 if (factor < 0)
403 removePopup(panzoom_div);
404 else
405 // remove the scale transformation
406 mangleStyle(fc.style, '10px', 'fixed', '50%', '50%', 'translateX(-50%) translateY(-50%)');
407 }
408 i++;
409 }, PanZoom.ZOOM_DURATION / PanZoom.ZOOM_STEPS);
410 }
411 }
412
413 // trigger the zoom-out
414 if (factor < 0)
415 return zoom_callback();
416
417 return { div : panzoom_div,
418 callback : zoom_callback };
419 }
420
421 /**
422 * Zoom-out (hides) the picture specified by div
423 */
424 var zoomOut = function(div) {
425 var imgs = div.getElementsByTagName('img'), found = false, i;
426 for (i = 0; i < imgs.length; i += 1)
427 if (0 === imgs[i].id.indexOf('panimg_')) {
428 // by convention if there is an image named panimg_ then we
429 // zoom-out the image else the div
430 found = true;
431 break;
432 }
433 if (found)
434 zoomImage(null, null, -1, div, false);
435 else
436 zoomDiv(null, null, -1, div, false);
437 };
438
439 return { zoomImage : zoomImage,
440 zoomDiv : zoomDiv,
441 zoomOut : zoomOut,
442 zoomGallery : zoomGallery,
443 popupCalcSize : popupCalcSize,
444 DIMMING : DIMMING,
445 MARGIN_COLOR : MARGIN_COLOR,
446 BORDER_RADIUS : BORDER_RADIUS,
447 ZOOM_DURATION : ZOOM_DURATION,
448 ZOOM_STEPS : ZOOM_STEPS,
449 SHADOW : SHADOW,
450 CURSOR : CURSOR };
451})(this);
452