PageSourceSearch

https://waylandunion.org/lib/lightbox2-2.11.4/js/lightbox.custom.js

js waylandunion.org collected 2026-10-01 13:01:14 UTC 22,131 bytes, 595 lines download raw bytes

vendor: 6,071 bytes, lines 1-143
1/*!
2 * Lightbox v2.11.4
3 * by Lokesh Dhakar
4 *
5 * More info:
6 * http://lokeshdhakar.com/projects/lightbox2/
7 *
8 * Copyright Lokesh Dhakar
9 * Released under the MIT license
10 * https://github.com/lokesh/lightbox2/blob/master/LICENSE
11 *
12 * @preserve
13 */
14
15// Uses Node, AMD or browser globals to create a module.
16(function (root, factory) {
17    if (typeof define === 'function' && define.amd) {
18        // AMD. Register as an anonymous module.
19        define(['jquery'], factory);
20    } else if (typeof exports === 'object') {
21        // Node. Does not work with strict CommonJS, but
22        // only CommonJS-like environments that support module.exports,
23        // like Node.
24        module.exports = factory(require('jquery'));
25    } else {
26        // Browser globals (root is window)
27        root.lightbox = factory(root.jQuery);
28    }
29}(this, function ($) {
30
31  function Lightbox(options) {
32    this.album = [];
33    this.currentImageIndex = void 0;
34    this.openingElement = null;
35    this.init();
36
37    // options
38    this.options = $.extend({}, this.constructor.defaults);
39    this.option(options);
40  }
41
42  // Descriptions of all options available on the demo site:
43  // http://lokeshdhakar.com/projects/lightbox2/index.html#options
44  Lightbox.defaults = {
45    albumLabel: 'Image %1 of %2',
46    alwaysShowNavOnTouchDevices: false,
47    fadeDuration: 600,
48    fitImagesInViewport: true,
49    imageFadeDuration: 600,
50    // maxWidth: 800,
51    // maxHeight: 600,
52    positionFromTop: 50,
53    resizeDuration: 700,
54    showImageNumberLabel: true,
55    wrapAround: false,
56    disableScrolling: false,
57    /*
58    Sanitize Title
59    If the caption data is trusted, for example you are hardcoding it in, then leave this to false.
60    This will free you to add html tags, such as links, in the caption.
61
62    If the caption data is user submitted or from some other untrusted source, then set this to true
63    to prevent xss and other injection attacks.
64     */
65    sanitizeTitle: false
66  };
67
68  Lightbox.prototype.option = function(options) {
69    $.extend(this.options, options);
70  };
71
72  Lightbox.prototype.imageCountLabel = function(currentImageNum, totalImages) {
73    return this.options.albumLabel.replace(/%1/g, currentImageNum).replace(/%2/g, totalImages);
74  };
75
76  Lightbox.prototype.init = function() {
77    var self = this;
78    // Both enable and build methods require the body tag to be in the DOM.
79    $(document).ready(function() {
80      self.enable();
81      self.build();
82    });
83  };
84
85  // Loop through anchors and areamaps looking for either data-lightbox attributes or rel attributes
86  // that contain 'lightbox'. When these are clicked, start lightbox.
87  Lightbox.prototype.enable = function() {
88    var self = this;
89    $('body').on('click', 'a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]', function(event) {
90      self.start($(event.currentTarget));
91      return false;
92    });
93  };
94
95  // Build html for the lightbox and the overlay.
96  // Attach event handlers to the new DOM elements. click click click
97  Lightbox.prototype.build = function() {
98    if ($('#lightbox').length > 0) {
99        return;
100    }
101
102    var self = this;
103
104    // The two root notes generated, #lightboxOverlay and #lightbox are given
105    // tabindex attrs so they are focusable. We attach our keyboard event
106    // listeners to these two elements, and not the document. Clicking anywhere
107    // while Lightbox is opened will keep the focus on or inside one of these
108    // two elements.
109    //
110    // We do this so we can prevent propogation of the Esc keypress when
111    // Lightbox is open. This prevents it from intefering with other components
112    // on the page below.
113    //
114    // Github issue: https://github.com/lokesh/lightbox2/issues/663
115      $('<div id="lightboxOverlay" tabindex="-1" class="lightboxOverlay"></div><div id="lightbox" tabindex="-1" class="lightbox"><div class="lb-instContainer"><span class="lb-inst-hilite">Esc</span> to exit; <span class="lb-inst-hilite">Left</span> and <span class="lb-inst-hilite">Right</span> arrows to navigate.</div><div class="lb-outerContainer"><div class="lb-container"><img class="lb-image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt=""/><div class="lb-nav"><a class="lb-prev" aria-label="Previous image" href="javascript:" ></a><a class="lb-next" aria-label="Next image" href="javascript:" ></a></div><div class="lb-loader"><a class="lb-cancel" rel="nofollow" href="javascript:void(0)" role="button" aria-label="Cancel"></a></div></div></div><div class="lb-dataContainer"><div class="lb-data"><div class="lb-details"><span class="lb-caption"></span><span class="lb-description"></span><span class="lb-number"></span></div><div class="lb-closeContainer"><a class="lb-close" rel="nofollow" href="javascript:void(0)" role="button" aria-label="Close"></a></div></div></div></div>').appendTo($('body'));
116
117    // Cache jQuery objects
118    this.$lightbox       = $('#lightbox');
119    this.$overlay        = $('#lightboxOverlay');
120    this.$outerContainer = this.$lightbox.find('.lb-outerContainer');
121    this.$container      = this.$lightbox.find('.lb-container');
122    this.$image          = this.$lightbox.find('.lb-image');
123    this.$nav            = this.$lightbox.find('.lb-nav');
124
125    // Store css values for future lookup
126    this.containerPadding = {
127      top: parseInt(this.$container.css('padding-top'), 10),
128      right: parseInt(this.$container.css('padding-right'), 10),
129      bottom: parseInt(this.$container.css('padding-bottom'), 10),
130      left: parseInt(this.$container.css('padding-left'), 10)
131    };
132
133    this.imageBorderWidth = {
134      top: parseInt(this.$image.css('border-top-width'), 10),
135      right: parseInt(this.$image.css('border-right-width'), 10),
136      bottom: parseInt(this.$image.css('border-bottom-width'), 10),
137      left: parseInt(this.$image.css('border-left-width'), 10)
138    };
139
140    // Attach event handlers to the newly minted DOM elements
141    this.$overlay.hide().on('click', function() {
142      self.end();
143      return false;
144    });
145
146    this.$lightbox.hide().on('click', function(event) {
147      if ($(event.target).attr('id') === 'lightbox') {
148        self.end();
149      }
150    });
151
152    this.$outerContainer.on('click', function(event) {
153      if ($(event.target).attr('id') === 'lightbox') {
154        self.end();
155      }
156      return false;
157    });
158
159    this.$lightbox.find('.lb-prev').on('click', function() {
160      if (self.currentImageIndex === 0) {
161        self.changeImage(self.album.length - 1);
162      } else {
163        self.changeImage(self.currentImageIndex - 1);
164      }
165      return false;
166    });
167
168    this.$lightbox.find('.lb-next').on('click', function() {
169      if (self.currentImageIndex === self.album.length - 1) {
170        self.changeImage(0);
171      } else {
172        self.changeImage(self.currentImageIndex + 1);
173      }
174      return false;
175    });
176
177    /*
178      Show context menu for image on right-click
179
180      There is a div containing the navigation that spans the entire image and lives above of it. If
181      you right-click, you are right clicking this div and not the image. This prevents users from
182      saving the image or using other context menu actions with the image.
183
184      To fix this, when we detect the right mouse button is pressed down, but not yet clicked, we
185      set pointer-events to none on the nav div. This is so that the upcoming right-click event on
186      the next mouseup will bubble down to the image. Once the right-click/contextmenu event occurs
187      we set the pointer events back to auto for the nav div so it can capture hover and left-click
188      events as usual.
189     */
190    this.$nav.on('mousedown', function(event) {
191      if (event.which === 3) {
192        self.$nav.css('pointer-events', 'none');
193
194        self.$lightbox.one('contextmenu', function() {
195          setTimeout(function() {
196              this.$nav.css('pointer-events', 'auto');
197          }.bind(self), 0);
198        });
199      }
200    });
201
202
203    this.$lightbox.find('.lb-loader, .lb-close').on('click keyup', function(e) {
204      // If mouse click OR 'enter' or 'space' keypress, close LB
205      if (
206        e.type === 'click' || (e.type === 'keyup' && (e.which === 13 || e.which === 32))) {
207        self.end();
208        return false;
209      }
210    });
211  };
212
213  // Show overlay and lightbox. If the image is part of a set, add siblings to album array.
214  Lightbox.prototype.start = function($link) {
215    var self    = this;
216    var $window = $(window);
217    
218    self.openingElement = document.activeElement;
219
220    $window.on('resize', $.proxy(this.sizeOverlay, this));
221
222    this.sizeOverlay();
223
224    this.album = [];
225    var imageNumber = 0;
226
227    function addToAlbum($link) {
228      self.album.push({
229        alt: $link.attr('data-alt'),
230        link: $link.attr('href'),
231        title: $link.attr('data-title') || $link.attr('title'),
232        description: $link.attr('data-description')
233      });
234    }
235
236    // Support both data-lightbox attribute and rel attribute implementations
237    var dataLightboxValue = $link.attr('data-lightbox');
238    var $links;
239
240    if (dataLightboxValue) {
241      $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]');
242      for (var i = 0; i < $links.length; i = ++i) {
243        addToAlbum($($links[i]));
244        if ($links[i] === $link[0]) {
245          imageNumber = i;
246        }
247      }
248    } else {
249      if ($link.attr('rel') === 'lightbox') {
250        // If image is not part of a set
251        addToAlbum($link);
252      } else {
253        // If image is part of a set
254        $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]');
255        for (var j = 0; j < $links.length; j = ++j) {
256          addToAlbum($($links[j]));
257          if ($links[j] === $link[0]) {
258            imageNumber = j;
259          }
260        }
261      }
262    }
263
264    // Position Lightbox
265    var top  = $window.scrollTop() + this.options.positionFromTop;
266    var left = $window.scrollLeft();
267    this.$lightbox.css({
268      top: top + 'px',
269      left: left + 'px'
270    }).fadeIn(this.options.fadeDuration);
271
272    // Disable scrolling of the page while open
273    if (this.options.disableScrolling) {
274      $('body').addClass('lb-disable-scrolling');
275    }
276
277    this.changeImage(imageNumber);
278  };
279
280  // Hide most UI elements in preparation for the animated resizing of the lightbox.
281  Lightbox.prototype.changeImage = function(imageNumber) {
282    var self = this;
283    var filename = this.album[imageNumber].link;
284    var filetype = filename.split('.').slice(-1)[0];
285    var $image = this.$lightbox.find('.lb-image');
286
287    // Disable keyboard nav during transitions
288    this.disableKeyboardNav();
289
290    // Show loading state
291    this.$overlay.fadeIn(this.options.fadeDuration);
292    $('.lb-loader').fadeIn('slow');
293    this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption, .lb-instContainer').hide();
294    this.$outerContainer.addClass('animating');
295
296    // When image to show is preloaded, we send the width and height to sizeContainer()
297    var preloader = new Image();
298    preloader.onload = function() {
299      var $preloader;
300      var imageHeight;
301      var imageWidth;
302      var maxImageHeight;
303      var maxImageWidth;
304      var windowHeight;
305      var windowWidth;
306
307      $image.attr({
308        'alt': self.album[imageNumber].alt,
309        'src': filename
310      });
311
312      $preloader = $(preloader);
313
314      $image.width(preloader.width);
315      $image.height(preloader.height);
316
317      var aspectRatio = preloader.width / preloader.height;
318
319      windowWidth = $(window).width();
320      windowHeight = $(window).height();
321
322      // Calculate the max image dimensions for the current viewport.
323      // Take into account the border around the image and an additional 10px gutter on each side.
324      maxImageWidth  = windowWidth - self.containerPadding.left - self.containerPadding.right - self.imageBorderWidth.left - self.imageBorderWidth.right - 20;
325      maxImageHeight = windowHeight - self.containerPadding.top - self.containerPadding.bottom - self.imageBorderWidth.top - self.imageBorderWidth.bottom - self.options.positionFromTop - 70;
326
327      /*
328      Since many SVGs have small intrinsic dimensions, but they support scaling
329      up without quality loss because of their vector format, max out their
330      size inside the viewport.
331      */
332      if (filetype === 'svg') {
333        if (aspectRatio >= 1) {
334          imageWidth = maxImageWidth;
335          imageHeight = parseInt(maxImageWidth / aspectRatio, 10);
336        } else {
337          imageWidth = parseInt(maxImageHeight / aspectRatio, 10);
338          imageHeight = maxImageHeight;
339        }
340        $image.width(imageWidth);
341        $image.height(imageHeight);
342
343      } else {
344
345        // Fit image inside the viewport.
346        if (self.options.fitImagesInViewport) {
347
348          // Check if image size is larger then maxWidth|maxHeight in settings
349          if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) {
350            maxImageWidth = self.options.maxWidth;
351          }
352          if (self.options.maxHeight && self.options.maxHeight < maxImageHeight) {
353            maxImageHeight = self.options.maxHeight;
354          }
355
356        } else {
357          maxImageWidth = self.options.maxWidth || preloader.width || maxImageWidth;
358          maxImageHeight = self.options.maxHeight || preloader.height || maxImageHeight;
359        }
360
361        // Is the current image's width or height is greater than the maxImageWidth or maxImageHeight
362        // option than we need to size down while maintaining the aspect ratio.
363        if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) {
364          if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) {
365            imageWidth  = maxImageWidth;
366            imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10);
367            $image.width(imageWidth);
368            $image.height(imageHeight);
369          } else {
370            imageHeight = maxImageHeight;
371            imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10);
372            $image.width(imageWidth);
373            $image.height(imageHeight);
374          }
375        }
376      }
377
378      self.sizeContainer($image.width(), $image.height());
379    };
380
381    // Preload image before showing
382    preloader.src = this.album[imageNumber].link;
383    this.currentImageIndex = imageNumber;
384  };
385
386  // Stretch overlay to fit the viewport
387  Lightbox.prototype.sizeOverlay = function() {
388    var self = this;
389    /*
390    We use a setTimeout 0 to pause JS execution and let the rendering catch-up.
391    Why do this? If the `disableScrolling` option is set to true, a class is added to the body
392    tag that disables scrolling and hides the scrollbar. We want to make sure the scrollbar is
393    hidden before we measure the document width, as the presence of the scrollbar will affect the
394    number.
395    */
396    setTimeout(function() {
397      self.$overlay
398        .width($(document).width())
399        .height($(document).height());
400
401    }, 0);
402  };
403
404  // Animate the size of the lightbox to fit the image we are showing
405  // This method also shows the the image.
406  Lightbox.prototype.sizeContainer = function(imageWidth, imageHeight) {
407    var self = this;
408
409    var oldWidth  = this.$outerContainer.outerWidth();
410    var oldHeight = this.$outerContainer.outerHeight();
411    var newWidth  = imageWidth + this.containerPadding.left + this.containerPadding.right + this.imageBorderWidth.left + this.imageBorderWidth.right;
412    var newHeight = imageHeight + this.containerPadding.top + this.containerPadding.bottom + this.imageBorderWidth.top + this.imageBorderWidth.bottom;
413
414    function postResize() {
415      self.$lightbox.find('.lb-instContainer').width(newWidth);
416      self.$lightbox.find('.lb-dataContainer').width(newWidth);
417      self.$lightbox.find('.lb-prevLink').height(newHeight);
418      self.$lightbox.find('.lb-nextLink').height(newHeight);
419
420      // Set focus on one of the two root nodes so keyboard events are captured.
421      self.$overlay.trigger('focus');
422
423      self.showImage();
424    }
425
426    if (oldWidth !== newWidth || oldHeight !== newHeight) {
427      this.$outerContainer.animate({
428        width: newWidth,
429        height: newHeight
430      }, this.options.resizeDuration, 'swing', function() {
431        postResize();
432      });
433    } else {
434      postResize();
435    }
436  };
437
438  // Display the image and its details and begin preload neighboring images.
439  Lightbox.prototype.showImage = function() {
440    this.$lightbox.find('.lb-loader').stop(true).hide();
441    this.$lightbox.find('.lb-image').fadeIn(this.options.imageFadeDuration);
442
443    this.updateNav();
444    this.updateDetails();
445    this.preloadNeighboringImages();
446    this.enableKeyboardNav();
447  };
448
449  // Display previous and next navigation if appropriate.
450  Lightbox.prototype.updateNav = function() {
451    // Check to see if the browser supports touch events. If so, we take the conservative approach
452    // and assume that mouse hover events are not supported and always show prev/next navigation
453    // arrows in image sets.
454    var alwaysShowNav = false;
455    try {
456      document.createEvent('TouchEvent');
457      alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false;
458    } catch (e) {}
459
460    this.$lightbox.find('.lb-nav').show();
461
462    if (this.album.length > 1) {
463      if (this.options.wrapAround) {
464        if (alwaysShowNav) {
465          this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1');
466        }
467        this.$lightbox.find('.lb-prev, .lb-next').show();
468      } else {
469        if (this.currentImageIndex > 0) {
470          this.$lightbox.find('.lb-prev').show();
471          if (alwaysShowNav) {
472            this.$lightbox.find('.lb-prev').css('opacity', '1');
473          }
474        }
475        if (this.currentImageIndex < this.album.length - 1) {
476          this.$lightbox.find('.lb-next').show();
477          if (alwaysShowNav) {
478            this.$lightbox.find('.lb-next').css('opacity', '1');
479          }
480        }
481      }
482    }
483  };
484
485  // Display caption, description, image number, and closing button.
486  Lightbox.prototype.updateDetails = function() {
487    var self = this;
488
489    // Enable anchor clicks in the injected caption html.
490    // Thanks Nate Wright for the fix. @https://github.com/NateWr
491    if ((typeof this.album[this.currentImageIndex].title !== 'undefined'
492      && this.album[this.currentImageIndex].title !== '')
493      || (typeof this.album[this.currentImageIndex].description !== 'undefined'
494      && this.album[this.currentImageIndex].description !== '')) 
495    {
496      var $caption = this.$lightbox.find('.lb-caption');
497      var $description = this.$lightbox.find('.lb-description');
498      if (this.options.sanitizeTitle) {
499        $caption.text(this.album[this.currentImageIndex].title);
500        $description.text(this.album[this.currentImageIndex].description);
501      } else {
502        $caption.html(this.album[this.currentImageIndex].title);
503        $description.html(this.album[this.currentImageIndex].description);
504      }
505      $caption.fadeIn('fast');
506      $description.fadeIn('fast');
507    }
508
509    if (this.album.length > 1 && this.options.showImageNumberLabel) {
510      var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length);
511      this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast');
512    } else {
513      this.$lightbox.find('.lb-number').hide();
514    }
515
516    this.$outerContainer.removeClass('animating');
517
518    this.$lightbox.find('.lb-dataContainer,.lb-instContainer').fadeIn(this.options.resizeDuration, function() {
519      return self.sizeOverlay();
520    });
521  };
522
523  // Preload previous and next images in set.
524  Lightbox.prototype.preloadNeighboringImages = function() {
525    if (this.album.length > this.currentImageIndex + 1) {
526      var preloadNext = new Image();
527      preloadNext.src = this.album[this.currentImageIndex + 1].link;
528    }
529    if (this.currentImageIndex > 0) {
530      var preloadPrev = new Image();
531      preloadPrev.src = this.album[this.currentImageIndex - 1].link;
532    }
533  };
534
535  Lightbox.prototype.enableKeyboardNav = function() {
536    this.$lightbox.on('keydown.keyboard', $.proxy(this.keyboardAction, this));
537    this.$overlay.on('keydown.keyboard', $.proxy(this.keyboardAction, this));
538  };
539
540  Lightbox.prototype.disableKeyboardNav = function() {
541    this.$lightbox.off('.keyboard');
542    this.$overlay.off('.keyboard');
543  };
544
545  Lightbox.prototype.keyboardAction = function(event) {
546    var KEYCODE_ESC        = 27;
547    var KEYCODE_LEFTARROW  = 37;
548    var KEYCODE_RIGHTARROW = 39;
549    var KEYCODE_TAB        = 9;
550
551    var keycode = event.keyCode;
552    if (keycode === KEYCODE_ESC) {
553      // Prevent bubbling so as to not affect other components on the page.
554      event.stopPropagation();
555      this.end();
556    } else if (keycode === KEYCODE_LEFTARROW) {
557      if (this.currentImageIndex !== 0) {
558        this.changeImage(this.currentImageIndex - 1);
559      } else if (this.options.wrapAround && this.album.length > 1) {
560        this.changeImage(this.album.length - 1);
561      }
562    } else if (keycode === KEYCODE_RIGHTARROW) {
563      if (this.currentImageIndex !== this.album.length - 1) {
564        this.changeImage(this.currentImageIndex + 1);
565      } else if (this.options.wrapAround && this.album.length > 1) {
566        this.changeImage(0);
567      }
568    } else if (keycode === KEYCODE_TAB) {
569      // Prevent bubbling so as to not affect other components on the page.
570      event.stopPropagation();
571      event.preventDefault();
572      return false;
573    }
574  };
575
576  // Closing time. :-(
577  Lightbox.prototype.end = function() {
578    this.disableKeyboardNav();
579    $(window).off('resize', this.sizeOverlay);
580    this.$lightbox.fadeOut(this.options.fadeDuration);
581    this.$overlay.fadeOut(this.options.fadeDuration);
582
583    if (this.options.disableScrolling) {
584      $('body').removeClass('lb-disable-scrolling');
585    }
586    
587    if (this.openingElement !== null)
588    {
589        this.openingElement.focus();
590        this.openingElement = null;
591    }
592  };
593
594  return new Lightbox();
595}));

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.