/source/plugin/fancybox/jquery.fancybox.js

https://github.com/Sariay/hexo-theme-Annie · JavaScript · 5640 lines · 3809 code · 1187 blank · 644 comment · 964 complexity · b2a3fa68c2229388adece6899ce79c84 MD5 · raw file

  1. // ==================================================
  2. // fancyBox v3.5.6
  3. //
  4. // Licensed GPLv3 for open source use
  5. // or fancyBox Commercial License for commercial use
  6. //
  7. // http://fancyapps.com/fancybox/
  8. // Copyright 2018 fancyApps
  9. //
  10. // ==================================================
  11. (function(window, document, $, undefined) {
  12. "use strict";
  13. window.console = window.console || {
  14. info: function(stuff) {}
  15. };
  16. // If there's no jQuery, fancyBox can't work
  17. // =========================================
  18. if (!$) {
  19. return;
  20. }
  21. // Check if fancyBox is already initialized
  22. // ========================================
  23. if ($.fn.fancybox) {
  24. console.info("fancyBox already initialized");
  25. return;
  26. }
  27. // Private default settings
  28. // ========================
  29. var defaults = {
  30. // Close existing modals
  31. // Set this to false if you do not need to stack multiple instances
  32. closeExisting: false,
  33. // Enable infinite gallery navigation
  34. loop: false,
  35. // Horizontal space between slides
  36. gutter: 50,
  37. // Enable keyboard navigation
  38. keyboard: true,
  39. // Should allow caption to overlap the content
  40. preventCaptionOverlap: true,
  41. // Should display navigation arrows at the screen edges
  42. arrows: true,
  43. // Should display counter at the top left corner
  44. infobar: true,
  45. // Should display close button (using `btnTpl.smallBtn` template) over the content
  46. // Can be true, false, "auto"
  47. // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items
  48. smallBtn: "auto",
  49. // Should display toolbar (buttons at the top)
  50. // Can be true, false, "auto"
  51. // If "auto" - will be automatically hidden if "smallBtn" is enabled
  52. toolbar: "auto",
  53. // What buttons should appear in the top right corner.
  54. // Buttons will be created using templates from `btnTpl` option
  55. // and they will be placed into toolbar (class="fancybox-toolbar"` element)
  56. buttons: [
  57. "zoom",
  58. //"share",
  59. "slideShow",
  60. //"fullScreen",
  61. //"download",
  62. "thumbs",
  63. "close"
  64. ],
  65. // Detect "idle" time in seconds
  66. idleTime: 3,
  67. // Disable right-click and use simple image protection for images
  68. protect: false,
  69. // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc
  70. modal: false,
  71. image: {
  72. // Wait for images to load before displaying
  73. // true - wait for image to load and then display;
  74. // false - display thumbnail and load the full-sized image over top,
  75. // requires predefined image dimensions (`data-width` and `data-height` attributes)
  76. preload: false
  77. },
  78. ajax: {
  79. // Object containing settings for ajax request
  80. settings: {
  81. // This helps to indicate that request comes from the modal
  82. // Feel free to change naming
  83. data: {
  84. fancybox: true
  85. }
  86. }
  87. },
  88. iframe: {
  89. // Iframe template
  90. tpl:
  91. '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" allowfullscreen="allowfullscreen" allow="autoplay; fullscreen" src=""></iframe>',
  92. // Preload iframe before displaying it
  93. // This allows to calculate iframe content width and height
  94. // (note: Due to "Same Origin Policy", you can't get cross domain data).
  95. preload: true,
  96. // Custom CSS styling for iframe wrapping element
  97. // You can use this to set custom iframe dimensions
  98. css: {},
  99. // Iframe tag attributes
  100. attr: {
  101. scrolling: "auto"
  102. }
  103. },
  104. // For HTML5 video only
  105. video: {
  106. tpl:
  107. '<video class="fancybox-video" controls controlsList="nodownload" poster="{{poster}}">' +
  108. '<source src="{{src}}" type="{{format}}" />' +
  109. 'Sorry, your browser doesn\'t support embedded videos, <a href="{{src}}">download</a> and watch with your favorite video player!' +
  110. "</video>",
  111. format: "", // custom video format
  112. autoStart: true
  113. },
  114. // Default content type if cannot be detected automatically
  115. defaultType: "image",
  116. // Open/close animation type
  117. // Possible values:
  118. // false - disable
  119. // "zoom" - zoom images from/to thumbnail
  120. // "fade"
  121. // "zoom-in-out"
  122. //
  123. animationEffect: "zoom",
  124. // Duration in ms for open/close animation
  125. animationDuration: 366,
  126. // Should image change opacity while zooming
  127. // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios
  128. zoomOpacity: "auto",
  129. // Transition effect between slides
  130. //
  131. // Possible values:
  132. // false - disable
  133. // "fade'
  134. // "slide'
  135. // "circular'
  136. // "tube'
  137. // "zoom-in-out'
  138. // "rotate'
  139. //
  140. transitionEffect: "fade",
  141. // Duration in ms for transition animation
  142. transitionDuration: 366,
  143. // Custom CSS class for slide element
  144. slideClass: "",
  145. // Custom CSS class for layout
  146. baseClass: "",
  147. // Base template for layout
  148. baseTpl:
  149. '<div class="fancybox-container" role="dialog" tabindex="-1">' +
  150. '<div class="fancybox-bg"></div>' +
  151. '<div class="fancybox-inner">' +
  152. '<div class="fancybox-infobar"><span data-fancybox-index></span>&nbsp;/&nbsp;<span data-fancybox-count></span></div>' +
  153. '<div class="fancybox-toolbar">{{buttons}}</div>' +
  154. '<div class="fancybox-navigation">{{arrows}}</div>' +
  155. '<div class="fancybox-stage"></div>' +
  156. '<div class="fancybox-caption"><div class="fancybox-caption__body"></div></div>' +
  157. "</div>" +
  158. "</div>",
  159. // Loading indicator template
  160. spinnerTpl: '<div class="fancybox-loading"></div>',
  161. // Error message template
  162. errorTpl: '<div class="fancybox-error"><p>{{ERROR}}</p></div>',
  163. btnTpl: {
  164. download:
  165. '<a download data-fancybox-download class="fancybox-button fancybox-button--download" title="{{DOWNLOAD}}" href="javascript:;">' +
  166. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.62 17.09V19H5.38v-1.91zm-2.97-6.96L17 11.45l-5 4.87-5-4.87 1.36-1.32 2.68 2.64V5h1.92v7.77z"/></svg>' +
  167. "</a>",
  168. zoom:
  169. '<button data-fancybox-zoom class="fancybox-button fancybox-button--zoom" title="{{ZOOM}}">' +
  170. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.7 17.3l-3-3a5.9 5.9 0 0 0-.6-7.6 5.9 5.9 0 0 0-8.4 0 5.9 5.9 0 0 0 0 8.4 5.9 5.9 0 0 0 7.7.7l3 3a1 1 0 0 0 1.3 0c.4-.5.4-1 0-1.5zM8.1 13.8a4 4 0 0 1 0-5.7 4 4 0 0 1 5.7 0 4 4 0 0 1 0 5.7 4 4 0 0 1-5.7 0z"/></svg>' +
  171. "</button>",
  172. close:
  173. '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}">' +
  174. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 10.6L6.6 5.2 5.2 6.6l5.4 5.4-5.4 5.4 1.4 1.4 5.4-5.4 5.4 5.4 1.4-1.4-5.4-5.4 5.4-5.4-1.4-1.4-5.4 5.4z"/></svg>' +
  175. "</button>",
  176. // Arrows
  177. arrowLeft:
  178. '<button data-fancybox-prev class="fancybox-button fancybox-button--arrow_left" title="{{PREV}}">' +
  179. '<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.28 15.7l-1.34 1.37L5 12l4.94-5.07 1.34 1.38-2.68 2.72H19v1.94H8.6z"/></svg></div>' +
  180. "</button>",
  181. arrowRight:
  182. '<button data-fancybox-next class="fancybox-button fancybox-button--arrow_right" title="{{NEXT}}">' +
  183. '<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.4 12.97l-2.68 2.72 1.34 1.38L19 12l-4.94-5.07-1.34 1.38 2.68 2.72H5v1.94z"/></svg></div>' +
  184. "</button>",
  185. // This small close button will be appended to your html/inline/ajax content by default,
  186. // if "smallBtn" option is not set to false
  187. smallBtn:
  188. '<button type="button" data-fancybox-close class="fancybox-button fancybox-close-small" title="{{CLOSE}}">' +
  189. '<svg xmlns="http://www.w3.org/2000/svg" version="1" viewBox="0 0 24 24"><path d="M13 12l5-5-1-1-5 5-5-5-1 1 5 5-5 5 1 1 5-5 5 5 1-1z"/></svg>' +
  190. "</button>"
  191. },
  192. // Container is injected into this element
  193. parentEl: "body",
  194. // Hide browser vertical scrollbars; use at your own risk
  195. hideScrollbar: true,
  196. // Focus handling
  197. // ==============
  198. // Try to focus on the first focusable element after opening
  199. autoFocus: true,
  200. // Put focus back to active element after closing
  201. backFocus: true,
  202. // Do not let user to focus on element outside modal content
  203. trapFocus: true,
  204. // Module specific options
  205. // =======================
  206. fullScreen: {
  207. autoStart: false
  208. },
  209. // Set `touch: false` to disable panning/swiping
  210. touch: {
  211. vertical: true, // Allow to drag content vertically
  212. momentum: true // Continue movement after releasing mouse/touch when panning
  213. },
  214. // Hash value when initializing manually,
  215. // set `false` to disable hash change
  216. hash: null,
  217. // Customize or add new media types
  218. // Example:
  219. /*
  220. media : {
  221. youtube : {
  222. params : {
  223. autoplay : 0
  224. }
  225. }
  226. }
  227. */
  228. media: {},
  229. slideShow: {
  230. autoStart: false,
  231. speed: 3000
  232. },
  233. thumbs: {
  234. autoStart: false, // Display thumbnails on opening
  235. hideOnClose: true, // Hide thumbnail grid when closing animation starts
  236. parentEl: ".fancybox-container", // Container is injected into this element
  237. axis: "y" // Vertical (y) or horizontal (x) scrolling
  238. },
  239. // Use mousewheel to navigate gallery
  240. // If 'auto' - enabled for images only
  241. wheel: "auto",
  242. // Callbacks
  243. //==========
  244. // See Documentation/API/Events for more information
  245. // Example:
  246. /*
  247. afterShow: function( instance, current ) {
  248. console.info( 'Clicked element:' );
  249. console.info( current.opts.$orig );
  250. }
  251. */
  252. onInit: $.noop, // When instance has been initialized
  253. beforeLoad: $.noop, // Before the content of a slide is being loaded
  254. afterLoad: $.noop, // When the content of a slide is done loading
  255. beforeShow: $.noop, // Before open animation starts
  256. afterShow: $.noop, // When content is done loading and animating
  257. beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close.
  258. afterClose: $.noop, // After instance has been closed
  259. onActivate: $.noop, // When instance is brought to front
  260. onDeactivate: $.noop, // When other instance has been activated
  261. // Interaction
  262. // ===========
  263. // Use options below to customize taken action when user clicks or double clicks on the fancyBox area,
  264. // each option can be string or method that returns value.
  265. //
  266. // Possible values:
  267. // "close" - close instance
  268. // "next" - move to next gallery item
  269. // "nextOrClose" - move to next gallery item or close if gallery has only one item
  270. // "toggleControls" - show/hide controls
  271. // "zoom" - zoom image (if loaded)
  272. // false - do nothing
  273. // Clicked on the content
  274. clickContent: function(current, event) {
  275. return current.type === "image" ? "zoom" : false;
  276. },
  277. // Clicked on the slide
  278. clickSlide: "close",
  279. // Clicked on the background (backdrop) element;
  280. // if you have not changed the layout, then most likely you need to use `clickSlide` option
  281. clickOutside: "close",
  282. // Same as previous two, but for double click
  283. dblclickContent: false,
  284. dblclickSlide: false,
  285. dblclickOutside: false,
  286. // Custom options when mobile device is detected
  287. // =============================================
  288. mobile: {
  289. preventCaptionOverlap: false,
  290. idleTime: false,
  291. clickContent: function(current, event) {
  292. return current.type === "image" ? "toggleControls" : false;
  293. },
  294. clickSlide: function(current, event) {
  295. return current.type === "image" ? "toggleControls" : "close";
  296. },
  297. dblclickContent: function(current, event) {
  298. return current.type === "image" ? "zoom" : false;
  299. },
  300. dblclickSlide: function(current, event) {
  301. return current.type === "image" ? "zoom" : false;
  302. }
  303. },
  304. // Internationalization
  305. // ====================
  306. lang: "en",
  307. i18n: {
  308. en: {
  309. CLOSE: "Close",
  310. NEXT: "Next",
  311. PREV: "Previous",
  312. ERROR: "The requested content cannot be loaded. <br/> Please try again later.",
  313. PLAY_START: "Start slideshow",
  314. PLAY_STOP: "Pause slideshow",
  315. FULL_SCREEN: "Full screen",
  316. THUMBS: "Thumbnails",
  317. DOWNLOAD: "Download",
  318. SHARE: "Share",
  319. ZOOM: "Zoom"
  320. },
  321. de: {
  322. CLOSE: "Schlie&szlig;en",
  323. NEXT: "Weiter",
  324. PREV: "Zur&uuml;ck",
  325. ERROR: "Die angeforderten Daten konnten nicht geladen werden. <br/> Bitte versuchen Sie es sp&auml;ter nochmal.",
  326. PLAY_START: "Diaschau starten",
  327. PLAY_STOP: "Diaschau beenden",
  328. FULL_SCREEN: "Vollbild",
  329. THUMBS: "Vorschaubilder",
  330. DOWNLOAD: "Herunterladen",
  331. SHARE: "Teilen",
  332. ZOOM: "Vergr&ouml;&szlig;ern"
  333. }
  334. }
  335. };
  336. // Few useful variables and methods
  337. // ================================
  338. var $W = $(window);
  339. var $D = $(document);
  340. var called = 0;
  341. // Check if an object is a jQuery object and not a native JavaScript object
  342. // ========================================================================
  343. var isQuery = function(obj) {
  344. return obj && obj.hasOwnProperty && obj instanceof $;
  345. };
  346. // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame"
  347. // ===============================================================================
  348. var requestAFrame = (function() {
  349. return (
  350. window.requestAnimationFrame ||
  351. window.webkitRequestAnimationFrame ||
  352. window.mozRequestAnimationFrame ||
  353. window.oRequestAnimationFrame ||
  354. // if all else fails, use setTimeout
  355. function(callback) {
  356. return window.setTimeout(callback, 1000 / 60);
  357. }
  358. );
  359. })();
  360. var cancelAFrame = (function() {
  361. return (
  362. window.cancelAnimationFrame ||
  363. window.webkitCancelAnimationFrame ||
  364. window.mozCancelAnimationFrame ||
  365. window.oCancelAnimationFrame ||
  366. function(id) {
  367. window.clearTimeout(id);
  368. }
  369. );
  370. })();
  371. // Detect the supported transition-end event property name
  372. // =======================================================
  373. var transitionEnd = (function() {
  374. var el = document.createElement("fakeelement"),
  375. t;
  376. var transitions = {
  377. transition: "transitionend",
  378. OTransition: "oTransitionEnd",
  379. MozTransition: "transitionend",
  380. WebkitTransition: "webkitTransitionEnd"
  381. };
  382. for (t in transitions) {
  383. if (el.style[t] !== undefined) {
  384. return transitions[t];
  385. }
  386. }
  387. return "transitionend";
  388. })();
  389. // Force redraw on an element.
  390. // This helps in cases where the browser doesn't redraw an updated element properly
  391. // ================================================================================
  392. var forceRedraw = function($el) {
  393. return $el && $el.length && $el[0].offsetHeight;
  394. };
  395. // Exclude array (`buttons`) options from deep merging
  396. // ===================================================
  397. var mergeOpts = function(opts1, opts2) {
  398. var rez = $.extend(true, {}, opts1, opts2);
  399. $.each(opts2, function(key, value) {
  400. if ($.isArray(value)) {
  401. rez[key] = value;
  402. }
  403. });
  404. return rez;
  405. };
  406. // How much of an element is visible in viewport
  407. // =============================================
  408. var inViewport = function(elem) {
  409. var elemCenter, rez;
  410. if (!elem || elem.ownerDocument !== document) {
  411. return false;
  412. }
  413. $(".fancybox-container").css("pointer-events", "none");
  414. elemCenter = {
  415. x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
  416. y: elem.getBoundingClientRect().top + elem.offsetHeight / 2
  417. };
  418. rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem;
  419. $(".fancybox-container").css("pointer-events", "");
  420. return rez;
  421. };
  422. // Class definition
  423. // ================
  424. var FancyBox = function(content, opts, index) {
  425. var self = this;
  426. self.opts = mergeOpts({index: index}, $.fancybox.defaults);
  427. if ($.isPlainObject(opts)) {
  428. self.opts = mergeOpts(self.opts, opts);
  429. }
  430. if ($.fancybox.isMobile) {
  431. self.opts = mergeOpts(self.opts, self.opts.mobile);
  432. }
  433. self.id = self.opts.id || ++called;
  434. self.currIndex = parseInt(self.opts.index, 10) || 0;
  435. self.prevIndex = null;
  436. self.prevPos = null;
  437. self.currPos = 0;
  438. self.firstRun = true;
  439. // All group items
  440. self.group = [];
  441. // Existing slides (for current, next and previous gallery items)
  442. self.slides = {};
  443. // Create group elements
  444. self.addContent(content);
  445. if (!self.group.length) {
  446. return;
  447. }
  448. self.init();
  449. };
  450. $.extend(FancyBox.prototype, {
  451. // Create DOM structure
  452. // ====================
  453. init: function() {
  454. var self = this,
  455. firstItem = self.group[self.currIndex],
  456. firstItemOpts = firstItem.opts,
  457. $container,
  458. buttonStr;
  459. if (firstItemOpts.closeExisting) {
  460. $.fancybox.close(true);
  461. }
  462. // Hide scrollbars
  463. // ===============
  464. $("body").addClass("fancybox-active");
  465. if (
  466. !$.fancybox.getInstance() &&
  467. firstItemOpts.hideScrollbar !== false &&
  468. !$.fancybox.isMobile &&
  469. document.body.scrollHeight > window.innerHeight
  470. ) {
  471. $("head").append(
  472. '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar{margin-right:' +
  473. (window.innerWidth - document.documentElement.clientWidth) +
  474. "px;}</style>"
  475. );
  476. $("body").addClass("compensate-for-scrollbar");
  477. }
  478. // Build html markup and set references
  479. // ====================================
  480. // Build html code for buttons and insert into main template
  481. buttonStr = "";
  482. $.each(firstItemOpts.buttons, function(index, value) {
  483. buttonStr += firstItemOpts.btnTpl[value] || "";
  484. });
  485. // Create markup from base template, it will be initially hidden to
  486. // avoid unnecessary work like painting while initializing is not complete
  487. $container = $(
  488. self.translate(
  489. self,
  490. firstItemOpts.baseTpl
  491. .replace("{{buttons}}", buttonStr)
  492. .replace("{{arrows}}", firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight)
  493. )
  494. )
  495. .attr("id", "fancybox-container-" + self.id)
  496. .addClass(firstItemOpts.baseClass)
  497. .data("FancyBox", self)
  498. .appendTo(firstItemOpts.parentEl);
  499. // Create object holding references to jQuery wrapped nodes
  500. self.$refs = {
  501. container: $container
  502. };
  503. ["bg", "inner", "infobar", "toolbar", "stage", "caption", "navigation"].forEach(function(item) {
  504. self.$refs[item] = $container.find(".fancybox-" + item);
  505. });
  506. self.trigger("onInit");
  507. // Enable events, deactive previous instances
  508. self.activate();
  509. // Build slides, load and reveal content
  510. self.jumpTo(self.currIndex);
  511. },
  512. // Simple i18n support - replaces object keys found in template
  513. // with corresponding values
  514. // ============================================================
  515. translate: function(obj, str) {
  516. var arr = obj.opts.i18n[obj.opts.lang] || obj.opts.i18n.en;
  517. return str.replace(/\{\{(\w+)\}\}/g, function(match, n) {
  518. return arr[n] === undefined ? match : arr[n];
  519. });
  520. },
  521. // Populate current group with fresh content
  522. // Check if each object has valid type and content
  523. // ===============================================
  524. addContent: function(content) {
  525. var self = this,
  526. items = $.makeArray(content),
  527. thumbs;
  528. $.each(items, function(i, item) {
  529. var obj = {},
  530. opts = {},
  531. $item,
  532. type,
  533. found,
  534. src,
  535. srcParts;
  536. // Step 1 - Make sure we have an object
  537. // ====================================
  538. if ($.isPlainObject(item)) {
  539. // We probably have manual usage here, something like
  540. // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] )
  541. obj = item;
  542. opts = item.opts || item;
  543. } else if ($.type(item) === "object" && $(item).length) {
  544. // Here we probably have jQuery collection returned by some selector
  545. $item = $(item);
  546. // Support attributes like `data-options='{"touch" : false}'` and `data-touch='false'`
  547. opts = $item.data() || {};
  548. opts = $.extend(true, {}, opts, opts.options);
  549. // Here we store clicked element
  550. opts.$orig = $item;
  551. obj.src = self.opts.src || opts.src || $item.attr("href");
  552. // Assume that simple syntax is used, for example:
  553. // `$.fancybox.open( $("#test"), {} );`
  554. if (!obj.type && !obj.src) {
  555. obj.type = "inline";
  556. obj.src = item;
  557. }
  558. } else {
  559. // Assume we have a simple html code, for example:
  560. // $.fancybox.open( '<div><h1>Hi!</h1></div>' );
  561. obj = {
  562. type: "html",
  563. src: item + ""
  564. };
  565. }
  566. // Each gallery object has full collection of options
  567. obj.opts = $.extend(true, {}, self.opts, opts);
  568. // Do not merge buttons array
  569. if ($.isArray(opts.buttons)) {
  570. obj.opts.buttons = opts.buttons;
  571. }
  572. if ($.fancybox.isMobile && obj.opts.mobile) {
  573. obj.opts = mergeOpts(obj.opts, obj.opts.mobile);
  574. }
  575. // Step 2 - Make sure we have content type, if not - try to guess
  576. // ==============================================================
  577. type = obj.type || obj.opts.type;
  578. src = obj.src || "";
  579. if (!type && src) {
  580. if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) {
  581. type = "video";
  582. if (!obj.opts.video.format) {
  583. obj.opts.video.format = "video/" + (found[1] === "ogv" ? "ogg" : found[1]);
  584. }
  585. } else if (src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) {
  586. type = "image";
  587. } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) {
  588. type = "iframe";
  589. obj = $.extend(true, obj, {contentType: "pdf", opts: {iframe: {preload: false}}});
  590. } else if (src.charAt(0) === "#") {
  591. type = "inline";
  592. }
  593. }
  594. if (type) {
  595. obj.type = type;
  596. } else {
  597. self.trigger("objectNeedsType", obj);
  598. }
  599. if (!obj.contentType) {
  600. obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type;
  601. }
  602. // Step 3 - Some adjustments
  603. // =========================
  604. obj.index = self.group.length;
  605. if (obj.opts.smallBtn == "auto") {
  606. obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1;
  607. }
  608. if (obj.opts.toolbar === "auto") {
  609. obj.opts.toolbar = !obj.opts.smallBtn;
  610. }
  611. // Find thumbnail image, check if exists and if is in the viewport
  612. obj.$thumb = obj.opts.$thumb || null;
  613. if (obj.opts.$trigger && obj.index === self.opts.index) {
  614. obj.$thumb = obj.opts.$trigger.find("img:first");
  615. if (obj.$thumb.length) {
  616. obj.opts.$orig = obj.opts.$trigger;
  617. }
  618. }
  619. if (!(obj.$thumb && obj.$thumb.length) && obj.opts.$orig) {
  620. obj.$thumb = obj.opts.$orig.find("img:first");
  621. }
  622. if (obj.$thumb && !obj.$thumb.length) {
  623. obj.$thumb = null;
  624. }
  625. obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null);
  626. // "caption" is a "special" option, it can be used to customize caption per gallery item
  627. if ($.type(obj.opts.caption) === "function") {
  628. obj.opts.caption = obj.opts.caption.apply(item, [self, obj]);
  629. }
  630. if ($.type(self.opts.caption) === "function") {
  631. obj.opts.caption = self.opts.caption.apply(item, [self, obj]);
  632. }
  633. // Make sure we have caption as a string or jQuery object
  634. if (!(obj.opts.caption instanceof $)) {
  635. obj.opts.caption = obj.opts.caption === undefined ? "" : obj.opts.caption + "";
  636. }
  637. // Check if url contains "filter" used to filter the content
  638. // Example: "ajax.html #something"
  639. if (obj.type === "ajax") {
  640. srcParts = src.split(/\s+/, 2);
  641. if (srcParts.length > 1) {
  642. obj.src = srcParts.shift();
  643. obj.opts.filter = srcParts.shift();
  644. }
  645. }
  646. // Hide all buttons and disable interactivity for modal items
  647. if (obj.opts.modal) {
  648. obj.opts = $.extend(true, obj.opts, {
  649. trapFocus: true,
  650. // Remove buttons
  651. infobar: 0,
  652. toolbar: 0,
  653. smallBtn: 0,
  654. // Disable keyboard navigation
  655. keyboard: 0,
  656. // Disable some modules
  657. slideShow: 0,
  658. fullScreen: 0,
  659. thumbs: 0,
  660. touch: 0,
  661. // Disable click event handlers
  662. clickContent: false,
  663. clickSlide: false,
  664. clickOutside: false,
  665. dblclickContent: false,
  666. dblclickSlide: false,
  667. dblclickOutside: false
  668. });
  669. }
  670. // Step 4 - Add processed object to group
  671. // ======================================
  672. self.group.push(obj);
  673. });
  674. // Update controls if gallery is already opened
  675. if (Object.keys(self.slides).length) {
  676. self.updateControls();
  677. // Update thumbnails, if needed
  678. thumbs = self.Thumbs;
  679. if (thumbs && thumbs.isActive) {
  680. thumbs.create();
  681. thumbs.focus();
  682. }
  683. }
  684. },
  685. // Attach an event handler functions for:
  686. // - navigation buttons
  687. // - browser scrolling, resizing;
  688. // - focusing
  689. // - keyboard
  690. // - detecting inactivity
  691. // ======================================
  692. addEvents: function() {
  693. var self = this;
  694. self.removeEvents();
  695. // Make navigation elements clickable
  696. // ==================================
  697. self.$refs.container
  698. .on("click.fb-close", "[data-fancybox-close]", function(e) {
  699. e.stopPropagation();
  700. e.preventDefault();
  701. self.close(e);
  702. })
  703. .on("touchstart.fb-prev click.fb-prev", "[data-fancybox-prev]", function(e) {
  704. e.stopPropagation();
  705. e.preventDefault();
  706. self.previous();
  707. })
  708. .on("touchstart.fb-next click.fb-next", "[data-fancybox-next]", function(e) {
  709. e.stopPropagation();
  710. e.preventDefault();
  711. self.next();
  712. })
  713. .on("click.fb", "[data-fancybox-zoom]", function(e) {
  714. // Click handler for zoom button
  715. self[self.isScaledDown() ? "scaleToActual" : "scaleToFit"]();
  716. });
  717. // Handle page scrolling and browser resizing
  718. // ==========================================
  719. $W.on("orientationchange.fb resize.fb", function(e) {
  720. if (e && e.originalEvent && e.originalEvent.type === "resize") {
  721. if (self.requestId) {
  722. cancelAFrame(self.requestId);
  723. }
  724. self.requestId = requestAFrame(function() {
  725. self.update(e);
  726. });
  727. } else {
  728. if (self.current && self.current.type === "iframe") {
  729. self.$refs.stage.hide();
  730. }
  731. setTimeout(
  732. function() {
  733. self.$refs.stage.show();
  734. self.update(e);
  735. },
  736. $.fancybox.isMobile ? 600 : 250
  737. );
  738. }
  739. });
  740. $D.on("keydown.fb", function(e) {
  741. var instance = $.fancybox ? $.fancybox.getInstance() : null,
  742. current = instance.current,
  743. keycode = e.keyCode || e.which;
  744. // Trap keyboard focus inside of the modal
  745. // =======================================
  746. if (keycode == 9) {
  747. if (current.opts.trapFocus) {
  748. self.focus(e);
  749. }
  750. return;
  751. }
  752. // Enable keyboard navigation
  753. // ==========================
  754. if (!current.opts.keyboard || e.ctrlKey || e.altKey || e.shiftKey || $(e.target).is("input,textarea,video,audio")) {
  755. return;
  756. }
  757. // Backspace and Esc keys
  758. if (keycode === 8 || keycode === 27) {
  759. e.preventDefault();
  760. self.close(e);
  761. return;
  762. }
  763. // Left arrow and Up arrow
  764. if (keycode === 37 || keycode === 38) {
  765. e.preventDefault();
  766. self.previous();
  767. return;
  768. }
  769. // Righ arrow and Down arrow
  770. if (keycode === 39 || keycode === 40) {
  771. e.preventDefault();
  772. self.next();
  773. return;
  774. }
  775. self.trigger("afterKeydown", e, keycode);
  776. });
  777. // Hide controls after some inactivity period
  778. if (self.group[self.currIndex].opts.idleTime) {
  779. self.idleSecondsCounter = 0;
  780. $D.on(
  781. "mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",
  782. function(e) {
  783. self.idleSecondsCounter = 0;
  784. if (self.isIdle) {
  785. self.showControls();
  786. }
  787. self.isIdle = false;
  788. }
  789. );
  790. self.idleInterval = window.setInterval(function() {
  791. self.idleSecondsCounter++;
  792. if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) {
  793. self.isIdle = true;
  794. self.idleSecondsCounter = 0;
  795. self.hideControls();
  796. }
  797. }, 1000);
  798. }
  799. },
  800. // Remove events added by the core
  801. // ===============================
  802. removeEvents: function() {
  803. var self = this;
  804. $W.off("orientationchange.fb resize.fb");
  805. $D.off("keydown.fb .fb-idle");
  806. this.$refs.container.off(".fb-close .fb-prev .fb-next");
  807. if (self.idleInterval) {
  808. window.clearInterval(self.idleInterval);
  809. self.idleInterval = null;
  810. }
  811. },
  812. // Change to previous gallery item
  813. // ===============================
  814. previous: function(duration) {
  815. return this.jumpTo(this.currPos - 1, duration);
  816. },
  817. // Change to next gallery item
  818. // ===========================
  819. next: function(duration) {
  820. return this.jumpTo(this.currPos + 1, duration);
  821. },
  822. // Switch to selected gallery item
  823. // ===============================
  824. jumpTo: function(pos, duration) {
  825. var self = this,
  826. groupLen = self.group.length,
  827. firstRun,
  828. isMoved,
  829. loop,
  830. current,
  831. previous,
  832. slidePos,
  833. stagePos,
  834. prop,
  835. diff;
  836. if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) {
  837. return;
  838. }
  839. // Should loop?
  840. pos = parseInt(pos, 10);
  841. loop = self.current ? self.current.opts.loop : self.opts.loop;
  842. if (!loop && (pos < 0 || pos >= groupLen)) {
  843. return false;
  844. }
  845. // Check if opening for the first time; this helps to speed things up
  846. firstRun = self.firstRun = !Object.keys(self.slides).length;
  847. // Create slides
  848. previous = self.current;
  849. self.prevIndex = self.currIndex;
  850. self.prevPos = self.currPos;
  851. current = self.createSlide(pos);
  852. if (groupLen > 1) {
  853. if (loop || current.index < groupLen - 1) {
  854. self.createSlide(pos + 1);
  855. }
  856. if (loop || current.index > 0) {
  857. self.createSlide(pos - 1);
  858. }
  859. }
  860. self.current = current;
  861. self.currIndex = current.index;
  862. self.currPos = current.pos;
  863. self.trigger("beforeShow", firstRun);
  864. self.updateControls();
  865. // Validate duration length
  866. current.forcedDuration = undefined;
  867. if ($.isNumeric(duration)) {
  868. current.forcedDuration = duration;
  869. } else {
  870. duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"];
  871. }
  872. duration = parseInt(duration, 10);
  873. // Check if user has swiped the slides or if still animating
  874. isMoved = self.isMoved(current);
  875. // Make sure current slide is visible
  876. current.$slide.addClass("fancybox-slide--current");
  877. // Fresh start - reveal container, current slide and start loading content
  878. if (firstRun) {
  879. if (current.opts.animationEffect && duration) {
  880. self.$refs.container.css("transition-duration", duration + "ms");
  881. }
  882. self.$refs.container.addClass("fancybox-is-open").trigger("focus");
  883. // Attempt to load content into slide
  884. // This will later call `afterLoad` -> `revealContent`
  885. self.loadSlide(current);
  886. self.preload("image");
  887. return;
  888. }
  889. // Get actual slide/stage positions (before cleaning up)
  890. slidePos = $.fancybox.getTranslate(previous.$slide);
  891. stagePos = $.fancybox.getTranslate(self.$refs.stage);
  892. // Clean up all slides
  893. $.each(self.slides, function(index, slide) {
  894. $.fancybox.stop(slide.$slide, true);
  895. });
  896. if (previous.pos !== current.pos) {
  897. previous.isComplete = false;
  898. }
  899. previous.$slide.removeClass("fancybox-slide--complete fancybox-slide--current");
  900. // If slides are out of place, then animate them to correct position
  901. if (isMoved) {
  902. // Calculate horizontal swipe distance
  903. diff = slidePos.left - (previous.pos * slidePos.width + previous.pos * previous.opts.gutter);
  904. $.each(self.slides, function(index, slide) {
  905. slide.$slide.removeClass("fancybox-animated").removeClass(function(index, className) {
  906. return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" ");
  907. });
  908. // Make sure that each slide is in equal distance
  909. // This is mostly needed for freshly added slides, because they are not yet positioned
  910. var leftPos = slide.pos * slidePos.width + slide.pos * slide.opts.gutter;
  911. $.fancybox.setTranslate(slide.$slide, {top: 0, left: leftPos - stagePos.left + diff});
  912. if (slide.pos !== current.pos) {
  913. slide.$slide.addClass("fancybox-slide--" + (slide.pos > current.pos ? "next" : "previous"));
  914. }
  915. // Redraw to make sure that transition will start
  916. forceRedraw(slide.$slide);
  917. // Animate the slide
  918. $.fancybox.animate(
  919. slide.$slide,
  920. {
  921. top: 0,
  922. left: (slide.pos - current.pos) * slidePos.width + (slide.pos - current.pos) * slide.opts.gutter
  923. },
  924. duration,
  925. function() {
  926. slide.$slide
  927. .css({
  928. transform: "",
  929. opacity: ""
  930. })
  931. .removeClass("fancybox-slide--next fancybox-slide--previous");
  932. if (slide.pos === self.currPos) {
  933. self.complete();
  934. }
  935. }
  936. );
  937. });
  938. } else if (duration && current.opts.transitionEffect) {
  939. // Set transition effect for previously active slide
  940. prop = "fancybox-animated fancybox-fx-" + current.opts.transitionEffect;
  941. previous.$slide.addClass("fancybox-slide--" + (previous.pos > current.pos ? "next" : "previous"));
  942. $.fancybox.animate(
  943. previous.$slide,
  944. prop,
  945. duration,
  946. function() {
  947. previous.$slide.removeClass(prop).removeClass("fancybox-slide--next fancybox-slide--previous");
  948. },
  949. false
  950. );
  951. }
  952. if (current.isLoaded) {
  953. self.revealContent(current);
  954. } else {
  955. self.loadSlide(current);
  956. }
  957. self.preload("image");
  958. },
  959. // Create new "slide" element
  960. // These are gallery items that are actually added to DOM
  961. // =======================================================
  962. createSlide: function(pos) {
  963. var self = this,
  964. $slide,
  965. index;
  966. index = pos % self.group.length;
  967. index = index < 0 ? self.group.length + index : index;
  968. if (!self.slides[pos] && self.group[index]) {
  969. $slide = $('<div class="fancybox-slide"></div>').appendTo(self.$refs.stage);
  970. self.slides[pos] = $.extend(true, {}, self.group[index], {
  971. pos: pos,
  972. $slide: $slide,
  973. isLoaded: false
  974. });
  975. self.updateSlide(self.slides[pos]);
  976. }
  977. return self.slides[pos];
  978. },
  979. // Scale image to the actual size of the image;
  980. // x and y values should be relative to the slide
  981. // ==============================================
  982. scaleToActual: function(x, y, duration) {
  983. var self = this,
  984. current = self.current,
  985. $content = current.$content,
  986. canvasWidth = $.fancybox.getTranslate(current.$slide).width,
  987. canvasHeight = $.fancybox.getTranslate(current.$slide).height,
  988. newImgWidth = current.width,
  989. newImgHeight = current.height,
  990. imgPos,
  991. posX,
  992. posY,
  993. scaleX,
  994. scaleY;
  995. if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) {
  996. return;
  997. }
  998. self.isAnimating = true;
  999. $.fancybox.stop($content);
  1000. x = x === undefined ? canvasWidth * 0.5 : x;
  1001. y = y === undefined ? canvasHeight * 0.5 : y;
  1002. imgPos = $.fancybox.getTranslate($content);
  1003. imgPos.top -= $.fancybox.getTranslate(current.$slide).top;
  1004. imgPos.left -= $.fancybox.getTranslate(current.$slide).left;
  1005. scaleX = newImgWidth / imgPos.width;
  1006. scaleY = newImgHeight / imgPos.height;
  1007. // Get center position for original image
  1008. posX = canvasWidth * 0.5 - newImgWidth * 0.5;
  1009. posY = canvasHeight * 0.5 - newImgHeight * 0.5;
  1010. // Make sure image does not move away from edges
  1011. if (newImgWidth > canvasWidth) {
  1012. posX = imgPos.left * scaleX - (x * scaleX - x);
  1013. if (posX > 0) {
  1014. posX = 0;
  1015. }
  1016. if (posX < canvasWidth - newImgWidth) {
  1017. posX = canvasWidth - newImgWidth;
  1018. }
  1019. }
  1020. if (newImgHeight > canvasHeight) {
  1021. posY = imgPos.top * scaleY - (y * scaleY - y);
  1022. if (posY > 0) {
  1023. posY = 0;
  1024. }
  1025. if (posY < canvasHeight - newImgHeight) {
  1026. posY = canvasHeight - newImgHeight;
  1027. }
  1028. }
  1029. self.updateCursor(newImgWidth, newImgHeight);
  1030. $.fancybox.animate(
  1031. $content,
  1032. {
  1033. top: posY,
  1034. left: posX,
  1035. scaleX: scaleX,
  1036. scaleY: scaleY
  1037. },
  1038. duration || 366,
  1039. function() {
  1040. self.isAnimating = false;
  1041. }
  1042. );
  1043. // Stop slideshow
  1044. if (self.SlideShow && self.SlideShow.isActive) {
  1045. self.SlideShow.stop();
  1046. }
  1047. },
  1048. // Scale image to fit inside parent element
  1049. // ========================================
  1050. scaleToFit: function(duration) {
  1051. var self = this,
  1052. current = self.current,
  1053. $content = current.$content,
  1054. end;
  1055. if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) {
  1056. return;
  1057. }
  1058. self.isAnimating = true;
  1059. $.fancybox.stop($content);
  1060. end = self.getFitPos(current);
  1061. self.updateCursor(end.width, end.height);
  1062. $.fancybox.animate(
  1063. $content,
  1064. {
  1065. top: end.top,
  1066. left: end.left,
  1067. scaleX: end.width / $content.width(),
  1068. scaleY: end.height / $content.height()
  1069. },
  1070. duration || 366,
  1071. function() {
  1072. self.isAnimating = false;
  1073. }
  1074. );
  1075. },
  1076. // Calculate image size to fit inside viewport
  1077. // ===========================================
  1078. getFitPos: function(slide) {
  1079. var self = this,
  1080. $content = slide.$content,
  1081. $slide = slide.$slide,
  1082. width = slide.width || slide.opts.width,
  1083. height = slide.height || slide.opts.height,
  1084. maxWidth,
  1085. maxHeight,
  1086. minRatio,
  1087. aspectRatio,
  1088. rez = {};
  1089. if (!slide.isLoaded || !$content || !$content.length) {
  1090. return false;
  1091. }
  1092. maxWidth = $.fancybox.getTranslate(self.$refs.stage).width;
  1093. maxHeight = $.fancybox.getTranslate(self.$refs.stage).height;
  1094. maxWidth -=
  1095. parseFloat($slide.css("paddingLeft")) +
  1096. parseFloat($slide.css("paddingRight")) +
  1097. parseFloat($content.css("marginLeft")) +
  1098. parseFloat($content.css("marginRight"));
  1099. maxHeight -=
  1100. parseFloat($slide.css("paddingTop")) +
  1101. parseFloat($slide.css("paddingBottom")) +
  1102. parseFloat($content.css("marginTop")) +
  1103. parseFloat($content.css("marginBottom"));
  1104. if (!width || !height) {
  1105. width = maxWidth;
  1106. height = maxHeight;
  1107. }
  1108. minRatio = Math.min(1, maxWidth / width, maxHeight / height);
  1109. width = minRatio * width;
  1110. height = minRatio * height;
  1111. // Adjust width/height to precisely fit into container
  1112. if (width > maxWidth - 0.5) {
  1113. width = maxWidth;
  1114. }
  1115. if (height > maxHeight - 0.5) {
  1116. height = maxHeight;
  1117. }
  1118. if (slide.type === "image") {
  1119. rez.top = Math.floor((maxHeight - height) * 0.5) + parseFloat($slide.css("paddingTop"));
  1120. rez.left = Math.floor((maxWidth - width) * 0.5) + parseFloat($slide.css("paddingLeft"));
  1121. } else if (slide.contentType === "video") {
  1122. // Force aspect ratio for the video
  1123. // "I say the whole world must learn of our peaceful ways… by force!"
  1124. aspectRatio = slide.opts.width && slide.opts.height ? width / height : slide.opts.ratio || 16 / 9;
  1125. if (height > width / aspectRatio) {
  1126. height = width / aspectRatio;
  1127. } else if (width > height * aspectRatio) {
  1128. width = height * aspectRatio;
  1129. }
  1130. }
  1131. rez.width = width;
  1132. rez.height = height;
  1133. return rez;
  1134. },
  1135. // Update content size and position for all slides
  1136. // ==============================================
  1137. update: function(e) {
  1138. var self = this;
  1139. $.each(self.slides, function(key, slide) {
  1140. self.updateSlide(slide, e);
  1141. });
  1142. },
  1143. // Update slide content position and size
  1144. // ======================================
  1145. updateSlide: function(slide, e) {
  1146. var self = this,
  1147. $content = slide && slide.$content,
  1148. width = slide.width || slide.opts.width,
  1149. height = slide.height || slide.opts.height,
  1150. $slide = slide.$slide;
  1151. // First, prevent caption overlap, if needed
  1152. self.adjustCaption(slide);
  1153. // Then resize content to fit inside the slide
  1154. if ($content && (width || height || slide.contentType === "video") && !slide.hasError) {
  1155. $.fancybox.stop($content);
  1156. $.fancybox.setTranslate($content, self.getFitPos(slide));
  1157. if (slide.pos === self.currPos) {
  1158. self.isAnimating = false;
  1159. self.updateCursor();
  1160. }
  1161. }
  1162. // Then some adjustments
  1163. self.adjustLayout(slide);
  1164. if ($slide.length) {
  1165. $slide.trigger("refresh");
  1166. if (slide.pos === self.currPos) {
  1167. self.$refs.toolbar
  1168. .add(self.$refs.navigation.find(".fancybox-button--arrow_right"))
  1169. .toggleClass("compensate-for-scrollbar", $slide.get(0).scrollHeight > $slide.get(0).clientHeight);
  1170. }
  1171. }
  1172. self.trigger("onUpdate", slide, e);
  1173. },
  1174. // Horizontally center slide
  1175. // =========================
  1176. centerSlide: function(duration) {
  1177. var self = this,
  1178. current = self.current,
  1179. $slide = current.$slide;
  1180. if (self.isClosing || !current) {
  1181. return;
  1182. }
  1183. $slide.siblings().css({
  1184. transform: "",
  1185. opacity: ""
  1186. });
  1187. $slide
  1188. .parent()
  1189. .children()
  1190. .removeClass("fancybox-slide--previous fancybox-slide--next");
  1191. $.fancybox.animate(
  1192. $slide,
  1193. {
  1194. top: 0,
  1195. left: 0,
  1196. opacity: 1
  1197. },
  1198. duration === undefined ? 0 : duration,
  1199. function() {
  1200. // Clean up
  1201. $slide.css({
  1202. transform: "",
  1203. opacity: ""
  1204. });
  1205. if (!current.isComplete) {
  1206. self.complete();
  1207. }
  1208. },
  1209. false
  1210. );
  1211. },
  1212. // Check if current slide is moved (swiped)
  1213. // ========================================
  1214. isMoved: function(slide) {
  1215. var current = slide || this.current,
  1216. slidePos,
  1217. stagePos;
  1218. if (!current) {
  1219. return false;
  1220. }
  1221. stagePos = $.fancybox.getTranslate(this.$refs.stage);
  1222. slidePos = $.fancybox.getTranslate(current.$slide);
  1223. return (
  1224. !current.$slide.hasClass("fancybox-animated") &&
  1225. (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5)
  1226. );
  1227. },
  1228. // Update cursor style depending if content can be zoomed
  1229. // ======================================================
  1230. updateCursor: function(nextWidth, nextHeight) {
  1231. var self = this,
  1232. current = self.current,
  1233. $container = self.$refs.container,
  1234. canPan,
  1235. isZoomable;
  1236. if (!current || self.isClosing || !self.Guestures) {
  1237. return;
  1238. }
  1239. $container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan");
  1240. canPan = self.canPan(nextWidth, nextHeight);
  1241. isZoomable = canPan ? true : self.isZoomable();
  1242. $container.toggleClass("fancybox-is-zoomable", isZoomable);
  1243. $("[data-fancybox-zoom]").prop("disabled", !isZoomable);
  1244. if (canPan) {
  1245. $container.addClass("fancybox-can-pan");
  1246. } else if (
  1247. isZoomable &&
  1248. (current.opts.clickContent === "zoom" || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) == "zoom"))
  1249. ) {
  1250. $container.addClass("fancybox-can-zoomIn");
  1251. } else if (current.opts.touch && (current.opts.touch.vertical || self.group.length > 1) && current.contentType !== "video") {
  1252. $container.addClass("fancybox-can-swipe");
  1253. }
  1254. },
  1255. // Check if current slide is zoomable
  1256. // ==================================
  1257. isZoomable: function() {
  1258. var self = this,
  1259. current = self.current,
  1260. fitPos;
  1261. // Assume that slide is zoomable if:
  1262. // - image is still loading
  1263. // - actual size of the image is smaller than available area
  1264. if (current && !self.isClosing && current.type === "image" && !current.hasError) {
  1265. if (!current.isLoaded) {
  1266. return true;
  1267. }
  1268. fitPos = self.getFitPos(current);
  1269. if (fitPos && (current.width > fitPos.width || current.height > fitPos.height)) {
  1270. return true;
  1271. }
  1272. }
  1273. return false;
  1274. },
  1275. // Check if current image dimensions are smaller than actual
  1276. // =========================================================
  1277. isScaledDown: function(nextWidth, nextHeight) {
  1278. var self = this,
  1279. rez = false,
  1280. current = self.current,
  1281. $content = current.$content;
  1282. if (nextWidth !== undefined && nextHeight !== undefined) {
  1283. rez = nextWidth < current.width && nextHeight < current.height;
  1284. } else if ($content) {
  1285. rez = $.fancybox.getTranslate($content);
  1286. rez = rez.width < current.width && rez.height < current.height;
  1287. }
  1288. return rez;
  1289. },
  1290. // Check if image dimensions exceed parent element
  1291. // ===============================================
  1292. canPan: function(nextWidth, nextHeight) {
  1293. var self = this,
  1294. current = self.current,
  1295. pos = null,
  1296. rez = false;
  1297. if (current.type === "image" && (current.isComplete || (nextWidth && nextHeight)) && !current.hasError) {
  1298. rez = self.getFitPos(current);
  1299. if (nextWidth !== undefined && nextHeight !== undefined) {
  1300. pos = {width: nextWidth, height: nextHeight};
  1301. } else if (current.isComplete) {
  1302. pos = $.fancybox.getTranslate(current.$content);
  1303. }
  1304. if (pos && rez) {
  1305. rez = Math.abs(pos.width - rez.width) > 1.5 || Math.abs(pos.height - rez.height) > 1.5;
  1306. }
  1307. }
  1308. return rez;
  1309. },
  1310. // Load content into the slide
  1311. // ===========================
  1312. loadSlide: function(slide) {
  1313. var self = this,
  1314. type,
  1315. $slide,
  1316. ajaxLoad;
  1317. if (slide.isLoading || slide.isLoaded) {
  1318. return;
  1319. }
  1320. slide.isLoading = true;
  1321. if (self.trigger("beforeLoad", slide) === false) {
  1322. slide.isLoading = false;
  1323. return false;
  1324. }
  1325. type = slide.type;
  1326. $slide = slide.$slide;
  1327. $slide
  1328. .off("refresh")
  1329. .trigger("onReset")
  1330. .addClass(slide.opts.slideClass);
  1331. // Create content depending on the type
  1332. switch (type) {
  1333. case "image":
  1334. self.setImage(slide);
  1335. break;
  1336. case "iframe":
  1337. self.setIframe(slide);
  1338. break;
  1339. case "html":
  1340. self.setContent(slide, slide.src || slide.content);
  1341. break;
  1342. case "video":
  1343. self.setContent(
  1344. slide,
  1345. slide.opts.video.tpl
  1346. .replace(/\{\{src\}\}/gi, slide.src)
  1347. .replace("{{format}}", slide.opts.videoFormat || slide.opts.video.format || "")
  1348. .replace("{{poster}}", slide.thumb || "")
  1349. );
  1350. break;
  1351. case "inline":
  1352. if ($(slide.src).length) {
  1353. self.setContent(slide, $(slide.src));
  1354. } else {
  1355. self.setError(slide);
  1356. }
  1357. break;
  1358. case "ajax":
  1359. self.showLoading(slide);
  1360. ajaxLoad = $.ajax(
  1361. $.extend({}, slide.opts.ajax.settings, {
  1362. url: slide.src,
  1363. success: function(data, textStatus) {
  1364. if (textStatus === "success") {
  1365. self.setContent(slide, data);
  1366. }
  1367. },
  1368. error: function(jqXHR, textStatus) {
  1369. if (jqXHR && textStatus !== "abort") {
  1370. self.setError(slide);
  1371. }
  1372. }
  1373. })
  1374. );
  1375. $slide.one("onReset", function() {
  1376. ajaxLoad.abort();
  1377. });
  1378. break;
  1379. default:
  1380. self.setError(slide);
  1381. break;
  1382. }
  1383. return true;
  1384. },
  1385. // Use thumbnail image, if possible
  1386. // ================================
  1387. setImage: function(slide) {
  1388. var self = this,
  1389. ghost;
  1390. // Check if need to show loading icon
  1391. setTimeout(function() {
  1392. var $img = slide.$image;
  1393. if (!self.isClosing && slide.isLoading && (!$img || !$img.length || !$img[0].complete) && !slide.hasError) {
  1394. self.showLoading(slide);
  1395. }
  1396. }, 50);
  1397. //Check if image has srcset
  1398. self.checkSrcset(slide);
  1399. // This will be wrapper containing both ghost and actual image
  1400. slide.$content = $('<div class="fancybox-content"></div>')
  1401. .addClass("fancybox-is-hidden")
  1402. .appendTo(slide.$slide.addClass("fancybox-slide--image"));
  1403. // If we have a thumbnail, we can display it while actual image is loading
  1404. // Users will not stare at black screen and actual image will appear gradually
  1405. if (slide.opts.preload !== false && slide.opts.width && slide.opts.height && slide.thumb) {
  1406. slide.width = slide.opts.width;
  1407. slide.height = slide.opts.height;
  1408. ghost = document.createElement("img");
  1409. ghost.onerror = function() {
  1410. $(this).remove();
  1411. slide.$ghost = null;
  1412. };
  1413. ghost.onload = function() {
  1414. self.afterLoad(slide);
  1415. };
  1416. slide.$ghost = $(ghost)
  1417. .addClass("fancybox-image")
  1418. .appendTo(slide.$content)
  1419. .attr("src", slide.thumb);
  1420. }
  1421. // Start loading actual image
  1422. self.setBigImage(slide);
  1423. },
  1424. // Check if image has srcset and get the source
  1425. // ============================================
  1426. checkSrcset: function(slide) {
  1427. var srcset = slide.opts.srcset || slide.opts.image.srcset,
  1428. found,
  1429. temp,
  1430. pxRatio,
  1431. windowWidth;
  1432. // If we have "srcset", then we need to find first matching "src" value.
  1433. // This is necessary, because when you set an src attribute, the browser will preload the image
  1434. // before any javascript or even CSS is applied.
  1435. if (srcset) {
  1436. pxRatio = window.devicePixelRatio || 1;
  1437. windowWidth = window.innerWidth * pxRatio;
  1438. temp = srcset.split(",").map(function(el) {
  1439. var ret = {};
  1440. el.trim()
  1441. .split(/\s+/)
  1442. .forEach(function(el, i) {
  1443. var value = parseInt(el.substring(0, el.length - 1), 10);
  1444. if (i === 0) {
  1445. return (ret.url = el);
  1446. }
  1447. if (value) {
  1448. ret.value = value;
  1449. ret.postfix = el[el.length - 1];
  1450. }
  1451. });
  1452. return ret;
  1453. });
  1454. // Sort by value
  1455. temp.sort(function(a, b) {
  1456. return a.value - b.value;
  1457. });
  1458. // Ok, now we have an array of all srcset values
  1459. for (var j = 0; j < temp.length; j++) {
  1460. var el = temp[j];
  1461. if ((el.postfix === "w" && el.value >= windowWidth) || (el.postfix === "x" && el.value >= pxRatio)) {
  1462. found = el;
  1463. break;
  1464. }
  1465. }
  1466. // If not found, take the last one
  1467. if (!found && temp.length) {
  1468. found = temp[temp.length - 1];
  1469. }
  1470. if (found) {
  1471. slide.src = found.url;
  1472. // If we have default width/height values, we can calculate height for matching source
  1473. if (slide.width && slide.height && found.postfix == "w") {
  1474. slide.height = (slide.width / slide.height) * found.value;
  1475. slide.width = found.value;
  1476. }
  1477. slide.opts.srcset = srcset;
  1478. }
  1479. }
  1480. },
  1481. // Create full-size image
  1482. // ======================
  1483. setBigImage: function(slide) {
  1484. var self = this,
  1485. img = document.createElement("img"),
  1486. $img = $(img);
  1487. slide.$image = $img
  1488. .one("error", function() {
  1489. self.setError(slide);
  1490. })
  1491. .one("load", function() {
  1492. var sizes;
  1493. if (!slide.$ghost) {
  1494. self.resolveImageSlideSize(slide, this.naturalWidth, this.naturalHeight);
  1495. self.afterLoad(slide);
  1496. }
  1497. if (self.isClosing) {
  1498. return;
  1499. }
  1500. if (slide.opts.srcset) {
  1501. sizes = slide.opts.sizes;
  1502. if (!sizes || sizes === "auto") {
  1503. sizes =
  1504. (slide.width / slide.height > 1 && $W.width() / $W.height() > 1 ? "100" : Math.round((slide.width / slide.height) * 100)) +
  1505. "vw";
  1506. }
  1507. $img.attr("sizes", sizes).attr("srcset", slide.opts.srcset);
  1508. }
  1509. // Hide temporary image after some delay
  1510. if (slide.$ghost) {
  1511. setTimeout(function() {
  1512. if (slide.$ghost && !self.isClosing) {
  1513. slide.$ghost.hide();
  1514. }
  1515. }, Math.min(300, Math.max(1000, slide.height / 1600)));
  1516. }
  1517. self.hideLoading(slide);
  1518. })
  1519. .addClass("fancybox-image")
  1520. .attr("src", slide.src)
  1521. .appendTo(slide.$content);
  1522. if ((img.complete || img.readyState == "complete") && $img.naturalWidth && $img.naturalHeight) {
  1523. $img.trigger("load");
  1524. } else if (img.error) {
  1525. $img.trigger("error");
  1526. }
  1527. },
  1528. // Computes the slide size from image size and maxWidth/maxHeight
  1529. // ==============================================================
  1530. resolveImageSlideSize: function(slide, imgWidth, imgHeight) {
  1531. var maxWidth = parseInt(slide.opts.width, 10),
  1532. maxHeight = parseInt(slide.opts.height, 10);
  1533. // Sets the default values from the image
  1534. slide.width = imgWidth;
  1535. slide.height = imgHeight;
  1536. if (maxWidth > 0) {
  1537. slide.width = maxWidth;
  1538. slide.height = Math.floor((maxWidth * imgHeight) / imgWidth);
  1539. }
  1540. if (maxHeight > 0) {
  1541. slide.width = Math.floor((maxHeight * imgWidth) / imgHeight);
  1542. slide.height = maxHeight;
  1543. }
  1544. },
  1545. // Create iframe wrapper, iframe and bindings
  1546. // ==========================================
  1547. setIframe: function(slide) {
  1548. var self = this,
  1549. opts = slide.opts.iframe,
  1550. $slide = slide.$slide,
  1551. $iframe;
  1552. slide.$content = $('<div class="fancybox-content' + (opts.preload ? " fancybox-is-hidden" : "") + '"></div>')
  1553. .css(opts.css)
  1554. .appendTo($slide);
  1555. $slide.addClass("fancybox-slide--" + slide.contentType);
  1556. slide.$iframe = $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime()))
  1557. .attr(opts.attr)
  1558. .appendTo(slide.$content);
  1559. if (opts.preload) {
  1560. self.showLoading(slide);
  1561. // Unfortunately, it is not always possible to determine if iframe is successfully loaded
  1562. // (due to browser security policy)
  1563. $iframe.on("load.fb error.fb", function(e) {
  1564. this.isReady = 1;
  1565. slide.$slide.trigger("refresh");
  1566. self.afterLoad(slide);
  1567. });
  1568. // Recalculate iframe content size
  1569. // ===============================
  1570. $slide.on("refresh.fb", function() {
  1571. var $content = slide.$content,
  1572. frameWidth = opts.css.width,
  1573. frameHeight = opts.css.height,
  1574. $contents,
  1575. $body;
  1576. if ($iframe[0].isReady !== 1) {
  1577. return;
  1578. }
  1579. try {
  1580. $contents = $iframe.contents();
  1581. $body = $contents.find("body");
  1582. } catch (ignore) {}
  1583. // Calculate content dimensions, if it is accessible
  1584. if ($body && $body.length && $body.children().length) {
  1585. // Avoid scrolling to top (if multiple instances)
  1586. $slide.css("overflow", "visible");
  1587. $content.css({
  1588. width: "100%",
  1589. "max-width": "100%",
  1590. height: "9999px"
  1591. });
  1592. if (frameWidth === undefined) {
  1593. frameWidth = Math.ceil(Math.max($body[0].clientWidth, $body.outerWidth(true)));
  1594. }
  1595. $content.css("width", frameWidth ? frameWidth : "").css("max-width", "");
  1596. if (frameHeight === undefined) {
  1597. frameHeight = Math.ceil(Math.max($body[0].clientHeight, $body.outerHeight(true)));
  1598. }
  1599. $content.css("height", frameHeight ? frameHeight : "");
  1600. $slide.css("overflow", "auto");
  1601. }
  1602. $content.removeClass("fancybox-is-hidden");
  1603. });
  1604. } else {
  1605. self.afterLoad(slide);
  1606. }
  1607. $iframe.attr("src", slide.src);
  1608. // Remove iframe if closing or changing gallery item
  1609. $slide.one("onReset", function() {
  1610. // This helps IE not to throw errors when closing
  1611. try {
  1612. $(this)
  1613. .find("iframe")
  1614. .hide()
  1615. .unbind()
  1616. .attr("src", "//about:blank");
  1617. } catch (ignore) {}
  1618. $(this)
  1619. .off("refresh.fb")
  1620. .empty();
  1621. slide.isLoaded = false;
  1622. slide.isRevealed = false;
  1623. });
  1624. },
  1625. // Wrap and append content to the slide
  1626. // ======================================
  1627. setContent: function(slide, content) {
  1628. var self = this;
  1629. if (self.isClosing) {
  1630. return;
  1631. }
  1632. self.hideLoading(slide);
  1633. if (slide.$content) {
  1634. $.fancybox.stop(slide.$content);
  1635. }
  1636. slide.$slide.empty();
  1637. // If content is a jQuery object, then it will be moved to the slide.
  1638. // The placeholder is created so we will know where to put it back.
  1639. if (isQuery(content) && content.parent().length) {
  1640. // Make sure content is not already moved to fancyBox
  1641. if (content.hasClass("fancybox-content") || content.parent().hasClass("fancybox-content")) {
  1642. content.parents(".fancybox-slide").trigger("onReset");
  1643. }
  1644. // Create temporary element marking original place of the content
  1645. slide.$placeholder = $("<div>")
  1646. .hide()
  1647. .insertAfter(content);
  1648. // Make sure content is visible
  1649. content.css("display", "inline-block");
  1650. } else if (!slide.hasError) {
  1651. // If content is just a plain text, try to convert it to html
  1652. if ($.type(content) === "string") {
  1653. content = $("<div>")
  1654. .append($.trim(content))
  1655. .contents();
  1656. }
  1657. // If "filter" option is provided, then filter content
  1658. if (slide.opts.filter) {
  1659. content = $("<div>")
  1660. .html(content)
  1661. .find(slide.opts.filter);
  1662. }
  1663. }
  1664. slide.$slide.one("onReset", function() {
  1665. // Pause all html5 video/audio
  1666. $(this)
  1667. .find("video,audio")
  1668. .trigger("pause");
  1669. // Put content back
  1670. if (slide.$placeholder) {
  1671. slide.$placeholder.after(content.removeClass("fancybox-content").hide()).remove();
  1672. slide.$placeholder = null;
  1673. }
  1674. // Remove custom close button
  1675. if (slide.$smallBtn) {
  1676. slide.$smallBtn.remove();
  1677. slide.$smallBtn = null;
  1678. }
  1679. // Remove content and mark slide as not loaded
  1680. if (!slide.hasError) {
  1681. $(this).empty();
  1682. slide.isLoaded = false;
  1683. slide.isRevealed = false;
  1684. }
  1685. });
  1686. $(content).appendTo(slide.$slide);
  1687. if ($(content).is("video,audio")) {
  1688. $(content).addClass("fancybox-video");
  1689. $(content).wrap("<div></div>");
  1690. slide.contentType = "video";
  1691. slide.opts.width = slide.opts.width || $(content).attr("width");
  1692. slide.opts.height = slide.opts.height || $(content).attr("height");
  1693. }
  1694. slide.$content = slide.$slide
  1695. .children()
  1696. .filter("div,form,main,video,audio,article,.fancybox-content")
  1697. .first();
  1698. slide.$content.siblings().hide();
  1699. // Re-check if there is a valid content
  1700. // (in some cases, ajax response can contain various elements or plain text)
  1701. if (!slide.$content.length) {
  1702. slide.$content = slide.$slide
  1703. .wrapInner("<div></div>")
  1704. .children()
  1705. .first();
  1706. }
  1707. slide.$content.addClass("fancybox-content");
  1708. slide.$slide.addClass("fancybox-slide--" + slide.contentType);
  1709. self.afterLoad(slide);
  1710. },
  1711. // Display error message
  1712. // =====================
  1713. setError: function(slide) {
  1714. slide.hasError = true;
  1715. slide.$slide
  1716. .trigger("onReset")
  1717. .removeClass("fancybox-slide--" + slide.contentType)
  1718. .addClass("fancybox-slide--error");
  1719. slide.contentType = "html";
  1720. this.setContent(slide, this.translate(slide, slide.opts.errorTpl));
  1721. if (slide.pos === this.currPos) {
  1722. this.isAnimating = false;
  1723. }
  1724. },
  1725. // Show loading icon inside the slide
  1726. // ==================================
  1727. showLoading: function(slide) {
  1728. var self = this;
  1729. slide = slide || self.current;
  1730. if (slide && !slide.$spinner) {
  1731. slide.$spinner = $(self.translate(self, self.opts.spinnerTpl))
  1732. .appendTo(slide.$slide)
  1733. .hide()
  1734. .fadeIn("fast");
  1735. }
  1736. },
  1737. // Remove loading icon from the slide
  1738. // ==================================
  1739. hideLoading: function(slide) {
  1740. var self = this;
  1741. slide = slide || self.current;
  1742. if (slide && slide.$spinner) {
  1743. slide.$spinner.stop().remove();
  1744. delete slide.$spinner;
  1745. }
  1746. },
  1747. // Adjustments after slide content has been loaded
  1748. // ===============================================
  1749. afterLoad: function(slide) {
  1750. var self = this;
  1751. if (self.isClosing) {
  1752. return;
  1753. }
  1754. slide.isLoading = false;
  1755. slide.isLoaded = true;
  1756. self.trigger("afterLoad", slide);
  1757. self.hideLoading(slide);
  1758. // Add small close button
  1759. if (slide.opts.smallBtn && (!slide.$smallBtn || !slide.$smallBtn.length)) {
  1760. slide.$smallBtn = $(self.translate(slide, slide.opts.btnTpl.smallBtn)).appendTo(slide.$content);
  1761. }
  1762. // Disable right click
  1763. if (slide.opts.protect && slide.$content && !slide.hasError) {
  1764. slide.$content.on("contextmenu.fb", function(e) {
  1765. if (e.button == 2) {
  1766. e.preventDefault();
  1767. }
  1768. return true;
  1769. });
  1770. // Add fake element on top of the image
  1771. // This makes a bit harder for user to select image
  1772. if (slide.type === "image") {
  1773. $('<div class="fancybox-spaceball"></div>').appendTo(slide.$content);
  1774. }
  1775. }
  1776. self.adjustCaption(slide);
  1777. self.adjustLayout(slide);
  1778. if (slide.pos === self.currPos) {
  1779. self.updateCursor();
  1780. }
  1781. self.revealContent(slide);
  1782. },
  1783. // Prevent caption overlap,
  1784. // fix css inconsistency across browsers
  1785. // =====================================
  1786. adjustCaption: function(slide) {
  1787. var self = this,
  1788. current = slide || self.current,
  1789. caption = current.opts.caption,
  1790. preventOverlap = current.opts.preventCaptionOverlap,
  1791. $caption = self.$refs.caption,
  1792. $clone,
  1793. captionH = false;
  1794. $caption.toggleClass("fancybox-caption--separate", preventOverlap);
  1795. if (preventOverlap && caption && caption.length) {
  1796. if (current.pos !== self.currPos) {
  1797. $clone = $caption.clone().appendTo($caption.parent());
  1798. $clone
  1799. .children()
  1800. .eq(0)
  1801. .empty()
  1802. .html(caption);
  1803. captionH = $clone.outerHeight(true);
  1804. $clone.empty().remove();
  1805. } else if (self.$caption) {
  1806. captionH = self.$caption.outerHeight(true);
  1807. }
  1808. current.$slide.css("padding-bottom", captionH || "");
  1809. }
  1810. },
  1811. // Simple hack to fix inconsistency across browsers, described here (affects Edge, too):
  1812. // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
  1813. // ====================================================================================
  1814. adjustLayout: function(slide) {
  1815. var self = this,
  1816. current = slide || self.current,
  1817. scrollHeight,
  1818. marginBottom,
  1819. inlinePadding,
  1820. actualPadding;
  1821. if (current.isLoaded && current.opts.disableLayoutFix !== true) {
  1822. current.$content.css("margin-bottom", "");
  1823. // If we would always set margin-bottom for the content,
  1824. // then it would potentially break vertical align
  1825. if (current.$content.outerHeight() > current.$slide.height() + 0.5) {
  1826. inlinePadding = current.$slide[0].style["padding-bottom"];
  1827. actualPadding = current.$slide.css("padding-bottom");
  1828. if (parseFloat(actualPadding) > 0) {
  1829. scrollHeight = current.$slide[0].scrollHeight;
  1830. current.$slide.css("padding-bottom", 0);
  1831. if (Math.abs(scrollHeight - current.$slide[0].scrollHeight) < 1) {
  1832. marginBottom = actualPadding;
  1833. }
  1834. current.$slide.css("padding-bottom", inlinePadding);
  1835. }
  1836. }
  1837. current.$content.css("margin-bottom", marginBottom);
  1838. }
  1839. },
  1840. // Make content visible
  1841. // This method is called right after content has been loaded or
  1842. // user navigates gallery and transition should start
  1843. // ============================================================
  1844. revealContent: function(slide) {
  1845. var self = this,
  1846. $slide = slide.$slide,
  1847. end = false,
  1848. start = false,
  1849. isMoved = self.isMoved(slide),
  1850. isRevealed = slide.isRevealed,
  1851. effect,
  1852. effectClassName,
  1853. duration,
  1854. opacity;
  1855. slide.isRevealed = true;
  1856. effect = slide.opts[self.firstRun ? "animationEffect" : "transitionEffect"];
  1857. duration = slide.opts[self.firstRun ? "animationDuration" : "transitionDuration"];
  1858. duration = parseInt(slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10);
  1859. if (isMoved || slide.pos !== self.currPos || !duration) {
  1860. effect = false;
  1861. }
  1862. // Check if can zoom
  1863. if (effect === "zoom") {
  1864. if (slide.pos === self.currPos && duration && slide.type === "image" && !slide.hasError && (start = self.getThumbPos(slide))) {
  1865. end = self.getFitPos(slide);
  1866. } else {
  1867. effect = "fade";
  1868. }
  1869. }
  1870. // Zoom animation
  1871. // ==============
  1872. if (effect === "zoom") {
  1873. self.isAnimating = true;
  1874. end.scaleX = end.width / start.width;
  1875. end.scaleY = end.height / start.height;
  1876. // Check if we need to animate opacity
  1877. opacity = slide.opts.zoomOpacity;
  1878. if (opacity == "auto") {
  1879. opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1;
  1880. }
  1881. if (opacity) {
  1882. start.opacity = 0.1;
  1883. end.opacity = 1;
  1884. }
  1885. // Draw image at start position
  1886. $.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start);
  1887. forceRedraw(slide.$content);
  1888. // Start animation
  1889. $.fancybox.animate(slide.$content, end, duration, function() {
  1890. self.isAnimating = false;
  1891. self.complete();
  1892. });
  1893. return;
  1894. }
  1895. self.updateSlide(slide);
  1896. // Simply show content if no effect
  1897. // ================================
  1898. if (!effect) {
  1899. slide.$content.removeClass("fancybox-is-hidden");
  1900. if (!isRevealed && isMoved && slide.type === "image" && !slide.hasError) {
  1901. slide.$content.hide().fadeIn("fast");
  1902. }
  1903. if (slide.pos === self.currPos) {
  1904. self.complete();
  1905. }
  1906. return;
  1907. }
  1908. // Prepare for CSS transiton
  1909. // =========================
  1910. $.fancybox.stop($slide);
  1911. //effectClassName = "fancybox-animated fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-fx-" + effect;
  1912. effectClassName = "fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-animated fancybox-fx-" + effect;
  1913. $slide.addClass(effectClassName).removeClass("fancybox-slide--current"); //.addClass(effectClassName);
  1914. slide.$content.removeClass("fancybox-is-hidden");
  1915. // Force reflow
  1916. forceRedraw($slide);
  1917. if (slide.type !== "image") {
  1918. slide.$content.hide().show(0);
  1919. }
  1920. $.fancybox.animate(
  1921. $slide,
  1922. "fancybox-slide--current",
  1923. duration,
  1924. function() {
  1925. $slide.removeClass(effectClassName).css({
  1926. transform: "",
  1927. opacity: ""
  1928. });
  1929. if (slide.pos === self.currPos) {
  1930. self.complete();
  1931. }
  1932. },
  1933. true
  1934. );
  1935. },
  1936. // Check if we can and have to zoom from thumbnail
  1937. //================================================
  1938. getThumbPos: function(slide) {
  1939. var rez = false,
  1940. $thumb = slide.$thumb,
  1941. thumbPos,
  1942. btw,
  1943. brw,
  1944. bbw,
  1945. blw;
  1946. if (!$thumb || !inViewport($thumb[0])) {
  1947. return false;
  1948. }
  1949. thumbPos = $.fancybox.getTranslate($thumb);
  1950. btw = parseFloat($thumb.css("border-top-width") || 0);
  1951. brw = parseFloat($thumb.css("border-right-width") || 0);
  1952. bbw = parseFloat($thumb.css("border-bottom-width") || 0);
  1953. blw = parseFloat($thumb.css("border-left-width") || 0);
  1954. rez = {
  1955. top: thumbPos.top + btw,
  1956. left: thumbPos.left + blw,
  1957. width: thumbPos.width - brw - blw,
  1958. height: thumbPos.height - btw - bbw,
  1959. scaleX: 1,
  1960. scaleY: 1
  1961. };
  1962. return thumbPos.width > 0 && thumbPos.height > 0 ? rez : false;
  1963. },
  1964. // Final adjustments after current gallery item is moved to position
  1965. // and it`s content is loaded
  1966. // ==================================================================
  1967. complete: function() {
  1968. var self = this,
  1969. current = self.current,
  1970. slides = {},
  1971. $el;
  1972. if (self.isMoved() || !current.isLoaded) {
  1973. return;
  1974. }
  1975. if (!current.isComplete) {
  1976. current.isComplete = true;
  1977. current.$slide.siblings().trigger("onReset");
  1978. self.preload("inline");
  1979. // Trigger any CSS transiton inside the slide
  1980. forceRedraw(current.$slide);
  1981. current.$slide.addClass("fancybox-slide--complete");
  1982. // Remove unnecessary slides
  1983. $.each(self.slides, function(key, slide) {
  1984. if (slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1) {
  1985. slides[slide.pos] = slide;
  1986. } else if (slide) {
  1987. $.fancybox.stop(slide.$slide);
  1988. slide.$slide.off().remove();
  1989. }
  1990. });
  1991. self.slides = slides;
  1992. }
  1993. self.isAnimating = false;
  1994. self.updateCursor();
  1995. self.trigger("afterShow");
  1996. // Autoplay first html5 video/audio
  1997. if (!!current.opts.video.autoStart) {
  1998. current.$slide
  1999. .find("video,audio")
  2000. .filter(":visible:first")
  2001. .trigger("play")
  2002. .one("ended", function() {
  2003. if (this.webkitExitFullscreen) {
  2004. this.webkitExitFullscreen();
  2005. }
  2006. self.next();
  2007. });
  2008. }
  2009. // Try to focus on the first focusable element
  2010. if (current.opts.autoFocus && current.contentType === "html") {
  2011. // Look for the first input with autofocus attribute
  2012. $el = current.$content.find("input[autofocus]:enabled:visible:first");
  2013. if ($el.length) {
  2014. $el.trigger("focus");
  2015. } else {
  2016. self.focus(null, true);
  2017. }
  2018. }
  2019. // Avoid jumping
  2020. current.$slide.scrollTop(0).scrollLeft(0);
  2021. },
  2022. // Preload next and previous slides
  2023. // ================================
  2024. preload: function(type) {
  2025. var self = this,
  2026. prev,
  2027. next;
  2028. if (self.group.length < 2) {
  2029. return;
  2030. }
  2031. next = self.slides[self.currPos + 1];
  2032. prev = self.slides[self.currPos - 1];
  2033. if (prev && prev.type === type) {
  2034. self.loadSlide(prev);
  2035. }
  2036. if (next && next.type === type) {
  2037. self.loadSlide(next);
  2038. }
  2039. },
  2040. // Try to find and focus on the first focusable element
  2041. // ====================================================
  2042. focus: function(e, firstRun) {
  2043. var self = this,
  2044. focusableStr = [
  2045. "a[href]",
  2046. "area[href]",
  2047. 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',
  2048. "select:not([disabled]):not([aria-hidden])",
  2049. "textarea:not([disabled]):not([aria-hidden])",
  2050. "button:not([disabled]):not([aria-hidden])",
  2051. "iframe",
  2052. "object",
  2053. "embed",
  2054. "video",
  2055. "audio",
  2056. "[contenteditable]",
  2057. '[tabindex]:not([tabindex^="-"])'
  2058. ].join(","),
  2059. focusableItems,
  2060. focusedItemIndex;
  2061. if (self.isClosing) {
  2062. return;
  2063. }
  2064. if (e || !self.current || !self.current.isComplete) {
  2065. // Focus on any element inside fancybox
  2066. focusableItems = self.$refs.container.find("*:visible");
  2067. } else {
  2068. // Focus inside current slide
  2069. focusableItems = self.current.$slide.find("*:visible" + (firstRun ? ":not(.fancybox-close-small)" : ""));
  2070. }
  2071. focusableItems = focusableItems.filter(focusableStr).filter(function() {
  2072. return $(this).css("visibility") !== "hidden" && !$(this).hasClass("disabled");
  2073. });
  2074. if (focusableItems.length) {
  2075. focusedItemIndex = focusableItems.index(document.activeElement);
  2076. if (e && e.shiftKey) {
  2077. // Back tab
  2078. if (focusedItemIndex < 0 || focusedItemIndex == 0) {
  2079. e.preventDefault();
  2080. focusableItems.eq(focusableItems.length - 1).trigger("focus");
  2081. }
  2082. } else {
  2083. // Outside or Forward tab
  2084. if (focusedItemIndex < 0 || focusedItemIndex == focusableItems.length - 1) {
  2085. if (e) {
  2086. e.preventDefault();
  2087. }
  2088. focusableItems.eq(0).trigger("focus");
  2089. }
  2090. }
  2091. } else {
  2092. self.$refs.container.trigger("focus");
  2093. }
  2094. },
  2095. // Activates current instance - brings container to the front and enables keyboard,
  2096. // notifies other instances about deactivating
  2097. // =================================================================================
  2098. activate: function() {
  2099. var self = this;
  2100. // Deactivate all instances
  2101. $(".fancybox-container").each(function() {
  2102. var instance = $(this).data("FancyBox");
  2103. // Skip self and closing instances
  2104. if (instance && instance.id !== self.id && !instance.isClosing) {
  2105. instance.trigger("onDeactivate");
  2106. instance.removeEvents();
  2107. instance.isVisible = false;
  2108. }
  2109. });
  2110. self.isVisible = true;
  2111. if (self.current || self.isIdle) {
  2112. self.update();
  2113. self.updateControls();
  2114. }
  2115. self.trigger("onActivate");
  2116. self.addEvents();
  2117. },
  2118. // Start closing procedure
  2119. // This will start "zoom-out" animation if needed and clean everything up afterwards
  2120. // =================================================================================
  2121. close: function(e, d) {
  2122. var self = this,
  2123. current = self.current,
  2124. effect,
  2125. duration,
  2126. $content,
  2127. domRect,
  2128. opacity,
  2129. start,
  2130. end;
  2131. var done = function() {
  2132. self.cleanUp(e);
  2133. };
  2134. if (self.isClosing) {
  2135. return false;
  2136. }
  2137. self.isClosing = true;
  2138. // If beforeClose callback prevents closing, make sure content is centered
  2139. if (self.trigger("beforeClose", e) === false) {
  2140. self.isClosing = false;
  2141. requestAFrame(function() {
  2142. self.update();
  2143. });
  2144. return false;
  2145. }
  2146. // Remove all events
  2147. // If there are multiple instances, they will be set again by "activate" method
  2148. self.removeEvents();
  2149. $content = current.$content;
  2150. effect = current.opts.animationEffect;
  2151. duration = $.isNumeric(d) ? d : effect ? current.opts.animationDuration : 0;
  2152. current.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated");
  2153. if (e !== true) {
  2154. $.fancybox.stop(current.$slide);
  2155. } else {
  2156. effect = false;
  2157. }
  2158. // Remove other slides
  2159. current.$slide
  2160. .siblings()
  2161. .trigger("onReset")
  2162. .remove();
  2163. // Trigger animations
  2164. if (duration) {
  2165. self.$refs.container
  2166. .removeClass("fancybox-is-open")
  2167. .addClass("fancybox-is-closing")
  2168. .css("transition-duration", duration + "ms");
  2169. }
  2170. // Clean up
  2171. self.hideLoading(current);
  2172. self.hideControls(true);
  2173. self.updateCursor();
  2174. // Check if possible to zoom-out
  2175. if (
  2176. effect === "zoom" &&
  2177. !($content && duration && current.type === "image" && !self.isMoved() && !current.hasError && (end = self.getThumbPos(current)))
  2178. ) {
  2179. effect = "fade";
  2180. }
  2181. if (effect === "zoom") {
  2182. $.fancybox.stop($content);
  2183. domRect = $.fancybox.getTranslate($content);
  2184. start = {
  2185. top: domRect.top,
  2186. left: domRect.left,
  2187. scaleX: domRect.width / end.width,
  2188. scaleY: domRect.height / end.height,
  2189. width: end.width,
  2190. height: end.height
  2191. };
  2192. // Check if we need to animate opacity
  2193. opacity = current.opts.zoomOpacity;
  2194. if (opacity == "auto") {
  2195. opacity = Math.abs(current.width / current.height - end.width / end.height) > 0.1;
  2196. }
  2197. if (opacity) {
  2198. end.opacity = 0;
  2199. }
  2200. $.fancybox.setTranslate($content, start);
  2201. forceRedraw($content);
  2202. $.fancybox.animate($content, end, duration, done);
  2203. return true;
  2204. }
  2205. if (effect && duration) {
  2206. $.fancybox.animate(
  2207. current.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"),
  2208. "fancybox-animated fancybox-fx-" + effect,
  2209. duration,
  2210. done
  2211. );
  2212. } else {
  2213. // If skip animation
  2214. if (e === true) {
  2215. setTimeout(done, duration);
  2216. } else {
  2217. done();
  2218. }
  2219. }
  2220. return true;
  2221. },
  2222. // Final adjustments after removing the instance
  2223. // =============================================
  2224. cleanUp: function(e) {
  2225. var self = this,
  2226. instance,
  2227. $focus = self.current.opts.$orig,
  2228. x,
  2229. y;
  2230. self.current.$slide.trigger("onReset");
  2231. self.$refs.container.empty().remove();
  2232. self.trigger("afterClose", e);
  2233. // Place back focus
  2234. if (!!self.current.opts.backFocus) {
  2235. if (!$focus || !$focus.length || !$focus.is(":visible")) {
  2236. $focus = self.$trigger;
  2237. }
  2238. if ($focus && $focus.length) {
  2239. x = window.scrollX;
  2240. y = window.scrollY;
  2241. $focus.trigger("focus");
  2242. $("html, body")
  2243. .scrollTop(y)
  2244. .scrollLeft(x);
  2245. }
  2246. }
  2247. self.current = null;
  2248. // Check if there are other instances
  2249. instance = $.fancybox.getInstance();
  2250. if (instance) {
  2251. instance.activate();
  2252. } else {
  2253. $("body").removeClass("fancybox-active compensate-for-scrollbar");
  2254. $("#fancybox-style-noscroll").remove();
  2255. }
  2256. },
  2257. // Call callback and trigger an event
  2258. // ==================================
  2259. trigger: function(name, slide) {
  2260. var args = Array.prototype.slice.call(arguments, 1),
  2261. self = this,
  2262. obj = slide && slide.opts ? slide : self.current,
  2263. rez;
  2264. if (obj) {
  2265. args.unshift(obj);
  2266. } else {
  2267. obj = self;
  2268. }
  2269. args.unshift(self);
  2270. if ($.isFunction(obj.opts[name])) {
  2271. rez = obj.opts[name].apply(obj, args);
  2272. }
  2273. if (rez === false) {
  2274. return rez;
  2275. }
  2276. if (name === "afterClose" || !self.$refs) {
  2277. $D.trigger(name + ".fb", args);
  2278. } else {
  2279. self.$refs.container.trigger(name + ".fb", args);
  2280. }
  2281. },
  2282. // Update infobar values, navigation button states and reveal caption
  2283. // ==================================================================
  2284. updateControls: function() {
  2285. var self = this,
  2286. current = self.current,
  2287. index = current.index,
  2288. $container = self.$refs.container,
  2289. $caption = self.$refs.caption,
  2290. caption = current.opts.caption;
  2291. // Recalculate content dimensions
  2292. current.$slide.trigger("refresh");
  2293. // Set caption
  2294. if (caption && caption.length) {
  2295. self.$caption = $caption;
  2296. $caption
  2297. .children()
  2298. .eq(0)
  2299. .html(caption);
  2300. } else {
  2301. self.$caption = null;
  2302. }
  2303. if (!self.hasHiddenControls && !self.isIdle) {
  2304. self.showControls();
  2305. }
  2306. // Update info and navigation elements
  2307. $container.find("[data-fancybox-count]").html(self.group.length);
  2308. $container.find("[data-fancybox-index]").html(index + 1);
  2309. $container.find("[data-fancybox-prev]").prop("disabled", !current.opts.loop && index <= 0);
  2310. $container.find("[data-fancybox-next]").prop("disabled", !current.opts.loop && index >= self.group.length - 1);
  2311. if (current.type === "image") {
  2312. // Re-enable buttons; update download button source
  2313. $container
  2314. .find("[data-fancybox-zoom]")
  2315. .show()
  2316. .end()
  2317. .find("[data-fancybox-download]")
  2318. .attr("href", current.opts.image.src || current.src)
  2319. .show();
  2320. } else if (current.opts.toolbar) {
  2321. $container.find("[data-fancybox-download],[data-fancybox-zoom]").hide();
  2322. }
  2323. // Make sure focus is not on disabled button/element
  2324. if ($(document.activeElement).is(":hidden,[disabled]")) {
  2325. self.$refs.container.trigger("focus");
  2326. }
  2327. },
  2328. // Hide toolbar and caption
  2329. // ========================
  2330. hideControls: function(andCaption) {
  2331. var self = this,
  2332. arr = ["infobar", "toolbar", "nav"];
  2333. if (andCaption || !self.current.opts.preventCaptionOverlap) {
  2334. arr.push("caption");
  2335. }
  2336. this.$refs.container.removeClass(
  2337. arr
  2338. .map(function(i) {
  2339. return "fancybox-show-" + i;
  2340. })
  2341. .join(" ")
  2342. );
  2343. this.hasHiddenControls = true;
  2344. },
  2345. showControls: function() {
  2346. var self = this,
  2347. opts = self.current ? self.current.opts : self.opts,
  2348. $container = self.$refs.container;
  2349. self.hasHiddenControls = false;
  2350. self.idleSecondsCounter = 0;
  2351. $container
  2352. .toggleClass("fancybox-show-toolbar", !!(opts.toolbar && opts.buttons))
  2353. .toggleClass("fancybox-show-infobar", !!(opts.infobar && self.group.length > 1))
  2354. .toggleClass("fancybox-show-caption", !!self.$caption)
  2355. .toggleClass("fancybox-show-nav", !!(opts.arrows && self.group.length > 1))
  2356. .toggleClass("fancybox-is-modal", !!opts.modal);
  2357. },
  2358. // Toggle toolbar and caption
  2359. // ==========================
  2360. toggleControls: function() {
  2361. if (this.hasHiddenControls) {
  2362. this.showControls();
  2363. } else {
  2364. this.hideControls();
  2365. }
  2366. }
  2367. });
  2368. $.fancybox = {
  2369. version: "3.5.6",
  2370. defaults: defaults,
  2371. // Get current instance and execute a command.
  2372. //
  2373. // Examples of usage:
  2374. //
  2375. // $instance = $.fancybox.getInstance();
  2376. // $.fancybox.getInstance().jumpTo( 1 );
  2377. // $.fancybox.getInstance( 'jumpTo', 1 );
  2378. // $.fancybox.getInstance( function() {
  2379. // console.info( this.currIndex );
  2380. // });
  2381. // ======================================================
  2382. getInstance: function(command) {
  2383. var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"),
  2384. args = Array.prototype.slice.call(arguments, 1);
  2385. if (instance instanceof FancyBox) {
  2386. if ($.type(command) === "string") {
  2387. instance[command].apply(instance, args);
  2388. } else if ($.type(command) === "function") {
  2389. command.apply(instance, args);
  2390. }
  2391. return instance;
  2392. }
  2393. return false;
  2394. },
  2395. // Create new instance
  2396. // ===================
  2397. open: function(items, opts, index) {
  2398. return new FancyBox(items, opts, index);
  2399. },
  2400. // Close current or all instances
  2401. // ==============================
  2402. close: function(all) {
  2403. var instance = this.getInstance();
  2404. if (instance) {
  2405. instance.close();
  2406. // Try to find and close next instance
  2407. if (all === true) {
  2408. this.close(all);
  2409. }
  2410. }
  2411. },
  2412. // Close all instances and unbind all events
  2413. // =========================================
  2414. destroy: function() {
  2415. this.close(true);
  2416. $D.add("body").off("click.fb-start", "**");
  2417. },
  2418. // Try to detect mobile devices
  2419. // ============================
  2420. isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
  2421. // Detect if 'translate3d' support is available
  2422. // ============================================
  2423. use3d: (function() {
  2424. var div = document.createElement("div");
  2425. return (
  2426. window.getComputedStyle &&
  2427. window.getComputedStyle(div) &&
  2428. window.getComputedStyle(div).getPropertyValue("transform") &&
  2429. !(document.documentMode && document.documentMode < 11)
  2430. );
  2431. })(),
  2432. // Helper function to get current visual state of an element
  2433. // returns array[ top, left, horizontal-scale, vertical-scale, opacity ]
  2434. // =====================================================================
  2435. getTranslate: function($el) {
  2436. var domRect;
  2437. if (!$el || !$el.length) {
  2438. return false;
  2439. }
  2440. domRect = $el[0].getBoundingClientRect();
  2441. return {
  2442. top: domRect.top || 0,
  2443. left: domRect.left || 0,
  2444. width: domRect.width,
  2445. height: domRect.height,
  2446. opacity: parseFloat($el.css("opacity"))
  2447. };
  2448. },
  2449. // Shortcut for setting "translate3d" properties for element
  2450. // Can set be used to set opacity, too
  2451. // ========================================================
  2452. setTranslate: function($el, props) {
  2453. var str = "",
  2454. css = {};
  2455. if (!$el || !props) {
  2456. return;
  2457. }
  2458. if (props.left !== undefined || props.top !== undefined) {
  2459. str =
  2460. (props.left === undefined ? $el.position().left : props.left) +
  2461. "px, " +
  2462. (props.top === undefined ? $el.position().top : props.top) +
  2463. "px";
  2464. if (this.use3d) {
  2465. str = "translate3d(" + str + ", 0px)";
  2466. } else {
  2467. str = "translate(" + str + ")";
  2468. }
  2469. }
  2470. if (props.scaleX !== undefined && props.scaleY !== undefined) {
  2471. str += " scale(" + props.scaleX + ", " + props.scaleY + ")";
  2472. } else if (props.scaleX !== undefined) {
  2473. str += " scaleX(" + props.scaleX + ")";
  2474. }
  2475. if (str.length) {
  2476. css.transform = str;
  2477. }
  2478. if (props.opacity !== undefined) {
  2479. css.opacity = props.opacity;
  2480. }
  2481. if (props.width !== undefined) {
  2482. css.width = props.width;
  2483. }
  2484. if (props.height !== undefined) {
  2485. css.height = props.height;
  2486. }
  2487. return $el.css(css);
  2488. },
  2489. // Simple CSS transition handler
  2490. // =============================
  2491. animate: function($el, to, duration, callback, leaveAnimationName) {
  2492. var self = this,
  2493. from;
  2494. if ($.isFunction(duration)) {
  2495. callback = duration;
  2496. duration = null;
  2497. }
  2498. self.stop($el);
  2499. from = self.getTranslate($el);
  2500. $el.on(transitionEnd, function(e) {
  2501. // Skip events from child elements and z-index change
  2502. if (e && e.originalEvent && (!$el.is(e.originalEvent.target) || e.originalEvent.propertyName == "z-index")) {
  2503. return;
  2504. }
  2505. self.stop($el);
  2506. if ($.isNumeric(duration)) {
  2507. $el.css("transition-duration", "");
  2508. }
  2509. if ($.isPlainObject(to)) {
  2510. if (to.scaleX !== undefined && to.scaleY !== undefined) {
  2511. self.setTranslate($el, {
  2512. top: to.top,
  2513. left: to.left,
  2514. width: from.width * to.scaleX,
  2515. height: from.height * to.scaleY,
  2516. scaleX: 1,
  2517. scaleY: 1
  2518. });
  2519. }
  2520. } else if (leaveAnimationName !== true) {
  2521. $el.removeClass(to);
  2522. }
  2523. if ($.isFunction(callback)) {
  2524. callback(e);
  2525. }
  2526. });
  2527. if ($.isNumeric(duration)) {
  2528. $el.css("transition-duration", duration + "ms");
  2529. }
  2530. // Start animation by changing CSS properties or class name
  2531. if ($.isPlainObject(to)) {
  2532. if (to.scaleX !== undefined && to.scaleY !== undefined) {
  2533. delete to.width;
  2534. delete to.height;
  2535. if ($el.parent().hasClass("fancybox-slide--image")) {
  2536. $el.parent().addClass("fancybox-is-scaling");
  2537. }
  2538. }
  2539. $.fancybox.setTranslate($el, to);
  2540. } else {
  2541. $el.addClass(to);
  2542. }
  2543. // Make sure that `transitionend` callback gets fired
  2544. $el.data(
  2545. "timer",
  2546. setTimeout(function() {
  2547. $el.trigger(transitionEnd);
  2548. }, duration + 33)
  2549. );
  2550. },
  2551. stop: function($el, callCallback) {
  2552. if ($el && $el.length) {
  2553. clearTimeout($el.data("timer"));
  2554. if (callCallback) {
  2555. $el.trigger(transitionEnd);
  2556. }
  2557. $el.off(transitionEnd).css("transition-duration", "");
  2558. $el.parent().removeClass("fancybox-is-scaling");
  2559. }
  2560. }
  2561. };
  2562. // Default click handler for "fancyboxed" links
  2563. // ============================================
  2564. function _run(e, opts) {
  2565. var items = [],
  2566. index = 0,
  2567. $target,
  2568. value,
  2569. instance;
  2570. // Avoid opening multiple times
  2571. if (e && e.isDefaultPrevented()) {
  2572. return;
  2573. }
  2574. e.preventDefault();
  2575. opts = opts || {};
  2576. if (e && e.data) {
  2577. opts = mergeOpts(e.data.options, opts);
  2578. }
  2579. $target = opts.$target || $(e.currentTarget).trigger("blur");
  2580. instance = $.fancybox.getInstance();
  2581. if (instance && instance.$trigger && instance.$trigger.is($target)) {
  2582. return;
  2583. }
  2584. if (opts.selector) {
  2585. items = $(opts.selector);
  2586. } else {
  2587. // Get all related items and find index for clicked one
  2588. value = $target.attr("data-fancybox") || "";
  2589. if (value) {
  2590. items = e.data ? e.data.items : [];
  2591. items = items.length ? items.filter('[data-fancybox="' + value + '"]') : $('[data-fancybox="' + value + '"]');
  2592. } else {
  2593. items = [$target];
  2594. }
  2595. }
  2596. index = $(items).index($target);
  2597. // Sometimes current item can not be found
  2598. if (index < 0) {
  2599. index = 0;
  2600. }
  2601. instance = $.fancybox.open(items, opts, index);
  2602. // Save last active element
  2603. instance.$trigger = $target;
  2604. }
  2605. // Create a jQuery plugin
  2606. // ======================
  2607. $.fn.fancybox = function(options) {
  2608. var selector;
  2609. options = options || {};
  2610. selector = options.selector || false;
  2611. if (selector) {
  2612. // Use body element instead of document so it executes first
  2613. $("body")
  2614. .off("click.fb-start", selector)
  2615. .on("click.fb-start", selector, {options: options}, _run);
  2616. } else {
  2617. this.off("click.fb-start").on(
  2618. "click.fb-start",
  2619. {
  2620. items: this,
  2621. options: options
  2622. },
  2623. _run
  2624. );
  2625. }
  2626. return this;
  2627. };
  2628. // Self initializing plugin for all elements having `data-fancybox` attribute
  2629. // ==========================================================================
  2630. $D.on("click.fb-start", "[data-fancybox]", _run);
  2631. // Enable "trigger elements"
  2632. // =========================
  2633. $D.on("click.fb-start", "[data-fancybox-trigger]", function(e) {
  2634. $('[data-fancybox="' + $(this).attr("data-fancybox-trigger") + '"]')
  2635. .eq($(this).attr("data-fancybox-index") || 0)
  2636. .trigger("click.fb-start", {
  2637. $trigger: $(this)
  2638. });
  2639. });
  2640. // Track focus event for better accessibility styling
  2641. // ==================================================
  2642. (function() {
  2643. var buttonStr = ".fancybox-button",
  2644. focusStr = "fancybox-focus",
  2645. $pressed = null;
  2646. $D.on("mousedown mouseup focus blur", buttonStr, function(e) {
  2647. switch (e.type) {
  2648. case "mousedown":
  2649. $pressed = $(this);
  2650. break;
  2651. case "mouseup":
  2652. $pressed = null;
  2653. break;
  2654. case "focusin":
  2655. $(buttonStr).removeClass(focusStr);
  2656. if (!$(this).is($pressed) && !$(this).is("[disabled]")) {
  2657. $(this).addClass(focusStr);
  2658. }
  2659. break;
  2660. case "focusout":
  2661. $(buttonStr).removeClass(focusStr);
  2662. break;
  2663. }
  2664. });
  2665. })();
  2666. })(window, document, jQuery);
  2667. // ==========================================================================
  2668. //
  2669. // Media
  2670. // Adds additional media type support
  2671. //
  2672. // ==========================================================================
  2673. (function($) {
  2674. "use strict";
  2675. // Object containing properties for each media type
  2676. var defaults = {
  2677. youtube: {
  2678. matcher: /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i,
  2679. params: {
  2680. autoplay: 1,
  2681. autohide: 1,
  2682. fs: 1,
  2683. rel: 0,
  2684. hd: 1,
  2685. wmode: "transparent",
  2686. enablejsapi: 1,
  2687. html5: 1
  2688. },
  2689. paramPlace: 8,
  2690. type: "iframe",
  2691. url: "https://www.youtube-nocookie.com/embed/$4",
  2692. thumb: "https://img.youtube.com/vi/$4/hqdefault.jpg"
  2693. },
  2694. vimeo: {
  2695. matcher: /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/,
  2696. params: {
  2697. autoplay: 1,
  2698. hd: 1,
  2699. show_title: 1,
  2700. show_byline: 1,
  2701. show_portrait: 0,
  2702. fullscreen: 1
  2703. },
  2704. paramPlace: 3,
  2705. type: "iframe",
  2706. url: "//player.vimeo.com/video/$2"
  2707. },
  2708. instagram: {
  2709. matcher: /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,
  2710. type: "image",
  2711. url: "//$1/p/$2/media/?size=l"
  2712. },
  2713. // Examples:
  2714. // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16
  2715. // https://www.google.com/maps/@37.7852006,-122.4146355,14.65z
  2716. // https://www.google.com/maps/@52.2111123,2.9237542,6.61z?hl=en
  2717. // https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572
  2718. gmap_place: {
  2719. matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,
  2720. type: "iframe",
  2721. url: function(rez) {
  2722. return (
  2723. "//maps.google." +
  2724. rez[2] +
  2725. "/?ll=" +
  2726. (rez[9] ? rez[9] + "&z=" + Math.floor(rez[10]) + (rez[12] ? rez[12].replace(/^\//, "&") : "") : rez[12] + "").replace(/\?/, "&") +
  2727. "&output=" +
  2728. (rez[12] && rez[12].indexOf("layer=c") > 0 ? "svembed" : "embed")
  2729. );
  2730. }
  2731. },
  2732. // Examples:
  2733. // https://www.google.com/maps/search/Empire+State+Building/
  2734. // https://www.google.com/maps/search/?api=1&query=centurylink+field
  2735. // https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393
  2736. gmap_search: {
  2737. matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,
  2738. type: "iframe",
  2739. url: function(rez) {
  2740. return "//maps.google." + rez[2] + "/maps?q=" + rez[5].replace("query=", "q=").replace("api=1", "") + "&output=embed";
  2741. }
  2742. }
  2743. };
  2744. // Formats matching url to final form
  2745. var format = function(url, rez, params) {
  2746. if (!url) {
  2747. return;
  2748. }
  2749. params = params || "";
  2750. if ($.type(params) === "object") {
  2751. params = $.param(params, true);
  2752. }
  2753. $.each(rez, function(key, value) {
  2754. url = url.replace("$" + key, value || "");
  2755. });
  2756. if (params.length) {
  2757. url += (url.indexOf("?") > 0 ? "&" : "?") + params;
  2758. }
  2759. return url;
  2760. };
  2761. $(document).on("objectNeedsType.fb", function(e, instance, item) {
  2762. var url = item.src || "",
  2763. type = false,
  2764. media,
  2765. thumb,
  2766. rez,
  2767. params,
  2768. urlParams,
  2769. paramObj,
  2770. provider;
  2771. media = $.extend(true, {}, defaults, item.opts.media);
  2772. // Look for any matching media type
  2773. $.each(media, function(providerName, providerOpts) {
  2774. rez = url.match(providerOpts.matcher);
  2775. if (!rez) {
  2776. return;
  2777. }
  2778. type = providerOpts.type;
  2779. provider = providerName;
  2780. paramObj = {};
  2781. if (providerOpts.paramPlace && rez[providerOpts.paramPlace]) {
  2782. urlParams = rez[providerOpts.paramPlace];
  2783. if (urlParams[0] == "?") {
  2784. urlParams = urlParams.substring(1);
  2785. }
  2786. urlParams = urlParams.split("&");
  2787. for (var m = 0; m < urlParams.length; ++m) {
  2788. var p = urlParams[m].split("=", 2);
  2789. if (p.length == 2) {
  2790. paramObj[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
  2791. }
  2792. }
  2793. }
  2794. params = $.extend(true, {}, providerOpts.params, item.opts[providerName], paramObj);
  2795. url =
  2796. $.type(providerOpts.url) === "function" ? providerOpts.url.call(this, rez, params, item) : format(providerOpts.url, rez, params);
  2797. thumb =
  2798. $.type(providerOpts.thumb) === "function" ? providerOpts.thumb.call(this, rez, params, item) : format(providerOpts.thumb, rez);
  2799. if (providerName === "youtube") {
  2800. url = url.replace(/&t=((\d+)m)?(\d+)s/, function(match, p1, m, s) {
  2801. return "&start=" + ((m ? parseInt(m, 10) * 60 : 0) + parseInt(s, 10));
  2802. });
  2803. } else if (providerName === "vimeo") {
  2804. url = url.replace("&%23", "#");
  2805. }
  2806. return false;
  2807. });
  2808. // If it is found, then change content type and update the url
  2809. if (type) {
  2810. if (!item.opts.thumb && !(item.opts.$thumb && item.opts.$thumb.length)) {
  2811. item.opts.thumb = thumb;
  2812. }
  2813. if (type === "iframe") {
  2814. item.opts = $.extend(true, item.opts, {
  2815. iframe: {
  2816. preload: false,
  2817. attr: {
  2818. scrolling: "no"
  2819. }
  2820. }
  2821. });
  2822. }
  2823. $.extend(item, {
  2824. type: type,
  2825. src: url,
  2826. origSrc: item.src,
  2827. contentSource: provider,
  2828. contentType: type === "image" ? "image" : provider == "gmap_place" || provider == "gmap_search" ? "map" : "video"
  2829. });
  2830. } else if (url) {
  2831. item.type = item.opts.defaultType;
  2832. }
  2833. });
  2834. // Load YouTube/Video API on request to detect when video finished playing
  2835. var VideoAPILoader = {
  2836. youtube: {
  2837. src: "https://www.youtube.com/iframe_api",
  2838. class: "YT",
  2839. loading: false,
  2840. loaded: false
  2841. },
  2842. vimeo: {
  2843. src: "https://player.vimeo.com/api/player.js",
  2844. class: "Vimeo",
  2845. loading: false,
  2846. loaded: false
  2847. },
  2848. load: function(vendor) {
  2849. var _this = this,
  2850. script;
  2851. if (this[vendor].loaded) {
  2852. setTimeout(function() {
  2853. _this.done(vendor);
  2854. });
  2855. return;
  2856. }
  2857. if (this[vendor].loading) {
  2858. return;
  2859. }
  2860. this[vendor].loading = true;
  2861. script = document.createElement("script");
  2862. script.type = "text/javascript";
  2863. script.src = this[vendor].src;
  2864. if (vendor === "youtube") {
  2865. window.onYouTubeIframeAPIReady = function() {
  2866. _this[vendor].loaded = true;
  2867. _this.done(vendor);
  2868. };
  2869. } else {
  2870. script.onload = function() {
  2871. _this[vendor].loaded = true;
  2872. _this.done(vendor);
  2873. };
  2874. }
  2875. document.body.appendChild(script);
  2876. },
  2877. done: function(vendor) {
  2878. var instance, $el, player;
  2879. if (vendor === "youtube") {
  2880. delete window.onYouTubeIframeAPIReady;
  2881. }
  2882. instance = $.fancybox.getInstance();
  2883. if (instance) {
  2884. $el = instance.current.$content.find("iframe");
  2885. if (vendor === "youtube" && YT !== undefined && YT) {
  2886. player = new YT.Player($el.attr("id"), {
  2887. events: {
  2888. onStateChange: function(e) {
  2889. if (e.data == 0) {
  2890. instance.next();
  2891. }
  2892. }
  2893. }
  2894. });
  2895. } else if (vendor === "vimeo" && Vimeo !== undefined && Vimeo) {
  2896. player = new Vimeo.Player($el);
  2897. player.on("ended", function() {
  2898. instance.next();
  2899. });
  2900. }
  2901. }
  2902. }
  2903. };
  2904. $(document).on({
  2905. "afterShow.fb": function(e, instance, current) {
  2906. if (instance.group.length > 1 && (current.contentSource === "youtube" || current.contentSource === "vimeo")) {
  2907. VideoAPILoader.load(current.contentSource);
  2908. }
  2909. }
  2910. });
  2911. })(jQuery);
  2912. // ==========================================================================
  2913. //
  2914. // Guestures
  2915. // Adds touch guestures, handles click and tap events
  2916. //
  2917. // ==========================================================================
  2918. (function(window, document, $) {
  2919. "use strict";
  2920. var requestAFrame = (function() {
  2921. return (
  2922. window.requestAnimationFrame ||
  2923. window.webkitRequestAnimationFrame ||
  2924. window.mozRequestAnimationFrame ||
  2925. window.oRequestAnimationFrame ||
  2926. // if all else fails, use setTimeout
  2927. function(callback) {
  2928. return window.setTimeout(callback, 1000 / 60);
  2929. }
  2930. );
  2931. })();
  2932. var cancelAFrame = (function() {
  2933. return (
  2934. window.cancelAnimationFrame ||
  2935. window.webkitCancelAnimationFrame ||
  2936. window.mozCancelAnimationFrame ||
  2937. window.oCancelAnimationFrame ||
  2938. function(id) {
  2939. window.clearTimeout(id);
  2940. }
  2941. );
  2942. })();
  2943. var getPointerXY = function(e) {
  2944. var result = [];
  2945. e = e.originalEvent || e || window.e;
  2946. e = e.touches && e.touches.length ? e.touches : e.changedTouches && e.changedTouches.length ? e.changedTouches : [e];
  2947. for (var key in e) {
  2948. if (e[key].pageX) {
  2949. result.push({
  2950. x: e[key].pageX,
  2951. y: e[key].pageY
  2952. });
  2953. } else if (e[key].clientX) {
  2954. result.push({
  2955. x: e[key].clientX,
  2956. y: e[key].clientY
  2957. });
  2958. }
  2959. }
  2960. return result;
  2961. };
  2962. var distance = function(point2, point1, what) {
  2963. if (!point1 || !point2) {
  2964. return 0;
  2965. }
  2966. if (what === "x") {
  2967. return point2.x - point1.x;
  2968. } else if (what === "y") {
  2969. return point2.y - point1.y;
  2970. }
  2971. return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
  2972. };
  2973. var isClickable = function($el) {
  2974. if (
  2975. $el.is('a,area,button,[role="button"],input,label,select,summary,textarea,video,audio,iframe') ||
  2976. $.isFunction($el.get(0).onclick) ||
  2977. $el.data("selectable")
  2978. ) {
  2979. return true;
  2980. }
  2981. // Check for attributes like data-fancybox-next or data-fancybox-close
  2982. for (var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++) {
  2983. if (atts[i].nodeName.substr(0, 14) === "data-fancybox-") {
  2984. return true;
  2985. }
  2986. }
  2987. return false;
  2988. };
  2989. var hasScrollbars = function(el) {
  2990. var overflowY = window.getComputedStyle(el)["overflow-y"],
  2991. overflowX = window.getComputedStyle(el)["overflow-x"],
  2992. vertical = (overflowY === "scroll" || overflowY === "auto") && el.scrollHeight > el.clientHeight,
  2993. horizontal = (overflowX === "scroll" || overflowX === "auto") && el.scrollWidth > el.clientWidth;
  2994. return vertical || horizontal;
  2995. };
  2996. var isScrollable = function($el) {
  2997. var rez = false;
  2998. while (true) {
  2999. rez = hasScrollbars($el.get(0));
  3000. if (rez) {
  3001. break;
  3002. }
  3003. $el = $el.parent();
  3004. if (!$el.length || $el.hasClass("fancybox-stage") || $el.is("body")) {
  3005. break;
  3006. }
  3007. }
  3008. return rez;
  3009. };
  3010. var Guestures = function(instance) {
  3011. var self = this;
  3012. self.instance = instance;
  3013. self.$bg = instance.$refs.bg;
  3014. self.$stage = instance.$refs.stage;
  3015. self.$container = instance.$refs.container;
  3016. self.destroy();
  3017. self.$container.on("touchstart.fb.touch mousedown.fb.touch", $.proxy(self, "ontouchstart"));
  3018. };
  3019. Guestures.prototype.destroy = function() {
  3020. var self = this;
  3021. self.$container.off(".fb.touch");
  3022. $(document).off(".fb.touch");
  3023. if (self.requestId) {
  3024. cancelAFrame(self.requestId);
  3025. self.requestId = null;
  3026. }
  3027. if (self.tapped) {
  3028. clearTimeout(self.tapped);
  3029. self.tapped = null;
  3030. }
  3031. };
  3032. Guestures.prototype.ontouchstart = function(e) {
  3033. var self = this,
  3034. $target = $(e.target),
  3035. instance = self.instance,
  3036. current = instance.current,
  3037. $slide = current.$slide,
  3038. $content = current.$content,
  3039. isTouchDevice = e.type == "touchstart";
  3040. // Do not respond to both (touch and mouse) events
  3041. if (isTouchDevice) {
  3042. self.$container.off("mousedown.fb.touch");
  3043. }
  3044. // Ignore right click
  3045. if (e.originalEvent && e.originalEvent.button == 2) {
  3046. return;
  3047. }
  3048. // Ignore taping on links, buttons, input elements
  3049. if (!$slide.length || !$target.length || isClickable($target) || isClickable($target.parent())) {
  3050. return;
  3051. }
  3052. // Ignore clicks on the scrollbar
  3053. if (!$target.is("img") && e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left) {
  3054. return;
  3055. }
  3056. // Ignore clicks while zooming or closing
  3057. if (!current || instance.isAnimating || current.$slide.hasClass("fancybox-animated")) {
  3058. e.stopPropagation();
  3059. e.preventDefault();
  3060. return;
  3061. }
  3062. self.realPoints = self.startPoints = getPointerXY(e);
  3063. if (!self.startPoints.length) {
  3064. return;
  3065. }
  3066. // Allow other scripts to catch touch event if "touch" is set to false
  3067. if (current.touch) {
  3068. e.stopPropagation();
  3069. }
  3070. self.startEvent = e;
  3071. self.canTap = true;
  3072. self.$target = $target;
  3073. self.$content = $content;
  3074. self.opts = current.opts.touch;
  3075. self.isPanning = false;
  3076. self.isSwiping = false;
  3077. self.isZooming = false;
  3078. self.isScrolling = false;
  3079. self.canPan = instance.canPan();
  3080. self.startTime = new Date().getTime();
  3081. self.distanceX = self.distanceY = self.distance = 0;
  3082. self.canvasWidth = Math.round($slide[0].clientWidth);
  3083. self.canvasHeight = Math.round($slide[0].clientHeight);
  3084. self.contentLastPos = null;
  3085. self.contentStartPos = $.fancybox.getTranslate(self.$content) || {top: 0, left: 0};
  3086. self.sliderStartPos = $.fancybox.getTranslate($slide);
  3087. // Since position will be absolute, but we need to make it relative to the stage
  3088. self.stagePos = $.fancybox.getTranslate(instance.$refs.stage);
  3089. self.sliderStartPos.top -= self.stagePos.top;
  3090. self.sliderStartPos.left -= self.stagePos.left;
  3091. self.contentStartPos.top -= self.stagePos.top;
  3092. self.contentStartPos.left -= self.stagePos.left;
  3093. $(document)
  3094. .off(".fb.touch")
  3095. .on(isTouchDevice ? "touchend.fb.touch touchcancel.fb.touch" : "mouseup.fb.touch mouseleave.fb.touch", $.proxy(self, "ontouchend"))
  3096. .on(isTouchDevice ? "touchmove.fb.touch" : "mousemove.fb.touch", $.proxy(self, "ontouchmove"));
  3097. if ($.fancybox.isMobile) {
  3098. document.addEventListener("scroll", self.onscroll, true);
  3099. }
  3100. // Skip if clicked outside the sliding area
  3101. if (!(self.opts || self.canPan) || !($target.is(self.$stage) || self.$stage.find($target).length)) {
  3102. if ($target.is(".fancybox-image")) {
  3103. e.preventDefault();
  3104. }
  3105. if (!($.fancybox.isMobile && $target.parents(".fancybox-caption").length)) {
  3106. return;
  3107. }
  3108. }
  3109. self.isScrollable = isScrollable($target) || isScrollable($target.parent());
  3110. // Check if element is scrollable and try to prevent default behavior (scrolling)
  3111. if (!($.fancybox.isMobile && self.isScrollable)) {
  3112. e.preventDefault();
  3113. }
  3114. // One finger or mouse click - swipe or pan an image
  3115. if (self.startPoints.length === 1 || current.hasError) {
  3116. if (self.canPan) {
  3117. $.fancybox.stop(self.$content);
  3118. self.isPanning = true;
  3119. } else {
  3120. self.isSwiping = true;
  3121. }
  3122. self.$container.addClass("fancybox-is-grabbing");
  3123. }
  3124. // Two fingers - zoom image
  3125. if (self.startPoints.length === 2 && current.type === "image" && (current.isLoaded || current.$ghost)) {
  3126. self.canTap = false;
  3127. self.isSwiping = false;
  3128. self.isPanning = false;
  3129. self.isZooming = true;
  3130. $.fancybox.stop(self.$content);
  3131. self.centerPointStartX = (self.startPoints[0].x + self.startPoints[1].x) * 0.5 - $(window).scrollLeft();
  3132. self.centerPointStartY = (self.startPoints[0].y + self.startPoints[1].y) * 0.5 - $(window).scrollTop();
  3133. self.percentageOfImageAtPinchPointX = (self.centerPointStartX - self.contentStartPos.left) / self.contentStartPos.width;
  3134. self.percentageOfImageAtPinchPointY = (self.centerPointStartY - self.contentStartPos.top) / self.contentStartPos.height;
  3135. self.startDistanceBetweenFingers = distance(self.startPoints[0], self.startPoints[1]);
  3136. }
  3137. };
  3138. Guestures.prototype.onscroll = function(e) {
  3139. var self = this;
  3140. self.isScrolling = true;
  3141. document.removeEventListener("scroll", self.onscroll, true);
  3142. };
  3143. Guestures.prototype.ontouchmove = function(e) {
  3144. var self = this;
  3145. // Make sure user has not released over iframe or disabled element
  3146. if (e.originalEvent.buttons !== undefined && e.originalEvent.buttons === 0) {
  3147. self.ontouchend(e);
  3148. return;
  3149. }
  3150. if (self.isScrolling) {
  3151. self.canTap = false;
  3152. return;
  3153. }
  3154. self.newPoints = getPointerXY(e);
  3155. if (!(self.opts || self.canPan) || !self.newPoints.length || !self.newPoints.length) {
  3156. return;
  3157. }
  3158. if (!(self.isSwiping && self.isSwiping === true)) {
  3159. e.preventDefault();
  3160. }
  3161. self.distanceX = distance(self.newPoints[0], self.startPoints[0], "x");
  3162. self.distanceY = distance(self.newPoints[0], self.startPoints[0], "y");
  3163. self.distance = distance(self.newPoints[0], self.startPoints[0]);
  3164. // Skip false ontouchmove events (Chrome)
  3165. if (self.distance > 0) {
  3166. if (self.isSwiping) {
  3167. self.onSwipe(e);
  3168. } else if (self.isPanning) {
  3169. self.onPan();
  3170. } else if (self.isZooming) {
  3171. self.onZoom();
  3172. }
  3173. }
  3174. };
  3175. Guestures.prototype.onSwipe = function(e) {
  3176. var self = this,
  3177. instance = self.instance,
  3178. swiping = self.isSwiping,
  3179. left = self.sliderStartPos.left || 0,
  3180. angle;
  3181. // If direction is not yet determined
  3182. if (swiping === true) {
  3183. // We need at least 10px distance to correctly calculate an angle
  3184. if (Math.abs(self.distance) > 10) {
  3185. self.canTap = false;
  3186. if (instance.group.length < 2 && self.opts.vertical) {
  3187. self.isSwiping = "y";
  3188. } else if (instance.isDragging || self.opts.vertical === false || (self.opts.vertical === "auto" && $(window).width() > 800)) {
  3189. self.isSwiping = "x";
  3190. } else {
  3191. angle = Math.abs((Math.atan2(self.distanceY, self.distanceX) * 180) / Math.PI);
  3192. self.isSwiping = angle > 45 && angle < 135 ? "y" : "x";
  3193. }
  3194. if (self.isSwiping === "y" && $.fancybox.isMobile && self.isScrollable) {
  3195. self.isScrolling = true;
  3196. return;
  3197. }
  3198. instance.isDragging = self.isSwiping;
  3199. // Reset points to avoid jumping, because we dropped first swipes to calculate the angle
  3200. self.startPoints = self.newPoints;
  3201. $.each(instance.slides, function(index, slide) {
  3202. var slidePos, stagePos;
  3203. $.fancybox.stop(slide.$slide);
  3204. slidePos = $.fancybox.getTranslate(slide.$slide);
  3205. stagePos = $.fancybox.getTranslate(instance.$refs.stage);
  3206. slide.$slide
  3207. .css({
  3208. transform: "",
  3209. opacity: "",
  3210. "transition-duration": ""
  3211. })
  3212. .removeClass("fancybox-animated")
  3213. .removeClass(function(index, className) {
  3214. return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" ");
  3215. });
  3216. if (slide.pos === instance.current.pos) {
  3217. self.sliderStartPos.top = slidePos.top - stagePos.top;
  3218. self.sliderStartPos.left = slidePos.left - stagePos.left;
  3219. }
  3220. $.fancybox.setTranslate(slide.$slide, {
  3221. top: slidePos.top - stagePos.top,
  3222. left: slidePos.left - stagePos.left
  3223. });
  3224. });
  3225. // Stop slideshow
  3226. if (instance.SlideShow && instance.SlideShow.isActive) {
  3227. instance.SlideShow.stop();
  3228. }
  3229. }
  3230. return;
  3231. }
  3232. // Sticky edges
  3233. if (swiping == "x") {
  3234. if (
  3235. self.distanceX > 0 &&
  3236. (self.instance.group.length < 2 || (self.instance.current.index === 0 && !self.instance.current.opts.loop))
  3237. ) {
  3238. left = left + Math.pow(self.distanceX, 0.8);
  3239. } else if (
  3240. self.distanceX < 0 &&
  3241. (self.instance.group.length < 2 ||
  3242. (self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop))
  3243. ) {
  3244. left = left - Math.pow(-self.distanceX, 0.8);
  3245. } else {
  3246. left = left + self.distanceX;
  3247. }
  3248. }
  3249. self.sliderLastPos = {
  3250. top: swiping == "x" ? 0 : self.sliderStartPos.top + self.distanceY,
  3251. left: left
  3252. };
  3253. if (self.requestId) {
  3254. cancelAFrame(self.requestId);
  3255. self.requestId = null;
  3256. }
  3257. self.requestId = requestAFrame(function() {
  3258. if (self.sliderLastPos) {
  3259. $.each(self.instance.slides, function(index, slide) {
  3260. var pos = slide.pos - self.instance.currPos;
  3261. $.fancybox.setTranslate(slide.$slide, {
  3262. top: self.sliderLastPos.top,
  3263. left: self.sliderLastPos.left + pos * self.canvasWidth + pos * slide.opts.gutter
  3264. });
  3265. });
  3266. self.$container.addClass("fancybox-is-sliding");
  3267. }
  3268. });
  3269. };
  3270. Guestures.prototype.onPan = function() {
  3271. var self = this;
  3272. // Prevent accidental movement (sometimes, when tapping casually, finger can move a bit)
  3273. if (distance(self.newPoints[0], self.realPoints[0]) < ($.fancybox.isMobile ? 10 : 5)) {
  3274. self.startPoints = self.newPoints;
  3275. return;
  3276. }
  3277. self.canTap = false;
  3278. self.contentLastPos = self.limitMovement();
  3279. if (self.requestId) {
  3280. cancelAFrame(self.requestId);
  3281. }
  3282. self.requestId = requestAFrame(function() {
  3283. $.fancybox.setTranslate(self.$content, self.contentLastPos);
  3284. });
  3285. };
  3286. // Make panning sticky to the edges
  3287. Guestures.prototype.limitMovement = function() {
  3288. var self = this;
  3289. var canvasWidth = self.canvasWidth;
  3290. var canvasHeight = self.canvasHeight;
  3291. var distanceX = self.distanceX;
  3292. var distanceY = self.distanceY;
  3293. var contentStartPos = self.contentStartPos;
  3294. var currentOffsetX = contentStartPos.left;
  3295. var currentOffsetY = contentStartPos.top;
  3296. var currentWidth = contentStartPos.width;
  3297. var currentHeight = contentStartPos.height;
  3298. var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY, newOffsetX, newOffsetY;
  3299. if (currentWidth > canvasWidth) {
  3300. newOffsetX = currentOffsetX + distanceX;
  3301. } else {
  3302. newOffsetX = currentOffsetX;
  3303. }
  3304. newOffsetY = currentOffsetY + distanceY;
  3305. // Slow down proportionally to traveled distance
  3306. minTranslateX = Math.max(0, canvasWidth * 0.5 - currentWidth * 0.5);
  3307. minTranslateY = Math.max(0, canvasHeight * 0.5 - currentHeight * 0.5);
  3308. maxTranslateX = Math.min(canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5);
  3309. maxTranslateY = Math.min(canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5);
  3310. // ->
  3311. if (distanceX > 0 && newOffsetX > minTranslateX) {
  3312. newOffsetX = minTranslateX - 1 + Math.pow(-minTranslateX + currentOffsetX + distanceX, 0.8) || 0;
  3313. }
  3314. // <-
  3315. if (distanceX < 0 && newOffsetX < maxTranslateX) {
  3316. newOffsetX = maxTranslateX + 1 - Math.pow(maxTranslateX - currentOffsetX - distanceX, 0.8) || 0;
  3317. }
  3318. // \/
  3319. if (distanceY > 0 && newOffsetY > minTranslateY) {
  3320. newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8) || 0;
  3321. }
  3322. // /\
  3323. if (distanceY < 0 && newOffsetY < maxTranslateY) {
  3324. newOffsetY = maxTranslateY + 1 - Math.pow(maxTranslateY - currentOffsetY - distanceY, 0.8) || 0;
  3325. }
  3326. return {
  3327. top: newOffsetY,
  3328. left: newOffsetX
  3329. };
  3330. };
  3331. Guestures.prototype.limitPosition = function(newOffsetX, newOffsetY, newWidth, newHeight) {
  3332. var self = this;
  3333. var canvasWidth = self.canvasWidth;
  3334. var canvasHeight = self.canvasHeight;
  3335. if (newWidth > canvasWidth) {
  3336. newOffsetX = newOffsetX > 0 ? 0 : newOffsetX;
  3337. newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX;
  3338. } else {
  3339. // Center horizontally
  3340. newOffsetX = Math.max(0, canvasWidth / 2 - newWidth / 2);
  3341. }
  3342. if (newHeight > canvasHeight) {
  3343. newOffsetY = newOffsetY > 0 ? 0 : newOffsetY;
  3344. newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY;
  3345. } else {
  3346. // Center vertically
  3347. newOffsetY = Math.max(0, canvasHeight / 2 - newHeight / 2);
  3348. }
  3349. return {
  3350. top: newOffsetY,
  3351. left: newOffsetX
  3352. };
  3353. };
  3354. Guestures.prototype.onZoom = function() {
  3355. var self = this;
  3356. // Calculate current distance between points to get pinch ratio and new width and height
  3357. var contentStartPos = self.contentStartPos;
  3358. var currentWidth = contentStartPos.width;
  3359. var currentHeight = contentStartPos.height;
  3360. var currentOffsetX = contentStartPos.left;
  3361. var currentOffsetY = contentStartPos.top;
  3362. var endDistanceBetweenFingers = distance(self.newPoints[0], self.newPoints[1]);
  3363. var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers;
  3364. var newWidth = Math.floor(currentWidth * pinchRatio);
  3365. var newHeight = Math.floor(currentHeight * pinchRatio);
  3366. // This is the translation due to pinch-zooming
  3367. var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX;
  3368. var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY;
  3369. // Point between the two touches
  3370. var centerPointEndX = (self.newPoints[0].x + self.newPoints[1].x) / 2 - $(window).scrollLeft();
  3371. var centerPointEndY = (self.newPoints[0].y + self.newPoints[1].y) / 2 - $(window).scrollTop();
  3372. // And this is the translation due to translation of the centerpoint
  3373. // between the two fingers
  3374. var translateFromTranslatingX = centerPointEndX - self.centerPointStartX;
  3375. var translateFromTranslatingY = centerPointEndY - self.centerPointStartY;
  3376. // The new offset is the old/current one plus the total translation
  3377. var newOffsetX = currentOffsetX + (translateFromZoomingX + translateFromTranslatingX);
  3378. var newOffsetY = currentOffsetY + (translateFromZoomingY + translateFromTranslatingY);
  3379. var newPos = {
  3380. top: newOffsetY,
  3381. left: newOffsetX,
  3382. scaleX: pinchRatio,
  3383. scaleY: pinchRatio
  3384. };
  3385. self.canTap = false;
  3386. self.newWidth = newWidth;
  3387. self.newHeight = newHeight;
  3388. self.contentLastPos = newPos;
  3389. if (self.requestId) {
  3390. cancelAFrame(self.requestId);
  3391. }
  3392. self.requestId = requestAFrame(function() {
  3393. $.fancybox.setTranslate(self.$content, self.contentLastPos);
  3394. });
  3395. };
  3396. Guestures.prototype.ontouchend = function(e) {
  3397. var self = this;
  3398. var swiping = self.isSwiping;
  3399. var panning = self.isPanning;
  3400. var zooming = self.isZooming;
  3401. var scrolling = self.isScrolling;
  3402. self.endPoints = getPointerXY(e);
  3403. self.dMs = Math.max(new Date().getTime() - self.startTime, 1);
  3404. self.$container.removeClass("fancybox-is-grabbing");
  3405. $(document).off(".fb.touch");
  3406. document.removeEventListener("scroll", self.onscroll, true);
  3407. if (self.requestId) {
  3408. cancelAFrame(self.requestId);
  3409. self.requestId = null;
  3410. }
  3411. self.isSwiping = false;
  3412. self.isPanning = false;
  3413. self.isZooming = false;
  3414. self.isScrolling = false;
  3415. self.instance.isDragging = false;
  3416. if (self.canTap) {
  3417. return self.onTap(e);
  3418. }
  3419. self.speed = 100;
  3420. // Speed in px/ms
  3421. self.velocityX = (self.distanceX / self.dMs) * 0.5;
  3422. self.velocityY = (self.distanceY / self.dMs) * 0.5;
  3423. if (panning) {
  3424. self.endPanning();
  3425. } else if (zooming) {
  3426. self.endZooming();
  3427. } else {
  3428. self.endSwiping(swiping, scrolling);
  3429. }
  3430. return;
  3431. };
  3432. Guestures.prototype.endSwiping = function(swiping, scrolling) {
  3433. var self = this,
  3434. ret = false,
  3435. len = self.instance.group.length,
  3436. distanceX = Math.abs(self.distanceX),
  3437. canAdvance = swiping == "x" && len > 1 && ((self.dMs > 130 && distanceX > 10) || distanceX > 50),
  3438. speedX = 300;
  3439. self.sliderLastPos = null;
  3440. // Close if swiped vertically / navigate if horizontally
  3441. if (swiping == "y" && !scrolling && Math.abs(self.distanceY) > 50) {
  3442. // Continue vertical movement
  3443. $.fancybox.animate(
  3444. self.instance.current.$slide,
  3445. {
  3446. top: self.sliderStartPos.top + self.distanceY + self.velocityY * 150,
  3447. opacity: 0
  3448. },
  3449. 200
  3450. );
  3451. ret = self.instance.close(true, 250);
  3452. } else if (canAdvance && self.distanceX > 0) {
  3453. ret = self.instance.previous(speedX);
  3454. } else if (canAdvance && self.distanceX < 0) {
  3455. ret = self.instance.next(speedX);
  3456. }
  3457. if (ret === false && (swiping == "x" || swiping == "y")) {
  3458. self.instance.centerSlide(200);
  3459. }
  3460. self.$container.removeClass("fancybox-is-sliding");
  3461. };
  3462. // Limit panning from edges
  3463. // ========================
  3464. Guestures.prototype.endPanning = function() {
  3465. var self = this,
  3466. newOffsetX,
  3467. newOffsetY,
  3468. newPos;
  3469. if (!self.contentLastPos) {
  3470. return;
  3471. }
  3472. if (self.opts.momentum === false || self.dMs > 350) {
  3473. newOffsetX = self.contentLastPos.left;
  3474. newOffsetY = self.contentLastPos.top;
  3475. } else {
  3476. // Continue movement
  3477. newOffsetX = self.contentLastPos.left + self.velocityX * 500;
  3478. newOffsetY = self.contentLastPos.top + self.velocityY * 500;
  3479. }
  3480. newPos = self.limitPosition(newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height);
  3481. newPos.width = self.contentStartPos.width;
  3482. newPos.height = self.contentStartPos.height;
  3483. $.fancybox.animate(self.$content, newPos, 366);
  3484. };
  3485. Guestures.prototype.endZooming = function() {
  3486. var self = this;
  3487. var current = self.instance.current;
  3488. var newOffsetX, newOffsetY, newPos, reset;
  3489. var newWidth = self.newWidth;
  3490. var newHeight = self.newHeight;
  3491. if (!self.contentLastPos) {
  3492. return;
  3493. }
  3494. newOffsetX = self.contentLastPos.left;
  3495. newOffsetY = self.contentLastPos.top;
  3496. reset = {
  3497. top: newOffsetY,
  3498. left: newOffsetX,
  3499. width: newWidth,
  3500. height: newHeight,
  3501. scaleX: 1,
  3502. scaleY: 1
  3503. };
  3504. // Reset scalex/scaleY values; this helps for perfomance and does not break animation
  3505. $.fancybox.setTranslate(self.$content, reset);
  3506. if (newWidth < self.canvasWidth && newHeight < self.canvasHeight) {
  3507. self.instance.scaleToFit(150);
  3508. } else if (newWidth > current.width || newHeight > current.height) {
  3509. self.instance.scaleToActual(self.centerPointStartX, self.centerPointStartY, 150);
  3510. } else {
  3511. newPos = self.limitPosition(newOffsetX, newOffsetY, newWidth, newHeight);
  3512. $.fancybox.animate(self.$content, newPos, 150);
  3513. }
  3514. };
  3515. Guestures.prototype.onTap = function(e) {
  3516. var self = this;
  3517. var $target = $(e.target);
  3518. var instance = self.instance;
  3519. var current = instance.current;
  3520. var endPoints = (e && getPointerXY(e)) || self.startPoints;
  3521. var tapX = endPoints[0] ? endPoints[0].x - $(window).scrollLeft() - self.stagePos.left : 0;
  3522. var tapY = endPoints[0] ? endPoints[0].y - $(window).scrollTop() - self.stagePos.top : 0;
  3523. var where;
  3524. var process = function(prefix) {
  3525. var action = current.opts[prefix];
  3526. if ($.isFunction(action)) {
  3527. action = action.apply(instance, [current, e]);
  3528. }
  3529. if (!action) {
  3530. return;
  3531. }
  3532. switch (action) {
  3533. case "close":
  3534. instance.close(self.startEvent);
  3535. break;
  3536. case "toggleControls":
  3537. instance.toggleControls();
  3538. break;
  3539. case "next":
  3540. instance.next();
  3541. break;
  3542. case "nextOrClose":
  3543. if (instance.group.length > 1) {
  3544. instance.next();
  3545. } else {
  3546. instance.close(self.startEvent);
  3547. }
  3548. break;
  3549. case "zoom":
  3550. if (current.type == "image" && (current.isLoaded || current.$ghost)) {
  3551. if (instance.canPan()) {
  3552. instance.scaleToFit();
  3553. } else if (instance.isScaledDown()) {
  3554. instance.scaleToActual(tapX, tapY);
  3555. } else if (instance.group.length < 2) {
  3556. instance.close(self.startEvent);
  3557. }
  3558. }
  3559. break;
  3560. }
  3561. };
  3562. // Ignore right click
  3563. if (e.originalEvent && e.originalEvent.button == 2) {
  3564. return;
  3565. }
  3566. // Skip if clicked on the scrollbar
  3567. if (!$target.is("img") && tapX > $target[0].clientWidth + $target.offset().left) {
  3568. return;
  3569. }
  3570. // Check where is clicked
  3571. if ($target.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container")) {
  3572. where = "Outside";
  3573. } else if ($target.is(".fancybox-slide")) {
  3574. where = "Slide";
  3575. } else if (
  3576. instance.current.$content &&
  3577. instance.current.$content
  3578. .find($target)
  3579. .addBack()
  3580. .filter($target).length
  3581. ) {
  3582. where = "Content";
  3583. } else {
  3584. return;
  3585. }
  3586. // Check if this is a double tap
  3587. if (self.tapped) {
  3588. // Stop previously created single tap
  3589. clearTimeout(self.tapped);
  3590. self.tapped = null;
  3591. // Skip if distance between taps is too big
  3592. if (Math.abs(tapX - self.tapX) > 50 || Math.abs(tapY - self.tapY) > 50) {
  3593. return this;
  3594. }
  3595. // OK, now we assume that this is a double-tap
  3596. process("dblclick" + where);
  3597. } else {
  3598. // Single tap will be processed if user has not clicked second time within 300ms
  3599. // or there is no need to wait for double-tap
  3600. self.tapX = tapX;
  3601. self.tapY = tapY;
  3602. if (current.opts["dblclick" + where] && current.opts["dblclick" + where] !== current.opts["click" + where]) {
  3603. self.tapped = setTimeout(function() {
  3604. self.tapped = null;
  3605. if (!instance.isAnimating) {
  3606. process("click" + where);
  3607. }
  3608. }, 500);
  3609. } else {
  3610. process("click" + where);
  3611. }
  3612. }
  3613. return this;
  3614. };
  3615. $(document)
  3616. .on("onActivate.fb", function(e, instance) {
  3617. if (instance && !instance.Guestures) {
  3618. instance.Guestures = new Guestures(instance);
  3619. }
  3620. })
  3621. .on("beforeClose.fb", function(e, instance) {
  3622. if (instance && instance.Guestures) {
  3623. instance.Guestures.destroy();
  3624. }
  3625. });
  3626. })(window, document, jQuery);
  3627. // ==========================================================================
  3628. //
  3629. // SlideShow
  3630. // Enables slideshow functionality
  3631. //
  3632. // Example of usage:
  3633. // $.fancybox.getInstance().SlideShow.start()
  3634. //
  3635. // ==========================================================================
  3636. (function(document, $) {
  3637. "use strict";
  3638. $.extend(true, $.fancybox.defaults, {
  3639. btnTpl: {
  3640. slideShow:
  3641. '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}">' +
  3642. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6.5 5.4v13.2l11-6.6z"/></svg>' +
  3643. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.33 5.75h2.2v12.5h-2.2V5.75zm5.15 0h2.2v12.5h-2.2V5.75z"/></svg>' +
  3644. "</button>"
  3645. },
  3646. slideShow: {
  3647. autoStart: false,
  3648. speed: 3000,
  3649. progress: true
  3650. }
  3651. });
  3652. var SlideShow = function(instance) {
  3653. this.instance = instance;
  3654. this.init();
  3655. };
  3656. $.extend(SlideShow.prototype, {
  3657. timer: null,
  3658. isActive: false,
  3659. $button: null,
  3660. init: function() {
  3661. var self = this,
  3662. instance = self.instance,
  3663. opts = instance.group[instance.currIndex].opts.slideShow;
  3664. self.$button = instance.$refs.toolbar.find("[data-fancybox-play]").on("click", function() {
  3665. self.toggle();
  3666. });
  3667. if (instance.group.length < 2 || !opts) {
  3668. self.$button.hide();
  3669. } else if (opts.progress) {
  3670. self.$progress = $('<div class="fancybox-progress"></div>').appendTo(instance.$refs.inner);
  3671. }
  3672. },
  3673. set: function(force) {
  3674. var self = this,
  3675. instance = self.instance,
  3676. current = instance.current;
  3677. // Check if reached last element
  3678. if (current && (force === true || current.opts.loop || instance.currIndex < instance.group.length - 1)) {
  3679. if (self.isActive && current.contentType !== "video") {
  3680. if (self.$progress) {
  3681. $.fancybox.animate(self.$progress.show(), {scaleX: 1}, current.opts.slideShow.speed);
  3682. }
  3683. self.timer = setTimeout(function() {
  3684. if (!instance.current.opts.loop && instance.current.index == instance.group.length - 1) {
  3685. instance.jumpTo(0);
  3686. } else {
  3687. instance.next();
  3688. }
  3689. }, current.opts.slideShow.speed);
  3690. }
  3691. } else {
  3692. self.stop();
  3693. instance.idleSecondsCounter = 0;
  3694. instance.showControls();
  3695. }
  3696. },
  3697. clear: function() {
  3698. var self = this;
  3699. clearTimeout(self.timer);
  3700. self.timer = null;
  3701. if (self.$progress) {
  3702. self.$progress.removeAttr("style").hide();
  3703. }
  3704. },
  3705. start: function() {
  3706. var self = this,
  3707. current = self.instance.current;
  3708. if (current) {
  3709. self.$button
  3710. .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_STOP)
  3711. .removeClass("fancybox-button--play")
  3712. .addClass("fancybox-button--pause");
  3713. self.isActive = true;
  3714. if (current.isComplete) {
  3715. self.set(true);
  3716. }
  3717. self.instance.trigger("onSlideShowChange", true);
  3718. }
  3719. },
  3720. stop: function() {
  3721. var self = this,
  3722. current = self.instance.current;
  3723. self.clear();
  3724. self.$button
  3725. .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_START)
  3726. .removeClass("fancybox-button--pause")
  3727. .addClass("fancybox-button--play");
  3728. self.isActive = false;
  3729. self.instance.trigger("onSlideShowChange", false);
  3730. if (self.$progress) {
  3731. self.$progress.removeAttr("style").hide();
  3732. }
  3733. },
  3734. toggle: function() {
  3735. var self = this;
  3736. if (self.isActive) {
  3737. self.stop();
  3738. } else {
  3739. self.start();
  3740. }
  3741. }
  3742. });
  3743. $(document).on({
  3744. "onInit.fb": function(e, instance) {
  3745. if (instance && !instance.SlideShow) {
  3746. instance.SlideShow = new SlideShow(instance);
  3747. }
  3748. },
  3749. "beforeShow.fb": function(e, instance, current, firstRun) {
  3750. var SlideShow = instance && instance.SlideShow;
  3751. if (firstRun) {
  3752. if (SlideShow && current.opts.slideShow.autoStart) {
  3753. SlideShow.start();
  3754. }
  3755. } else if (SlideShow && SlideShow.isActive) {
  3756. SlideShow.clear();
  3757. }
  3758. },
  3759. "afterShow.fb": function(e, instance, current) {
  3760. var SlideShow = instance && instance.SlideShow;
  3761. if (SlideShow && SlideShow.isActive) {
  3762. SlideShow.set();
  3763. }
  3764. },
  3765. "afterKeydown.fb": function(e, instance, current, keypress, keycode) {
  3766. var SlideShow = instance && instance.SlideShow;
  3767. // "P" or Spacebar
  3768. if (SlideShow && current.opts.slideShow && (keycode === 80 || keycode === 32) && !$(document.activeElement).is("button,a,input")) {
  3769. keypress.preventDefault();
  3770. SlideShow.toggle();
  3771. }
  3772. },
  3773. "beforeClose.fb onDeactivate.fb": function(e, instance) {
  3774. var SlideShow = instance && instance.SlideShow;
  3775. if (SlideShow) {
  3776. SlideShow.stop();
  3777. }
  3778. }
  3779. });
  3780. // Page Visibility API to pause slideshow when window is not active
  3781. $(document).on("visibilitychange", function() {
  3782. var instance = $.fancybox.getInstance(),
  3783. SlideShow = instance && instance.SlideShow;
  3784. if (SlideShow && SlideShow.isActive) {
  3785. if (document.hidden) {
  3786. SlideShow.clear();
  3787. } else {
  3788. SlideShow.set();
  3789. }
  3790. }
  3791. });
  3792. })(document, jQuery);
  3793. // ==========================================================================
  3794. //
  3795. // FullScreen
  3796. // Adds fullscreen functionality
  3797. //
  3798. // ==========================================================================
  3799. (function(document, $) {
  3800. "use strict";
  3801. // Collection of methods supported by user browser
  3802. var fn = (function() {
  3803. var fnMap = [
  3804. ["requestFullscreen", "exitFullscreen", "fullscreenElement", "fullscreenEnabled", "fullscreenchange", "fullscreenerror"],
  3805. // new WebKit
  3806. [
  3807. "webkitRequestFullscreen",
  3808. "webkitExitFullscreen",
  3809. "webkitFullscreenElement",
  3810. "webkitFullscreenEnabled",
  3811. "webkitfullscreenchange",
  3812. "webkitfullscreenerror"
  3813. ],
  3814. // old WebKit (Safari 5.1)
  3815. [
  3816. "webkitRequestFullScreen",
  3817. "webkitCancelFullScreen",
  3818. "webkitCurrentFullScreenElement",
  3819. "webkitCancelFullScreen",
  3820. "webkitfullscreenchange",
  3821. "webkitfullscreenerror"
  3822. ],
  3823. [
  3824. "mozRequestFullScreen",
  3825. "mozCancelFullScreen",
  3826. "mozFullScreenElement",
  3827. "mozFullScreenEnabled",
  3828. "mozfullscreenchange",
  3829. "mozfullscreenerror"
  3830. ],
  3831. ["msRequestFullscreen", "msExitFullscreen", "msFullscreenElement", "msFullscreenEnabled", "MSFullscreenChange", "MSFullscreenError"]
  3832. ];
  3833. var ret = {};
  3834. for (var i = 0; i < fnMap.length; i++) {
  3835. var val = fnMap[i];
  3836. if (val && val[1] in document) {
  3837. for (var j = 0; j < val.length; j++) {
  3838. ret[fnMap[0][j]] = val[j];
  3839. }
  3840. return ret;
  3841. }
  3842. }
  3843. return false;
  3844. })();
  3845. if (fn) {
  3846. var FullScreen = {
  3847. request: function(elem) {
  3848. elem = elem || document.documentElement;
  3849. elem[fn.requestFullscreen](elem.ALLOW_KEYBOARD_INPUT);
  3850. },
  3851. exit: function() {
  3852. document[fn.exitFullscreen]();
  3853. },
  3854. toggle: function(elem) {
  3855. elem = elem || document.documentElement;
  3856. if (this.isFullscreen()) {
  3857. this.exit();
  3858. } else {
  3859. this.request(elem);
  3860. }
  3861. },
  3862. isFullscreen: function() {
  3863. return Boolean(document[fn.fullscreenElement]);
  3864. },
  3865. enabled: function() {
  3866. return Boolean(document[fn.fullscreenEnabled]);
  3867. }
  3868. };
  3869. $.extend(true, $.fancybox.defaults, {
  3870. btnTpl: {
  3871. fullScreen:
  3872. '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fsenter" title="{{FULL_SCREEN}}">' +
  3873. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>' +
  3874. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5zm3-8H5v2h5V5H8zm6 11h2v-3h3v-2h-5zm2-11V5h-2v5h5V8z"/></svg>' +
  3875. "</button>"
  3876. },
  3877. fullScreen: {
  3878. autoStart: false
  3879. }
  3880. });
  3881. $(document).on(fn.fullscreenchange, function() {
  3882. var isFullscreen = FullScreen.isFullscreen(),
  3883. instance = $.fancybox.getInstance();
  3884. if (instance) {
  3885. // If image is zooming, then force to stop and reposition properly
  3886. if (instance.current && instance.current.type === "image" && instance.isAnimating) {
  3887. instance.isAnimating = false;
  3888. instance.update(true, true, 0);
  3889. if (!instance.isComplete) {
  3890. instance.complete();
  3891. }
  3892. }
  3893. instance.trigger("onFullscreenChange", isFullscreen);
  3894. instance.$refs.container.toggleClass("fancybox-is-fullscreen", isFullscreen);
  3895. instance.$refs.toolbar
  3896. .find("[data-fancybox-fullscreen]")
  3897. .toggleClass("fancybox-button--fsenter", !isFullscreen)
  3898. .toggleClass("fancybox-button--fsexit", isFullscreen);
  3899. }
  3900. });
  3901. }
  3902. $(document).on({
  3903. "onInit.fb": function(e, instance) {
  3904. var $container;
  3905. if (!fn) {
  3906. instance.$refs.toolbar.find("[data-fancybox-fullscreen]").remove();
  3907. return;
  3908. }
  3909. if (instance && instance.group[instance.currIndex].opts.fullScreen) {
  3910. $container = instance.$refs.container;
  3911. $container.on("click.fb-fullscreen", "[data-fancybox-fullscreen]", function(e) {
  3912. e.stopPropagation();
  3913. e.preventDefault();
  3914. FullScreen.toggle();
  3915. });
  3916. if (instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true) {
  3917. FullScreen.request();
  3918. }
  3919. // Expose API
  3920. instance.FullScreen = FullScreen;
  3921. } else if (instance) {
  3922. instance.$refs.toolbar.find("[data-fancybox-fullscreen]").hide();
  3923. }
  3924. },
  3925. "afterKeydown.fb": function(e, instance, current, keypress, keycode) {
  3926. // "F"
  3927. if (instance && instance.FullScreen && keycode === 70) {
  3928. keypress.preventDefault();
  3929. instance.FullScreen.toggle();
  3930. }
  3931. },
  3932. "beforeClose.fb": function(e, instance) {
  3933. if (instance && instance.FullScreen && instance.$refs.container.hasClass("fancybox-is-fullscreen")) {
  3934. FullScreen.exit();
  3935. }
  3936. }
  3937. });
  3938. })(document, jQuery);
  3939. // ==========================================================================
  3940. //
  3941. // Thumbs
  3942. // Displays thumbnails in a grid
  3943. //
  3944. // ==========================================================================
  3945. (function(document, $) {
  3946. "use strict";
  3947. var CLASS = "fancybox-thumbs",
  3948. CLASS_ACTIVE = CLASS + "-active";
  3949. // Make sure there are default values
  3950. $.fancybox.defaults = $.extend(
  3951. true,
  3952. {
  3953. btnTpl: {
  3954. thumbs:
  3955. '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}">' +
  3956. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14.59 14.59h3.76v3.76h-3.76v-3.76zm-4.47 0h3.76v3.76h-3.76v-3.76zm-4.47 0h3.76v3.76H5.65v-3.76zm8.94-4.47h3.76v3.76h-3.76v-3.76zm-4.47 0h3.76v3.76h-3.76v-3.76zm-4.47 0h3.76v3.76H5.65v-3.76zm8.94-4.47h3.76v3.76h-3.76V5.65zm-4.47 0h3.76v3.76h-3.76V5.65zm-4.47 0h3.76v3.76H5.65V5.65z"/></svg>' +
  3957. "</button>"
  3958. },
  3959. thumbs: {
  3960. autoStart: false, // Display thumbnails on opening
  3961. hideOnClose: true, // Hide thumbnail grid when closing animation starts
  3962. parentEl: ".fancybox-container", // Container is injected into this element
  3963. axis: "y" // Vertical (y) or horizontal (x) scrolling
  3964. }
  3965. },
  3966. $.fancybox.defaults
  3967. );
  3968. var FancyThumbs = function(instance) {
  3969. this.init(instance);
  3970. };
  3971. $.extend(FancyThumbs.prototype, {
  3972. $button: null,
  3973. $grid: null,
  3974. $list: null,
  3975. isVisible: false,
  3976. isActive: false,
  3977. init: function(instance) {
  3978. var self = this,
  3979. group = instance.group,
  3980. enabled = 0;
  3981. self.instance = instance;
  3982. self.opts = group[instance.currIndex].opts.thumbs;
  3983. instance.Thumbs = self;
  3984. self.$button = instance.$refs.toolbar.find("[data-fancybox-thumbs]");
  3985. // Enable thumbs if at least two group items have thumbnails
  3986. for (var i = 0, len = group.length; i < len; i++) {
  3987. if (group[i].thumb) {
  3988. enabled++;
  3989. }
  3990. if (enabled > 1) {
  3991. break;
  3992. }
  3993. }
  3994. if (enabled > 1 && !!self.opts) {
  3995. self.$button.removeAttr("style").on("click", function() {
  3996. self.toggle();
  3997. });
  3998. self.isActive = true;
  3999. } else {
  4000. self.$button.hide();
  4001. }
  4002. },
  4003. create: function() {
  4004. var self = this,
  4005. instance = self.instance,
  4006. parentEl = self.opts.parentEl,
  4007. list = [],
  4008. src;
  4009. if (!self.$grid) {
  4010. // Create main element
  4011. self.$grid = $('<div class="' + CLASS + " " + CLASS + "-" + self.opts.axis + '"></div>').appendTo(
  4012. instance.$refs.container
  4013. .find(parentEl)
  4014. .addBack()
  4015. .filter(parentEl)
  4016. );
  4017. // Add "click" event that performs gallery navigation
  4018. self.$grid.on("click", "a", function() {
  4019. instance.jumpTo($(this).attr("data-index"));
  4020. });
  4021. }
  4022. // Build the list
  4023. if (!self.$list) {
  4024. self.$list = $('<div class="' + CLASS + '__list">').appendTo(self.$grid);
  4025. }
  4026. $.each(instance.group, function(i, item) {
  4027. src = item.thumb;
  4028. if (!src && item.type === "image") {
  4029. src = item.src;
  4030. }
  4031. list.push(
  4032. '<a href="javascript:;" tabindex="0" data-index="' +
  4033. i +
  4034. '"' +
  4035. (src && src.length ? ' style="background-image:url(' + src + ')"' : 'class="fancybox-thumbs-missing"') +
  4036. "></a>"
  4037. );
  4038. });
  4039. self.$list[0].innerHTML = list.join("");
  4040. if (self.opts.axis === "x") {
  4041. // Set fixed width for list element to enable horizontal scrolling
  4042. self.$list.width(
  4043. parseInt(self.$grid.css("padding-right"), 10) +
  4044. instance.group.length *
  4045. self.$list
  4046. .children()
  4047. .eq(0)
  4048. .outerWidth(true)
  4049. );
  4050. }
  4051. },
  4052. focus: function(duration) {
  4053. var self = this,
  4054. $list = self.$list,
  4055. $grid = self.$grid,
  4056. thumb,
  4057. thumbPos;
  4058. if (!self.instance.current) {
  4059. return;
  4060. }
  4061. thumb = $list
  4062. .children()
  4063. .removeClass(CLASS_ACTIVE)
  4064. .filter('[data-index="' + self.instance.current.index + '"]')
  4065. .addClass(CLASS_ACTIVE);
  4066. thumbPos = thumb.position();
  4067. // Check if need to scroll to make current thumb visible
  4068. if (self.opts.axis === "y" && (thumbPos.top < 0 || thumbPos.top > $list.height() - thumb.outerHeight())) {
  4069. $list.stop().animate(
  4070. {
  4071. scrollTop: $list.scrollTop() + thumbPos.top
  4072. },
  4073. duration
  4074. );
  4075. } else if (
  4076. self.opts.axis === "x" &&
  4077. (thumbPos.left < $grid.scrollLeft() || thumbPos.left > $grid.scrollLeft() + ($grid.width() - thumb.outerWidth()))
  4078. ) {
  4079. $list
  4080. .parent()
  4081. .stop()
  4082. .animate(
  4083. {
  4084. scrollLeft: thumbPos.left
  4085. },
  4086. duration
  4087. );
  4088. }
  4089. },
  4090. update: function() {
  4091. var that = this;
  4092. that.instance.$refs.container.toggleClass("fancybox-show-thumbs", this.isVisible);
  4093. if (that.isVisible) {
  4094. if (!that.$grid) {
  4095. that.create();
  4096. }
  4097. that.instance.trigger("onThumbsShow");
  4098. that.focus(0);
  4099. } else if (that.$grid) {
  4100. that.instance.trigger("onThumbsHide");
  4101. }
  4102. // Update content position
  4103. that.instance.update();
  4104. },
  4105. hide: function() {
  4106. this.isVisible = false;
  4107. this.update();
  4108. },
  4109. show: function() {
  4110. this.isVisible = true;
  4111. this.update();
  4112. },
  4113. toggle: function() {
  4114. this.isVisible = !this.isVisible;
  4115. this.update();
  4116. }
  4117. });
  4118. $(document).on({
  4119. "onInit.fb": function(e, instance) {
  4120. var Thumbs;
  4121. if (instance && !instance.Thumbs) {
  4122. Thumbs = new FancyThumbs(instance);
  4123. if (Thumbs.isActive && Thumbs.opts.autoStart === true) {
  4124. Thumbs.show();
  4125. }
  4126. }
  4127. },
  4128. "beforeShow.fb": function(e, instance, item, firstRun) {
  4129. var Thumbs = instance && instance.Thumbs;
  4130. if (Thumbs && Thumbs.isVisible) {
  4131. Thumbs.focus(firstRun ? 0 : 250);
  4132. }
  4133. },
  4134. "afterKeydown.fb": function(e, instance, current, keypress, keycode) {
  4135. var Thumbs = instance && instance.Thumbs;
  4136. // "G"
  4137. if (Thumbs && Thumbs.isActive && keycode === 71) {
  4138. keypress.preventDefault();
  4139. Thumbs.toggle();
  4140. }
  4141. },
  4142. "beforeClose.fb": function(e, instance) {
  4143. var Thumbs = instance && instance.Thumbs;
  4144. if (Thumbs && Thumbs.isVisible && Thumbs.opts.hideOnClose !== false) {
  4145. Thumbs.$grid.hide();
  4146. }
  4147. }
  4148. });
  4149. })(document, jQuery);
  4150. //// ==========================================================================
  4151. //
  4152. // Share
  4153. // Displays simple form for sharing current url
  4154. //
  4155. // ==========================================================================
  4156. (function(document, $) {
  4157. "use strict";
  4158. $.extend(true, $.fancybox.defaults, {
  4159. btnTpl: {
  4160. share:
  4161. '<button data-fancybox-share class="fancybox-button fancybox-button--share" title="{{SHARE}}">' +
  4162. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2.55 19c1.4-8.4 9.1-9.8 11.9-9.8V5l7 7-7 6.3v-3.5c-2.8 0-10.5 2.1-11.9 4.2z"/></svg>' +
  4163. "</button>"
  4164. },
  4165. share: {
  4166. url: function(instance, item) {
  4167. return (
  4168. (!instance.currentHash && !(item.type === "inline" || item.type === "html") ? item.origSrc || item.src : false) || window.location
  4169. );
  4170. },
  4171. tpl:
  4172. '<div class="fancybox-share">' +
  4173. "<h1>{{SHARE}}</h1>" +
  4174. "<p>" +
  4175. '<a class="fancybox-share__button fancybox-share__button--fb" href="https://www.facebook.com/sharer/sharer.php?u={{url}}">' +
  4176. '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m287 456v-299c0-21 6-35 35-35h38v-63c-7-1-29-3-55-3-54 0-91 33-91 94v306m143-254h-205v72h196" /></svg>' +
  4177. "<span>Facebook</span>" +
  4178. "</a>" +
  4179. '<a class="fancybox-share__button fancybox-share__button--tw" href="https://twitter.com/intent/tweet?url={{url}}&text={{descr}}">' +
  4180. '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m456 133c-14 7-31 11-47 13 17-10 30-27 37-46-15 10-34 16-52 20-61-62-157-7-141 75-68-3-129-35-169-85-22 37-11 86 26 109-13 0-26-4-37-9 0 39 28 72 65 80-12 3-25 4-37 2 10 33 41 57 77 57-42 30-77 38-122 34 170 111 378-32 359-208 16-11 30-25 41-42z" /></svg>' +
  4181. "<span>Twitter</span>" +
  4182. "</a>" +
  4183. '<a class="fancybox-share__button fancybox-share__button--pt" href="https://www.pinterest.com/pin/create/button/?url={{url}}&description={{descr}}&media={{media}}">' +
  4184. '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m265 56c-109 0-164 78-164 144 0 39 15 74 47 87 5 2 10 0 12-5l4-19c2-6 1-8-3-13-9-11-15-25-15-45 0-58 43-110 113-110 62 0 96 38 96 88 0 67-30 122-73 122-24 0-42-19-36-44 6-29 20-60 20-81 0-19-10-35-31-35-25 0-44 26-44 60 0 21 7 36 7 36l-30 125c-8 37-1 83 0 87 0 3 4 4 5 2 2-3 32-39 42-75l16-64c8 16 31 29 56 29 74 0 124-67 124-157 0-69-58-132-146-132z" fill="#fff"/></svg>' +
  4185. "<span>Pinterest</span>" +
  4186. "</a>" +
  4187. "</p>" +
  4188. '<p><input class="fancybox-share__input" type="text" value="{{url_raw}}" onclick="select()" /></p>' +
  4189. "</div>"
  4190. }
  4191. });
  4192. function escapeHtml(string) {
  4193. var entityMap = {
  4194. "&": "&amp;",
  4195. "<": "&lt;",
  4196. ">": "&gt;",
  4197. '"': "&quot;",
  4198. "'": "&#39;",
  4199. "/": "&#x2F;",
  4200. "`": "&#x60;",
  4201. "=": "&#x3D;"
  4202. };
  4203. return String(string).replace(/[&<>"'`=\/]/g, function(s) {
  4204. return entityMap[s];
  4205. });
  4206. }
  4207. $(document).on("click", "[data-fancybox-share]", function() {
  4208. var instance = $.fancybox.getInstance(),
  4209. current = instance.current || null,
  4210. url,
  4211. tpl;
  4212. if (!current) {
  4213. return;
  4214. }
  4215. if ($.type(current.opts.share.url) === "function") {
  4216. url = current.opts.share.url.apply(current, [instance, current]);
  4217. }
  4218. tpl = current.opts.share.tpl
  4219. .replace(/\{\{media\}\}/g, current.type === "image" ? encodeURIComponent(current.src) : "")
  4220. .replace(/\{\{url\}\}/g, encodeURIComponent(url))
  4221. .replace(/\{\{url_raw\}\}/g, escapeHtml(url))
  4222. .replace(/\{\{descr\}\}/g, instance.$caption ? encodeURIComponent(instance.$caption.text()) : "");
  4223. $.fancybox.open({
  4224. src: instance.translate(instance, tpl),
  4225. type: "html",
  4226. opts: {
  4227. touch: false,
  4228. animationEffect: false,
  4229. afterLoad: function(shareInstance, shareCurrent) {
  4230. // Close self if parent instance is closing
  4231. instance.$refs.container.one("beforeClose.fb", function() {
  4232. shareInstance.close(null, 0);
  4233. });
  4234. // Opening links in a popup window
  4235. shareCurrent.$content.find(".fancybox-share__button").click(function() {
  4236. window.open(this.href, "Share", "width=550, height=450");
  4237. return false;
  4238. });
  4239. },
  4240. mobile: {
  4241. autoFocus: false
  4242. }
  4243. }
  4244. });
  4245. });
  4246. })(document, jQuery);
  4247. // ==========================================================================
  4248. //
  4249. // Hash
  4250. // Enables linking to each modal
  4251. //
  4252. // ==========================================================================
  4253. (function(window, document, $) {
  4254. "use strict";
  4255. // Simple $.escapeSelector polyfill (for jQuery prior v3)
  4256. if (!$.escapeSelector) {
  4257. $.escapeSelector = function(sel) {
  4258. var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
  4259. var fcssescape = function(ch, asCodePoint) {
  4260. if (asCodePoint) {
  4261. // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
  4262. if (ch === "\0") {
  4263. return "\uFFFD";
  4264. }
  4265. // Control characters and (dependent upon position) numbers get escaped as code points
  4266. return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " ";
  4267. }
  4268. // Other potentially-special ASCII characters get backslash-escaped
  4269. return "\\" + ch;
  4270. };
  4271. return (sel + "").replace(rcssescape, fcssescape);
  4272. };
  4273. }
  4274. // Get info about gallery name and current index from url
  4275. function parseUrl() {
  4276. var hash = window.location.hash.substr(1),
  4277. rez = hash.split("-"),
  4278. index = rez.length > 1 && /^\+?\d+$/.test(rez[rez.length - 1]) ? parseInt(rez.pop(-1), 10) || 1 : 1,
  4279. gallery = rez.join("-");
  4280. return {
  4281. hash: hash,
  4282. /* Index is starting from 1 */
  4283. index: index < 1 ? 1 : index,
  4284. gallery: gallery
  4285. };
  4286. }
  4287. // Trigger click evnt on links to open new fancyBox instance
  4288. function triggerFromUrl(url) {
  4289. if (url.gallery !== "") {
  4290. // If we can find element matching 'data-fancybox' atribute,
  4291. // then triggering click event should start fancyBox
  4292. $("[data-fancybox='" + $.escapeSelector(url.gallery) + "']")
  4293. .eq(url.index - 1)
  4294. .focus()
  4295. .trigger("click.fb-start");
  4296. }
  4297. }
  4298. // Get gallery name from current instance
  4299. function getGalleryID(instance) {
  4300. var opts, ret;
  4301. if (!instance) {
  4302. return false;
  4303. }
  4304. opts = instance.current ? instance.current.opts : instance.opts;
  4305. ret = opts.hash || (opts.$orig ? opts.$orig.data("fancybox") || opts.$orig.data("fancybox-trigger") : "");
  4306. return ret === "" ? false : ret;
  4307. }
  4308. // Start when DOM becomes ready
  4309. $(function() {
  4310. // Check if user has disabled this module
  4311. if ($.fancybox.defaults.hash === false) {
  4312. return;
  4313. }
  4314. // Update hash when opening/closing fancyBox
  4315. $(document).on({
  4316. "onInit.fb": function(e, instance) {
  4317. var url, gallery;
  4318. if (instance.group[instance.currIndex].opts.hash === false) {
  4319. return;
  4320. }
  4321. url = parseUrl();
  4322. gallery = getGalleryID(instance);
  4323. // Make sure gallery start index matches index from hash
  4324. if (gallery && url.gallery && gallery == url.gallery) {
  4325. instance.currIndex = url.index - 1;
  4326. }
  4327. },
  4328. "beforeShow.fb": function(e, instance, current, firstRun) {
  4329. var gallery;
  4330. if (!current || current.opts.hash === false) {
  4331. return;
  4332. }
  4333. // Check if need to update window hash
  4334. gallery = getGalleryID(instance);
  4335. if (!gallery) {
  4336. return;
  4337. }
  4338. // Variable containing last hash value set by fancyBox
  4339. // It will be used to determine if fancyBox needs to close after hash change is detected
  4340. instance.currentHash = gallery + (instance.group.length > 1 ? "-" + (current.index + 1) : "");
  4341. // If current hash is the same (this instance most likely is opened by hashchange), then do nothing
  4342. if (window.location.hash === "#" + instance.currentHash) {
  4343. return;
  4344. }
  4345. if (firstRun && !instance.origHash) {
  4346. instance.origHash = window.location.hash;
  4347. }
  4348. if (instance.hashTimer) {
  4349. clearTimeout(instance.hashTimer);
  4350. }
  4351. // Update hash
  4352. instance.hashTimer = setTimeout(function() {
  4353. if ("replaceState" in window.history) {
  4354. window.history[firstRun ? "pushState" : "replaceState"](
  4355. {},
  4356. document.title,
  4357. window.location.pathname + window.location.search + "#" + instance.currentHash
  4358. );
  4359. if (firstRun) {
  4360. instance.hasCreatedHistory = true;
  4361. }
  4362. } else {
  4363. window.location.hash = instance.currentHash;
  4364. }
  4365. instance.hashTimer = null;
  4366. }, 300);
  4367. },
  4368. "beforeClose.fb": function(e, instance, current) {
  4369. if (!current || current.opts.hash === false) {
  4370. return;
  4371. }
  4372. clearTimeout(instance.hashTimer);
  4373. // Goto previous history entry
  4374. if (instance.currentHash && instance.hasCreatedHistory) {
  4375. window.history.back();
  4376. } else if (instance.currentHash) {
  4377. if ("replaceState" in window.history) {
  4378. window.history.replaceState({}, document.title, window.location.pathname + window.location.search + (instance.origHash || ""));
  4379. } else {
  4380. window.location.hash = instance.origHash;
  4381. }
  4382. }
  4383. instance.currentHash = null;
  4384. }
  4385. });
  4386. // Check if need to start/close after url has changed
  4387. $(window).on("hashchange.fb", function() {
  4388. var url = parseUrl(),
  4389. fb = null;
  4390. // Find last fancyBox instance that has "hash"
  4391. $.each(
  4392. $(".fancybox-container")
  4393. .get()
  4394. .reverse(),
  4395. function(index, value) {
  4396. var tmp = $(value).data("FancyBox");
  4397. if (tmp && tmp.currentHash) {
  4398. fb = tmp;
  4399. return false;
  4400. }
  4401. }
  4402. );
  4403. if (fb) {
  4404. // Now, compare hash values
  4405. if (fb.currentHash !== url.gallery + "-" + url.index && !(url.index === 1 && fb.currentHash == url.gallery)) {
  4406. fb.currentHash = null;
  4407. fb.close();
  4408. }
  4409. } else if (url.gallery !== "") {
  4410. triggerFromUrl(url);
  4411. }
  4412. });
  4413. // Check current hash and trigger click event on matching element to start fancyBox, if needed
  4414. setTimeout(function() {
  4415. if (!$.fancybox.getInstance()) {
  4416. triggerFromUrl(parseUrl());
  4417. }
  4418. }, 50);
  4419. });
  4420. })(window, document, jQuery);
  4421. // ==========================================================================
  4422. //
  4423. // Wheel
  4424. // Basic mouse weheel support for gallery navigation
  4425. //
  4426. // ==========================================================================
  4427. (function(document, $) {
  4428. "use strict";
  4429. var prevTime = new Date().getTime();
  4430. $(document).on({
  4431. "onInit.fb": function(e, instance, current) {
  4432. instance.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll", function(e) {
  4433. var current = instance.current,
  4434. currTime = new Date().getTime();
  4435. if (instance.group.length < 2 || current.opts.wheel === false || (current.opts.wheel === "auto" && current.type !== "image")) {
  4436. return;
  4437. }
  4438. e.preventDefault();
  4439. e.stopPropagation();
  4440. if (current.$slide.hasClass("fancybox-animated")) {
  4441. return;
  4442. }
  4443. e = e.originalEvent || e;
  4444. if (currTime - prevTime < 250) {
  4445. return;
  4446. }
  4447. prevTime = currTime;
  4448. instance[(-e.deltaY || -e.deltaX || e.wheelDelta || -e.detail) < 0 ? "next" : "previous"]();
  4449. });
  4450. }
  4451. });
  4452. })(document, jQuery);