1// ---------------------------------------- 2// Lightbox 3// 4// @author DxF5H 5// @version 0.9.8 6// @url https://github.com/DxF5H/lightbox 7// 8// MIT License 9// ---------------------------------------- 10 11// Universal Module Definition 12(function (window, factory) { 13 if (typeof define === "function" && define.amd) { 14 // AMD 15 define(factory); 16 } else if (typeof module === "object" && module.exports) { 17 // CommonJS 18 module.exports = factory(); 19 } else { 20 // Browser global 21 window.lightbox = factory(); 22 } 23}(typeof window !== "undefined" ? window : this, function () { 24 var lightbox = function (selector, userOptions) { 25 "use strict"; 26 27 28 // ----------------- 29 // Default options 30 // ----------------- 31 32 var options = { 33 // Display captions, if available. 34 captions: true, 35 36 // Set the element where the caption is. Set it to "self" for the a-tag itself 37 captionsSelector: "self", 38 39 // Get the caption from given attribute. 40 captionAttribute: "title", 41 42 // Display navigation buttons. "auto" hides buttons on touch-enabled devices. 43 nav: "auto", 44 45 // Text or HTML for the navigation buttons. 46 navText: ["‹", "›"], 47 48 // Display close button. 49 close: true, 50 51 // Text or HTML for the close button. 52 closeText: "×", 53 54 // Display current image index 55 counter: true, 56 57 // Allow keyboard navigation. 58 keyboard: true, 59 60 // Display zoom icon. 61 zoom: true, 62 63 // Text or HTML for the zoom icon 64 zoomText: "+", 65 66 // Closes the lightbox when clicking outside 67 docClose: false, 68 69 // Swipe up to close lightbox 70 swipeClose: true, 71 72 // Hide scrollbars if lightbox is displayed. 73 scroll: false 74 }; 75 76 77 // ------------------ 78 // Global variables 79 // ------------------ 80 81 // Array with all images 82 var gallery = []; 83 84 // Cache array length 85 var galleryLength = null; 86 87 // Array with all slider elements 88 var sliderElement = []; 89 90 // Current index 91 var currentIndex = 0; 92 93 // Object with touch positions 94 var touch = {}; 95 96 // If set to true ignore touch events because animation was already fired 97 var touchFlag = false; 98 99 // The last focused element before opening the overlay 100 var lastFocus = null; 101 102 103 // -------------------------- 104 // Create lightbox components 105 // -------------------------- 106 107 var overlay = document.createElement("div"); 108 overlay.setAttribute("role", "dialog"); 109 overlay.setAttribute("aria-hidden", "true"); 110 overlay.classList.add("lightbox-overlay"); 111 document.getElementsByTagName("body")[0].appendChild(overlay); 112 113 114 var slider = document.createElement("div"); 115 slider.classList.add("lightbox-slider"); 116 overlay.appendChild(slider); 117 118 119 var prevButton = document.createElement("button"); 120 prevButton.setAttribute("type", "button"); 121 prevButton.setAttribute("aria-label", "Previous"); 122 overlay.appendChild(prevButton); 123 124 125 var nextButton = document.createElement("button"); 126 nextButton.setAttribute("type", "button"); 127 nextButton.setAttribute("aria-label", "Next"); 128 overlay.appendChild(nextButton); 129 130 131 var closeButton = document.createElement("button"); 132 closeButton.setAttribute("type", "button"); 133 closeButton.setAttribute("aria-label", "Close"); 134 overlay.appendChild(closeButton); 135 136 137 var counter = document.createElement("div"); 138 counter.classList.add("lightbox-counter"); 139 overlay.appendChild(counter); 140 141 142 // ------------------ 143 // Global functions 144 // ------------------ 145 146 var mergeOptions = function (userOptions, options) { 147 Object.keys(userOptions).forEach(function (key) { 148 options[key] = userOptions[key]; 149 }); 150 }; 151 152 153 var loadImage = function (index, callback) { 154 if (typeof gallery[index] === "undefined" || typeof sliderElement[index] === "undefined") { 155 return; 156 157 // If image is already loaded run callback and return 158 } else if (!sliderElement[index].getElementsByTagName("img")[0].hasAttribute("data-src")) { 159 if (callback) { 160 callback(); 161 } 162 return; 163 } 164 165 var figure = sliderElement[index].getElementsByTagName("figure")[0], 166 image = figure.getElementsByTagName("img")[0], 167 figcaption = figure.getElementsByTagName("figcaption")[0]; 168 169 image.onload = function () { 170 var loader = figure.querySelector(".lightbox-loader"); 171 figure.removeChild(loader); 172 image.style.opacity = "1";
173 if (figcaption) { 174 figcaption.style.opacity = "1"; 175 } 176 }; 177 178 image.setAttribute("src", image.getAttribute("data-src")); 179 image.removeAttribute("data-src"); 180 181 // Run callback 182 if (callback) { 183 callback(); 184 } 185 }; 186 187 188 var updateOffset = function () { 189 var offset = -currentIndex * 100 + "%"; 190 191 if (typeof slider.style.perspective !== "undefined") { 192 slider.style.transform = "translate(" + offset + ", 0)"; 193 } else { 194 slider.style.left = offset; 195 } 196 }; 197 198 199 var updateCounter = function () { 200 counter.innerHTML = (currentIndex + 1) + "/" + galleryLength; 201 }; 202 203 204 var updateFocus = function (direction) { 205 prevButton.disabled = false; 206 nextButton.disabled = false; 207 208 if (currentIndex === galleryLength - 1) { 209 prevButton.disabled = false; 210 nextButton.disabled = true; 211 } else if (currentIndex === 0) { 212 prevButton.disabled = true; 213 nextButton.disabled = false; 214 } 215 216 if (options.nav) { 217 if (!nextButton.disabled && direction !== "left") { 218 nextButton.focus(); 219 } else { 220 prevButton.focus(); 221 } 222 } else if (options.close) { 223 closeButton.focus(); 224 } 225 }; 226 227 228 var preloadImage = function (index) { 229 loadImage(index); 230 }; 231 232 233 var nextImage = function () { 234 // If next image exists 235 if (currentIndex < galleryLength - 1) { 236 currentIndex++; 237 238 // Updates 239 updateOffset(); 240 updateCounter(); 241 updateFocus(); 242 243 preloadImage(currentIndex + 1); 244 } 245 }; 246 247 248 var prevImage = function () { 249 // If previous image exists 250 if (currentIndex > 0) { 251 currentIndex--; 252 253 // Updates 254 updateOffset(); 255 updateCounter(); 256 updateFocus(); 257 258 preloadImage(currentIndex - 1); 259 } 260 }; 261 262 263 var createOverlay = function () { 264 var i = 0, 265 x = 0, 266 figure = null, 267 figuresIds = [], 268 figcaption = null, 269 figcaptionsIds = []; 270 271 for (; i < galleryLength; ++i) { 272 sliderElement[i] = document.createElement("div"); 273 sliderElement[i].classList.add("lightbox-content"); 274 sliderElement[i].id = "lightbox-content-" + i; 275 276 // Create figure 277 figure = document.createElement("figure"); 278 figure.innerHTML = "<div class=\"lightbox-loader\"></div>"; 279 280 // Create image 281 var image = document.createElement("img"); 282 image.style.opacity = "0"; 283 284 if (gallery[i].selector.getElementsByTagName("img")[0] && gallery[i].selector.getElementsByTagName("img")[0].alt) { 285 image.alt = gallery[i].selector.getElementsByTagName("img")[0].alt; 286 } else { 287 image.alt = ""; 288 } 289 image.setAttribute("src", ""); 290 image.setAttribute("data-src", gallery[i].selector.href); 291 292 // Add image to figure 293 figure.appendChild(image); 294 295 // Create figcaption 296 if (options.captions) { 297 figcaption = document.createElement("figcaption"); 298 figcaption.style.opacity = "0"; 299 300 if (options.captionsSelector == "self" && gallery[i].selector.getAttribute(options.captionAttribute)) { 301 figcaption.innerHTML = gallery[i].selector.getAttribute(options.captionAttribute); 302 } else if (options.captionsSelector == "img" && gallery[i].selector.getElementsByTagName("img")[0].getAttribute(options.captionAttribute)) { 303 figcaption.innerHTML = gallery[i].selector.getElementsByTagName("img")[0].getAttribute(options.captionAttribute); 304 } 305 306 if (figcaption.innerHTML) { 307 figure.id = "lightbox-figure-" + x; 308 figcaption.id = "lightbox-figcaption-" + x; 309 figure.appendChild(figcaption); 310 311 figuresIds.push("lightbox-figure-" + x); 312 figcaptionsIds.push("lightbox-figcaption-" + x); 313 ++x; 314 } 315 } 316 317 // Add figure to slider element 318 sliderElement[i].appendChild(figure); 319 320 // Add slider element to slider 321 slider.appendChild(sliderElement[i]); 322 } 323 324 if (x !== 0) { 325 overlay.setAttribute("aria-labelledby", figuresIds.join(" ")); 326 overlay.setAttribute("aria-describedby", figcaptionsIds.join(" ")); 327 } 328 329 // Hide buttons if necessary 330 if (!options.nav || galleryLength === 1 || (options.nav === "auto" && "ontouchstart" in window)) { 331 prevButton.style.display = "none"; 332 nextButton.style.display = "none"; 333 } else { 334 prevButton.innerHTML = options.navText[0]; 335 nextButton.innerHTML = options.navText[1]; 336 } 337 338 // Hide counter if necessary 339 if (!options.counter || galleryLength === 1) { 340 counter.style.display = "none"; 341 } 342 343 // Hide close if necessary 344 if (!options.close) { 345 closeButton.style.display = "none"; 346 } else { 347 closeButton.innerHTML = options.closeText; 348 } 349 }; 350 351 352 var openOverlay = function (index) {
353 if (overlay.getAttribute("aria-hidden") === "false") { 354 return; 355 } 356 357 if (!options.scroll) { 358 document.documentElement.classList.add("lightbox-no-scroll"); 359 document.body.classList.add("lightbox-no-scroll"); 360 } 361 362 // Save last focused element 363 lastFocus = document.activeElement; 364 365 // Set current index 366 currentIndex = index; 367 368 // Reset touch positions 369 touch = { 370 count: 0, 371 startX: null, 372 startY: null, 373 moveX: null, 374 moveY: null, 375 }; 376 377 // Bind events 378 bindEvents(); 379 380 // load image 381 loadImage(currentIndex, function () { 382 preloadImage(currentIndex + 1); 383 preloadImage(currentIndex - 1); 384 }); 385 386 updateOffset(); 387 updateCounter(); 388 overlay.setAttribute("aria-hidden", "false"); 389 390 updateFocus(); 391 }; 392 393 394 var closeOverlay = function () { 395 if (overlay.getAttribute("aria-hidden") === "true") { 396 return; 397 } 398 399 if (!options.scroll) { 400 document.documentElement.classList.remove("lightbox-no-scroll"); 401 document.body.classList.remove("lightbox-no-scroll"); 402 } 403 404 // Unbind events 405 unbindEvents(); 406 407 overlay.setAttribute("aria-hidden", "true"); 408 409 // Focus 410 lastFocus.focus(); 411 }; 412 413 414 // ------------------ 415 // Event handler 416 // ------------------ 417 418 var clickHandler = function (event) { 419 if (this === prevButton) { 420 prevImage(); 421 updateFocus("left"); 422 } else if (this === nextButton) { 423 nextImage(); 424 updateFocus("right"); 425 } else if (this === closeButton || this === overlay && event.target.id.indexOf("lightbox-content") !== -1) { 426 closeOverlay(); 427 } 428 429 event.preventDefault(); 430 }; 431 432 433 var keyupHandler = function (event) { 434 switch (event.keyCode) { 435 // Left arrow 436 case 37: 437 prevImage(); 438 updateFocus("left"); 439 break; 440 441 // Right arrow 442 case 39: 443 nextImage(); 444 updateFocus("right"); 445 break; 446 447 // Esc 448 case 27: 449 closeOverlay(); 450 break; 451 } 452 }; 453 454 455 var touchstartHandler = function (event) { 456 touch.count++; 457 if (touch.count > 1) { 458 touch.multitouch = true; 459 } 460 // Get the touch position 461 touch.startX = event.changedTouches[0].pageX; 462 touch.startY = event.changedTouches[0].pageY; 463 }; 464 465 var touchmoveHandler = function (event) { 466 // If action was already triggered or multitouch return 467 if (touchFlag || touch.multitouch) { 468 return; 469 } 470 471 touch.moveX = event.changedTouches[0].pageX; 472 touch.moveY = event.changedTouches[0].pageY; 473 474 // Move > 50 pixels left or right to navigate 475 if (touch.moveX - touch.startX > 50) { 476 touchFlag = true; 477 prevImage(); 478 } else if (touch.moveX - touch.startX < -50) { 479 touchFlag = true; 480 nextImage(); 481 482 // Move > 100 pixels up to close the overlay 483 } else if (options.swipeClose && touch.startY - touch.moveY > 100) { 484 closeOverlay(); 485 } 486 487 event.preventDefault(); 488 }; 489 490 491 var touchendHandler = function () { 492 touch.count--; 493 if (touch.count <= 0) { 494 touch.multitouch = false; 495 } 496 touchFlag = false; 497 }; 498 499 500 var trapFocus = function (event) { 501 if (overlay.getAttribute("aria-hidden") === "false" && !overlay.contains(event.target)) { 502 event.stopPropagation(); 503 updateFocus(); 504 } 505 }; 506 507 508 var bindEvents = function () { 509 if (options.keyboard) { 510 document.addEventListener("keyup", keyupHandler, false); 511 } 512 513 if (options.docClose) { 514 overlay.addEventListener("click", clickHandler, false); 515 } 516 517 prevButton.addEventListener("click", clickHandler, false); 518 nextButton.addEventListener("click", clickHandler, false); 519 closeButton.addEventListener("click", clickHandler, false); 520 overlay.addEventListener("touchstart", touchstartHandler, false); 521 overlay.addEventListener("touchmove", touchmoveHandler, false); 522 overlay.addEventListener("touchend", touchendHandler, false); 523 document.addEventListener("focus", trapFocus, true); 524 }; 525 526 527 var unbindEvents = function () { 528 if (options.keyboard) { 529 document.removeEventListener("keyup", keyupHandler, false); 530 } 531 532 if (options.docClose) { 533 overlay.removeEventListener("click", clickHandler, false); 534 } 535 536 prevButton.removeEventListener("click", clickHandler, false); 537 nextButton.removeEventListener("click", clickHandler, false); 538 closeButton.removeEventListener("click", clickHandler, false); 539 overlay.removeEventListener("touchstart", touchstartHandler, false); 540 overlay.removeEventListener("touchmove", touchmoveHandler, false); 541 overlay.removeEventListener("touchend", touchendHandler, false); 542 document.removeEventListener("focus", trapFocus, true); 543 }; 544 545 546 // ------ 547 // Setup 548 // ------ 549 550 var setup = function (selector, userOptions) { 551 // Merge user options into defaults 552 if (userOptions) { 553 mergeOptions(userOptions, options); 554 } 555 556 // Get a list of all elements within the document 557 var elements = document.querySelectorAll(selector); 558 559 if (!elements.length) { 560 console.log("Ups, I can't find the selector \"" + selector + "\"."); 561 return; 562 } 563 564 // Execute a few things once per element 565 [].forEach.call(elements, function(element, index) { 566 // Set zoom icon if necessary 567 if (options.zoom && element.getElementsByTagName("img")[0]) { 568 var lightboxZoom = document.createElement("div"); 569 570 lightboxZoom.classList.add("lightbox-zoom"); 571 lightboxZoom.innerHTML = options.zoomText; 572 573 element.appendChild(lightboxZoom); 574 } 575 576 // Bind event 577 element.addEventListener("click", function (event) { 578 openOverlay(index); 579 580 event.preventDefault(); 581 }, true); 582 583 // Add element to gallery 584 gallery.push({ 585 selector: element 586 }); 587 }); 588 589 galleryLength = gallery.length; 590 createOverlay(); 591 }; 592 593 594 setup(selector, userOptions); 595 }; 596 597 return lightbox; 598}));
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.