lightbox.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*!
  2. * Lightbox v2.8.1
  3. * by Lokesh Dhakar
  4. *
  5. * More info:
  6. * http://lokeshdhakar.com/projects/lightbox2/
  7. *
  8. * Copyright 2007, 2015 Lokesh Dhakar
  9. * Released under the MIT license
  10. * https://github.com/lokesh/lightbox2/blob/master/LICENSE
  11. */
  12. // Uses Node, AMD or browser globals to create a module.
  13. (function (root, factory) {
  14. if (typeof define === 'function' && define.amd) {
  15. // AMD. Register as an anonymous module.
  16. define(['jquery'], factory);
  17. } else if (typeof exports === 'object') {
  18. // Node. Does not work with strict CommonJS, but
  19. // only CommonJS-like environments that support module.exports,
  20. // like Node.
  21. module.exports = factory(require('jquery'));
  22. } else {
  23. // Browser globals (root is window)
  24. root.lightbox = factory(root.jQuery);
  25. }
  26. }(this, function ($) {
  27. function Lightbox(options) {
  28. this.album = [];
  29. this.currentImageIndex = void 0;
  30. this.init();
  31. // options
  32. this.options = $.extend({}, this.constructor.defaults);
  33. this.option(options);
  34. }
  35. // Descriptions of all options available on the demo site:
  36. // http://lokeshdhakar.com/projects/lightbox2/index.html#options
  37. Lightbox.defaults = {
  38. albumLabel: 'Image %1 of %2',
  39. alwaysShowNavOnTouchDevices: false,
  40. fadeDuration: 500,
  41. fitImagesInViewport: true,
  42. // maxWidth: 800,
  43. // maxHeight: 600,
  44. positionFromTop: 50,
  45. resizeDuration: 700,
  46. showImageNumberLabel: true,
  47. wrapAround: false
  48. };
  49. Lightbox.prototype.option = function(options) {
  50. $.extend(this.options, options);
  51. };
  52. Lightbox.prototype.imageCountLabel = function(currentImageNum, totalImages) {
  53. return this.options.albumLabel.replace(/%1/g, currentImageNum).replace(/%2/g, totalImages);
  54. };
  55. Lightbox.prototype.init = function() {
  56. this.enable();
  57. this.build();
  58. };
  59. // Loop through anchors and areamaps looking for either data-lightbox attributes or rel attributes
  60. // that contain 'lightbox'. When these are clicked, start lightbox.
  61. Lightbox.prototype.enable = function() {
  62. var self = this;
  63. $('body').on('click', 'a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]', function(event) {
  64. self.start($(event.currentTarget));
  65. return false;
  66. });
  67. };
  68. // Build html for the lightbox and the overlay.
  69. // Attach event handlers to the new DOM elements. click click click
  70. Lightbox.prototype.build = function() {
  71. var self = this;
  72. $('<div id="lightboxOverlay" class="lightboxOverlay"></div><div id="lightbox" class="lightbox"><div class="lb-outerContainer"><div class="lb-container"><img class="lb-image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" /><div class="lb-nav"><a class="lb-prev" href="" ></a><a class="lb-next" href="" ></a></div><div class="lb-loader"><a class="lb-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-number"></span></div><div class="lb-closeContainer"><a class="lb-close"></a></div></div></div></div>').appendTo($('body'));
  73. // Cache jQuery objects
  74. this.$lightbox = $('#lightbox');
  75. this.$overlay = $('#lightboxOverlay');
  76. this.$outerContainer = this.$lightbox.find('.lb-outerContainer');
  77. this.$container = this.$lightbox.find('.lb-container');
  78. // Store css values for future lookup
  79. this.containerTopPadding = parseInt(this.$container.css('padding-top'), 10);
  80. this.containerRightPadding = parseInt(this.$container.css('padding-right'), 10);
  81. this.containerBottomPadding = parseInt(this.$container.css('padding-bottom'), 10);
  82. this.containerLeftPadding = parseInt(this.$container.css('padding-left'), 10);
  83. // Attach event handlers to the newly minted DOM elements
  84. this.$overlay.hide().on('click', function() {
  85. self.end();
  86. return false;
  87. });
  88. this.$lightbox.hide().on('click', function(event) {
  89. if ($(event.target).attr('id') === 'lightbox') {
  90. self.end();
  91. }
  92. return false;
  93. });
  94. this.$outerContainer.on('click', function(event) {
  95. if ($(event.target).attr('id') === 'lightbox') {
  96. self.end();
  97. }
  98. return false;
  99. });
  100. this.$lightbox.find('.lb-prev').on('click', function() {
  101. if (self.currentImageIndex === 0) {
  102. self.changeImage(self.album.length - 1);
  103. } else {
  104. self.changeImage(self.currentImageIndex - 1);
  105. }
  106. return false;
  107. });
  108. this.$lightbox.find('.lb-next').on('click', function() {
  109. if (self.currentImageIndex === self.album.length - 1) {
  110. self.changeImage(0);
  111. } else {
  112. self.changeImage(self.currentImageIndex + 1);
  113. }
  114. return false;
  115. });
  116. this.$lightbox.find('.lb-loader, .lb-close').on('click', function() {
  117. self.end();
  118. return false;
  119. });
  120. };
  121. // Show overlay and lightbox. If the image is part of a set, add siblings to album array.
  122. Lightbox.prototype.start = function($link) {
  123. var self = this;
  124. var $window = $(window);
  125. $window.on('resize', $.proxy(this.sizeOverlay, this));
  126. $('select, object, embed').css({
  127. visibility: 'hidden'
  128. });
  129. this.sizeOverlay();
  130. this.album = [];
  131. var imageNumber = 0;
  132. function addToAlbum($link) {
  133. self.album.push({
  134. link: $link.attr('href'),
  135. title: $link.attr('data-title') || $link.attr('title')
  136. });
  137. }
  138. // Support both data-lightbox attribute and rel attribute implementations
  139. var dataLightboxValue = $link.attr('data-lightbox');
  140. var $links;
  141. if (dataLightboxValue) {
  142. $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]');
  143. for (var i = 0; i < $links.length; i = ++i) {
  144. addToAlbum($($links[i]));
  145. if ($links[i] === $link[0]) {
  146. imageNumber = i;
  147. }
  148. }
  149. } else {
  150. if ($link.attr('rel') === 'lightbox') {
  151. // If image is not part of a set
  152. addToAlbum($link);
  153. } else {
  154. // If image is part of a set
  155. $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]');
  156. for (var j = 0; j < $links.length; j = ++j) {
  157. addToAlbum($($links[j]));
  158. if ($links[j] === $link[0]) {
  159. imageNumber = j;
  160. }
  161. }
  162. }
  163. }
  164. // Position Lightbox
  165. var top = $window.scrollTop() + this.options.positionFromTop;
  166. var left = $window.scrollLeft();
  167. this.$lightbox.css({
  168. top: top + 'px',
  169. left: left + 'px'
  170. }).fadeIn(this.options.fadeDuration);
  171. this.changeImage(imageNumber);
  172. };
  173. // Hide most UI elements in preparation for the animated resizing of the lightbox.
  174. Lightbox.prototype.changeImage = function(imageNumber) {
  175. var self = this;
  176. this.disableKeyboardNav();
  177. var $image = this.$lightbox.find('.lb-image');
  178. this.$overlay.fadeIn(this.options.fadeDuration);
  179. $('.lb-loader').fadeIn('slow');
  180. this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption').hide();
  181. this.$outerContainer.addClass('animating');
  182. // When image to show is preloaded, we send the width and height to sizeContainer()
  183. var preloader = new Image();
  184. preloader.onload = function() {
  185. var $preloader;
  186. var imageHeight;
  187. var imageWidth;
  188. var maxImageHeight;
  189. var maxImageWidth;
  190. var windowHeight;
  191. var windowWidth;
  192. $image.attr('src', self.album[imageNumber].link);
  193. $preloader = $(preloader);
  194. $image.width(preloader.width);
  195. $image.height(preloader.height);
  196. if (self.options.fitImagesInViewport) {
  197. // Fit image inside the viewport.
  198. // Take into account the border around the image and an additional 10px gutter on each side.
  199. windowWidth = $(window).width();
  200. windowHeight = $(window).height();
  201. maxImageWidth = windowWidth - self.containerLeftPadding - self.containerRightPadding - 20;
  202. maxImageHeight = windowHeight - self.containerTopPadding - self.containerBottomPadding - 120;
  203. // Check if image size is larger then maxWidth|maxHeight in settings
  204. if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) {
  205. maxImageWidth = self.options.maxWidth;
  206. }
  207. if (self.options.maxHeight && self.options.maxHeight < maxImageWidth) {
  208. maxImageHeight = self.options.maxHeight;
  209. }
  210. // Is there a fitting issue?
  211. if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) {
  212. if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) {
  213. imageWidth = maxImageWidth;
  214. imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10);
  215. $image.width(imageWidth);
  216. $image.height(imageHeight);
  217. } else {
  218. imageHeight = maxImageHeight;
  219. imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10);
  220. $image.width(imageWidth);
  221. $image.height(imageHeight);
  222. }
  223. }
  224. }
  225. self.sizeContainer($image.width(), $image.height());
  226. };
  227. preloader.src = this.album[imageNumber].link;
  228. this.currentImageIndex = imageNumber;
  229. };
  230. // Stretch overlay to fit the viewport
  231. Lightbox.prototype.sizeOverlay = function() {
  232. this.$overlay
  233. .width($(window).width())
  234. .height($(document).height());
  235. };
  236. // Animate the size of the lightbox to fit the image we are showing
  237. Lightbox.prototype.sizeContainer = function(imageWidth, imageHeight) {
  238. var self = this;
  239. var oldWidth = this.$outerContainer.outerWidth();
  240. var oldHeight = this.$outerContainer.outerHeight();
  241. var newWidth = imageWidth + this.containerLeftPadding + this.containerRightPadding;
  242. var newHeight = imageHeight + this.containerTopPadding + this.containerBottomPadding;
  243. function postResize() {
  244. self.$lightbox.find('.lb-dataContainer').width(newWidth);
  245. self.$lightbox.find('.lb-prevLink').height(newHeight);
  246. self.$lightbox.find('.lb-nextLink').height(newHeight);
  247. self.showImage();
  248. }
  249. if (oldWidth !== newWidth || oldHeight !== newHeight) {
  250. this.$outerContainer.animate({
  251. width: newWidth,
  252. height: newHeight
  253. }, this.options.resizeDuration, 'swing', function() {
  254. postResize();
  255. });
  256. } else {
  257. postResize();
  258. }
  259. };
  260. // Display the image and its details and begin preload neighboring images.
  261. Lightbox.prototype.showImage = function() {
  262. this.$lightbox.find('.lb-loader').stop(true).hide();
  263. this.$lightbox.find('.lb-image').fadeIn('slow');
  264. this.updateNav();
  265. this.updateDetails();
  266. this.preloadNeighboringImages();
  267. this.enableKeyboardNav();
  268. };
  269. // Display previous and next navigation if appropriate.
  270. Lightbox.prototype.updateNav = function() {
  271. // Check to see if the browser supports touch events. If so, we take the conservative approach
  272. // and assume that mouse hover events are not supported and always show prev/next navigation
  273. // arrows in image sets.
  274. var alwaysShowNav = false;
  275. try {
  276. document.createEvent('TouchEvent');
  277. alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false;
  278. } catch (e) {}
  279. this.$lightbox.find('.lb-nav').show();
  280. if (this.album.length > 1) {
  281. if (this.options.wrapAround) {
  282. if (alwaysShowNav) {
  283. this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1');
  284. }
  285. this.$lightbox.find('.lb-prev, .lb-next').show();
  286. } else {
  287. if (this.currentImageIndex > 0) {
  288. this.$lightbox.find('.lb-prev').show();
  289. if (alwaysShowNav) {
  290. this.$lightbox.find('.lb-prev').css('opacity', '1');
  291. }
  292. }
  293. if (this.currentImageIndex < this.album.length - 1) {
  294. this.$lightbox.find('.lb-next').show();
  295. if (alwaysShowNav) {
  296. this.$lightbox.find('.lb-next').css('opacity', '1');
  297. }
  298. }
  299. }
  300. }
  301. };
  302. // Display caption, image number, and closing button.
  303. Lightbox.prototype.updateDetails = function() {
  304. var self = this;
  305. // Enable anchor clicks in the injected caption html.
  306. // Thanks Nate Wright for the fix. @https://github.com/NateWr
  307. if (typeof this.album[this.currentImageIndex].title !== 'undefined' &&
  308. this.album[this.currentImageIndex].title !== '') {
  309. this.$lightbox.find('.lb-caption')
  310. .html(this.album[this.currentImageIndex].title)
  311. .fadeIn('fast')
  312. .find('a').on('click', function(event) {
  313. if ($(this).attr('target') !== undefined) {
  314. window.open($(this).attr('href'), $(this).attr('target'));
  315. } else {
  316. location.href = $(this).attr('href');
  317. }
  318. });
  319. }
  320. if (this.album.length > 1 && this.options.showImageNumberLabel) {
  321. var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length);
  322. this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast');
  323. } else {
  324. this.$lightbox.find('.lb-number').hide();
  325. }
  326. this.$outerContainer.removeClass('animating');
  327. this.$lightbox.find('.lb-dataContainer').fadeIn(this.options.resizeDuration, function() {
  328. return self.sizeOverlay();
  329. });
  330. };
  331. // Preload previous and next images in set.
  332. Lightbox.prototype.preloadNeighboringImages = function() {
  333. if (this.album.length > this.currentImageIndex + 1) {
  334. var preloadNext = new Image();
  335. preloadNext.src = this.album[this.currentImageIndex + 1].link;
  336. }
  337. if (this.currentImageIndex > 0) {
  338. var preloadPrev = new Image();
  339. preloadPrev.src = this.album[this.currentImageIndex - 1].link;
  340. }
  341. };
  342. Lightbox.prototype.enableKeyboardNav = function() {
  343. $(document).on('keyup.keyboard', $.proxy(this.keyboardAction, this));
  344. };
  345. Lightbox.prototype.disableKeyboardNav = function() {
  346. $(document).off('.keyboard');
  347. };
  348. Lightbox.prototype.keyboardAction = function(event) {
  349. var KEYCODE_ESC = 27;
  350. var KEYCODE_LEFTARROW = 37;
  351. var KEYCODE_RIGHTARROW = 39;
  352. var keycode = event.keyCode;
  353. var key = String.fromCharCode(keycode).toLowerCase();
  354. if (keycode === KEYCODE_ESC || key.match(/x|o|c/)) {
  355. this.end();
  356. } else if (key === 'p' || keycode === KEYCODE_LEFTARROW) {
  357. if (this.currentImageIndex !== 0) {
  358. this.changeImage(this.currentImageIndex - 1);
  359. } else if (this.options.wrapAround && this.album.length > 1) {
  360. this.changeImage(this.album.length - 1);
  361. }
  362. } else if (key === 'n' || keycode === KEYCODE_RIGHTARROW) {
  363. if (this.currentImageIndex !== this.album.length - 1) {
  364. this.changeImage(this.currentImageIndex + 1);
  365. } else if (this.options.wrapAround && this.album.length > 1) {
  366. this.changeImage(0);
  367. }
  368. }
  369. };
  370. // Closing time. :-(
  371. Lightbox.prototype.end = function() {
  372. this.disableKeyboardNav();
  373. $(window).off('resize', this.sizeOverlay);
  374. this.$lightbox.fadeOut(this.options.fadeDuration);
  375. this.$overlay.fadeOut(this.options.fadeDuration);
  376. $('select, object, embed').css({
  377. visibility: 'visible'
  378. });
  379. };
  380. return new Lightbox();
  381. }));