").html(content);
}
}
// If "filter" option is provided, then filter content
if (slide.opts.filter) {
content = $("
")
.html(content)
.find(slide.opts.filter);
}
}
slide.$slide.one("onReset", function() {
// Pause all html5 video/audio
$(this)
.find("video,audio")
.trigger("pause");
// Put content back
if (slide.$placeholder) {
slide.$placeholder.after(content.hide()).remove();
slide.$placeholder = null;
}
// Remove custom close button
if (slide.$smallBtn) {
slide.$smallBtn.remove();
slide.$smallBtn = null;
}
// Remove content and mark slide as not loaded
if (!slide.hasError) {
$(this).empty();
slide.isLoaded = false;
}
});
$(content).appendTo(slide.$slide);
if ($(content).is("video,audio")) {
$(content).addClass("fancybox-video");
$(content).wrap("
");
slide.contentType = "video";
slide.opts.width = slide.opts.width || $(content).attr("width");
slide.opts.height = slide.opts.height || $(content).attr("height");
}
slide.$content = slide.$slide
.children()
.filter("div,form,main,video,audio")
.first()
.addClass("fancybox-content");
slide.$slide.addClass("fancybox-slide--" + slide.contentType);
this.afterLoad(slide);
},
// Display error message
// =====================
setError: function(slide) {
slide.hasError = true;
slide.$slide
.trigger("onReset")
.removeClass("fancybox-slide--" + slide.contentType)
.addClass("fancybox-slide--error");
slide.contentType = "html";
this.setContent(slide, this.translate(slide, slide.opts.errorTpl));
if (slide.pos === this.currPos) {
this.isAnimating = false;
}
},
// Show loading icon inside the slide
// ==================================
showLoading: function(slide) {
var self = this;
slide = slide || self.current;
if (slide && !slide.$spinner) {
slide.$spinner = $(self.translate(self, self.opts.spinnerTpl)).appendTo(slide.$slide);
}
},
// Remove loading icon from the slide
// ==================================
hideLoading: function(slide) {
var self = this;
slide = slide || self.current;
if (slide && slide.$spinner) {
slide.$spinner.remove();
delete slide.$spinner;
}
},
// Adjustments after slide content has been loaded
// ===============================================
afterLoad: function(slide) {
var self = this;
if (self.isClosing) {
return;
}
slide.isLoading = false;
slide.isLoaded = true;
self.trigger("afterLoad", slide);
self.hideLoading(slide);
if (slide.pos === self.currPos) {
self.updateCursor();
}
if (slide.opts.smallBtn && (!slide.$smallBtn || !slide.$smallBtn.length)) {
slide.$smallBtn = $(self.translate(slide, slide.opts.btnTpl.smallBtn)).prependTo(slide.$content);
}
if (slide.opts.protect && slide.$content && !slide.hasError) {
// Disable right click
slide.$content.on("contextmenu.fb", function(e) {
if (e.button == 2) {
e.preventDefault();
}
return true;
});
// Add fake element on top of the image
// This makes a bit harder for user to select image
if (slide.type === "image") {
$('
').appendTo(slide.$content);
}
}
self.revealContent(slide);
},
// Make content visible
// This method is called right after content has been loaded or
// user navigates gallery and transition should start
// ============================================================
revealContent: function(slide) {
var self = this,
$slide = slide.$slide,
end = false,
start = false,
effect,
effectClassName,
duration,
opacity;
effect = slide.opts[self.firstRun ? "animationEffect" : "transitionEffect"];
duration = slide.opts[self.firstRun ? "animationDuration" : "transitionDuration"];
duration = parseInt(slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10);
// Do not animate if revealing the same slide
if (slide.pos === self.currPos) {
if (slide.isComplete) {
effect = false;
} else {
self.isAnimating = true;
}
}
if (slide.isMoved || slide.pos !== self.currPos || !duration) {
effect = false;
}
// Check if can zoom
if (effect === "zoom") {
if (slide.pos === self.currPos && duration && slide.type === "image" && !slide.hasError && (start = self.getThumbPos(slide))) {
end = self.getFitPos(slide);
} else {
effect = "fade";
}
}
// Zoom animation
// ==============
if (effect === "zoom") {
end.scaleX = end.width / start.width;
end.scaleY = end.height / start.height;
// Check if we need to animate opacity
opacity = slide.opts.zoomOpacity;
if (opacity == "auto") {
opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1;
}
if (opacity) {
start.opacity = 0.1;
end.opacity = 1;
}
// Draw image at start position
$.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start);
forceRedraw(slide.$content);
// Start animation
$.fancybox.animate(slide.$content, end, duration, function() {
self.isAnimating = false;
self.complete();
});
return;
}
self.updateSlide(slide);
// Simply show content
// ===================
if (!effect) {
forceRedraw($slide);
slide.$content.removeClass("fancybox-is-hidden");
if (slide.pos === self.currPos) {
self.complete();
}
return;
}
$.fancybox.stop($slide);
effectClassName = "fancybox-animated fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-fx-" + effect;
$slide
.removeAttr("style")
.removeClass("fancybox-slide--current fancybox-slide--next fancybox-slide--previous")
.addClass(effectClassName);
slide.$content.removeClass("fancybox-is-hidden");
// Force reflow for CSS3 transitions
forceRedraw($slide);
$.fancybox.animate(
$slide,
"fancybox-slide--current",
duration,
function(e) {
$slide.removeClass(effectClassName).removeAttr("style");
if (slide.pos === self.currPos) {
self.complete();
}
},
true
);
},
// Check if we can and have to zoom from thumbnail
//================================================
getThumbPos: function(slide) {
var self = this,
rez = false,
$thumb = slide.opts.$thumb,
thumbPos = $thumb && $thumb.length && $thumb[0].ownerDocument === document ? $thumb.offset() : 0,
slidePos;
// Check if element is inside the viewport by at least 1 pixel
var isElementVisible = function($el) {
var element = $el[0],
elementRect = element.getBoundingClientRect(),
parentRects = [],
visibleInAllParents;
while (element.parentElement !== null) {
if ($(element.parentElement).css("overflow") === "hidden" || $(element.parentElement).css("overflow") === "auto") {
parentRects.push(element.parentElement.getBoundingClientRect());
}
element = element.parentElement;
}
visibleInAllParents = parentRects.every(function(parentRect) {
var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
return visiblePixelX > 0 && visiblePixelY > 0;
});
return (
visibleInAllParents &&
elementRect.bottom > 0 &&
elementRect.right > 0 &&
elementRect.left < $(window).width() &&
elementRect.top < $(window).height()
);
};
if (thumbPos && isElementVisible($thumb)) {
slidePos = self.$refs.stage.offset();
rez = {
top: thumbPos.top - slidePos.top + parseFloat($thumb.css("border-top-width") || 0),
left: thumbPos.left - slidePos.left + parseFloat($thumb.css("border-left-width") || 0),
width: $thumb.width(),
height: $thumb.height(),
scaleX: 1,
scaleY: 1
};
}
return rez;
},
// Final adjustments after current gallery item is moved to position
// and it`s content is loaded
// ==================================================================
complete: function() {
var self = this,
current = self.current,
slides = {};
if (current.isMoved || !current.isLoaded) {
return;
}
if (!current.isComplete) {
current.isComplete = true;
current.$slide.siblings().trigger("onReset");
self.preload("inline");
// Trigger any CSS3 transiton inside the slide
forceRedraw(current.$slide);
current.$slide.addClass("fancybox-slide--complete");
// Remove unnecessary slides
$.each(self.slides, function(key, slide) {
if (slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1) {
slides[slide.pos] = slide;
} else if (slide) {
$.fancybox.stop(slide.$slide);
slide.$slide.off().remove();
}
});
self.slides = slides;
}
self.isAnimating = false;
self.updateCursor();
self.trigger("afterShow");
// Play first html5 video/audio
current.$slide
.find("video,audio")
.filter(":visible:first")
.trigger("play");
// Try to focus on the first focusable element
if (
$(document.activeElement).is("[disabled]") ||
(current.opts.autoFocus && !(current.type == "image" || current.type === "iframe"))
) {
self.focus();
}
},
// Preload next and previous slides
// ================================
preload: function(type) {
var self = this,
next = self.slides[self.currPos + 1],
prev = self.slides[self.currPos - 1];
if (next && next.type === type) {
self.loadSlide(next);
}
if (prev && prev.type === type) {
self.loadSlide(prev);
}
},
// Try to find and focus on the first focusable element
// ====================================================
focus: function() {
var current = this.current,
$el;
if (this.isClosing) {
return;
}
if (current && current.isComplete && current.$content) {
// Look for first input with autofocus attribute
$el = current.$content.find("input[autofocus]:enabled:visible:first");
if (!$el.length) {
$el = current.$content.find("button,:input,[tabindex],a").filter(":enabled:visible:first");
}
$el = $el && $el.length ? $el : current.$content;
$el.trigger("focus");
}
},
// Activates current instance - brings container to the front and enables keyboard,
// notifies other instances about deactivating
// =================================================================================
activate: function() {
var self = this;
// Deactivate all instances
$(".fancybox-container").each(function() {
var instance = $(this).data("FancyBox");
// Skip self and closing instances
if (instance && instance.id !== self.id && !instance.isClosing) {
instance.trigger("onDeactivate");
instance.removeEvents();
instance.isVisible = false;
}
});
self.isVisible = true;
if (self.current || self.isIdle) {
self.update();
self.updateControls();
}
self.trigger("onActivate");
self.addEvents();
},
// Start closing procedure
// This will start "zoom-out" animation if needed and clean everything up afterwards
// =================================================================================
close: function(e, d) {
var self = this,
current = self.current,
effect,
duration,
$content,
domRect,
opacity,
start,
end;
var done = function() {
self.cleanUp(e);
};
if (self.isClosing) {
return false;
}
self.isClosing = true;
// If beforeClose callback prevents closing, make sure content is centered
if (self.trigger("beforeClose", e) === false) {
self.isClosing = false;
requestAFrame(function() {
self.update();
});
return false;
}
// Remove all events
// If there are multiple instances, they will be set again by "activate" method
self.removeEvents();
if (current.timouts) {
clearTimeout(current.timouts);
}
$content = current.$content;
effect = current.opts.animationEffect;
duration = $.isNumeric(d) ? d : effect ? current.opts.animationDuration : 0;
// Remove other slides
current.$slide
.off(transitionEnd)
.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated");
current.$slide
.siblings()
.trigger("onReset")
.remove();
// Trigger animations
if (duration) {
self.$refs.container.removeClass("fancybox-is-open").addClass("fancybox-is-closing");
}
// Clean up
self.hideLoading(current);
self.hideControls();
self.updateCursor();
// Check if possible to zoom-out
if (
effect === "zoom" &&
!(e !== true && $content && duration && current.type === "image" && !current.hasError && (end = self.getThumbPos(current)))
) {
effect = "fade";
}
if (effect === "zoom") {
$.fancybox.stop($content);
domRect = $.fancybox.getTranslate($content);
start = {
top: domRect.top,
left: domRect.left,
scaleX: domRect.width / end.width,
scaleY: domRect.height / end.height,
width: end.width,
height: end.height
};
// Check if we need to animate opacity
opacity = current.opts.zoomOpacity;
if (opacity == "auto") {
opacity = Math.abs(current.width / current.height - end.width / end.height) > 0.1;
}
if (opacity) {
end.opacity = 0;
}
$.fancybox.setTranslate($content, start);
forceRedraw($content);
$.fancybox.animate($content, end, duration, done);
return true;
}
if (effect && duration) {
// If skip animation
if (e === true) {
setTimeout(done, duration);
} else {
$.fancybox.animate(
current.$slide.removeClass("fancybox-slide--current"),
"fancybox-animated fancybox-slide--previous fancybox-fx-" + effect,
duration,
done
);
}
} else {
done();
}
return true;
},
// Final adjustments after removing the instance
// =============================================
cleanUp: function(e) {
var self = this,
$body = $("body"),
instance,
scrollTop;
self.current.$slide.trigger("onReset");
self.$refs.container.empty().remove();
self.trigger("afterClose", e);
// Place back focus
if (self.$lastFocus && !!self.current.opts.backFocus) {
self.$lastFocus.trigger("focus");
}
self.current = null;
// Check if there are other instances
instance = $.fancybox.getInstance();
if (instance) {
instance.activate();
} else {
$body.removeClass("fancybox-active compensate-for-scrollbar");
$("#fancybox-style-noscroll").remove();
}
},
// Call callback and trigger an event
// ==================================
trigger: function(name, slide) {
var args = Array.prototype.slice.call(arguments, 1),
self = this,
obj = slide && slide.opts ? slide : self.current,
rez;
if (obj) {
args.unshift(obj);
} else {
obj = self;
}
args.unshift(self);
if ($.isFunction(obj.opts[name])) {
rez = obj.opts[name].apply(obj, args);
}
if (rez === false) {
return rez;
}
if (name === "afterClose" || !self.$refs) {
$D.trigger(name + ".fb", args);
} else {
self.$refs.container.trigger(name + ".fb", args);
}
},
// Update infobar values, navigation button states and reveal caption
// ==================================================================
updateControls: function(force) {
var self = this,
current = self.current,
index = current.index,
caption = current.opts.caption,
$container = self.$refs.container,
$caption = self.$refs.caption;
// Recalculate content dimensions
current.$slide.trigger("refresh");
self.$caption = caption && caption.length ? $caption.html(caption) : null;
if (!self.isHiddenControls && !self.isIdle) {
self.showControls();
}
// Update info and navigation elements
$container.find("[data-fancybox-count]").html(self.group.length);
$container.find("[data-fancybox-index]").html(index + 1);
$container.find("[data-fancybox-prev]").toggleClass("disabled", !current.opts.loop && index <= 0);
$container.find("[data-fancybox-next]").toggleClass("disabled", !current.opts.loop && index >= self.group.length - 1);
if (current.type === "image") {
// Re-enable buttons; update download button source
$container
.find("[data-fancybox-zoom]")
.show()
.end()
.find("[data-fancybox-download]")
.attr("href", current.opts.image.src || current.src)
.show();
} else if (current.opts.toolbar) {
$container.find("[data-fancybox-download],[data-fancybox-zoom]").hide();
}
},
// Hide toolbar and caption
// ========================
hideControls: function() {
this.isHiddenControls = true;
this.$refs.container.removeClass("fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav");
},
showControls: function() {
var self = this,
opts = self.current ? self.current.opts : self.opts,
$container = self.$refs.container;
self.isHiddenControls = false;
self.idleSecondsCounter = 0;
$container
.toggleClass("fancybox-show-toolbar", !!(opts.toolbar && opts.buttons))
.toggleClass("fancybox-show-infobar", !!(opts.infobar && self.group.length > 1))
.toggleClass("fancybox-show-nav", !!(opts.arrows && self.group.length > 1))
.toggleClass("fancybox-is-modal", !!opts.modal);
if (self.$caption) {
$container.addClass("fancybox-show-caption ");
} else {
$container.removeClass("fancybox-show-caption");
}
},
// Toggle toolbar and caption
// ==========================
toggleControls: function() {
if (this.isHiddenControls) {
this.showControls();
} else {
this.hideControls();
}
}
});
$.fancybox = {
version: "{fancybox-version}",
defaults: defaults,
// Get current instance and execute a command.
//
// Examples of usage:
//
// $instance = $.fancybox.getInstance();
// $.fancybox.getInstance().jumpTo( 1 );
// $.fancybox.getInstance( 'jumpTo', 1 );
// $.fancybox.getInstance( function() {
// console.info( this.currIndex );
// });
// ======================================================
getInstance: function(command) {
var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"),
args = Array.prototype.slice.call(arguments, 1);
if (instance instanceof FancyBox) {
if ($.type(command) === "string") {
instance[command].apply(instance, args);
} else if ($.type(command) === "function") {
command.apply(instance, args);
}
return instance;
}
return false;
},
// Create new instance
// ===================
open: function(items, opts, index) {
return new FancyBox(items, opts, index);
},
// Close current or all instances
// ==============================
close: function(all) {
var instance = this.getInstance();
if (instance) {
instance.close();
// Try to find and close next instance
if (all === true) {
this.close();
}
}
},
// Close all instances and unbind all events
// =========================================
destroy: function() {
this.close(true);
$D.add("body").off("click.fb-start", "**");
},
// Try to detect mobile devices
// ============================
isMobile:
document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
// Detect if 'translate3d' support is available
// ============================================
use3d: (function() {
var div = document.createElement("div");
return (
window.getComputedStyle &&
window.getComputedStyle(div) &&
window.getComputedStyle(div).getPropertyValue("transform") &&
!(document.documentMode && document.documentMode < 11)
);
})(),
// Helper function to get current visual state of an element
// returns array[ top, left, horizontal-scale, vertical-scale, opacity ]
// =====================================================================
getTranslate: function($el) {
var domRect;
if (!$el || !$el.length) {
return false;
}
domRect = $el[0].getBoundingClientRect();
return {
top: domRect.top || 0,
left: domRect.left || 0,
width: domRect.width,
height: domRect.height,
opacity: parseFloat($el.css("opacity"))
};
},
// Shortcut for setting "translate3d" properties for element
// Can set be used to set opacity, too
// ========================================================
setTranslate: function($el, props) {
var str = "",
css = {};
if (!$el || !props) {
return;
}
if (props.left !== undefined || props.top !== undefined) {
str =
(props.left === undefined ? $el.position().left : props.left) +
"px, " +
(props.top === undefined ? $el.position().top : props.top) +
"px";
if (this.use3d) {
str = "translate3d(" + str + ", 0px)";
} else {
str = "translate(" + str + ")";
}
}
if (props.scaleX !== undefined && props.scaleY !== undefined) {
str = (str.length ? str + " " : "") + "scale(" + props.scaleX + ", " + props.scaleY + ")";
}
if (str.length) {
css.transform = str;
}
if (props.opacity !== undefined) {
css.opacity = props.opacity;
}
if (props.width !== undefined) {
css.width = props.width;
}
if (props.height !== undefined) {
css.height = props.height;
}
return $el.css(css);
},
// Simple CSS transition handler
// =============================
animate: function($el, to, duration, callback, leaveAnimationName) {
var final = false;
if ($.isFunction(duration)) {
callback = duration;
duration = null;
}
if (!$.isPlainObject(to)) {
$el.removeAttr("style");
}
$.fancybox.stop($el);
$el.on(transitionEnd, function(e) {
// Skip events from child elements and z-index change
if (e && e.originalEvent && (!$el.is(e.originalEvent.target) || e.originalEvent.propertyName == "z-index")) {
return;
}
$.fancybox.stop($el);
if (final) {
$.fancybox.setTranslate($el, final);
}
if ($.isPlainObject(to)) {
if (leaveAnimationName === false) {
$el.removeAttr("style");
}
} else if (leaveAnimationName !== true) {
$el.removeClass(to);
}
if ($.isFunction(callback)) {
callback(e);
}
});
if ($.isNumeric(duration)) {
$el.css("transition-duration", duration + "ms");
}
// Start animation by changing CSS properties or class name
if ($.isPlainObject(to)) {
if (to.scaleX !== undefined && to.scaleY !== undefined) {
final = $.extend({}, to, {
width: $el.width() * to.scaleX,
height: $el.height() * to.scaleY,
scaleX: 1,
scaleY: 1
});
delete to.width;
delete to.height;
if ($el.parent().hasClass("fancybox-slide--image")) {
$el.parent().addClass("fancybox-is-scaling");
}
}
$.fancybox.setTranslate($el, to);
} else {
$el.addClass(to);
}
// Make sure that `transitionend` callback gets fired
$el.data(
"timer",
setTimeout(function() {
$el.trigger("transitionend");
}, duration + 16)
);
},
stop: function($el) {
if ($el && $el.length) {
clearTimeout($el.data("timer"));
$el.off("transitionend").css("transition-duration", "");
$el.parent().removeClass("fancybox-is-scaling");
}
}
};
// Default click handler for "fancyboxed" links
// ============================================
function _run(e, opts) {
var items = [],
index = 0,
$target,
value;
// Avoid opening multiple times
if (e && e.isDefaultPrevented()) {
return;
}
e.preventDefault();
opts = e && e.data ? e.data.options : opts || {};
$target = opts.$target || $(e.currentTarget);
value = $target.attr("data-fancybox") || "";
// Get all related items and find index for clicked one
if (value) {
items = opts.selector ? $(opts.selector) : e.data ? e.data.items : [];
items = items.length ? items.filter('[data-fancybox="' + value + '"]') : $('[data-fancybox="' + value + '"]');
index = items.index($target);
// Sometimes current item can not be found (for example, if some script clones items)
if (index < 0) {
index = 0;
}
} else {
items = [$target];
}
$.fancybox.open(items, opts, index);
}
// Create a jQuery plugin
// ======================
$.fn.fancybox = function(options) {
var selector;
options = options || {};
selector = options.selector || false;
if (selector) {
// Use body element instead of document so it executes first
$("body")
.off("click.fb-start", selector)
.on("click.fb-start", selector, {options: options}, _run);
} else {
this.off("click.fb-start").on(
"click.fb-start",
{
items: this,
options: options
},
_run
);
}
return this;
};
// Self initializing plugin for all elements having `data-fancybox` attribute
// ==========================================================================
$D.on("click.fb-start", "[data-fancybox]", _run);
// Enable "trigger elements"
// =========================
$D.on("click.fb-start", "[data-trigger]", function(e) {
_run(e, {
$target: $('[data-fancybox="' + $(e.currentTarget).attr("data-trigger") + '"]').eq($(e.currentTarget).attr("data-index") || 0),
$trigger: $(this)
});
});
})(window, document, window.jQuery || jQuery);