You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5137 lines
168 KiB

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