1 | /*! |
---|
2 | Slimbox v1.8 - The ultimate lightweight Lightbox clone |
---|
3 | (c) 2007-2011 Christophe Beyls <http://www.digitalia.be> |
---|
4 | MIT-style license. |
---|
5 | */ |
---|
6 | |
---|
7 | var Slimbox = (function() { |
---|
8 | |
---|
9 | // Global variables, accessible to Slimbox only |
---|
10 | var win = window, ie6 = Browser.ie6, options, images, activeImage = -1, activeURL, prevImage, nextImage, compatibleOverlay, middle, centerWidth, centerHeight, |
---|
11 | |
---|
12 | // Preload images |
---|
13 | preload = {}, preloadPrev = new Image(), preloadNext = new Image(), |
---|
14 | |
---|
15 | // DOM elements |
---|
16 | overlay, center, image, sizer, prevLink, nextLink, bottomContainer, bottom, caption, number, |
---|
17 | |
---|
18 | // Effects |
---|
19 | fxOverlay, fxResize, fxImage, fxBottom; |
---|
20 | |
---|
21 | /* |
---|
22 | Initialization |
---|
23 | */ |
---|
24 | |
---|
25 | win.addEvent("domready", function() { |
---|
26 | // Append the Slimbox HTML code at the bottom of the document |
---|
27 | $(document.body).adopt( |
---|
28 | $$( |
---|
29 | overlay = new Element("div#lbOverlay", {events: {click: close}}), |
---|
30 | center = new Element("div#lbCenter"), |
---|
31 | bottomContainer = new Element("div#lbBottomContainer") |
---|
32 | ).setStyle("display", "none") |
---|
33 | ); |
---|
34 | |
---|
35 | image = new Element("div#lbImage").inject(center).adopt( |
---|
36 | sizer = new Element("div", {styles: {position: "relative"}}).adopt( |
---|
37 | prevLink = new Element("a#lbPrevLink[href=#]", {events: {click: previous}}), |
---|
38 | nextLink = new Element("a#lbNextLink[href=#]", {events: {click: next}}) |
---|
39 | ) |
---|
40 | ); |
---|
41 | |
---|
42 | bottom = new Element("div#lbBottom").inject(bottomContainer).adopt( |
---|
43 | new Element("a#lbCloseLink[href=#]", {events: {click: close}}), |
---|
44 | caption = new Element("div#lbCaption"), |
---|
45 | number = new Element("div#lbNumber"), |
---|
46 | new Element("div", {styles: {clear: "both"}}) |
---|
47 | ); |
---|
48 | }); |
---|
49 | |
---|
50 | |
---|
51 | /* |
---|
52 | Internal functions |
---|
53 | */ |
---|
54 | |
---|
55 | function position() { |
---|
56 | var scroll = win.getScroll(), size = win.getSize(); |
---|
57 | $$(center, bottomContainer).setStyle("left", scroll.x + (size.x / 2)); |
---|
58 | if (compatibleOverlay) overlay.setStyles({left: scroll.x, top: scroll.y, width: size.x, height: size.y}); |
---|
59 | } |
---|
60 | |
---|
61 | function setup(open) { |
---|
62 | ["object", ie6 ? "select" : "embed"].forEach(function(tag) { |
---|
63 | Array.forEach(document.getElementsByTagName(tag), function(el) { |
---|
64 | if (open) el._slimbox = el.style.visibility; |
---|
65 | el.style.visibility = open ? "hidden" : el._slimbox; |
---|
66 | }); |
---|
67 | }); |
---|
68 | |
---|
69 | overlay.style.display = open ? "" : "none"; |
---|
70 | |
---|
71 | var fn = open ? "addEvent" : "removeEvent"; |
---|
72 | win[fn]("scroll", position)[fn]("resize", position); |
---|
73 | document[fn]("keydown", keyDown); |
---|
74 | } |
---|
75 | |
---|
76 | function keyDown(event) { |
---|
77 | var code = event.code; |
---|
78 | // Prevent default keyboard action (like navigating inside the page) |
---|
79 | return options.closeKeys.contains(code) ? close() |
---|
80 | : options.nextKeys.contains(code) ? next() |
---|
81 | : options.previousKeys.contains(code) ? previous() |
---|
82 | : false; |
---|
83 | } |
---|
84 | |
---|
85 | function previous() { |
---|
86 | return changeImage(prevImage); |
---|
87 | } |
---|
88 | |
---|
89 | function next() { |
---|
90 | return changeImage(nextImage); |
---|
91 | } |
---|
92 | |
---|
93 | function changeImage(imageIndex) { |
---|
94 | if (imageIndex >= 0) { |
---|
95 | activeImage = imageIndex; |
---|
96 | activeURL = images[imageIndex][0]; |
---|
97 | prevImage = (activeImage || (options.loop ? images.length : 0)) - 1; |
---|
98 | nextImage = ((activeImage + 1) % images.length) || (options.loop ? 0 : -1); |
---|
99 | |
---|
100 | stop(); |
---|
101 | center.className = "lbLoading"; |
---|
102 | |
---|
103 | preload = new Image(); |
---|
104 | preload.onload = animateBox; |
---|
105 | preload.src = activeURL; |
---|
106 | } |
---|
107 | |
---|
108 | return false; |
---|
109 | } |
---|
110 | |
---|
111 | function animateBox() { |
---|
112 | center.className = ""; |
---|
113 | fxImage.set(0); |
---|
114 | image.setStyles({backgroundImage: "url(" + activeURL + ")", display: ""}); |
---|
115 | sizer.setStyle("width", preload.width); |
---|
116 | $$(sizer, prevLink, nextLink).setStyle("height", preload.height); |
---|
117 | |
---|
118 | caption.set("html", images[activeImage][1] || ""); |
---|
119 | number.set("html", (((images.length > 1) && options.counterText) || "").replace(/{x}/, activeImage + 1).replace(/{y}/, images.length)); |
---|
120 | |
---|
121 | if (prevImage >= 0) preloadPrev.src = images[prevImage][0]; |
---|
122 | if (nextImage >= 0) preloadNext.src = images[nextImage][0]; |
---|
123 | |
---|
124 | centerWidth = image.offsetWidth; |
---|
125 | centerHeight = image.offsetHeight; |
---|
126 | var top = Math.max(0, middle - (centerHeight / 2)), check = 0, fn; |
---|
127 | if (center.offsetHeight != centerHeight) { |
---|
128 | check = fxResize.start({height: centerHeight, top: top}); |
---|
129 | } |
---|
130 | if (center.offsetWidth != centerWidth) { |
---|
131 | check = fxResize.start({width: centerWidth, marginLeft: -centerWidth/2}); |
---|
132 | } |
---|
133 | fn = function() { |
---|
134 | bottomContainer.setStyles({width: centerWidth, top: top + centerHeight, marginLeft: -centerWidth/2, visibility: "hidden", display: ""}); |
---|
135 | fxImage.start(1); |
---|
136 | }; |
---|
137 | if (check) { |
---|
138 | fxResize.chain(fn); |
---|
139 | } |
---|
140 | else { |
---|
141 | fn(); |
---|
142 | } |
---|
143 | } |
---|
144 | |
---|
145 | function animateCaption() { |
---|
146 | if (prevImage >= 0) prevLink.style.display = ""; |
---|
147 | if (nextImage >= 0) nextLink.style.display = ""; |
---|
148 | fxBottom.set(-bottom.offsetHeight).start(0); |
---|
149 | bottomContainer.style.visibility = ""; |
---|
150 | } |
---|
151 | |
---|
152 | function stop() { |
---|
153 | preload.onload = null; |
---|
154 | preload.src = preloadPrev.src = preloadNext.src = activeURL; |
---|
155 | fxResize.cancel(); |
---|
156 | fxImage.cancel(); |
---|
157 | fxBottom.cancel(); |
---|
158 | $$(prevLink, nextLink, image, bottomContainer).setStyle("display", "none"); |
---|
159 | } |
---|
160 | |
---|
161 | function close() { |
---|
162 | if (activeImage >= 0) { |
---|
163 | stop(); |
---|
164 | activeImage = prevImage = nextImage = -1; |
---|
165 | center.style.display = "none"; |
---|
166 | fxOverlay.cancel().chain(setup).start(0); |
---|
167 | } |
---|
168 | |
---|
169 | return false; |
---|
170 | } |
---|
171 | |
---|
172 | |
---|
173 | /* |
---|
174 | API |
---|
175 | */ |
---|
176 | |
---|
177 | Element.implement({ |
---|
178 | slimbox: function(_options, linkMapper) { |
---|
179 | // The processing of a single element is similar to the processing of a collection with a single element |
---|
180 | $$(this).slimbox(_options, linkMapper); |
---|
181 | |
---|
182 | return this; |
---|
183 | } |
---|
184 | }); |
---|
185 | |
---|
186 | Elements.implement({ |
---|
187 | /* |
---|
188 | options: Optional options object, see Slimbox.open() |
---|
189 | linkMapper: Optional function taking a link DOM element and an index as arguments and returning an array containing 2 elements: |
---|
190 | the image URL and the image caption (may contain HTML) |
---|
191 | linksFilter: Optional function taking a link DOM element and an index as arguments and returning true if the element is part of |
---|
192 | the image collection that will be shown on click, false if not. "this" refers to the element that was clicked. |
---|
193 | This function must always return true when the DOM element argument is "this". |
---|
194 | */ |
---|
195 | slimbox: function(_options, linkMapper, linksFilter) { |
---|
196 | linkMapper = linkMapper || function(el) { |
---|
197 | return [el.href, el.title]; |
---|
198 | }; |
---|
199 | |
---|
200 | linksFilter = linksFilter || function() { |
---|
201 | return true; |
---|
202 | }; |
---|
203 | |
---|
204 | var links = this; |
---|
205 | |
---|
206 | links.removeEvents("click").addEvent("click", function() { |
---|
207 | // Build the list of images that will be displayed |
---|
208 | var filteredLinks = links.filter(linksFilter, this); |
---|
209 | return Slimbox.open(filteredLinks.map(linkMapper), filteredLinks.indexOf(this), _options); |
---|
210 | }); |
---|
211 | |
---|
212 | return links; |
---|
213 | } |
---|
214 | }); |
---|
215 | |
---|
216 | return { |
---|
217 | open: function(_images, startImage, _options) { |
---|
218 | options = Object.append({ |
---|
219 | loop: false, // Allows to navigate between first and last images |
---|
220 | overlayOpacity: 0.8, // 1 is opaque, 0 is completely transparent (change the color in the CSS file) |
---|
221 | overlayFadeDuration: 400, // Duration of the overlay fade-in and fade-out animations (in milliseconds) |
---|
222 | resizeDuration: 400, // Duration of each of the box resize animations (in milliseconds) |
---|
223 | resizeTransition: false, // false uses the mootools default transition |
---|
224 | initialWidth: 250, // Initial width of the box (in pixels) |
---|
225 | initialHeight: 250, // Initial height of the box (in pixels) |
---|
226 | imageFadeDuration: 400, // Duration of the image fade-in animation (in milliseconds) |
---|
227 | captionAnimationDuration: 400, // Duration of the caption animation (in milliseconds) |
---|
228 | counterText: "Image {x} of {y}", // Translate or change as you wish, or set it to false to disable counter text for image groups |
---|
229 | closeKeys: [27, 88, 67], // Array of keycodes to close Slimbox, default: Esc (27), 'x' (88), 'c' (67) |
---|
230 | previousKeys: [37, 80], // Array of keycodes to navigate to the previous image, default: Left arrow (37), 'p' (80) |
---|
231 | nextKeys: [39, 78] // Array of keycodes to navigate to the next image, default: Right arrow (39), 'n' (78) |
---|
232 | }, _options || {}); |
---|
233 | |
---|
234 | // Setup effects |
---|
235 | fxOverlay = new Fx.Tween(overlay, {property: "opacity", duration: options.overlayFadeDuration}); |
---|
236 | fxResize = new Fx.Morph(center, Object.append({duration: options.resizeDuration, link: "chain"}, options.resizeTransition ? {transition: options.resizeTransition} : {})); |
---|
237 | fxImage = new Fx.Tween(image, {property: "opacity", duration: options.imageFadeDuration, onComplete: animateCaption}); |
---|
238 | fxBottom = new Fx.Tween(bottom, {property: "margin-top", duration: options.captionAnimationDuration}); |
---|
239 | |
---|
240 | // The function is called for a single image, with URL and Title as first two arguments |
---|
241 | if (typeof _images == "string") { |
---|
242 | _images = [[_images, startImage]]; |
---|
243 | startImage = 0; |
---|
244 | } |
---|
245 | |
---|
246 | middle = win.getScrollTop() + (win.getHeight() / 2); |
---|
247 | centerWidth = options.initialWidth; |
---|
248 | centerHeight = options.initialHeight; |
---|
249 | center.setStyles({top: Math.max(0, middle - (centerHeight / 2)), width: centerWidth, height: centerHeight, marginLeft: -centerWidth/2, display: ""}); |
---|
250 | compatibleOverlay = ie6 || (overlay.currentStyle && (overlay.currentStyle.position != "fixed")); |
---|
251 | if (compatibleOverlay) overlay.style.position = "absolute"; |
---|
252 | fxOverlay.set(0).start(options.overlayOpacity); |
---|
253 | position(); |
---|
254 | setup(1); |
---|
255 | |
---|
256 | images = _images; |
---|
257 | options.loop = options.loop && (images.length > 1); |
---|
258 | return changeImage(startImage); |
---|
259 | } |
---|
260 | }; |
---|
261 | |
---|
262 | })(); |
---|