1/*! 2 * baguetteBox.js 3 * @author feimosi 4 * @version 0.7.0 5 * @url https://github.com/feimosi/baguetteBox.js 6 */ 7 8var baguetteBox = (function() { 9 // SVG shapes used in buttons 10 var leftArrow = '<svg width="40" height="60" xmlns="http://www.w3.org/2000/svg" version="1.1">' + 11 '<polyline points="30 10 10 30 30 50" stroke="rgba(255,255,255,0.5)" stroke-width="4"' + 12 'stroke-linecap="butt" fill="none" stroke-linejoin="round"><</polyline>' + 13 '</svg>', 14 rightArrow = '<svg width="40" height="60" xmlns="http://www.w3.org/2000/svg" version="1.1">' + 15 '<polyline points="10 10 30 30 10 50" stroke="rgba(255,255,255,0.5)" stroke-width="4"' + 16 'stroke-linecap="butt" fill="none" stroke-linejoin="round">></polyline>' + 17 '</svg>', 18 closeX = '<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" version="1.1">' + 19 '<g stroke="rgb(160, 160, 160)" stroke-width="4">' + 20 '<line x1="5" y1="5" x2="25" y2="25"/>' + 21 '<line x1="5" y1="25" x2="25" y2="5"/>' + 22 'X</g></svg>'; 23 // Main ID names 24 var overlayID = 'baguetteBox-overlay'; 25 var sliderID = 'baguetteBox-slider'; 26 // Global options and their defaults 27 var options = {}, defaults = { 28 captions: true, 29 buttons: 'auto', 30 async: false, 31 preload: 2, 32 animation: 'slideIn' 33 }; 34 // DOM Elements references 35 var overlay, slider, previousButton, nextButton, closeButton; 36 // Current image index inside the slider and displayed gallery index 37 var currentIndex = 0, currentGallery = -1; 38 // Touch event start position (for slide gesture) 39 var touchStartX; 40 // If set to true ignore touch events because animation was already fired 41 var touchFlag = false; 42 // Array of all used galleries (DOM elements) 43 var galleries = []; 44 // 2D array of galleries and images inside them 45 var imagesMap = []; 46 // Array containing temporary images DOM elements 47 var imagesArray = []; 48 49 // forEach polyfill for IE8 50 if(!Array.prototype.forEach) { 51 Array.prototype.forEach = function(callback, thisArg) { 52 var len = this.length; 53 for(var i = 0; i < len; i++) { 54 callback.call(thisArg, this[i], i, this); 55 } 56 }; 57 } 58 59 // Script entry point 60 function run(selector, userOptions) { 61 buildOverlay(); 62 // For each gallery bind a click event to every image inside it 63 galleries = document.querySelectorAll(selector); 64 [].forEach.call( 65 galleries, 66 function (galleryElement, galleryIndex) { 67 var galleryID = imagesMap.length; 68 imagesMap.push(galleryElement.getElementsByTagName('a')); 69 imagesMap[galleryID].options = userOptions; 70 [].forEach.call( 71 imagesMap[galleryID], 72 function (imageElement, imageIndex) { 73 bind(imageElement, 'click', function(event) { 74 /*jshint -W030 */ 75 event.preventDefault ? event.preventDefault() : event.returnValue = false; 76 prepareOverlay(galleryID); 77 showOverlay(imageIndex); 78 }); 79 } 80 ); 81 } 82 ); 83 defaults.transforms = testTransformsSupport(); 84 } 85 86 function buildOverlay() { 87 overlay = document.getElementById(overlayID); 88 // Check if the overlay already exists 89 if(overlay) { 90 slider = document.getElementById(sliderID); 91 previousButton = document.getElementById('previous-button'); 92 nextButton = document.getElementById('next-button'); 93 closeButton = document.getElementById('close-button'); 94 return; 95 } 96 // Create overlay element 97 overlay = document.createElement('div'); 98 overlay.id = overlayID; 99 document.getElementsByTagName('body')[0].appendChild(overlay); 100 // Create gallery slider element 101 slider = document.createElement('div'); 102 slider.id = sliderID; 103 overlay.appendChild(slider); 104 // Create all necessary buttons 105 previousButton = document.createElement('button'); 106 previousButton.id = 'previous-button'; 107 previousButton.innerHTML = leftArrow; 108 overlay.appendChild(previousButton); 109 110 nextButton = document.createElement('button'); 111 nextButton.id = 'next-button'; 112 nextButton.innerHTML = rightArrow; 113 overlay.appendChild(nextButton); 114 115 closeButton = document.createElement('button'); 116 closeButton.id = 'close-button'; 117 closeButton.innerHTML = closeX; 118 overlay.appendChild(closeButton); 119 120 previousButton.className = nextButton.className = closeButton.className = 'baguetteBox-button'; 121 122 bindEvents(); 123 } 124 125 function bindEvents() { 126 // When clicked on the overlay (outside displayed image) close it 127 bind(overlay, 'click', function(event) { 128 if(event.target && event.target.nodeName !== "IMG") 129 hideOverlay(); 130 }); 131 // Add event listeners for buttons 132 bind(document.getElementById('previous-button'), 'click', function(event) { 133 /*jshint -W030 */ 134 event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true; 135 showPreviousImage(); 136 }); 137 bind(document.getElementById('next-button'), 'click', function(event) { 138 /*jshint -W030 */ 139 event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true; 140 showNextImage(); 141 }); 142 bind(document.getElementById('close-button'), 'click', function(event) { 143 /*jshint -W030 */ 144 event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true; 145 hideOverlay(); 146 }); 147 // Add touch events 148 bind(overlay, 'touchstart', function(event) { 149 // Save x axis position 150 touchStartX = event.changedTouches[0].pageX; 151 }); 152 bind(overlay, 'touchmove', function(event) { 153 if(touchFlag) 154 return; 155 /*jshint -W030 */ 156 event.preventDefault ? event.preventDefault() : event.returnValue = false;
157 touch = event.touches[0] || event.changedTouches[0]; 158 if(touch.pageX - touchStartX > 40) { 159 touchFlag = true; 160 showPreviousImage(); 161 } else if (touch.pageX - touchStartX < -40) { 162 touchFlag = true; 163 showNextImage(); 164 } 165 }); 166 bind(overlay, 'touchend', function(event) { 167 touchFlag = false; 168 }); 169 // Activate keyboard shortcuts 170 bind(document, 'keydown', function(event) { 171 switch(event.keyCode) { 172 case 37: // Left arrow 173 showPreviousImage(); 174 break; 175 case 39: // Right arrow 176 showNextImage(); 177 break; 178 case 27: // Esc 179 hideOverlay(); 180 break; 181 } 182 }); 183 } 184 185 function prepareOverlay(galleryIndex) { 186 // If the same gallery is being opened prevent from loading it once again 187 if(currentGallery === galleryIndex) 188 return; 189 currentGallery = galleryIndex; 190 // Update gallery specific options 191 setOptions(imagesMap[galleryIndex].options); 192 // Empty slider of previous contents (more effective than .innerHTML = "") 193 while(slider.firstChild) 194 slider.removeChild(slider.firstChild); 195 imagesArray.length = 0; 196 // Prepare and append images containers 197 for(var i = 0; i < imagesMap[galleryIndex].length; i++) { 198 imagesArray.push(returnImageContainer()); 199 slider.appendChild(imagesArray[i]); 200 } 201 } 202 203 function setOptions(newOptions) { 204 if(!newOptions) 205 newOptions = {}; 206 for(var item in defaults) { 207 options[item] = defaults[item]; 208 if(typeof newOptions[item] !== 'undefined') 209 options[item] = newOptions[item]; 210 } 211 /* Apply new options */ 212 // Change transition for proper animation 213 slider.style.transition = slider.style.webkitTransition = options.animation === 'fadeIn' ? 'opacity .4s ease' : ''; 214 // Hide buttons if necessary 215 if(options.buttons === 'auto' && ('ontouchstart' in window || imagesMap[currentGallery].length === 1)) 216 options.buttons = false; 217 // Set buttons style to hide or display them 218 previousButton.style.display = nextButton.style.display = options.buttons ? '' : 'none'; 219 } 220 221 // Return DOM element for image container <div class="full-image">...</div> 222 function returnImageContainer() { 223 var fullImage = document.createElement('div'); 224 fullImage.className = 'full-image'; 225 return fullImage; 226 } 227 228 function showOverlay(index) { 229 // Return if overlay is already visible 230 if(overlay.style.display === 'block') 231 return; 232 // Set current index to a new value and show proper image 233 currentIndex = index; 234 loadImage(currentIndex, function() { 235 preloadNext(currentIndex); 236 preloadPrev(currentIndex); 237 }); 238 updateOffset(); 239 overlay.style.display = 'block'; 240 // Fade in overlay 241 setTimeout(function() { 242 overlay.className = 'visible'; 243 }, 50); 244 } 245 246 function hideOverlay() { 247 // Return if overlay is already hidden 248 if(overlay.style.display === 'none') 249 return; 250 // Fade out and hide the overlay 251 overlay.className = ''; 252 setTimeout(function() { 253 overlay.style.display = 'none'; 254 }, 500); 255 } 256 257 function loadImage(index, callback) { 258 var imageContainer = imagesArray[index]; 259 // If index is invalid return 260 if(typeof imageContainer === 'undefined') 261 return; 262 // If image is already loaded run callback and return 263 if(imageContainer.getElementsByTagName('img')[0]) { 264 if(callback) 265 callback(); 266 return; 267 } 268 // Get element reference, optional caption and source path 269 imageElement = imagesMap[currentGallery][index]; 270 imageCaption = imageElement.getAttribute('data-caption') || imageElement.title; 271 imageSrc = getImageSrc(imageElement); 272 // Prepare image container elements 273 var figure = document.createElement('figure'); 274 var image = document.createElement('img'); 275 var figcaption = document.createElement('figcaption'); 276 imageContainer.appendChild(figure); 277 // Add loader element 278 figure.innerHTML = '<div class="spinner">' + 279 '<div class="double-bounce1"></div>' + 280 '<div class="double-bounce2"></div>' + 281 '</div>'; 282 // Set callback function when image loads 283 image.onload = function() { 284 // Remove loader element 285 var spinner = this.parentNode.querySelector('.spinner'); 286 this.parentNode.removeChild(spinner); 287 if(!options.async && callback) 288 callback(); 289 }; 290 image.setAttribute('src', imageSrc); 291 figure.appendChild(image); 292 // Insert caption if available 293 if(options.captions && imageCaption) { 294 figcaption.innerHTML = imageCaption; 295 figure.appendChild(figcaption); 296 } 297 // Run callback 298 if(options.async && callback) 299 callback(); 300 } 301 302 function getImageSrc(image) { 303 // Set dafult image path from href 304 var result = imageElement.getAttribute('href'); 305 // If dataset is supported find the most suitable image 306 if(image.dataset) { 307 var srcs = []; 308 // Get all possible image versions depending on the resolution 309 for(var item in image.dataset) { 310 if(item.substring(0, 3) === 'at-' && !isNaN(item.substring(3))) 311 srcs[item.replace('at-', '')] = image.dataset[item]; 312 } 313 // Sort resolutions ascending 314 keys = Object.keys(srcs).sort(function(a, b) { 315 return parseInt(a) < parseInt(b) ? -1 : 1; 316 }); 317 // Get real screen resolution 318 var width = window.innerWidth * window.devicePixelRatio; 319 // Find first image bigger than or equal to the current width 320 for(var i = 0; i < keys.length; i++) { 321 if(keys[i] >= width) { 322 result = srcs[keys[i]]; 323 break; 324 } 325 result = srcs[keys[i]]; 326 } 327 } 328 return result; 329 } 330 331 function showNextImage() { 332 if(currentIndex <= imagesArray.length - 2) { 333 currentIndex++; 334 updateOffset(); 335 preloadNext(currentIndex); 336 } else { 337 slider.className = 'bounce-from-right'; 338 setTimeout(function() { 339 slider.className = ''; 340 }, 400); 341 } 342 } 343 344 function showPreviousImage() { 345 if(currentIndex >= 1) { 346 currentIndex--; 347 updateOffset(); 348 preloadPrev(currentIndex); 349 } else { 350 slider.className = 'bounce-from-left'; 351 setTimeout(function() { 352 slider.className = ''; 353 }, 400); 354 } 355 } 356 357 function updateOffset() { 358 var offset = -currentIndex * 100 + '%'; 359 if(options.animation === 'fadeIn') { 360 slider.style.opacity = 0; 361 setTimeout(function() { 362 /*jshint -W030 */ 363 options.transforms ? 364 slider.style.transform = slider.style.webkitTransform = 'translate3d(' + offset + ',0,0)' 365 : slider.style.left = offset; 366 slider.style.opacity = 1; 367 }, 400); 368 } else { 369 /*jshint -W030 */ 370 options.transforms ? 371 slider.style.transform = slider.style.webkitTransform = 'translate3d(' + offset + ',0,0)' 372 : slider.style.left = offset; 373 } 374 } 375 376 function testTransformsSupport() { 377 var div = document.createElement('div'), 378 support = false;
379 support = typeof div.style.perspective !== 'undefined' || typeof div.style.webkitPerspective !== 'undefined'; 380 return support; 381 } 382 383 function preloadNext(index) { 384 if(index - currentIndex >= options.preload) 385 return; 386 loadImage(index + 1, function() { preloadNext(index + 1); }); 387 } 388 389 function preloadPrev(index) { 390 if(currentIndex - index >= options.preload) 391 return; 392 loadImage(index - 1, function() { preloadPrev(index - 1); }); 393 } 394 395 function bind(element, event, callback) { 396 if(element.addEventListener) 397 element.addEventListener(event, callback, false); 398 else 399 element.attachEvent('on' + event, callback); 400 } 401 402 return { 403 run: run 404 }; 405 406})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.