GPQAPP
668 строк · 24.4 Кб
1/*!
2* Lightbox for Bootstrap by @ashleydw
3* https://github.com/ashleydw/lightbox
4*
5* License: https://github.com/ashleydw/lightbox/blob/master/LICENSE
6*/
7+function ($) {8
9'use strict';10
11var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();12
13function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }14
15var Lightbox = (function ($) {16
17var NAME = 'ekkoLightbox';18var JQUERY_NO_CONFLICT = $.fn[NAME];19
20var Default = {21title: '',22footer: '',23maxWidth: 9999,24maxHeight: 9999,25showArrows: true, //display the left / right arrows or not26wrapping: true, //if true, gallery loops infinitely27type: null, //force the lightbox into image / youtube mode. if null, or not image|youtube|vimeo; detect it28alwaysShowClose: false, //always show the close button, even if there is no title29loadingMessage: '<div class="ekko-lightbox-loader"><div><div></div><div></div></div></div>', // http://tobiasahlin.com/spinkit/30leftArrow: '<span>❮</span>',31rightArrow: '<span>❯</span>',32strings: {33close: 'Close',34fail: 'Failed to load image:',35type: 'Could not detect remote target type. Force the type using data-type'36},37doc: document, // if in an iframe can specify top.document38onShow: function onShow() {},39onShown: function onShown() {},40onHide: function onHide() {},41onHidden: function onHidden() {},42onNavigate: function onNavigate() {},43onContentLoaded: function onContentLoaded() {}44};45
46var Lightbox = (function () {47_createClass(Lightbox, null, [{48key: 'Default',49
50/**51Class properties:
52_$element: null -> the <a> element currently being displayed
53_$modal: The bootstrap modal generated
54_$modalDialog: The .modal-dialog
55_$modalContent: The .modal-content
56_$modalBody: The .modal-body
57_$modalHeader: The .modal-header
58_$modalFooter: The .modal-footer
59_$lightboxContainerOne: Container of the first lightbox element
60_$lightboxContainerTwo: Container of the second lightbox element
61_$lightboxBody: First element in the container
62_$modalArrows: The overlayed arrows container
63_$galleryItems: Other <a>'s available for this gallery
64_galleryName: Name of the current data('gallery') showing
65_galleryIndex: The current index of the _$galleryItems being shown
66_config: {} the options for the modal
67_modalId: unique id for the current lightbox
68_padding / _border: CSS properties for the modal container; these are used to calculate the available space for the content
69*/
70
71get: function get() {72return Default;73}74}]);75
76function Lightbox($element, config) {77var _this = this;78
79_classCallCheck(this, Lightbox);80
81this._config = $.extend({}, Default, config);82this._$modalArrows = null;83this._galleryIndex = 0;84this._galleryName = null;85this._padding = null;86this._border = null;87this._titleIsShown = false;88this._footerIsShown = false;89this._wantedWidth = 0;90this._wantedHeight = 0;91this._touchstartX = 0;92this._touchendX = 0;93
94this._modalId = 'ekkoLightbox-' + Math.floor(Math.random() * 1000 + 1);95this._$element = $element instanceof jQuery ? $element : $($element);96
97this._isBootstrap3 = $.fn.modal.Constructor.VERSION[0] == 3;98
99var h4 = '<h4 class="modal-title">' + (this._config.title || " ") + '</h4>';100var btn = '<button type="button" class="close" data-dismiss="modal" aria-label="' + this._config.strings.close + '"><span aria-hidden="true">×</span></button>';101
102var header = '<div class="modal-header' + (this._config.title || this._config.alwaysShowClose ? '' : ' hide') + '">' + (this._isBootstrap3 ? btn + h4 : h4 + btn) + '</div>';103var footer = '<div class="modal-footer' + (this._config.footer ? '' : ' hide') + '">' + (this._config.footer || " ") + '</div>';104var body = '<div class="modal-body"><div class="ekko-lightbox-container"><div class="ekko-lightbox-item fade in show"></div><div class="ekko-lightbox-item fade"></div></div></div>';105var dialog = '<div class="modal-dialog" role="document"><div class="modal-content">' + header + body + footer + '</div></div>';106$(this._config.doc.body).append('<div id="' + this._modalId + '" class="ekko-lightbox modal fade" tabindex="-1" tabindex="-1" role="dialog" aria-hidden="true">' + dialog + '</div>');107
108this._$modal = $('#' + this._modalId, this._config.doc);109this._$modalDialog = this._$modal.find('.modal-dialog').first();110this._$modalContent = this._$modal.find('.modal-content').first();111this._$modalBody = this._$modal.find('.modal-body').first();112this._$modalHeader = this._$modal.find('.modal-header').first();113this._$modalFooter = this._$modal.find('.modal-footer').first();114
115this._$lightboxContainer = this._$modalBody.find('.ekko-lightbox-container').first();116this._$lightboxBodyOne = this._$lightboxContainer.find('> div:first-child').first();117this._$lightboxBodyTwo = this._$lightboxContainer.find('> div:last-child').first();118
119this._border = this._calculateBorders();120this._padding = this._calculatePadding();121
122this._galleryName = this._$element.data('gallery');123if (this._galleryName) {124this._$galleryItems = $(document.body).find('*[data-gallery="' + this._galleryName + '"]');125this._galleryIndex = this._$galleryItems.index(this._$element);126$(document).on('keydown.ekkoLightbox', this._navigationalBinder.bind(this));127
128// add the directional arrows to the modal129if (this._config.showArrows && this._$galleryItems.length > 1) {130this._$lightboxContainer.append('<div class="ekko-lightbox-nav-overlay"><a href="#">' + this._config.leftArrow + '</a><a href="#">' + this._config.rightArrow + '</a></div>');131this._$modalArrows = this._$lightboxContainer.find('div.ekko-lightbox-nav-overlay').first();132this._$lightboxContainer.on('click', 'a:first-child', function (event) {133event.preventDefault();134return _this.navigateLeft();135});136this._$lightboxContainer.on('click', 'a:last-child', function (event) {137event.preventDefault();138return _this.navigateRight();139});140this.updateNavigation();141}142}143
144this._$modal.on('show.bs.modal', this._config.onShow.bind(this)).on('shown.bs.modal', function () {145_this._toggleLoading(true);146_this._handle();147return _this._config.onShown.call(_this);148}).on('hide.bs.modal', this._config.onHide.bind(this)).on('hidden.bs.modal', function () {149if (_this._galleryName) {150$(document).off('keydown.ekkoLightbox');151$(window).off('resize.ekkoLightbox');152}153_this._$modal.remove();154return _this._config.onHidden.call(_this);155}).modal(this._config);156
157$(window).on('resize.ekkoLightbox', function () {158_this._resize(_this._wantedWidth, _this._wantedHeight);159});160this._$lightboxContainer.on('touchstart', function () {161_this._touchstartX = event.changedTouches[0].screenX;162}).on('touchend', function () {163_this._touchendX = event.changedTouches[0].screenX;164_this._swipeGesure();165});166}167
168_createClass(Lightbox, [{169key: 'element',170value: function element() {171return this._$element;172}173}, {174key: 'modal',175value: function modal() {176return this._$modal;177}178}, {179key: 'navigateTo',180value: function navigateTo(index) {181
182if (index < 0 || index > this._$galleryItems.length - 1) return this;183
184this._galleryIndex = index;185
186this.updateNavigation();187
188this._$element = $(this._$galleryItems.get(this._galleryIndex));189this._handle();190}191}, {192key: 'navigateLeft',193value: function navigateLeft() {194
195if (!this._$galleryItems) return;196
197if (this._$galleryItems.length === 1) return;198
199if (this._galleryIndex === 0) {200if (this._config.wrapping) this._galleryIndex = this._$galleryItems.length - 1;else return;201} else //circular202this._galleryIndex--;203
204this._config.onNavigate.call(this, 'left', this._galleryIndex);205return this.navigateTo(this._galleryIndex);206}207}, {208key: 'navigateRight',209value: function navigateRight() {210
211if (!this._$galleryItems) return;212
213if (this._$galleryItems.length === 1) return;214
215if (this._galleryIndex === this._$galleryItems.length - 1) {216if (this._config.wrapping) this._galleryIndex = 0;else return;217} else //circular218this._galleryIndex++;219
220this._config.onNavigate.call(this, 'right', this._galleryIndex);221return this.navigateTo(this._galleryIndex);222}223}, {224key: 'updateNavigation',225value: function updateNavigation() {226if (!this._config.wrapping) {227var $nav = this._$lightboxContainer.find('div.ekko-lightbox-nav-overlay');228if (this._galleryIndex === 0) $nav.find('a:first-child').addClass('disabled');else $nav.find('a:first-child').removeClass('disabled');229
230if (this._galleryIndex === this._$galleryItems.length - 1) $nav.find('a:last-child').addClass('disabled');else $nav.find('a:last-child').removeClass('disabled');231}232}233}, {234key: 'close',235value: function close() {236return this._$modal.modal('hide');237}238
239// helper private methods240}, {241key: '_navigationalBinder',242value: function _navigationalBinder(event) {243event = event || window.event;244if (event.keyCode === 39) return this.navigateRight();245if (event.keyCode === 37) return this.navigateLeft();246}247
248// type detection private methods249}, {250key: '_detectRemoteType',251value: function _detectRemoteType(src, type) {252
253type = type || false;254
255if (!type && this._isImage(src)) type = 'image';256if (!type && this._getYoutubeId(src)) type = 'youtube';257if (!type && this._getVimeoId(src)) type = 'vimeo';258if (!type && this._getInstagramId(src)) type = 'instagram';259
260if (!type || ['image', 'youtube', 'vimeo', 'instagram', 'video', 'url'].indexOf(type) < 0) type = 'url';261
262return type;263}264}, {265key: '_isImage',266value: function _isImage(string) {267return string && string.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i);268}269}, {270key: '_containerToUse',271value: function _containerToUse() {272var _this2 = this;273
274// if currently showing an image, fade it out and remove275var $toUse = this._$lightboxBodyTwo;276var $current = this._$lightboxBodyOne;277
278if (this._$lightboxBodyTwo.hasClass('in')) {279$toUse = this._$lightboxBodyOne;280$current = this._$lightboxBodyTwo;281}282
283$current.removeClass('in show');284setTimeout(function () {285if (!_this2._$lightboxBodyTwo.hasClass('in')) _this2._$lightboxBodyTwo.empty();286if (!_this2._$lightboxBodyOne.hasClass('in')) _this2._$lightboxBodyOne.empty();287}, 500);288
289$toUse.addClass('in show');290return $toUse;291}292}, {293key: '_handle',294value: function _handle() {295
296var $toUse = this._containerToUse();297this._updateTitleAndFooter();298
299var currentRemote = this._$element.attr('data-remote') || this._$element.attr('href');300var currentType = this._detectRemoteType(currentRemote, this._$element.attr('data-type') || false);301
302if (['image', 'youtube', 'vimeo', 'instagram', 'video', 'url'].indexOf(currentType) < 0) return this._error(this._config.strings.type);303
304switch (currentType) {305case 'image':306this._preloadImage(currentRemote, $toUse);307this._preloadImageByIndex(this._galleryIndex, 3);308break;309case 'youtube':310this._showYoutubeVideo(currentRemote, $toUse);311break;312case 'vimeo':313this._showVimeoVideo(this._getVimeoId(currentRemote), $toUse);314break;315case 'instagram':316this._showInstagramVideo(this._getInstagramId(currentRemote), $toUse);317break;318case 'video':319this._showHtml5Video(currentRemote, $toUse);320break;321default:322// url323this._loadRemoteContent(currentRemote, $toUse);324break;325}326
327return this;328}329}, {330key: '_getYoutubeId',331value: function _getYoutubeId(string) {332if (!string) return false;333var matches = string.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/);334return matches && matches[2].length === 11 ? matches[2] : false;335}336}, {337key: '_getVimeoId',338value: function _getVimeoId(string) {339return string && string.indexOf('vimeo') > 0 ? string : false;340}341}, {342key: '_getInstagramId',343value: function _getInstagramId(string) {344return string && string.indexOf('instagram') > 0 ? string : false;345}346
347// layout private methods348}, {349key: '_toggleLoading',350value: function _toggleLoading(show) {351show = show || false;352if (show) {353this._$modalDialog.css('display', 'none');354this._$modal.removeClass('in show');355$('.modal-backdrop').append(this._config.loadingMessage);356} else {357this._$modalDialog.css('display', 'block');358this._$modal.addClass('in show');359$('.modal-backdrop').find('.ekko-lightbox-loader').remove();360}361return this;362}363}, {364key: '_calculateBorders',365value: function _calculateBorders() {366return {367top: this._totalCssByAttribute('border-top-width'),368right: this._totalCssByAttribute('border-right-width'),369bottom: this._totalCssByAttribute('border-bottom-width'),370left: this._totalCssByAttribute('border-left-width')371};372}373}, {374key: '_calculatePadding',375value: function _calculatePadding() {376return {377top: this._totalCssByAttribute('padding-top'),378right: this._totalCssByAttribute('padding-right'),379bottom: this._totalCssByAttribute('padding-bottom'),380left: this._totalCssByAttribute('padding-left')381};382}383}, {384key: '_totalCssByAttribute',385value: function _totalCssByAttribute(attribute) {386return parseInt(this._$modalDialog.css(attribute), 10) + parseInt(this._$modalContent.css(attribute), 10) + parseInt(this._$modalBody.css(attribute), 10);387}388}, {389key: '_updateTitleAndFooter',390value: function _updateTitleAndFooter() {391var title = this._$element.data('title') || "";392var caption = this._$element.data('footer') || "";393
394this._titleIsShown = false;395if (title || this._config.alwaysShowClose) {396this._titleIsShown = true;397this._$modalHeader.css('display', '').find('.modal-title').html(title || " ");398} else this._$modalHeader.css('display', 'none');399
400this._footerIsShown = false;401if (caption) {402this._footerIsShown = true;403this._$modalFooter.css('display', '').html(caption);404} else this._$modalFooter.css('display', 'none');405
406return this;407}408}, {409key: '_showYoutubeVideo',410value: function _showYoutubeVideo(remote, $containerForElement) {411var id = this._getYoutubeId(remote);412var query = remote.indexOf('&') > 0 ? remote.substr(remote.indexOf('&')) : '';413var width = this._$element.data('width') || 560;414var height = this._$element.data('height') || width / (560 / 315);415return this._showVideoIframe('//www.youtube.com/embed/' + id + '?badge=0&autoplay=1&html5=1' + query, width, height, $containerForElement);416}417}, {418key: '_showVimeoVideo',419value: function _showVimeoVideo(id, $containerForElement) {420var width = this._$element.data('width') || 500;421var height = this._$element.data('height') || width / (560 / 315);422return this._showVideoIframe(id + '?autoplay=1', width, height, $containerForElement);423}424}, {425key: '_showInstagramVideo',426value: function _showInstagramVideo(id, $containerForElement) {427// instagram load their content into iframe's so this can be put straight into the element428var width = this._$element.data('width') || 612;429var height = width + 80;430id = id.substr(-1) !== '/' ? id + '/' : id; // ensure id has trailing slash431$containerForElement.html('<iframe width="' + width + '" height="' + height + '" src="' + id + 'embed/" frameborder="0" allowfullscreen></iframe>');432this._resize(width, height);433this._config.onContentLoaded.call(this);434if (this._$modalArrows) //hide the arrows when showing video435this._$modalArrows.css('display', 'none');436this._toggleLoading(false);437return this;438}439}, {440key: '_showVideoIframe',441value: function _showVideoIframe(url, width, height, $containerForElement) {442// should be used for videos only. for remote content use loadRemoteContent (data-type=url)443height = height || width; // default to square444$containerForElement.html('<div class="embed-responsive embed-responsive-16by9"><iframe width="' + width + '" height="' + height + '" src="' + url + '" frameborder="0" allowfullscreen class="embed-responsive-item"></iframe></div>');445this._resize(width, height);446this._config.onContentLoaded.call(this);447if (this._$modalArrows) this._$modalArrows.css('display', 'none'); //hide the arrows when showing video448this._toggleLoading(false);449return this;450}451}, {452key: '_showHtml5Video',453value: function _showHtml5Video(url, $containerForElement) {454// should be used for videos only. for remote content use loadRemoteContent (data-type=url)455var width = this._$element.data('width') || 560;456var height = this._$element.data('height') || width / (560 / 315);457$containerForElement.html('<div class="embed-responsive embed-responsive-16by9"><video width="' + width + '" height="' + height + '" src="' + url + '" preload="auto" autoplay controls class="embed-responsive-item"></video></div>');458this._resize(width, height);459this._config.onContentLoaded.call(this);460if (this._$modalArrows) this._$modalArrows.css('display', 'none'); //hide the arrows when showing video461this._toggleLoading(false);462return this;463}464}, {465key: '_loadRemoteContent',466value: function _loadRemoteContent(url, $containerForElement) {467var _this3 = this;468
469var width = this._$element.data('width') || 560;470var height = this._$element.data('height') || 560;471
472var disableExternalCheck = this._$element.data('disableExternalCheck') || false;473this._toggleLoading(false);474
475// external urls are loading into an iframe476// local ajax can be loaded into the container itself477if (!disableExternalCheck && !this._isExternal(url)) {478$containerForElement.load(url, $.proxy(function () {479return _this3._$element.trigger('loaded.bs.modal');l;480}));481} else {482$containerForElement.html('<iframe src="' + url + '" frameborder="0" allowfullscreen></iframe>');483this._config.onContentLoaded.call(this);484}485
486if (this._$modalArrows) //hide the arrows when remote content487this._$modalArrows.css('display', 'none');488
489this._resize(width, height);490return this;491}492}, {493key: '_isExternal',494value: function _isExternal(url) {495var match = url.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);496if (typeof match[1] === "string" && match[1].length > 0 && match[1].toLowerCase() !== location.protocol) return true;497
498if (typeof match[2] === "string" && match[2].length > 0 && match[2].replace(new RegExp(':(' + ({499"http:": 80,500"https:": 443501})[location.protocol] + ')?$'), "") !== location.host) return true;502
503return false;504}505}, {506key: '_error',507value: function _error(message) {508console.error(message);509this._containerToUse().html(message);510this._resize(300, 300);511return this;512}513}, {514key: '_preloadImageByIndex',515value: function _preloadImageByIndex(startIndex, numberOfTimes) {516
517if (!this._$galleryItems) return;518
519var next = $(this._$galleryItems.get(startIndex), false);520if (typeof next == 'undefined') return;521
522var src = next.attr('data-remote') || next.attr('href');523if (next.attr('data-type') === 'image' || this._isImage(src)) this._preloadImage(src, false);524
525if (numberOfTimes > 0) return this._preloadImageByIndex(startIndex + 1, numberOfTimes - 1);526}527}, {528key: '_preloadImage',529value: function _preloadImage(src, $containerForImage) {530var _this4 = this;531
532$containerForImage = $containerForImage || false;533
534var img = new Image();535if ($containerForImage) {536(function () {537
538// if loading takes > 200ms show a loader539var loadingTimeout = setTimeout(function () {540$containerForImage.append(_this4._config.loadingMessage);541}, 200);542
543img.onload = function () {544if (loadingTimeout) clearTimeout(loadingTimeout);545loadingTimeout = null;546var image = $('<img />');547image.attr('src', img.src);548image.addClass('img-fluid');549
550// backward compatibility for bootstrap v3551image.css('width', '100%');552
553$containerForImage.html(image);554if (_this4._$modalArrows) _this4._$modalArrows.css('display', ''); // remove display to default to css property555
556_this4._resize(img.width, img.height);557_this4._toggleLoading(false);558return _this4._config.onContentLoaded.call(_this4);559};560img.onerror = function () {561_this4._toggleLoading(false);562return _this4._error(_this4._config.strings.fail + (' ' + src));563};564})();565}566
567img.src = src;568return img;569}570}, {571key: '_swipeGesure',572value: function _swipeGesure() {573if (this._touchendX < this._touchstartX) {574return this.navigateRight();575}576if (this._touchendX > this._touchstartX) {577return this.navigateLeft();578}579}580}, {581key: '_resize',582value: function _resize(width, height) {583
584height = height || width;585this._wantedWidth = width;586this._wantedHeight = height;587
588var imageAspecRatio = width / height;589
590// if width > the available space, scale down the expected width and height591var widthBorderAndPadding = this._padding.left + this._padding.right + this._border.left + this._border.right;592
593// force 10px margin if window size > 575px594var addMargin = this._config.doc.body.clientWidth > 575 ? 20 : 0;595var discountMargin = this._config.doc.body.clientWidth > 575 ? 0 : 20;596
597var maxWidth = Math.min(width + widthBorderAndPadding, this._config.doc.body.clientWidth - addMargin, this._config.maxWidth);598
599if (width + widthBorderAndPadding > maxWidth) {600height = (maxWidth - widthBorderAndPadding - discountMargin) / imageAspecRatio;601width = maxWidth;602} else width = width + widthBorderAndPadding;603
604var headerHeight = 0,605footerHeight = 0;606
607// as the resize is performed the modal is show, the calculate might fail608// if so, default to the default sizes609if (this._footerIsShown) footerHeight = this._$modalFooter.outerHeight(true) || 55;610
611if (this._titleIsShown) headerHeight = this._$modalHeader.outerHeight(true) || 67;612
613var borderPadding = this._padding.top + this._padding.bottom + this._border.bottom + this._border.top;614
615//calculated each time as resizing the window can cause them to change due to Bootstraps fluid margins616var margins = parseFloat(this._$modalDialog.css('margin-top')) + parseFloat(this._$modalDialog.css('margin-bottom'));617
618var maxHeight = Math.min(height, $(window).height() - borderPadding - margins - headerHeight - footerHeight, this._config.maxHeight - borderPadding - headerHeight - footerHeight);619
620if (height > maxHeight) {621// if height > the available height, scale down the width622width = Math.ceil(maxHeight * imageAspecRatio) + widthBorderAndPadding;623}624
625this._$lightboxContainer.css('height', maxHeight);626this._$modalDialog.css('flex', 1).css('maxWidth', width);627
628var modal = this._$modal.data('bs.modal');629if (modal) {630// v4 method is mistakenly protected631try {632modal._handleUpdate();633} catch (Exception) {634modal.handleUpdate();635}636}637return this;638}639}], [{640key: '_jQueryInterface',641value: function _jQueryInterface(config) {642var _this5 = this;643
644config = config || {};645return this.each(function () {646var $this = $(_this5);647var _config = $.extend({}, Lightbox.Default, $this.data(), typeof config === 'object' && config);648
649new Lightbox(_this5, _config);650});651}652}]);653
654return Lightbox;655})();656
657$.fn[NAME] = Lightbox._jQueryInterface;658$.fn[NAME].Constructor = Lightbox;659$.fn[NAME].noConflict = function () {660$.fn[NAME] = JQUERY_NO_CONFLICT;661return Lightbox._jQueryInterface;662};663
664return Lightbox;665})(jQuery);666//# sourceMappingURL=ekko-lightbox.js.map
667
668}(jQuery);669