You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 line
14KB

  1. (function(root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. define([], factory);
  4. } else if (typeof module === 'object' && module.exports) {
  5. module.exports = factory();
  6. } else {
  7. root.SimpleLightbox = factory();
  8. }
  9. }(this, function() {
  10. function assign(target) {
  11. for (var i = 1; i < arguments.length; i++) {
  12. var obj = arguments[i];
  13. if (obj) {
  14. for (var key in obj) {
  15. obj.hasOwnProperty(key) && (target[key] = obj[key]);
  16. }
  17. }
  18. }
  19. return target;
  20. }
  21. function addClass(element, className) {
  22. if (element && className) {
  23. element.className += ' ' + className;
  24. }
  25. }
  26. function removeClass(element, className) {
  27. if (element && className) {
  28. element.className = element.className.replace(
  29. new RegExp('(\\s|^)' + className + '(\\s|$)'), ' '
  30. ).trim();
  31. }
  32. }
  33. function parseHtml(html) {
  34. var div = document.createElement('div');
  35. div.innerHTML = html.trim();
  36. return div.childNodes[0];
  37. }
  38. function matches(el, selector) {
  39. return (el.matches || el.matchesSelector || el.msMatchesSelector).call(el, selector);
  40. }
  41. function getWindowHeight() {
  42. return 'innerHeight' in window
  43. ? window.innerHeight
  44. : document.documentElement.offsetHeight;
  45. }
  46. function SimpleLightbox(options) {
  47. this.init.apply(this, arguments);
  48. }
  49. SimpleLightbox.defaults = {
  50. // add custom classes to lightbox elements
  51. elementClass: '',
  52. elementLoadingClass: 'slbLoading',
  53. htmlClass: 'slbActive',
  54. closeBtnClass: '',
  55. nextBtnClass: '',
  56. prevBtnClass: '',
  57. loadingTextClass: '',
  58. // customize / localize controls captions
  59. closeBtnCaption: 'Close',
  60. nextBtnCaption: 'Next',
  61. prevBtnCaption: 'Previous',
  62. loadingCaption: 'Loading...',
  63. bindToItems: true, // set click event handler to trigger lightbox on provided $items
  64. closeOnOverlayClick: true,
  65. closeOnEscapeKey: true,
  66. nextOnImageClick: true,
  67. showCaptions: true,
  68. captionAttribute: 'title', // choose data source for library to glean image caption from
  69. urlAttribute: 'href', // where to expect large image
  70. startAt: 0, // start gallery at custom index
  71. loadingTimeout: 100, // time after loading element will appear
  72. appendTarget: 'body', // append elsewhere if needed
  73. beforeSetContent: null, // convenient hooks for extending library behavoiur
  74. beforeClose: null,
  75. afterClose: null,
  76. beforeDestroy: null,
  77. afterDestroy: null,
  78. videoRegex: new RegExp(/youtube.com|vimeo.com/) // regex which tests load url for iframe content
  79. };
  80. assign(SimpleLightbox.prototype, {
  81. init: function(options) {
  82. options = this.options = assign({}, SimpleLightbox.defaults, options);
  83. var self = this;
  84. var elements;
  85. if (options.$items) {
  86. elements = options.$items.get();
  87. }
  88. if (options.elements) {
  89. elements = [].slice.call(
  90. typeof options.elements === 'string'
  91. ? document.querySelectorAll(options.elements)
  92. : options.elements
  93. );
  94. }
  95. this.eventRegistry = {lightbox: [], thumbnails: []};
  96. this.items = [];
  97. this.captions = [];
  98. if (elements) {
  99. elements.forEach(function(element, index) {
  100. self.items.push(element.getAttribute(options.urlAttribute));
  101. self.captions.push(element.getAttribute(options.captionAttribute));
  102. if (options.bindToItems) {
  103. self.addEvent(element, 'click', function(e) {
  104. e.preventDefault();
  105. self.showPosition(index);
  106. }, 'thumbnails');
  107. }
  108. });
  109. }
  110. if (options.items) {
  111. this.items = options.items;
  112. }
  113. if (options.captions) {
  114. this.captions = options.captions;
  115. }
  116. },
  117. addEvent: function(element, eventName, callback, scope) {
  118. this.eventRegistry[scope || 'lightbox'].push({
  119. element: element,
  120. eventName: eventName,
  121. callback: callback
  122. });
  123. element.addEventListener(eventName, callback);
  124. return this;
  125. },
  126. removeEvents: function(scope) {
  127. this.eventRegistry[scope].forEach(function(item) {
  128. item.element.removeEventListener(item.eventName, item.callback);
  129. });
  130. this.eventRegistry[scope] = [];
  131. return this;
  132. },
  133. next: function() {
  134. return this.showPosition(this.currentPosition + 1);
  135. },
  136. prev: function() {
  137. return this.showPosition(this.currentPosition - 1);
  138. },
  139. normalizePosition: function(position) {
  140. if (position >= this.items.length) {
  141. position = 0;
  142. } else if (position < 0) {
  143. position = this.items.length - 1;
  144. }
  145. return position;
  146. },
  147. showPosition: function(position) {
  148. var newPosition = this.normalizePosition(position);
  149. if (typeof this.currentPosition !== 'undefined') {
  150. this.direction = newPosition > this.currentPosition ? 'next' : 'prev';
  151. }
  152. this.currentPosition = newPosition;
  153. return this.setupLightboxHtml()
  154. .prepareItem(this.currentPosition, this.setContent)
  155. .show();
  156. },
  157. loading: function(on) {
  158. var self = this;
  159. var options = this.options;
  160. if (on) {
  161. this.loadingTimeout = setTimeout(function() {
  162. addClass(self.$el, options.elementLoadingClass);
  163. self.$content.innerHTML =
  164. '<p class="slbLoadingText ' + options.loadingTextClass + '">' +
  165. options.loadingCaption +
  166. '</p>';
  167. self.show();
  168. }, options.loadingTimeout);
  169. } else {
  170. removeClass(this.$el, options.elementLoadingClass);
  171. clearTimeout(this.loadingTimeout);
  172. }
  173. },
  174. prepareItem: function(position, callback) {
  175. var self = this;
  176. var url = this.items[position];
  177. this.loading(true);
  178. if (this.options.videoRegex.test(url)) {
  179. callback.call(self, parseHtml(
  180. '<div class="slbIframeCont"><iframe class="slbIframe" frameborder="0" allowfullscreen src="' + url + '"></iframe></div>')
  181. );
  182. } else {
  183. var $imageCont = parseHtml(
  184. '<div class="slbImageWrap"><img class="slbImage" src="' + url + '" /></div>'
  185. );
  186. this.$currentImage = $imageCont.querySelector('.slbImage');
  187. if (this.options.showCaptions && this.captions[position]) {
  188. $imageCont.appendChild(parseHtml(
  189. '<div class="slbCaption">' + this.captions[position] + '</div>')
  190. );
  191. }
  192. this.loadImage(url, function() {
  193. self.setImageDimensions();
  194. callback.call(self, $imageCont);
  195. self.loadImage(self.items[self.normalizePosition(self.currentPosition + 1)]);
  196. });
  197. }
  198. return this;
  199. },
  200. loadImage: function(url, callback) {
  201. if (!this.options.videoRegex.test(url)) {
  202. var image = new Image();
  203. callback && (image.onload = callback);
  204. image.src = url;
  205. }
  206. },
  207. setupLightboxHtml: function() {
  208. var o = this.options;
  209. if (!this.$el) {
  210. this.$el = parseHtml(
  211. '<div class="slbElement ' + o.elementClass + '">' +
  212. '<div class="slbOverlay"></div>' +
  213. '<div class="slbWrapOuter">' +
  214. '<div class="slbWrap">' +
  215. '<div class="slbContentOuter">' +
  216. '<div class="slbContent"></div>' +
  217. '<button type="button" title="' + o.closeBtnCaption + '" class="slbCloseBtn ' + o.closeBtnClass + '">×</button>' +
  218. (this.items.length > 1
  219. ? '<div class="slbArrows">' +
  220. '<button type="button" title="' + o.prevBtnCaption + '" class="prev slbArrow' + o.prevBtnClass + '">' + o.prevBtnCaption + '</button>' +
  221. '<button type="button" title="' + o.nextBtnCaption + '" class="next slbArrow' + o.nextBtnClass + '">' + o.nextBtnCaption + '</button>' +
  222. '</div>'
  223. : ''
  224. ) +
  225. '</div>' +
  226. '</div>' +
  227. '</div>' +
  228. '</div>'
  229. );
  230. this.$content = this.$el.querySelector('.slbContent');
  231. }
  232. this.$content.innerHTML = '';
  233. return this;
  234. },
  235. show: function() {
  236. if (!this.modalInDom) {
  237. document.querySelector(this.options.appendTarget).appendChild(this.$el);
  238. addClass(document.documentElement, this.options.htmlClass);
  239. this.setupLightboxEvents();
  240. this.modalInDom = true;
  241. }
  242. return this;
  243. },
  244. setContent: function(content) {
  245. var $content = typeof content === 'string'
  246. ? parseHtml(content)
  247. : content
  248. ;
  249. this.loading(false);
  250. this.setupLightboxHtml();
  251. removeClass(this.$content, 'slbDirectionNext');
  252. removeClass(this.$content, 'slbDirectionPrev');
  253. if (this.direction) {
  254. addClass(this.$content, this.direction === 'next'
  255. ? 'slbDirectionNext'
  256. : 'slbDirectionPrev'
  257. );
  258. }
  259. if (this.options.beforeSetContent) {
  260. this.options.beforeSetContent($content, this);
  261. }
  262. this.$content.appendChild($content);
  263. return this;
  264. },
  265. setImageDimensions: function() {
  266. if (this.$currentImage) {
  267. this.$currentImage.style.maxHeight = getWindowHeight() + 'px';
  268. }
  269. },
  270. setupLightboxEvents: function() {
  271. var self = this;
  272. if (this.eventRegistry.lightbox.length) {
  273. return this;
  274. }
  275. this.addEvent(this.$el, 'click', function(e) {
  276. var $target = e.target;
  277. if (matches($target, '.slbCloseBtn') || (self.options.closeOnOverlayClick && matches($target, '.slbWrap'))) {
  278. self.close();
  279. } else if (matches($target, '.slbArrow')) {
  280. matches($target, '.next') ? self.next() : self.prev();
  281. } else if (self.options.nextOnImageClick && self.items.length > 1 && matches($target, '.slbImage')) {
  282. self.next();
  283. }
  284. }).addEvent(document, 'keyup', function(e) {
  285. self.options.closeOnEscapeKey && e.keyCode === 27 && self.close();
  286. if (self.items.length > 1) {
  287. (e.keyCode === 39 || e.keyCode === 68) && self.next();
  288. (e.keyCode === 37 || e.keyCode === 65) && self.prev();
  289. }
  290. }).addEvent(window, 'resize', function() {
  291. self.setImageDimensions();
  292. });
  293. return this;
  294. },
  295. close: function() {
  296. if (this.modalInDom) {
  297. this.runHook('beforeClose');
  298. this.removeEvents('lightbox');
  299. this.$el && this.$el.parentNode.removeChild(this.$el);
  300. removeClass(document.documentElement, this.options.htmlClass);
  301. this.modalInDom = false;
  302. this.runHook('afterClose');
  303. }
  304. this.direction = undefined;
  305. this.currentPosition = this.options.startAt;
  306. },
  307. destroy: function() {
  308. this.close();
  309. this.runHook('beforeDestroy');
  310. this.removeEvents('thumbnails');
  311. this.runHook('afterDestroy');
  312. },
  313. runHook: function(name) {
  314. this.options[name] && this.options[name](this);
  315. }
  316. });
  317. SimpleLightbox.open = function(options) {
  318. var instance = new SimpleLightbox(options);
  319. return options.content
  320. ? instance.setContent(options.content).show()
  321. : instance.showPosition(instance.options.startAt);
  322. };
  323. SimpleLightbox.registerAsJqueryPlugin = function($) {
  324. $.fn.simpleLightbox = function(options) {
  325. var lightboxInstance;
  326. var $items = this;
  327. return this.each(function() {
  328. if (!$.data(this, 'simpleLightbox')) {
  329. lightboxInstance = lightboxInstance || new SimpleLightbox($.extend({}, options, {$items: $items}));
  330. $.data(this, 'simpleLightbox', lightboxInstance);
  331. }
  332. });
  333. };
  334. $.SimpleLightbox = SimpleLightbox;
  335. };
  336. if (typeof window !== 'undefined' && window.jQuery) {
  337. SimpleLightbox.registerAsJqueryPlugin(window.jQuery);
  338. }
  339. return SimpleLightbox;
  340. }));