/*! * littleLightBox - jQuery Plugin * version: 0.9 (Tues, 2 Aug 2016) * requires jQuery v1.7 or later * * * Copyright 2016 Sherry Tao - tao_shiyu@hotmail.com * */ (function(win, doc, $, undefined) { "use strict"; var window = $(win), document = $(doc), isQuery = function(obj) { return obj && obj.hasOwnProperty && obj instanceof $; }, lightbox = $.littleLightBox = function () { lightbox.open.apply( this, arguments ); }; $.extend(lightbox, { defaults: { helpers: { }, tpl: { wrap: '', img: '', loading: '', closeBtn: '', prevBtn: '', nextBtn: '', }, padding: 15, minWidth: 200, minHeight: 200, maxWidth: 9999, maxHeight: 9999, wrapRatio: 0.8, top: 0.5, left: 0.5, loop: false, // Open Animate. openMethod: 'fadeIn', // including 'changeIn','fadeIn', 'elasticIn' openDirect: 'down', // Avaliable if openMethod is 'changeIn', 'down', 'up', 'left', 'right' is avaliable. openSpeed: 400, distance: 200, // Avaliable if openMethod is 'changeIn', 200 and 'hide' is avaliable. // Close Animate. closeMethod: 'fadeOut', closeDirect: 'up', closeDistance: 'hide', closeSpeed: 400, // Change Animate. prevMethod: 'changeOut', nextMethod: 'changeIn', changeSpeed: 600, direction: { next: 'left', prev: 'right' }, // key settings keys: { close: [27, 46], // esc key & delete key next: { 13: 'left', // enter key 39: 'left', // right arrow 68: 'left', // D key 40: 'up', // down arrow 34: 'up', // pgdn key 83: 'up', // S key }, prev: { 8: 'right', // backspace key 37: 'right', // left arrow 65: 'right', // A key 38: 'down', // up arrow 33: 'down', // pgup key 87: 'down', // W key }, }, }, helpers: {}, isOpen: false, isClosing: false, isDisplay: false, doUpdate: null, coming: null, current: null, open: function(group, options) { // prevent $.littleLightBox() or new lightbox(). if (!group) { return; } this.opts = $.extend({}, this.defaults, options || {}); if (options && options.keys) { $.extend(this.opts, this.defaults.keys, options.keys); } // Normalize group if (!$.isArray(group)) { group = isQuery(group) ? group.get() : [group]; } // Get images information from group ( caption, href, index in group) $.each(group, function(index, element) { var obj = {}, text = '', href = '', idx = 0; idx = index + 1; if (element.nodeType) { element = $(element); } if (isQuery(element)) { href = element.data('lightbox-href') || element.attr('href'); text = element.data('lightbox-title') || element.attr('title') || ''; } href || (element.attr('src') && (href = element.attr('src'))) || (href = element.find('img[src]').attr('src')); if (!href) { return false; } $.extend(obj, { index: idx, href: href, text: text, element: element, }); group[index] = obj; }); this.group = group; this._run(this.opts.index); }, next: function(direct) { if (lightbox.isClosing) { return ; } var current = lightbox.current, index, direction; if (current) { index = current.index; direction = (direct && $.type(direct) === "string") ? direct : current.direction.next; this._jump(index + 1, direction); } }, prev: function(direct) { if (lightbox.isClosing) { return ; } var current = lightbox.current, index, direction; if (current) { index = current.index; direction = (direct && $.type(direct) === "string") ? direct : current.direction.prev; this._jump(index - 1, direction); } }, _jump: function(index, direction) { if (!this.isDisplay) return ; var imageNum = this.group.length, index = index - 1; lightbox.direction = direction; this._hideLoading(); if (this.opts.loop) { // Get Really index if (index < 0) { index = imageNum + (index % imageNum); } index = index % imageNum; } this._run(index); }, _run: function(index) { var obj = (this.group)[index], coming = $.extend({}, this.opts, obj); if (!obj) { return ; } if (!this.isOpen) { this.helpers.mask.open(this.opts.helpers.mask || {}); } // prepare wrap struct this.wrap = coming.wrap = $(coming.tpl.wrap).appendTo(coming.parent || 'body').hide(); if ($.type(coming.padding) === 'number') { coming.padding = [coming.padding, coming.padding, coming.padding, coming.padding]; } $.extend(coming, { skin: $('.lightbox-skin', coming.wrap), outer: $('.lightbox-outer', coming.wrap), inner: $('.lightbox-inner', coming.wrap), loading: null, }); coming.inner.width(0).height(0); this.width = coming.padding[1] + coming.padding[3]; this.height = coming.padding[0] + coming.padding[2]; coming.skin.css('padding', coming.padding[0] + 'px ' + coming.padding[1] + 'px ' + coming.padding[2] + 'px ' + coming.padding[3] + 'px'); this._setPosition(coming.wrap, false); this.coming = coming; this._loadImage(); }, _loadImage: function() { var image = new Image(), that = this; image.src = this.coming.href; image.onload = function () { that.coming.width = this.width; that.coming.height = this.height; that._afterLoad(); }; if (!image.complete) { this._showLoading(); } }, _showLoading: function() { var that = this, pos = this._getPosition(); if (this.coming.loading) { return ; } this._hideLoading(); this.coming.loading = $(this.coming.tpl.loading).appendTo(this.coming.parent || 'body'); this.coming.loading.css({ top: window.height() * 0.5 + pos.scrollTop, left: window.width() * 0.5 + pos.scrollLeft, }); }, _hideLoading: function() { if (this.coming && this.coming.loading) { this.coming.loading.stop().off('.loading').remove(); this.coming.loading = null; } }, _afterLoad: function() { this._hideLoading(); var href, current = this.coming, previous = this.current, content; $.extend(lightbox, { wrap: current.wrap, skin: current.skin, outer: current.outer, inner: current.inner, current: current, previous: previous, coming: null, }); this.unbindEvent(); if (previous) { this.width = 'auto'; this.height = 'auto'; this.helpers.title.hide(); previous.wrap.stop(true, true).find('.lightbox-nav, .lightbox-closeBtn').remove(); } href = current.href; content = current.tpl.img.replace(/\{href\}/g, href); $(content).appendTo(current.inner); // create title helper. var titleOpts = $.extend({'text': current.text, 'idxInfo': '( ' + current.index + ' of ' + this.group.length +' )'}, current.helpers.title); this.helpers.title.show(titleOpts); current.closeBtn = $(current.tpl.closeBtn).appendTo(current.skin); current.wrap.show(); if (current.index > 1 || this.opts.loop) { current.prevBtn = $(current.tpl.prevBtn).appendTo(current.skin); } if (current.index < this.group.length || this.opts.loop) { current.nextBtn = $(current.tpl.nextBtn).appendTo(current.skin); } this.bindEvent(); this._setDimension(false); this._setPosition(this.wrap, false); // Transition if (previous) { this.isDisplay = false; this.trasition[previous.prevMethod](); } this.trasition[this.isOpen ? current.nextMethod : current.openMethod](); }, unbindEvent: function() { var current = this.current; if (current && isQuery(current.wrap)) { current.wrap.off('.btn'); } window.off('.lb'); document.off('.lb'); }, bindEvent: function() { var current = lightbox.current, keys; if (!current) { return ; } window.off('.lb').on('resize.lb', this.update); // Binding Key Board Event keys = current.keys; if (keys) { document.on('keydown.lb', function(event) { var key = event.which || event.keyCode; $.each(keys, function(name, value) { // test close key if ($.inArray(key, value) > -1 && !lightbox.coming) { lightbox[name](); return false; } // test nav key if (lightbox.group.length > 1 && value[key] !== undefined ) { lightbox[name](value[key]); return false; } }); event.preventDefault(); }); } }, update: function() { var current = lightbox.current, doUpdate = lightbox.doUpdate; if (!lightbox.isOpen || doUpdate) { return ; } lightbox.doUpdate = setTimeout(function() { if (current && !lightbox.isClosing) { lightbox.trigger('onUpdate'); lightbox._setDimension(true); lightbox._setPosition(current.wrap, true); lightbox.doUpdate = null; } }, 300); }, trigger: function(eventType, obj) { var obj = obj || lightbox; if (obj) { if ($.isFunction(obj[eventType])) { obj[eventType].apply(obj, Array.prototype.slice.call(arguments, 1)); } $.each(obj.helpers, function(index, elem) { if (elem && $.isFunction(elem[eventType])) { elem[eventType].apply(elem, Array.prototype.slice.call(arguments, 1)); } }); } document.trigger(eventType); }, _setDimension: function(isAnimate) { var that = this, width, height, winWidth, winHeight, minWidth, minHeight, maxWidth, maxHeight, innerWidth, innerHeight, ratio, aspectRatioImage; ratio = this.current.wrapRatio; winWidth = window.width() * ratio; winHeight = window.height() * ratio; minWidth = this.current.minWidth; minHeight = this.current.minHeight; maxWidth = this.current.maxWidth; maxHeight = this.current.maxHeight; width = this.current.width + this.current.padding[1] + this.current.padding[3]; height = this.current.height + this.current.padding[0] + this.current.padding[2]; aspectRatioImage = this.current.width * 1.0 / this.current.height; maxWidth = Math.min(winWidth, maxWidth); maxHeight = Math.min(winHeight, maxHeight); if (width > maxWidth) { width = maxWidth; height = width / aspectRatioImage; } if (height > maxHeight) { height = maxHeight; width = height * aspectRatioImage; } if (width < minWidth) { width = minWidth; height = width / aspectRatioImage; } if (height < minHeight) { height = minHeight; width = height * aspectRatioImage; } this.innerWidth = innerWidth = width - this.current.padding[1] - this.current.padding[3]; this.innerHeight = innerHeight = height - this.current.padding[0] - this.current.padding[2]; this.width = width; this.height = height; if (isAnimate) { this.inner.animate({ width: innerWidth, height: innerHeight, }); } else { this.inner.width(innerWidth).height(innerHeight); } }, _setPosition: function(obj, isAnimate) { var pos = this._getPosition(); if (isAnimate) { obj.animate(pos); } else { obj.css({ 'top': pos.top, 'left': pos.left, }); } }, _getPosition: function() { var pos = { 'scrollTop': window.scrollTop(), 'scrollLeft': window.scrollLeft(), }; pos.top = (window.height() - lightbox.height) * this.opts.top + pos.scrollTop; pos.left = (window.width() - lightbox.width) * this.opts.left + pos.scrollLeft; return pos; }, _afterLoadIn: function() { lightbox.isOpen = true; // load prev, next and close btn. var current = lightbox.current; lightbox.index = current.index; if (current.closeBtn) { current.closeBtn.off('.btn').on('click.btn', function(event) { event.preventDefault(); lightbox.close(); }); } if (current.prevBtn) { current.prevBtn.off('.btn').on('click.btn', function(event) { event.preventDefault(); lightbox.prev(); }); } if (current.nextBtn) { current.nextBtn.off('.btn').on('click.btn', function(event) { event.preventDefault(); lightbox.next(); }); } lightbox.isDisplay = true; }, _afterLoadOut: function() { lightbox.isOpen = !lightbox.isClosing; lightbox.isClosing = false; if (lightbox.isOpen && lightbox.previous) { lightbox.previous.wrap.remove(); } else { lightbox.wrap.remove(); lightbox.helpers.mask.close(); lightbox.helpers.title.hide(); // initial lightbox status lightbox.mask = null; lightbox.doUpdate = null; lightbox.current = null; lightbox.coming = null; } }, close: function() { if (this.isOpen && !this.isClosing) { this.isClosing = true; lightbox.isDisplay = false; this.unbindEvent(); this._hideLoading(); this.trasition[this.opts.closeMethod](); } } }); /* Lightbox trasition */ lightbox.trasition = { getCenter: function() { var pos = { w: window.width(), h: window.height(), y: window.scrollTop(), x: window.scrollLeft(), } return pos; }, fadeIn: function() { var startPos = lightbox._getPosition(), endPos = $.extend({opacity: 1}, startPos), speed = lightbox.isOpen ? lightbox.opts.changeSpeed : lightbox.opts.openSpeed; startPos.opacity = 0.1; lightbox.wrap.stop(true, true).css(startPos).animate(endPos, { duration: speed, complete: lightbox._afterLoadIn, }); }, elasticIn: function() { var offsetTopRatio = 0.9, offsetLeftRatio = 0.8, pos = this.getCenter(), endSize = {width: lightbox.innerWidth, height: lightbox.innerHeight, }, startWidth = lightbox.opts.minWidth, startHeight = lightbox.opts.minHeight, startSize = {width: startWidth, height: startHeight, }, endPos = $.extend({opacity: 1}, lightbox._getPosition()), startPos = {opacity: 0, top: (pos.h * offsetTopRatio) * lightbox.opts.top + pos.y, left: (pos.w * offsetLeftRatio) * lightbox.opts.left + pos.x}, speed = lightbox.isOpen ? lightbox.opts.changeSpeed : lightbox.opts.openSpeed; lightbox.inner.stop(true, true).css(startSize).animate(endSize,{ duration: speed, }); lightbox.wrap.stop(true, true).css(startPos).animate(endPos, { duration: speed, complete: lightbox._afterLoadIn, }); }, changeIn: function() { var field, current = lightbox.current, direct = lightbox.isOpen ? lightbox.direction : current.openDirect, distance = lightbox.opts.openDistance === 'hide' ? 'hide' : 200, speed = lightbox.isOpen ? lightbox.opts.changeSpeed : lightbox.opts.openSpeed, startPos = lightbox._getPosition(), endPos = {opacity: 1}; field = direct === 'down' || direct === 'up' ? 'top' : 'left'; startPos.opacity = 0.1; if (distance === 'hide') { distance = field === 'top' ? startPos.top : startPos.left; } else { distance = field === 'top' ? Math.min(distance, startPos.top) : Math.min(distance, startPos.left); } if (direct === 'down' || direct === 'right') { startPos[field] = (startPos[field] - distance) + 'px'; endPos[field] = '+=' + distance + 'px'; } else { startPos[field] = (startPos[field] + distance) + 'px'; endPos[field] = '-=' + distance + 'px'; } current.wrap.stop(true, true).css(startPos).animate(endPos, { duration: speed, complete: lightbox._afterLoadIn, }); }, fadeOut: function() { var previous = lightbox.previous, target = lightbox.isClosing ? lightbox.wrap : previous.wrap, speed = lightbox.isClosing ? lightbox.opts.changeSpeed : lightbox.opts.closeSpeed; target.stop(true, true).animate({opacity: 0}, { duration: speed, complete: lightbox._afterLoadOut, }); }, elasticOut: function() { var offsetTopRatio = 0.9, offsetLeftRatio = 0.8, previous = lightbox.previous, target = lightbox.isClosing ? lightbox.wrap : previous.wrap, pos = this.getCenter(), speed = lightbox.isClosing ? lightbox.opts.changeSpeed : lightbox.opts.closeSpeed, endPos = { opacity: 0, top: (pos.h * offsetTopRatio) * lightbox.opts.top + pos.y, left: (pos.w * offsetLeftRatio) * lightbox.opts.left + pos.x, }, endSize = { width: 0, height: 0, }; target.find('.lightbox-title').fadeOut(); target.find('.lightbox-inner').stop(true, true).animate(endSize,{ duration: speed, }); target.stop(true, true).animate(endPos, { duration: speed, complete: lightbox._afterLoadOut, }); }, changeOut: function() { var field, previous = lightbox.previous, target = lightbox.isClosing ? lightbox.wrap : previous.wrap, direct = lightbox.isClosing ? lightbox.opts.closeDirect : lightbox.direction, distance = lightbox.isClosing ? (lightbox.opts.closeDistance === 'hide' ? 'hide' : 200) : lightbox.opts.distance, speed = lightbox.isClosing ? lightbox.opts.changeSpeed : lightbox.opts.closeSpeed, startPos = lightbox._getPosition(), endPos = {opacity: 0.1}; field = direct === 'down' || direct === 'up' ? 'top' : 'left'; if (distance === 'hide') { distance = field === 'top' ? startPos.top : startPos.left; } else { distance = field === 'top' ? Math.min(distance, startPos.top) : Math.min(distance, startPos.left); } if (direct === 'down' || direct === 'right') { endPos[field] = '+=' + distance + 'px'; } else { endPos[field] = '-=' + distance + 'px'; } target.stop(true, true).animate(endPos, { duration: speed, complete: lightbox._afterLoadOut, }); }, } /* Lightbox Mask helper */ lightbox.helpers.mask = { defaults: { closeClick: true, speed: 200, fix: true, }, tpl: '', mask: null, parent: 'body', create: function(opts) { opts = opts || {}; this.opts = $.extend({}, this.defaults, opts); if (this.mask) { this.close(); } this.mask = $(this.tpl).appendTo(this.parent); }, open: function(opts) { var that = this; if (!this.mask) { this.create(opts); } this.mask.width(document.width()).height(document.height()); if (this.opts.closeClick) { this.mask.off('.mask').on('click.mask', function(e) { if ($(e.target).hasClass('lightbox-mask')) { if (lightbox.isOpen) { lightbox.close(); } else { that.close(); } } }); } this.mask.fadeIn(that.opts.speed); }, close: function() { var that = this; if (!lightbox.isClosing) { lightbox._hideLoading(); that.mask.off('.mask').fadeOut(that.opts.speed, function() { that.mask.remove(); that.mask = null; }); } }, onUpdate: function() { if (this.mask) { this.update(); } }, update: function() { this.mask.width(document.width()).height(document.height()); }, }; /* Lightbox Title helper */ lightbox.helpers.title = { defaults: { position: 'bottom', // title position: top, bottom. type: 'over', // title type: inside, over. text: 'photo title', idxInfo: 'image 0 of 0', }, title: null, isExist: false, create: function(opts) { var that = this, current = lightbox.current; this.opts = $.extend({}, this.defaults, opts || {}); if (this.title) { this.title.stop(true, true).remove(); } // Get the wrap of title helper. switch (this.opts.type) { case 'inside': this.parent = lightbox.skin; this.title = $(''); break; case 'over': default: this.parent = lightbox.outer; this.title = $(''); break; } this.title[this.opts.position === 'bottom' ? 'appendTo' : 'prependTo'](this.parent); this.isExist = true; }, show: function(opts) { var that = this; this.beforeShow(opts); if (!this.isExist) { this.create(opts); } this.title.fadeIn(); this.afterShow(opts); return this; }, hide: function() { var that = this; if (this.isExist) { this.isExist = false; this.title.fadeOut(function() { that.title.remove(); that.title = null; }); } return this; }, beforeShow: function(opts) { $.noop; }, afterShow: function(opts) { $.noop; } }; $.fn.littleLightBox = function(opts) { opts = opts ? opts : {}; var that = $(this), index = 0, selector = this.selector || '', run = function(e) { var what = $(this).blur(), idx = 0; var relType = opts.groupType ? opts.groupType : 'data-littlelightbox-group', relValue = what.attr(relType); if (!relValue) { relType = 'rel'; relValue = what.attr(relType); } // get all images from group 'relValue'. what = selector.length ? $(selector) : that; what = what.filter('[' + relType + '=' + relValue + ']'); if (what.length === 0) { what = $(this); } idx = what.index(this); opts.index = idx; lightbox.open(what, opts); e.preventDefault(); }; $(selector).off('click').on('click', run); return this; }; }(window, document, jQuery));