/*!
* 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 = $('' + this.opts.text + '
' + '
' + this.opts.idxInfo + ' ');
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));