/** * MODIFIED BY LIQUID THEMES * Fresco - A Beautiful Responsive Lightbox - v2.3.2 * (c) 2012-2021 Nick Stakenburg * * https://github.com/staaky/fresco * * @license: https://creativecommons.org/licenses/by/4.0 */ // UMD wrapper (function(root, factory) { if (typeof define === "function" && define.amd) { // AMD define(["jquery"], factory); } else if (typeof module === "object" && module.exports) { // Node/CommonJS module.exports = factory(require("jquery")); } else { // Browser global root.Fresco = factory(jQuery); } })(this, function($) { var Fresco = {}; $.extend(Fresco, { version: "2.3.2", }); Fresco.Skins = { // the default skin fresco: {}, }; var Bounds = { viewport: function() { var dimensions = { width: $(window).width(), }; // Mobile Safari has a bugged viewport height after scrolling // Firefox on Android also has problems with height if (Browser.MobileSafari || (Browser.Android && Browser.Gecko)) { var zoom = document.documentElement.clientWidth / window.innerWidth; dimensions.height = window.innerHeight * zoom; } else { // default dimensions.height = $(window).height(); } return dimensions; }, }; var Browser = (function(uA) { function getVersion(identifier) { var version = new RegExp(identifier + "([\\d.]+)").exec(uA); return version ? parseFloat(version[1]) : true; } return { IE: !!(window.attachEvent && uA.indexOf("Opera") === -1) && getVersion("MSIE "), Opera: uA.indexOf("Opera") > -1 && ((!!window.opera && opera.version && parseFloat(opera.version())) || 7.55), WebKit: uA.indexOf("AppleWebKit/") > -1 && getVersion("AppleWebKit/"), Gecko: uA.indexOf("Gecko") > -1 && uA.indexOf("KHTML") === -1 && getVersion("rv:"), MobileSafari: !!uA.match(/Apple.*Mobile.*Safari/), Chrome: uA.indexOf("Chrome") > -1 && getVersion("Chrome/"), ChromeMobile: uA.indexOf("CrMo") > -1 && getVersion("CrMo/"), Android: uA.indexOf("Android") > -1 && getVersion("Android "), IEMobile: uA.indexOf("IEMobile") > -1 && getVersion("IEMobile/"), }; })(navigator.userAgent); var _slice = Array.prototype.slice; function baseToString(value) { if (typeof value === "string") { return value; } return value == null ? "" : value + ""; } var _ = { isElement: function(object) { return object && object.nodeType === 1; }, String: { capitalize: function(string) { string = baseToString(string); return string && string.charAt(0).toUpperCase() + string.slice(1); }, }, }; //mousewheel (function() { function wheel(event) { /** * Added by Liquid Themes */ if ( ! event || ! event.originalEvent ) return; var realDelta; // normalize the delta if (event.originalEvent.wheelDelta) // IE & Opera realDelta = event.originalEvent.wheelDelta / 120; else if (event.originalEvent.detail) // W3C realDelta = -event.originalEvent.detail / 3; if (!realDelta) return; var customEvent = $.Event("fresco:mousewheel"); $(event.target).trigger(customEvent, realDelta); if (customEvent.isPropagationStopped()) { event.stopPropagation(); } if (customEvent.isDefaultPrevented()) { event.preventDefault(); } } $(document.documentElement).on("mousewheel DOMMouseScroll", wheel); })(); // Fit var Fit = { within: function(bounds, dimensions) { var options = $.extend( { height: true, width: true, }, arguments[2] || {} ); var size = $.extend({}, dimensions), scale = 1, attempts = 5; var fit = { width: options.width, height: options.height }; // adjust the bounds depending on what to fit (width/height) // start while ( attempts > 0 && ((fit.width && size.width > bounds.width) || (fit.height && size.height > bounds.height)) ) { // if both dimensions fall underneath a minimum, then don't event continue //if (size.width < 100 && size.height < 100) { var scaleX = 1, scaleY = 1; if (fit.width && size.width > bounds.width) { scaleX = bounds.width / size.width; } if (fit.height && size.height > bounds.height) { scaleY = bounds.height / size.height; } // we'll end up using the largest scaled down factor scale = Math.min(scaleX, scaleY); // adjust current size, based on original dimensions size = { width: dimensions.width * scale, height: dimensions.height * scale, }; //} attempts--; } // make sure size is never pressed into negative size.width = Math.max(size.width, 0); size.height = Math.max(size.height, 0); return size; }, }; // we only uses some of the jQueryUI easing functions // add those with a prefix to prevent conflicts $.extend($.easing, { frescoEaseInCubic: function(x, t, b, c, d) { return c * (t /= d) * t * t + b; }, frescoEaseInSine: function(x, t, b, c, d) { return -c * Math.cos((t / d) * (Math.PI / 2)) + c + b; }, frescoEaseOutSine: function(x, t, b, c, d) { return c * Math.sin((t / d) * (Math.PI / 2)) + b; }, }); var Support = (function() { var testElement = document.createElement("div"), domPrefixes = "Webkit Moz O ms Khtml".split(" "); function prefixed(property) { return testAllProperties(property, "prefix"); } function testProperties(properties, prefixed) { for (var i in properties) { if (testElement.style[properties[i]] !== undefined) { return prefixed === "prefix" ? properties[i] : true; } } return false; } function testAllProperties(property, prefixed) { var ucProperty = property.charAt(0).toUpperCase() + property.substr(1), properties = ( property + " " + domPrefixes.join(ucProperty + " ") + ucProperty ).split(" "); return testProperties(properties, prefixed); } // feature detect return { canvas: (function() { var canvas = document.createElement("canvas"); return !!(canvas.getContext && canvas.getContext("2d")); })(), css: { animation: testAllProperties("animation"), transform: testAllProperties("transform"), prefixed: prefixed, }, svg: !!document.createElementNS && !!document.createElementNS("http://www.w3.org/2000/svg", "svg") .createSVGRect, touch: (function() { try { return !!( "ontouchstart" in window || (window.DocumentTouch && document instanceof DocumentTouch) ); // firefox on Android } catch (e) { return false; } })(), }; })(); // add mobile touch to support Support.detectMobileTouch = function() { Support.mobileTouch = Support.touch && (Browser.MobileSafari || Browser.Android || Browser.IEMobile || Browser.ChromeMobile || !/^(Win|Mac|Linux)/.test(navigator.platform)); // otherwise, assume anything not on Windows, Mac or Linux is a mobile device //Support.mobileTouch = true; }; Support.detectMobileTouch(); /* ImageReady (standalone) - part of VoilĂ  * http://voila.nickstakenburg.com * MIT License */ var ImageReady = function() { return this.initialize.apply(this, Array.prototype.slice.call(arguments)); }; $.extend(ImageReady.prototype, { supports: { naturalWidth: (function() { return "naturalWidth" in new Image(); })(), }, // NOTE: setTimeouts allow callbacks to be attached initialize: function(img, successCallback, errorCallback) { this.img = $(img)[0]; this.successCallback = successCallback; this.errorCallback = errorCallback; this.isLoaded = false; this.options = $.extend( { method: "naturalWidth", pollFallbackAfter: 1000, }, arguments[3] || {} ); // a fallback is used when we're not polling for naturalWidth/Height // IE6-7 also use this to add support for naturalWidth/Height if (!this.supports.naturalWidth || this.options.method === "onload") { setTimeout(this.fallback.bind(this)); return; } // can exit out right away if we have a naturalWidth if (this.img.complete && typeof this.img.naturalWidth !== "undefined") { setTimeout( function() { if (this.img.naturalWidth > 0) { this.success(); } else { this.error(); } }.bind(this) ); return; } // we instantly bind to onerror so we catch right away $(this.img).bind( "error", function() { setTimeout( function() { this.error(); }.bind(this) ); }.bind(this) ); this.intervals = [ [1000, 10], [2 * 1000, 50], [4 * 1000, 100], [20 * 1000, 500], ]; // for testing, 2sec delay // this.intervals = [[20 * 1000, 2000]]; this._ipos = 0; this._time = 0; this._delay = this.intervals[this._ipos][1]; // start polling this.poll(); }, poll: function() { this._polling = setTimeout( function() { if (this.img.naturalWidth > 0) { this.success(); return; } // update time spend this._time += this._delay; // use a fallback after waiting if ( this.options.pollFallbackAfter && this._time >= this.options.pollFallbackAfter && !this._usedPollFallback ) { this._usedPollFallback = true; this.fallback(); } // next i within the interval if (this._time > this.intervals[this._ipos][0]) { // if there's no next interval, we asume // the image image errored out if (!this.intervals[this._ipos + 1]) { this.error(); return; } this._ipos++; // update to the new bracket this._delay = this.intervals[this._ipos][1]; } this.poll(); }.bind(this), this._delay ); }, fallback: function() { var img = new Image(); this._fallbackImg = img; img.onload = function() { img.onload = function() {}; if (!this.supports.naturalWidth) { this.img.naturalWidth = img.width; this.img.naturalHeight = img.height; } this.success(); }.bind(this); img.onerror = this.error.bind(this); img.src = this.img.src; }, abort: function() { if (this._fallbackImg) { this._fallbackImg.onload = function() {}; } if (this._polling) { clearTimeout(this._polling); this._polling = null; } }, success: function() { if (this._calledSuccess) return; this._calledSuccess = true; this.isLoaded = true; this.successCallback(this); }, error: function() { if (this._calledError) return; this._calledError = true; this.abort(); if (this.errorCallback) this.errorCallback(this); }, }); function Timers() { return this.initialize.apply(this, _slice.call(arguments)); } $.extend(Timers.prototype, { initialize: function() { this._timers = {}; }, set: function(name, handler, ms) { this._timers[name] = setTimeout(handler, ms); }, get: function(name) { return this._timers[name]; }, clear: function(name) { if (name) { if (this._timers[name]) { clearTimeout(this._timers[name]); delete this._timers[name]; } } else { this.clearAll(); } }, clearAll: function() { $.each(this._timers, function(i, timer) { clearTimeout(timer); }); this._timers = {}; }, }); // uses Types to scan a URI for info function getURIData(url) { var result = { type: "image" }; $.each(Types, function(i, type) { var data = type.data(url); if (data) { result = data; result.type = i; result.url = url; } }); return result; } function detectExtension(url) { var ext = (url || "").replace(/\?.*/g, "").match(/\.([^.]{3,4})$/); return ext ? ext[1].toLowerCase() : null; } var Type = { isVideo: function(type) { return /^(youtube|vimeo)$/.test(type); }, }; var Types = { image: { extensions: "bmp gif jpeg jpg png webp", detect: function(url) { return $.inArray(detectExtension(url), this.extensions.split(" ")) > -1; }, data: function(url) { if (!this.detect()) return false; return { extension: detectExtension(url), }; }, }, vimeo: { detect: function(url) { var res = /(vimeo\.com)\/([a-zA-Z0-9-_]+)(?:\S+)?$/i.exec(url); if (res && res[2]) return res[2]; return false; }, data: function(url) { var id = this.detect(url); if (!id) return false; return { id: id, }; }, }, youtube: { detect: function(url) { var res = /(youtube\.com|youtu\.be)\/watch\?(?=.*vi?=([a-zA-Z0-9-_]+))(?:\S+)?$/.exec( url ); if (res && res[2]) return res[2]; res = /(youtube\.com|youtu\.be)\/(vi?\/|u\/|embed\/)?([a-zA-Z0-9-_]+)(?:\S+)?$/i.exec( url ); if (res && res[3]) return res[3]; return false; }, data: function(url) { var id = this.detect(url); if (!id) return false; return { id: id, }; }, }, }; var VimeoThumbnail = (function() { var VimeoThumbnail = function() { return this.initialize.apply(this, _slice.call(arguments)); }; $.extend(VimeoThumbnail.prototype, { initialize: function(url, successCallback, errorCallback) { this.url = url; this.successCallback = successCallback; this.errorCallback = errorCallback; this.load(); }, load: function() { // first try the cache var cache = Cache.get(this.url); if (cache) { return this.successCallback(cache.data.url); } var protocol = "http" + (window.location && window.location.protocol === "https:" ? "s" : "") + ":", video_id = getURIData(this.url).id; this._xhr = $.getJSON( protocol + "//vimeo.com/api/oembed.json?url=" + protocol + "//vimeo.com/" + video_id + "&callback=?", function(_data) { if (_data && _data.thumbnail_url) { var data = { url: _data.thumbnail_url, }; Cache.set(this.url, data); this.successCallback(data.url); } else { this.errorCallback(); } }.bind(this) ); }, abort: function() { if (this._xhr) { this._xhr.abort(); this._xhr = null; } }, }); var Cache = { cache: [], get: function(url) { var entry = null; for (var i = 0; i < this.cache.length; i++) { if (this.cache[i] && this.cache[i].url === url) entry = this.cache[i]; } return entry; }, set: function(url, data) { this.remove(url); this.cache.push({ url: url, data: data }); }, remove: function(url) { for (var i = 0; i < this.cache.length; i++) { if (this.cache[i] && this.cache[i].url === url) { delete this.cache[i]; } } }, }; return VimeoThumbnail; })(); var VimeoReady = (function() { var VimeoReady = function() { return this.initialize.apply(this, _slice.call(arguments)); }; $.extend(VimeoReady.prototype, { initialize: function(url, callback) { this.url = url; this.callback = callback; this.load(); }, load: function() { // first try the cache var cache = Cache.get(this.url); if (cache) { return this.callback(cache.data); } var protocol = "http" + (window.location && window.location.protocol === "https:" ? "s" : "") + ":", video_id = getURIData(this.url).id; // NOTE: We're using a maxwidth/maxheight hack because of a regression in the oEmbed API // see: https://vimeo.com/forums/api/topic:283559 this._xhr = $.getJSON( protocol + "//vimeo.com/api/oembed.json?url=" + protocol + "//vimeo.com/" + video_id + "&maxwidth=9999999&maxheight=9999999&callback=?", function(_data) { var data = { dimensions: { width: _data.width, height: _data.height, }, }; Cache.set(this.url, data); if (this.callback) this.callback(data); }.bind(this) ); }, abort: function() { if (this._xhr) { this._xhr.abort(); this._xhr = null; } }, }); var Cache = { cache: [], get: function(url) { var entry = null; for (var i = 0; i < this.cache.length; i++) { if (this.cache[i] && this.cache[i].url === url) entry = this.cache[i]; } return entry; }, set: function(url, data) { this.remove(url); this.cache.push({ url: url, data: data }); }, remove: function(url) { for (var i = 0; i < this.cache.length; i++) { if (this.cache[i] && this.cache[i].url === url) { delete this.cache[i]; } } }, }; return VimeoReady; })(); var Options = { defaults: { effects: { content: { show: 0, hide: 0 }, spinner: { show: 150, hide: 150 }, window: { show: 440, hide: 300 }, thumbnail: { show: 300, delay: 150 }, thumbnails: { slide: 0 }, }, keyboard: { left: true, right: true, esc: true, }, loadedMethod: "naturalWidth", loop: false, onClick: "previous-next", overflow: false, overlay: { close: true, }, preload: [1, 2], position: true, skin: "fresco", spinner: true, spinnerDelay: 300, sync: true, thumbnails: "horizontal", ui: "outside", uiDelay: 3000, vimeo: { autoplay: 1, api: 1, title: 1, byline: 1, portrait: 0, loop: 0, }, youtube: { autoplay: 1, controls: 1, //cc_load_policy: 0, enablejsapi: 1, hd: 1, iv_load_policy: 3, loop: 0, modestbranding: 1, rel: 0, vq: "hd1080", // force hd: http://stackoverflow.com/a/12467865 }, initialTypeOptions: { image: {}, vimeo: { width: 1280, }, // Youtube needs both dimensions, it doesn't support fetching video dimensions like Vimeo yet. // Star this ticket if you'd like to get support for it at some point: // https://code.google.com/p/gdata-issues/issues/detail?id=4329 youtube: { width: 1280, height: 720, }, }, }, create: function(opts, type, data) { opts = opts || {}; data = data || {}; opts.skin = opts.skin || this.defaults.skin; var selected = opts.skin ? $.extend( {}, Fresco.Skins[opts.skin] || Fresco.Skins[this.defaults.skin] ) : {}, merged = $.extend(true, {}, this.defaults, selected); // merge initial type options if (merged.initialTypeOptions) { if (type && merged.initialTypeOptions[type]) { merged = $.extend(true, {}, merged.initialTypeOptions[type], merged); } // these aren't used further, so remove them delete merged.initialTypeOptions; } // safe options to work with var options = $.extend(true, {}, merged, opts); // touch should never use ui:inside if (Support.mobileTouch && options.ui === "inside") { options.ui = "outside"; } // set all effect duration to 0 for effects: false // IE8 and below never use effects if (!options.effects || (Browser.IE && Browser.IE < 9)) { options.effects = {}; $.each(this.defaults.effects, function(name, effect) { $.each((options.effects[name] = $.extend({}, effect)), function( option ) { options.effects[name][option] = 0; }); }); // disable the spinner when effects are disabled options.spinner = false; } // keyboard if (options.keyboard) { // when keyboard is true, enable all keys if (typeof options.keyboard === "boolean") { options.keyboard = {}; $.each(this.defaults.keyboard, function(key, bool) { options.keyboard[key] = true; }); } // disable left and right keys for video, because players like // youtube use these keys if (type === "vimeo" || type === "youtube") { $.extend(options.keyboard, { left: false, right: false }); } } // overflow if (!options.overflow || Support.mobileTouch) { // false options.overflow = { x: false, y: false }; } else { if (typeof options.overflow === "boolean") { // true options.overflow = { x: false, y: true }; } } // vimeo & youtube always have no overlap if (type === "vimeo" || type === "youtube") { options.overlap = false; } // disabled thumbnails IE < 9 & touch based devices if ((Browser.IE && Browser.IE < 9) || Support.mobileTouch) { options.thumbnail = false; options.thumbnails = false; } // width/height are only used for youtube // convert it to maxWidth/Height for the other content // when no max values have been set if (type !== "youtube") { if (options.width && !options.maxWidth) { options.maxWidth = options.width; } if (options.height && !options.maxHeight) { options.maxHeight = options.height; } } // youtube thumbnails if (!options.thumbnail && typeof options.thumbnail !== "boolean") { // only continue if undefined, forced false stays false var thumbnail = false; switch (type) { case "youtube": var protocol = "http" + (window.location && window.location.protocol === "https:" ? "s" : "") + ":"; thumbnail = protocol + "//img.youtube.com/vi/" + data.id + "/0.jpg"; break; case "image": case "vimeo": thumbnail = true; break; } options.thumbnail = thumbnail; } return options; }, }; var Overlay = { initialize: function() { this.build(); this.visible = false; }, build: function() { this.element = $("
") .addClass("fr-overlay") .hide() .append($("
").addClass("fr-overlay-background")); this.element.on( "click", function() { var page = Pages.page; if ( page && page.view && page.view.options.overlay && !page.view.options.overlay.close ) { return; } Window.hide(); }.bind(this) ); if (Support.mobileTouch) { this.element.addClass("fr-mobile-touch"); } // prevent mousewheel scroll this.element.on("fresco:mousewheel", function(event) { event.preventDefault(); }); }, setSkin: function(skin) { if (this.skin) { this.element.removeClass("fr-overlay-skin-" + this.skin); } this.element.addClass("fr-overlay-skin-" + skin); this.skin = skin; }, attach: function() { $(document.body).append(this.element); }, detach: function() { this.element.detach(); }, show: function(callback, alternateDuration) { if (this.visible) { if (callback) callback(); return; } this.visible = true; this.attach(); this.max(); var pDuration = (Pages.page && Pages.page.view.options.effects.window.show) || 0, duration = (typeof alternateDuration === "number" ? alternateDuration : pDuration) || 0; this.element.stop(true).fadeTo(duration, 1, callback); }, hide: function(callback, alternateDuration) { if (!this.visible) { if (callback) callback(); return; } var pDuration = (Pages.page && Pages.page.view.options.effects.window.hide) || 0, duration = (typeof alternateDuration === "number" ? alternateDuration : pDuration) || 0; this.element.stop(true).fadeOut( duration || 0, function() { this.detach(); this.visible = false; if (callback) callback(); }.bind(this) ); }, getScrollDimensions: function() { var dimensions = {}; $.each(["width", "height"], function(i, d) { var D = d.substr(0, 1).toUpperCase() + d.substr(1), ddE = document.documentElement; dimensions[d] = (Browser.IE ? Math.max(ddE["offset" + D], ddE["scroll" + D]) : Browser.WebKit ? document.body["scroll" + D] : ddE["scroll" + D]) || 0; }); return dimensions; }, max: function() { var scrollDimensions; if (Browser.MobileSafari && (Browser.WebKit && Browser.WebKit < 533.18)) { scrollDimensions = this.getScrollDimensions(); this.element.css(scrollDimensions); } if (Browser.IE && Browser.IE < 9) { var viewport = Bounds.viewport(); this.element.css({ height: viewport.height, width: viewport.width }); } if (Support.mobileTouch && !scrollDimensions) { this.element.css({ height: this.getScrollDimensions().height, }); } }, }; var Window = { initialize: function() { this.queues = []; this.queues.hide = $({}); this.pages = []; this._tracking = []; this._first = true; this.timers = new Timers(); this.build(); this.setSkin(Options.defaults.skin); }, build: function() { // window this.element = $("
") .addClass("fr-window fr-measured") .hide() // start hidden .append( (this._box = $("
") .addClass("fr-box") .append((this._pages = $("
").addClass("fr-pages")))) ) .append((this._thumbnails = $("
").addClass("fr-thumbnails"))); Overlay.initialize(); Pages.initialize(this._pages); Thumbnails.initialize(this._thumbnails); Spinner.initialize(); UI.initialize(); // support classes this.element.addClass( "fr" + (!Support.mobileTouch ? "-no" : "") + "-mobile-touch" ); this.element.addClass("fr" + (!Support.svg ? "-no" : "") + "-svg"); if (Browser.IE) { for (var i = 7; i <= 9; i++) { if (Browser.IE < i) { this.element.addClass("fr-ltIE" + i); } } } // prevent mousewheel scroll this.element.on("fresco:mousewheel", function(event) { event.preventDefault(); }); }, attach: function() { if (this._attached) return; $(document.body).append(this.element); this._attached = true; }, detach: function() { if (!this._attached) return; this.element.detach(); this._attached = false; }, setSkin: function(skin) { if (this._skin) { this.element.removeClass("fr-window-skin-" + this._skin); } this.element.addClass("fr-window-skin-" + skin); Overlay.setSkin(skin); this._skin = skin; }, setShowingType: function(type) { if (this._showingType === type) return; if (this._showingType) { this.element.removeClass("fr-showing-type-" + this._showingType); if (Type.isVideo(this._showingType)) { this.element.removeClass("fr-showing-type-video"); } } this.element.addClass("fr-showing-type-" + type); if (Type.isVideo(type)) { this.element.addClass("fr-showing-type-video"); } this._showingType = type; }, // Resize startObservingResize: function() { if (this._onWindowResizeHandler) return; $(window).on( "resize orientationchange", (this._onWindowResizeHandler = this._onWindowResize.bind(this)) ); }, stopObservingResize: function() { if (this._onWindowResizeHandler) { $(window).off("resize orientationchange", this._onWindowResizeHandler); this._onWindowResizeHandler = null; } }, _onScroll: function() { if (!Support.mobileTouch) return; // the timeout is a hack for iOS not responding this.timers.set("scroll", this.adjustToScroll.bind(this), 0); }, _onWindowResize: function() { var page; if (!(page = Pages.page)) return; Thumbnails.fitToViewport(); this.updateBoxDimensions(); page.fitToBox(); // update the UI to the current size UI.update(); // instantly update previous/next UI.adjustPrevNext(null, 0); // reposition spinner Spinner.center(); Overlay.max(); // IE7-8 // show UI for touch on resize UI._onWindowResize(); this._onScroll(); }, adjustToScroll: function() { if (!Support.mobileTouch) return; this.element.css({ top: $(window).scrollTop(), }); }, getBoxDimensions: function() { return this._boxDimensions; }, updateBoxDimensions: function() { var page; if (!(page = Pages.page)) return; var viewport = Bounds.viewport(), thumbnails = Thumbnails.getDimensions(); var isHorizontal = Thumbnails._orientation === "horizontal"; this._boxDimensions = { width: isHorizontal ? viewport.width : viewport.width - thumbnails.width, height: isHorizontal ? viewport.height - thumbnails.height : viewport.height, }; // resize this._boxPosition = { top: 0, left: isHorizontal ? 0 : thumbnails.width, }; this._box.css($.extend({}, this._boxDimensions, this._boxPosition)); }, show: function(callback, alternateDuration) { if (this.visible) { if (callback) callback(); return; } this.visible = true; this.opening = true; this.attach(); // clear timers that possible break toggling between show/hide() this.timers.clear("show-window"); this.timers.clear("hide-overlay"); // position the window at the top if mobile touch this.adjustToScroll(); var duration = (typeof alternateDuration === "number" ? alternateDuration : Pages.page && Pages.page.view.options.effects.window.show) || 0; var fx = 2; // overlay Overlay[Pages.page && Pages.page.view.options.overlay ? "show" : "hide"]( function() { if (callback && --fx < 1) callback(); }, duration ); // window // using a timeout here removes a sharp visible edge of the window while fading in // because the fading happens on top of a solid area this.timers.set( "show-window", function() { this._show( function() { this.opening = false; if (callback && --fx < 1) callback(); }.bind(this), duration ); }.bind(this), duration > 1 ? Math.min(duration * 0.5, 50) : 1 ); }, _show: function(callback, alternateDuration) { var duration = (typeof alternateDuration === "number" ? alternateDuration : Pages.page && Pages.page.view.options.effects.window.show) || 0; this.element.stop(true).fadeTo(duration, 1, callback); }, hide: function(callback) { if (!this.view) return; var hideQueue = this.queues.hide; hideQueue.queue([]); // clear queue // clear timers that possible break toggling between show/hide() this.timers.clear("show-window"); this.timers.clear("hide-overlay"); var duration = Pages.page ? Pages.page.view.options.effects.window.hide : 0; hideQueue.queue( function(next_stop) { Pages.stop(); // hide the spinner here so its effect ends early enough Spinner.hide(); next_stop(); }.bind(this) ); hideQueue.queue( function(next_unbinds) { // ui UI.disable(); UI.hide(null, duration); // keyboard Keyboard.disable(); next_unbinds(); }.bind(this) ); hideQueue.queue( function(next_hidden) { var fx = 2; this._hide(function() { if (--fx < 1) next_hidden(); }, duration); // using a timeout here removes a sharp visible edge of the window while fading out this.timers.set( "hide-overlay", function() { Overlay.hide(function() { if (--fx < 1) next_hidden(); }, duration); }.bind(this), duration > 1 ? Math.min(duration * 0.5, 150) : 1 ); // after we initiate hide, the next show() should bring up the UI // we to this using a flag this._first = true; }.bind(this) ); // callbacks after resize in a separate queue // so we can stop the hideQueue without stopping the resize hideQueue.queue( function(next_after_resize) { this._reset(); // all of the below we cannot safely call safely this.stopObservingResize(); //this.stopObservingScroll(); Pages.removeAll(); Thumbnails.clear(); this.timers.clear(); this._position = -1; // afterHide callback var afterHide = Pages.page && Pages.page.view.options.afterHide; if (typeof afterHide === "function") { afterHide.call(Fresco); } this.view = null; this.opening = false; this.closing = false; // remove from DOM this.detach(); next_after_resize(); }.bind(this) ); if (typeof callback === "function") { hideQueue.queue( function(next_callback) { callback(); next_callback(); }.bind(this) ); } }, _hide: function(callback, alternateDuration) { var duration = (typeof alternateDuration === "number" ? alternateDuration : Pages.page && Pages.page.view.options.effects.window.hide) || 0; this.element.stop(true).fadeOut(duration, callback); }, // Load load: function(views, position) { this.views = views; // dimension and visibility based code needs // the window attached this.attach(); Thumbnails.load(views); Pages.load(views); this.startObservingResize(); //this.startObservingScroll(); if (position) { this.setPosition(position); } }, // loading indicator /* startLoading: function() { if (!Spinner.supported) return; Spinner.show(); Spinner.center(); }, stopLoading: function() { if (!Spinner.supported) return; // we only stop loading if there are no loading pages anymore var loadingCount = Pages.getLoadingCount(); if (loadingCount < 1) { Spinner.hide(); } },*/ setPosition: function(position, callback) { this._position = position; // store the current view this.view = this.views[position - 1]; // we need to make sure that a possible hide effect doesn't // trigger its callbacks, as that would cancel the showing/loading // of the page started below this.stopHideQueue(); // store the page and show it this.page = Pages.show( position, function() { if (callback) callback(); }.bind(this) ); }, // stop all callbacks possibly queued up into a hide animation // this allows the hide animation to finish as we start showing/loading // a new page, a callback could otherwise interrupt this stopHideQueue: function() { this.queues.hide.queue([]); }, _reset: function() { this.visible = false; UI.hide(null, 0); UI.reset(); }, // Previous / Next mayPrevious: function() { return ( (this.view && this.view.options.loop && this.views && this.views.length > 1) || this._position !== 1 ); }, previous: function(force) { var mayPrevious = this.mayPrevious(); if (force || mayPrevious) { this.setPosition(this.getSurroundingIndexes().previous); } }, mayNext: function() { var hasViews = this.views && this.views.length > 1; return ( (this.view && this.view.options.loop && hasViews) || (hasViews && this.getSurroundingIndexes().next !== 1) ); }, next: function(force) { var mayNext = this.mayNext(); if (force || mayNext) { this.setPosition(this.getSurroundingIndexes().next); } }, // surrounding getSurroundingIndexes: function() { if (!this.views) return {}; var pos = this._position, length = this.views.length; var previous = pos <= 1 ? length : pos - 1, next = pos >= length ? 1 : pos + 1; return { previous: previous, next: next, }; }, }; // Keyboard // keeps track of keyboard events when enabled var Keyboard = { enabled: false, keyCode: { left: 37, right: 39, esc: 27, }, // enable is passed the keyboard option of a page, which can be false // or contains multiple buttons to toggle enable: function(enabled) { this.disable(); if (!enabled) return; $(document) .on("keydown", (this._onKeyDownHandler = this.onKeyDown.bind(this))) .on("keyup", (this._onKeyUpHandler = this.onKeyUp.bind(this))); this.enabled = enabled; }, disable: function() { this.enabled = false; if (this._onKeyUpHandler) { $(document) .off("keyup", this._onKeyUpHandler) .off("keydown", this._onKeyDownHandler); this._onKeyUpHandler = this._onKeyDownHandler = null; } }, onKeyDown: function(event) { if (!this.enabled) return; var key = this.getKeyByKeyCode(event.keyCode); if (!key || (key && this.enabled && !this.enabled[key])) return; event.preventDefault(); event.stopPropagation(); switch (key) { case "left": Window.previous(); break; case "right": Window.next(); break; } }, onKeyUp: function(event) { if (!this.enabled) return; var key = this.getKeyByKeyCode(event.keyCode); if (!key || (key && this.enabled && !this.enabled[key])) return; switch (key) { case "esc": Window.hide(); break; } }, getKeyByKeyCode: function(keyCode) { for (var key in this.keyCode) { if (this.keyCode[key] === keyCode) return key; } return null; }, }; var Page = (function() { var _uid = 0, _loadedUrlCache = {}, // a group of elements defining the 1px stroke, cloned later on _strokes = $("
") .addClass("fr-stroke fr-stroke-top fr-stroke-horizontal") .append($("
").addClass("fr-stroke-color")) .add( $("
") .addClass("fr-stroke fr-stroke-bottom fr-stroke-horizontal") .append($("
").addClass("fr-stroke-color")) ) .add( $("
") .addClass("fr-stroke fr-stroke-left fr-stroke-vertical") .append($("
").addClass("fr-stroke-color")) ) .add( $("
") .addClass("fr-stroke fr-stroke-right fr-stroke-vertical") .append($("
").addClass("fr-stroke-color")) ); function Page() { return this.initialize.apply(this, _slice.call(arguments)); } $.extend(Page.prototype, { initialize: function(view, position, total) { this.view = view; this.dimensions = { width: 0, height: 0 }; this.uid = _uid++; // store position/total views for later use this._position = position; this._total = total; this._fullClick = false; this._visible = false; this.queues = {}; this.queues.showhide = $({}); }, // create the page, this doesn't mean it's loaded // should happen instantly create: function() { if (this._created) return; Pages.element.append( (this.element = $("
") .addClass("fr-page") .append((this.container = $("
").addClass("fr-container"))) .css({ opacity: 0 }) .hide()) ); // check if we have a position var hasPosition = this.view.options.position && this._total > 1; if (hasPosition) { // mark it if so this.element.addClass("fr-has-position"); } // info (caption/position) if (this.view.caption || hasPosition) { this.element.append( (this.info = $("
") .addClass("fr-info") .append($("
").addClass("fr-info-background")) .append(_strokes.clone(true)) .append((this.infoPadder = $("
").addClass("fr-info-padder")))) ); // insert position first because it floats right if (hasPosition) { this.element.addClass("fr-has-position"); this.infoPadder.append( (this.pos = $("
") .addClass("fr-position") .append( $("") .addClass("fr-position-text") .html(this._position + " / " + this._total) )) ); } if (this.view.caption) { this.infoPadder.append( (this.caption = $("
") .addClass("fr-caption") .html(this.view.caption)) ); } } // background this.container .append( (this.background = $("
").addClass("fr-content-background")) ) .append((this.content = $("
").addClass("fr-content"))); // append images instantly if (this.view.type == "image") { this.content.append( (this.image = $("") .addClass("fr-content-element") .attr({ src: this.view.url })) ); this.content.append(_strokes.clone(true)); } // ui:outside needs a position outside of the info bar if (hasPosition && this.view.options.ui == "outside") { this.container.append( (this.positionOutside = $("
") .addClass("fr-position-outside") .append($("
").addClass("fr-position-background")) .append( $("") .addClass("fr-position-text") .html(this._position + " / " + this._total) )) ); } // ui:inside has everything inside the content if (this.view.options.ui == "inside") { // buttons this.content // < previous .append( (this.previousInside = $("
") .addClass("fr-side fr-side-previous fr-toggle-ui") .append( $("
") .addClass("fr-side-button") .append($("
").addClass("fr-side-button-background")) .append($("
").addClass("fr-side-button-icon")) )) ) // > next .append( (this.nextInside = $("
") .addClass("fr-side fr-side-next fr-toggle-ui") .append( $("
") .addClass("fr-side-button") .append($("
").addClass("fr-side-button-background")) .append($("
").addClass("fr-side-button-icon")) )) ) // X close .append( (this.closeInside = $("
") .addClass("fr-close fr-toggle-ui") .append($("
").addClass("fr-close-background")) .append($("
").addClass("fr-close-icon"))) ); // info (only inserted when there is a caption) // if there is no caption we insert a separate position element below // but if 1 item in the group has a caption we insert the info bar if (this.view.caption || (hasPosition && this.view.grouped.caption)) { this.content.append( (this.infoInside = $("
") .addClass("fr-info fr-toggle-ui") .append($("
").addClass("fr-info-background")) .append(_strokes.clone(true)) .append( (this.infoPadderInside = $("
").addClass("fr-info-padder")) )) ); // insert position first because it floats right if (hasPosition) { this.infoPadderInside.append( (this.posInside = $("
") .addClass("fr-position") .append( $("") .addClass("fr-position-text") .html(this._position + " / " + this._total) )) ); } if (this.view.caption) { this.infoPadderInside.append( (this.captionInside = $("
") .addClass("fr-caption") .html(this.view.caption)) ); } } // insert a separate position for when there's no caption // avoid adding it when the group has at least one caption, // the info bar is shown then if (!this.view.caption && hasPosition && !this.view.grouped.caption) { this.content.append( (this.positionInside = $("
") .addClass("fr-position-inside fr-toggle-ui") .append($("
").addClass("fr-position-background")) .append( $("") .addClass("fr-position-text") .html(this._position + " / " + this._total) )) ); } // disabled states on buttons var mayPrevious = (this.view.options.loop && this._total > 1) || this._position != 1, mayNext = (this.view.options.loop && this._total > 1) || this._position < this._total; this.previousInside[(mayPrevious ? "remove" : "add") + "Class"]( "fr-side-disabled" ); this.nextInside[(mayNext ? "remove" : "add") + "Class"]( "fr-side-disabled" ); } // overlap (this affects padding) $.each( ["x", "y"], function(i, z) { if (this.view.options.overflow[z]) { this.element.addClass("fr-overflow-" + z); } }.bind(this) ); // add the type this.element.addClass("fr-type-" + this.view.type); // add type-video if (Type.isVideo(this.view.type)) { this.element.addClass("fr-type-video"); } // no sides if (this._total < 2) { this.element.addClass("fr-no-sides"); } this._created = true; }, //surrounding _getSurroundingPages: function() { var preload; if (!(preload = this.view.options.preload)) return []; var pages = [], begin = Math.max(1, this._position - preload[0]), end = Math.min(this._position + preload[1], this._total), pos = this._position; // add the pages after this one first for the preloading order for (var i = pos; i <= end; i++) { var page = Pages.pages[i - 1]; if (page._position != pos) pages.push(page); } for (var i = pos; i >= begin; i--) { var page = Pages.pages[i - 1]; if (page._position != pos) pages.push(page); } return pages; }, preloadSurroundingImages: function() { var pages = this._getSurroundingPages(); $.each( pages, function(i, page) { page.preload(); }.bind(this) ); }, // preload is a non-abortable preloader, // so that it doesn't interfere with our regular load preload: function() { if ( this.preloading || this.preloaded || this.view.type != "image" || !this.view.options.preload || this.loaded // page might be loaded before it's preloaded so also stop there ) { return; } // make sure the page is created this.create(); this.preloading = true; this.preloadReady = new ImageReady( this.image[0], function(imageReady) { // mark this page as loaded, without hiding the spinner this.loaded = true; _loadedUrlCache[this.view.url] = true; this.preloading = false; this.preloaded = true; this.dimensions = { width: imageReady.img.naturalWidth, height: imageReady.img.naturalHeight, }; }.bind(this), null, { // have the preload always use naturalWidth, // this avoid an extra new Image() request method: "naturalWidth", } ); }, // the purpose of load is to set dimensions // we use it to set dimensions even for content that doesn't load like youtube load: function(callback, isPreload) { // make sure the page is created this.create(); // exit early if already loaded if (this.loaded) { if (callback) callback(); return; } // abort possible previous (pre)load this.abort(); // mark as loading this.loading = true; // start the spinner after waiting for some duration if (this.view.options.spinner) { // && !_loadedUrlCache[this.view.url] this._spinnerDelay = setTimeout( function() { Spinner.show(); }.bind(this), this.view.options.spinnerDelay || 0 ); } switch (this.view.type) { case "image": // if we had an error before just go through if (this.error) { if (callback) callback(); return; } this.imageReady = new ImageReady( this.image[0], function(imageReady) { // mark as loaded this._markAsLoaded(); this.setDimensions({ width: imageReady.img.naturalWidth, height: imageReady.img.naturalHeight, }); if (callback) callback(); }.bind(this), function() { // mark as loaded this._markAsLoaded(); this.image.hide(); this.content.prepend( (this.error = $("
") .addClass("fr-error fr-content-element") .append($("
").addClass("fr-error-icon"))) ); this.element.addClass("fr-has-error"); this.setDimensions({ width: this.error.outerWidth(), height: this.error.outerHeight(), }); // allow resizing this.error.css({ width: "100%", height: "100%" }); if (callback) callback(); }.bind(this), { method: this.view.options.loadedMethod, } ); break; case "vimeo": this.vimeoReady = new VimeoReady( this.view.url, function(data) { // mark as loaded this._markAsLoaded(); this.setDimensions({ width: data.dimensions.width, height: data.dimensions.height, }); if (callback) callback(); }.bind(this) ); break; case "youtube": // mark as loaded this._markAsLoaded(); this.setDimensions({ width: this.view.options.width, height: this.view.options.height, }); if (callback) callback(); break; } }, // sets dimensions taking maxWidth/Height into account setDimensions: function(dimensions) { this.dimensions = dimensions; if (this.view.options.maxWidth || this.view.options.maxHeight) { var opts = this.view.options, bounds = { width: opts.maxWidth ? opts.maxWidth : this.dimensions.width, height: opts.maxHeight ? opts.maxHeight : this.dimensions.height, }; this.dimensions = Fit.within(bounds, this.dimensions); } }, // helper for load() _markAsLoaded: function() { this._abortSpinnerDelay(); this.loading = false; this.loaded = true; // mark url as loaded so we can avoid // showing the spinner again _loadedUrlCache[this.view.url] = true; Spinner.hide(null, null, this._position); }, isVideo: function() { return Type.isVideo(this.view.type); }, insertVideo: function(callback) { // don't insert a video twice // and stop if not a video if (this.playerIframe || !this.isVideo()) { if (callback) callback(); return; } var protocol = "http" + (window.location && window.location.protocol === "https:" ? "s" : "") + ":"; var playerVars = $.extend({}, this.view.options[this.view.type] || {}), queryString = $.param(playerVars), urls = { vimeo: protocol + "//player.vimeo.com/video/{id}?{queryString}", youtube: protocol + "//www.youtube.com/embed/{id}?{queryString}", }, src = urls[this.view.type] .replace("{id}", this.view._data.id) .replace("{queryString}", queryString); this.content.prepend( (this.playerIframe = $( "