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