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

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

Large files are truncated click here to view the full file

  1. // ==================================================
  2. // fancyBox v3.5.6
  3. //
  4. // Licensed GPLv3 for open source use
  5. // or fancyBox Commercial License for commercial use
  6. //
  7. // http://fancyapps.com/fancybox/
  8. // Copyright 2018 fancyApps
  9. //
  10. // ==================================================
  11. (function(window, document, $, undefined) {
  12. "use strict";
  13. window.console = window.console || {
  14. info: function(stuff) {}
  15. };
  16. // If there's no jQuery, fancyBox can't work
  17. // =========================================
  18. if (!$) {
  19. return;
  20. }
  21. // Check if fancyBox is already initialized
  22. // ========================================
  23. if ($.fn.fancybox) {
  24. console.info("fancyBox already initialized");
  25. return;
  26. }
  27. // Private default settings
  28. // ========================
  29. var defaults = {
  30. // Close existing modals
  31. // Set this to false if you do not need to stack multiple instances
  32. closeExisting: false,
  33. // Enable infinite gallery navigation
  34. loop: false,
  35. // Horizontal space between slides
  36. gutter: 50,
  37. // Enable keyboard navigation
  38. keyboard: true,
  39. // Should allow caption to overlap the content
  40. preventCaptionOverlap: true,
  41. // Should display navigation arrows at the screen edges
  42. arrows: true,
  43. // Should display counter at the top left corner
  44. infobar: true,
  45. // Should display close button (using `btnTpl.smallBtn` template) over the content
  46. // Can be true, false, "auto"
  47. // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items
  48. smallBtn: "auto",
  49. // Should display toolbar (buttons at the top)
  50. // Can be true, false, "auto"
  51. // If "auto" - will be automatically hidden if "smallBtn" is enabled
  52. toolbar: "auto",
  53. // What buttons should appear in the top right corner.
  54. // Buttons will be created using templates from `btnTpl` option
  55. // and they will be placed into toolbar (class="fancybox-toolbar"` element)
  56. buttons: [
  57. "zoom",
  58. //"share",
  59. "slideShow",
  60. //"fullScreen",
  61. //"download",
  62. "thumbs",
  63. "close"
  64. ],
  65. // Detect "idle" time in seconds
  66. idleTime: 3,
  67. // Disable right-click and use simple image protection for images
  68. protect: false,
  69. // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc
  70. modal: false,
  71. image: {
  72. // Wait for images to load before displaying
  73. // true - wait for image to load and then display;
  74. // false - display thumbnail and load the full-sized image over top,
  75. // requires predefined image dimensions (`data-width` and `data-height` attributes)
  76. preload: false
  77. },
  78. ajax: {
  79. // Object containing settings for ajax request
  80. settings: {
  81. // This helps to indicate that request comes from the modal
  82. // Feel free to change naming
  83. data: {
  84. fancybox: true
  85. }
  86. }
  87. },
  88. iframe: {
  89. // Iframe template
  90. tpl:
  91. '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" allowfullscreen="allowfullscreen" allow="autoplay; fullscreen" src=""></iframe>',
  92. // Preload iframe before displaying it
  93. // This allows to calculate iframe content width and height
  94. // (note: Due to "Same Origin Policy", you can't get cross domain data).
  95. preload: true,
  96. // Custom CSS styling for iframe wrapping element
  97. // You can use this to set custom iframe dimensions
  98. css: {},
  99. // Iframe tag attributes
  100. attr: {
  101. scrolling: "auto"
  102. }
  103. },
  104. // For HTML5 video only
  105. video: {
  106. tpl:
  107. '<video class="fancybox-video" controls controlsList="nodownload" poster="{{poster}}">' +
  108. '<source src="{{src}}" type="{{format}}" />' +
  109. 'Sorry, your browser doesn\'t support embedded videos, <a href="{{src}}">download</a> and watch with your favorite video player!' +
  110. "</video>",
  111. format: "", // custom video format
  112. autoStart: true
  113. },
  114. // Default content type if cannot be detected automatically
  115. defaultType: "image",
  116. // Open/close animation type
  117. // Possible values:
  118. // false - disable
  119. // "zoom" - zoom images from/to thumbnail
  120. // "fade"
  121. // "zoom-in-out"
  122. //
  123. animationEffect: "zoom",
  124. // Duration in ms for open/close animation
  125. animationDuration: 366,
  126. // Should image change opacity while zooming
  127. // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios
  128. zoomOpacity: "auto",
  129. // Transition effect between slides
  130. //
  131. // Possible values:
  132. // false - disable
  133. // "fade'
  134. // "slide'
  135. // "circular'
  136. // "tube'
  137. // "zoom-in-out'
  138. // "rotate'
  139. //
  140. transitionEffect: "fade",
  141. // Duration in ms for transition animation
  142. transitionDuration: 366,
  143. // Custom CSS class for slide element
  144. slideClass: "",
  145. // Custom CSS class for layout
  146. baseClass: "",
  147. // Base template for layout
  148. baseTpl:
  149. '<div class="fancybox-container" role="dialog" tabindex="-1">' +
  150. '<div class="fancybox-bg"></div>' +
  151. '<div class="fancybox-inner">' +
  152. '<div class="fancybox-infobar"><span data-fancybox-index></span>&nbsp;/&nbsp;<span data-fancybox-count></span></div>' +
  153. '<div class="fancybox-toolbar">{{buttons}}</div>' +
  154. '<div class="fancybox-navigation">{{arrows}}</div>' +
  155. '<div class="fancybox-stage"></div>' +
  156. '<div class="fancybox-caption"><div class="fancybox-caption__body"></div></div>' +
  157. "</div>" +
  158. "</div>",
  159. // Loading indicator template
  160. spinnerTpl: '<div class="fancybox-loading"></div>',
  161. // Error message template
  162. errorTpl: '<div class="fancybox-error"><p>{{ERROR}}</p></div>',
  163. btnTpl: {
  164. download:
  165. '<a download data-fancybox-download class="fancybox-button fancybox-button--download" title="{{DOWNLOAD}}" href="javascript:;">' +
  166. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.62 17.09V19H5.38v-1.91zm-2.97-6.96L17 11.45l-5 4.87-5-4.87 1.36-1.32 2.68 2.64V5h1.92v7.77z"/></svg>' +
  167. "</a>",
  168. zoom:
  169. '<button data-fancybox-zoom class="fancybox-button fancybox-button--zoom" title="{{ZOOM}}">' +
  170. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.7 17.3l-3-3a5.9 5.9 0 0 0-.6-7.6 5.9 5.9 0 0 0-8.4 0 5.9 5.9 0 0 0 0 8.4 5.9 5.9 0 0 0 7.7.7l3 3a1 1 0 0 0 1.3 0c.4-.5.4-1 0-1.5zM8.1 13.8a4 4 0 0 1 0-5.7 4 4 0 0 1 5.7 0 4 4 0 0 1 0 5.7 4 4 0 0 1-5.7 0z"/></svg>' +
  171. "</button>",
  172. close:
  173. '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}">' +
  174. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 10.6L6.6 5.2 5.2 6.6l5.4 5.4-5.4 5.4 1.4 1.4 5.4-5.4 5.4 5.4 1.4-1.4-5.4-5.4 5.4-5.4-1.4-1.4-5.4 5.4z"/></svg>' +
  175. "</button>",
  176. // Arrows
  177. arrowLeft:
  178. '<button data-fancybox-prev class="fancybox-button fancybox-button--arrow_left" title="{{PREV}}">' +
  179. '<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.28 15.7l-1.34 1.37L5 12l4.94-5.07 1.34 1.38-2.68 2.72H19v1.94H8.6z"/></svg></div>' +
  180. "</button>",
  181. arrowRight:
  182. '<button data-fancybox-next class="fancybox-button fancybox-button--arrow_right" title="{{NEXT}}">' +
  183. '<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.4 12.97l-2.68 2.72 1.34 1.38L19 12l-4.94-5.07-1.34 1.38 2.68 2.72H5v1.94z"/></svg></div>' +
  184. "</button>",
  185. // This small close button will be appended to your html/inline/ajax content by default,
  186. // if "smallBtn" option is not set to false
  187. smallBtn:
  188. '<button type="button" data-fancybox-close class="fancybox-button fancybox-close-small" title="{{CLOSE}}">' +
  189. '<svg xmlns="http://www.w3.org/2000/svg" version="1" viewBox="0 0 24 24"><path d="M13 12l5-5-1-1-5 5-5-5-1 1 5 5-5 5 1 1 5-5 5 5 1-1z"/></svg>' +
  190. "</button>"
  191. },
  192. // Container is injected into this element
  193. parentEl: "body",
  194. // Hide browser vertical scrollbars; use at your own risk
  195. hideScrollbar: true,
  196. // Focus handling
  197. // ==============
  198. // Try to focus on the first focusable element after opening
  199. autoFocus: true,
  200. // Put focus back to active element after closing
  201. backFocus: true,
  202. // Do not let user to focus on element outside modal content
  203. trapFocus: true,
  204. // Module specific options
  205. // =======================
  206. fullScreen: {
  207. autoStart: false
  208. },
  209. // Set `touch: false` to disable panning/swiping
  210. touch: {
  211. vertical: true, // Allow to drag content vertically
  212. momentum: true // Continue movement after releasing mouse/touch when panning
  213. },
  214. // Hash value when initializing manually,
  215. // set `false` to disable hash change
  216. hash: null,
  217. // Customize or add new media types
  218. // Example:
  219. /*
  220. media : {
  221. youtube : {
  222. params : {
  223. autoplay : 0
  224. }
  225. }
  226. }
  227. */
  228. media: {},
  229. slideShow: {
  230. autoStart: false,
  231. speed: 3000
  232. },
  233. thumbs: {
  234. autoStart: false, // Display thumbnails on opening
  235. hideOnClose: true, // Hide thumbnail grid when closing animation starts
  236. parentEl: ".fancybox-container", // Container is injected into this element
  237. axis: "y" // Vertical (y) or horizontal (x) scrolling
  238. },
  239. // Use mousewheel to navigate gallery
  240. // If 'auto' - enabled for images only
  241. wheel: "auto",
  242. // Callbacks
  243. //==========
  244. // See Documentation/API/Events for more information
  245. // Example:
  246. /*
  247. afterShow: function( instance, current ) {
  248. console.info( 'Clicked element:' );
  249. console.info( current.opts.$orig );
  250. }
  251. */
  252. onInit: $.noop, // When instance has been initialized
  253. beforeLoad: $.noop, // Before the content of a slide is being loaded
  254. afterLoad: $.noop, // When the content of a slide is done loading
  255. beforeShow: $.noop, // Before open animation starts
  256. afterShow: $.noop, // When content is done loading and animating
  257. beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close.
  258. afterClose: $.noop, // After instance has been closed
  259. onActivate: $.noop, // When instance is brought to front
  260. onDeactivate: $.noop, // When other instance has been activated
  261. // Interaction
  262. // ===========
  263. // Use options below to customize taken action when user clicks or double clicks on the fancyBox area,
  264. // each option can be string or method that returns value.
  265. //
  266. // Possible values:
  267. // "close" - close instance
  268. // "next" - move to next gallery item
  269. // "nextOrClose" - move to next gallery item or close if gallery has only one item
  270. // "toggleControls" - show/hide controls
  271. // "zoom" - zoom image (if loaded)
  272. // false - do nothing
  273. // Clicked on the content
  274. clickContent: function(current, event) {
  275. return current.type === "image" ? "zoom" : false;
  276. },
  277. // Clicked on the slide
  278. clickSlide: "close",
  279. // Clicked on the background (backdrop) element;
  280. // if you have not changed the layout, then most likely you need to use `clickSlide` option
  281. clickOutside: "close",
  282. // Same as previous two, but for double click
  283. dblclickContent: false,
  284. dblclickSlide: false,
  285. dblclickOutside: false,
  286. // Custom options when mobile device is detected
  287. // =============================================
  288. mobile: {
  289. preventCaptionOverlap: false,
  290. idleTime: false,
  291. clickContent: function(current, event) {
  292. return current.type === "image" ? "toggleControls" : false;
  293. },
  294. clickSlide: function(current, event) {
  295. return current.type === "image" ? "toggleControls" : "close";
  296. },
  297. dblclickContent: function(current, event) {
  298. return current.type === "image" ? "zoom" : false;
  299. },
  300. dblclickSlide: function(current, event) {
  301. return current.type === "image" ? "zoom" : false;
  302. }
  303. },
  304. // Internationalization
  305. // ====================
  306. lang: "en",
  307. i18n: {
  308. en: {
  309. CLOSE: "Close",
  310. NEXT: "Next",
  311. PREV: "Previous",
  312. ERROR: "The requested content cannot be loaded. <br/> Please try again later.",
  313. PLAY_START: "Start slideshow",
  314. PLAY_STOP: "Pause slideshow",
  315. FULL_SCREEN: "Full screen",
  316. THUMBS: "Thumbnails",
  317. DOWNLOAD: "Download",
  318. SHARE: "Share",
  319. ZOOM: "Zoom"
  320. },
  321. de: {
  322. CLOSE: "Schlie&szlig;en",
  323. NEXT: "Weiter",
  324. PREV: "Zur&uuml;ck",
  325. ERROR: "Die angeforderten Daten konnten nicht geladen werden. <br/> Bitte versuchen Sie es sp&auml;ter nochmal.",
  326. PLAY_START: "Diaschau starten",
  327. PLAY_STOP: "Diaschau beenden",
  328. FULL_SCREEN: "Vollbild",
  329. THUMBS: "Vorschaubilder",
  330. DOWNLOAD: "Herunterladen",
  331. SHARE: "Teilen",
  332. ZOOM: "Vergr&ouml;&szlig;ern"
  333. }
  334. }
  335. };
  336. // Few useful variables and methods
  337. // ================================
  338. var $W = $(window);
  339. var $D = $(document);
  340. var called = 0;
  341. // Check if an object is a jQuery object and not a native JavaScript object
  342. // ========================================================================
  343. var isQuery = function(obj) {
  344. return obj && obj.hasOwnProperty && obj instanceof $;
  345. };
  346. // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame"
  347. // ===============================================================================
  348. var requestAFrame = (function() {
  349. return (
  350. window.requestAnimationFrame ||
  351. window.webkitRequestAnimationFrame ||
  352. window.mozRequestAnimationFrame ||
  353. window.oRequestAnimationFrame ||
  354. // if all else fails, use setTimeout
  355. function(callback) {
  356. return window.setTimeout(callback, 1000 / 60);
  357. }
  358. );
  359. })();
  360. var cancelAFrame = (function() {
  361. return (
  362. window.cancelAnimationFrame ||
  363. window.webkitCancelAnimationFrame ||
  364. window.mozCancelAnimationFrame ||
  365. window.oCancelAnimationFrame ||
  366. function(id) {
  367. window.clearTimeout(id);
  368. }
  369. );
  370. })();
  371. // Detect the supported transition-end event property name
  372. // =======================================================
  373. var transitionEnd = (function() {
  374. var el = document.createElement("fakeelement"),
  375. t;
  376. var transitions = {
  377. transition: "transitionend",
  378. OTransition: "oTransitionEnd",
  379. MozTransition: "transitionend",
  380. WebkitTransition: "webkitTransitionEnd"
  381. };
  382. for (t in transitions) {
  383. if (el.style[t] !== undefined) {
  384. return transitions[t];
  385. }
  386. }
  387. return "transitionend";
  388. })();
  389. // Force redraw on an element.
  390. // This helps in cases where the browser doesn't redraw an updated element properly
  391. // ================================================================================
  392. var forceRedraw = function($el) {
  393. return $el && $el.length && $el[0].offsetHeight;
  394. };
  395. // Exclude array (`buttons`) options from deep merging
  396. // ===================================================
  397. var mergeOpts = function(opts1, opts2) {
  398. var rez = $.extend(true, {}, opts1, opts2);
  399. $.each(opts2, function(key, value) {
  400. if ($.isArray(value)) {
  401. rez[key] = value;
  402. }
  403. });
  404. return rez;
  405. };
  406. // How much of an element is visible in viewport
  407. // =============================================
  408. var inViewport = function(elem) {
  409. var elemCenter, rez;
  410. if (!elem || elem.ownerDocument !== document) {
  411. return false;
  412. }
  413. $(".fancybox-container").css("pointer-events", "none");
  414. elemCenter = {
  415. x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
  416. y: elem.getBoundingClientRect().top + elem.offsetHeight / 2
  417. };
  418. rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem;
  419. $(".fancybox-container").css("pointer-events", "");
  420. return rez;
  421. };
  422. // Class definition
  423. // ================
  424. var FancyBox = function(content, opts, index) {
  425. var self = this;
  426. self.opts = mergeOpts({index: index}, $.fancybox.defaults);
  427. if ($.isPlainObject(opts)) {
  428. self.opts = mergeOpts(self.opts, opts);
  429. }
  430. if ($.fancybox.isMobile) {
  431. self.opts = mergeOpts(self.opts, self.opts.mobile);
  432. }
  433. self.id = self.opts.id || ++called;
  434. self.currIndex = parseInt(self.opts.index, 10) || 0;
  435. self.prevIndex = null;
  436. self.prevPos = null;
  437. self.currPos = 0;
  438. self.firstRun = true;
  439. // All group items
  440. self.group = [];
  441. // Existing slides (for current, next and previous gallery items)
  442. self.slides = {};
  443. // Create group elements
  444. self.addContent(content);
  445. if (!self.group.length) {
  446. return;
  447. }
  448. self.init();
  449. };
  450. $.extend(FancyBox.prototype, {
  451. // Create DOM structure
  452. // ====================
  453. init: function() {
  454. var self = this,
  455. firstItem = self.group[self.currIndex],
  456. firstItemOpts = firstItem.opts,
  457. $container,
  458. buttonStr;
  459. if (firstItemOpts.closeExisting) {
  460. $.fancybox.close(true);
  461. }
  462. // Hide scrollbars
  463. // ===============
  464. $("body").addClass("fancybox-active");
  465. if (
  466. !$.fancybox.getInstance() &&
  467. firstItemOpts.hideScrollbar !== false &&
  468. !$.fancybox.isMobile &&
  469. document.body.scrollHeight > window.innerHeight
  470. ) {
  471. $("head").append(
  472. '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar{margin-right:' +
  473. (window.innerWidth - document.documentElement.clientWidth) +
  474. "px;}</style>"
  475. );
  476. $("body").addClass("compensate-for-scrollbar");
  477. }
  478. // Build html markup and set references
  479. // ====================================
  480. // Build html code for buttons and insert into main template
  481. buttonStr = "";
  482. $.each(firstItemOpts.buttons, function(index, value) {
  483. buttonStr += firstItemOpts.btnTpl[value] || "";
  484. });
  485. // Create markup from base template, it will be initially hidden to
  486. // avoid unnecessary work like painting while initializing is not complete
  487. $container = $(
  488. self.translate(
  489. self,
  490. firstItemOpts.baseTpl
  491. .replace("{{buttons}}", buttonStr)
  492. .replace("{{arrows}}", firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight)
  493. )
  494. )
  495. .attr("id", "fancybox-container-" + self.id)
  496. .addClass(firstItemOpts.baseClass)
  497. .data("FancyBox", self)
  498. .appendTo(firstItemOpts.parentEl);
  499. // Create object holding references to jQuery wrapped nodes
  500. self.$refs = {
  501. container: $container
  502. };
  503. ["bg", "inner", "infobar", "toolbar", "stage", "caption", "navigation"].forEach(function(item) {
  504. self.$refs[item] = $container.find(".fancybox-" + item);
  505. });
  506. self.trigger("onInit");
  507. // Enable events, deactive previous instances
  508. self.activate();
  509. // Build slides, load and reveal content
  510. self.jumpTo(self.currIndex);
  511. },
  512. // Simple i18n support - replaces object keys found in template
  513. // with corresponding values
  514. // ============================================================
  515. translate: function(obj, str) {
  516. var arr = obj.opts.i18n[obj.opts.lang] || obj.opts.i18n.en;
  517. return str.replace(/\{\{(\w+)\}\}/g, function(match, n) {
  518. return arr[n] === undefined ? match : arr[n];
  519. });
  520. },
  521. // Populate current group with fresh content
  522. // Check if each object has valid type and content
  523. // ===============================================
  524. addContent: function(content) {
  525. var self = this,
  526. items = $.makeArray(content),
  527. thumbs;
  528. $.each(items, function(i, item) {
  529. var obj = {},
  530. opts = {},
  531. $item,
  532. type,
  533. found,
  534. src,
  535. srcParts;
  536. // Step 1 - Make sure we have an object
  537. // ====================================
  538. if ($.isPlainObject(item)) {
  539. // We probably have manual usage here, something like
  540. // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] )
  541. obj = item;
  542. opts = item.opts || item;
  543. } else if ($.type(item) === "object" && $(item).length) {
  544. // Here we probably have jQuery collection returned by some selector
  545. $item = $(item);
  546. // Support attributes like `data-options='{"touch" : false}'` and `data-touch='false'`
  547. opts = $item.data() || {};
  548. opts = $.extend(true, {}, opts, opts.options);
  549. // Here we store clicked element
  550. opts.$orig = $item;
  551. obj.src = self.opts.src || opts.src || $item.attr("href");
  552. // Assume that simple syntax is used, for example:
  553. // `$.fancybox.open( $("#test"), {} );`
  554. if (!obj.type && !obj.src) {
  555. obj.type = "inline";
  556. obj.src = item;
  557. }
  558. } else {
  559. // Assume we have a simple html code, for example:
  560. // $.fancybox.open( '<div><h1>Hi!</h1></div>' );
  561. obj = {
  562. type: "html",
  563. src: item + ""
  564. };
  565. }
  566. // Each gallery object has full collection of options
  567. obj.opts = $.extend(true, {}, self.opts, opts);
  568. // Do not merge buttons array
  569. if ($.isArray(opts.buttons)) {
  570. obj.opts.buttons = opts.buttons;
  571. }
  572. if ($.fancybox.isMobile && obj.opts.mobile) {
  573. obj.opts = mergeOpts(obj.opts, obj.opts.mobile);
  574. }
  575. // Step 2 - Make sure we have content type, if not - try to guess
  576. // ==============================================================
  577. type = obj.type || obj.opts.type;
  578. src = obj.src || "";
  579. if (!type && src) {
  580. if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) {
  581. type = "video";
  582. if (!obj.opts.video.format) {
  583. obj.opts.video.format = "video/" + (found[1] === "ogv" ? "ogg" : found[1]);
  584. }
  585. } else if (src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) {
  586. type = "image";
  587. } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) {
  588. type = "iframe";
  589. obj = $.extend(true, obj, {contentType: "pdf", opts: {iframe: {preload: false}}});
  590. } else if (src.charAt(0) === "#") {
  591. type = "inline";
  592. }
  593. }
  594. if (type) {
  595. obj.type = type;
  596. } else {
  597. self.trigger("objectNeedsType", obj);
  598. }
  599. if (!obj.contentType) {
  600. obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type;
  601. }
  602. // Step 3 - Some adjustments
  603. // =========================
  604. obj.index = self.group.length;
  605. if (obj.opts.smallBtn == "auto") {
  606. obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1;
  607. }
  608. if (obj.opts.toolbar === "auto") {
  609. obj.opts.toolbar = !obj.opts.smallBtn;
  610. }
  611. // Find thumbnail image, check if exists and if is in the viewport
  612. obj.$thumb = obj.opts.$thumb || null;
  613. if (obj.opts.$trigger && obj.index === self.opts.index) {
  614. obj.$thumb = obj.opts.$trigger.find("img:first");
  615. if (obj.$thumb.length) {
  616. obj.opts.$orig = obj.opts.$trigger;
  617. }
  618. }
  619. if (!(obj.$thumb && obj.$thumb.length) && obj.opts.$orig) {
  620. obj.$thumb = obj.opts.$orig.find("img:first");
  621. }
  622. if (obj.$thumb && !obj.$thumb.length) {
  623. obj.$thumb = null;
  624. }
  625. obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null);
  626. // "caption" is a "special" option, it can be used to customize caption per gallery item
  627. if ($.type(obj.opts.caption) === "function") {
  628. obj.opts.caption = obj.opts.caption.apply(item, [self, obj]);
  629. }
  630. if ($.type(self.opts.caption) === "function") {
  631. obj.opts.caption = self.opts.caption.apply(item, [self, obj]);
  632. }
  633. // Make sure we have caption as a string or jQuery object
  634. if (!(obj.opts.caption instanceof $)) {
  635. obj.opts.caption = obj.opts.caption === undefined ? "" : obj.opts.caption + "";
  636. }
  637. // Check if url contains "filter" used to filter the content
  638. // Example: "ajax.html #something"
  639. if (obj.type === "ajax") {
  640. srcParts = src.split(/\s+/, 2);
  641. if (srcParts.length > 1) {
  642. obj.src = srcParts.shift();
  643. obj.opts.filter = srcParts.shift();
  644. }
  645. }
  646. // Hide all buttons and disable interactivity for modal items
  647. if (obj.opts.modal) {
  648. obj.opts = $.extend(true, obj.opts, {
  649. trapFocus: true,
  650. // Remove buttons
  651. infobar: 0,
  652. toolbar: 0,
  653. smallBtn: 0,
  654. // Disable keyboard navigation
  655. keyboard: 0,
  656. // Disable some modules
  657. slideShow: 0,
  658. fullScreen: 0,
  659. thumbs: 0,
  660. touch: 0,
  661. // Disable click event handlers
  662. clickContent: false,
  663. clickSlide: false,
  664. clickOutside: false,
  665. dblclickContent: false,
  666. dblclickSlide: false,
  667. dblclickOutside: false
  668. });
  669. }
  670. // Step 4 - Add processed object to group
  671. // ======================================
  672. self.group.push(obj);
  673. });
  674. // Update controls if gallery is already opened
  675. if (Object.keys(self.slides).length) {
  676. self.updateControls();
  677. // Update thumbnails, if needed
  678. thumbs = self.Thumbs;
  679. if (thumbs && thumbs.isActive) {
  680. thumbs.create();
  681. thumbs.focus();
  682. }
  683. }
  684. },
  685. // Attach an event handler functions for:
  686. // - navigation buttons
  687. // - browser scrolling, resizing;
  688. // - focusing
  689. // - keyboard
  690. // - detecting inactivity
  691. // ======================================
  692. addEvents: function() {
  693. var self = this;
  694. self.removeEvents();
  695. // Make navigation elements clickable
  696. // ==================================
  697. self.$refs.container
  698. .on("click.fb-close", "[data-fancybox-close]", function(e) {
  699. e.stopPropagation();
  700. e.preventDefault();
  701. self.close(e);
  702. })
  703. .on("touchstart.fb-prev click.fb-prev", "[data-fancybox-prev]", function(e) {
  704. e.stopPropagation();
  705. e.preventDefault();
  706. self.previous();
  707. })
  708. .on("touchstart.fb-next click.fb-next", "[data-fancybox-next]", function(e) {
  709. e.stopPropagation();
  710. e.preventDefault();
  711. self.next();
  712. })
  713. .on("click.fb", "[data-fancybox-zoom]", function(e) {
  714. // Click handler for zoom button
  715. self[self.isScaledDown() ? "scaleToActual" : "scaleToFit"]();
  716. });
  717. // Handle page scrolling and browser resizing
  718. // ==========================================
  719. $W.on("orientationchange.fb resize.fb", function(e) {
  720. if (e && e.originalEvent && e.originalEvent.type === "resize") {
  721. if (self.requestId) {
  722. cancelAFrame(self.requestId);
  723. }
  724. self.requestId = requestAFrame(function() {
  725. self.update(e);
  726. });
  727. } else {
  728. if (self.current && self.current.type === "iframe") {
  729. self.$refs.stage.hide();
  730. }
  731. setTimeout(
  732. function() {
  733. self.$refs.stage.show();
  734. self.update(e);
  735. },
  736. $.fancybox.isMobile ? 600 : 250
  737. );
  738. }
  739. });
  740. $D.on("keydown.fb", function(e) {
  741. var instance = $.fancybox ? $.fancybox.getInstance() : null,
  742. current = instance.current,
  743. keycode = e.keyCode || e.which;
  744. // Trap keyboard focus inside of the modal
  745. // =======================================
  746. if (keycode == 9) {
  747. if (current.opts.trapFocus) {
  748. self.focus(e);
  749. }
  750. return;
  751. }
  752. // Enable keyboard navigation
  753. // ==========================
  754. if (!current.opts.keyboard || e.ctrlKey || e.altKey || e.shiftKey || $(e.target).is("input,textarea,video,audio")) {
  755. return;
  756. }
  757. // Backspace and Esc keys
  758. if (keycode === 8 || keycode === 27) {
  759. e.preventDefault();
  760. self.close(e);
  761. return;
  762. }
  763. // Left arrow and Up arrow
  764. if (keycode === 37 || keycode === 38) {
  765. e.preventDefault();
  766. self.previous();
  767. return;
  768. }
  769. // Righ arrow and Down arrow
  770. if (keycode === 39 || keycode === 40) {
  771. e.preventDefault();
  772. self.next();
  773. return;
  774. }
  775. self.trigger("afterKeydown", e, keycode);
  776. });
  777. // Hide controls after some inactivity period
  778. if (self.group[self.currIndex].opts.idleTime) {
  779. self.idleSecondsCounter = 0;
  780. $D.on(
  781. "mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",
  782. function(e) {
  783. self.idleSecondsCounter = 0;
  784. if (self.isIdle) {
  785. self.showControls();
  786. }
  787. self.isIdle = false;
  788. }
  789. );
  790. self.idleInterval = window.setInterval(function() {
  791. self.idleSecondsCounter++;
  792. if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) {
  793. self.isIdle = true;
  794. self.idleSecondsCounter = 0;
  795. self.hideControls();
  796. }
  797. }, 1000);
  798. }
  799. },
  800. // Remove events added by the core
  801. // ===============================
  802. removeEvents: function() {
  803. var self = this;
  804. $W.off("orientationchange.fb resize.fb");
  805. $D.off("keydown.fb .fb-idle");
  806. this.$refs.container.off(".fb-close .fb-prev .fb-next");
  807. if (self.idleInterval) {
  808. window.clearInterval(self.idleInterval);
  809. self.idleInterval = null;
  810. }
  811. },
  812. // Change to previous gallery item
  813. // ===============================
  814. previous: function(duration) {
  815. return this.jumpTo(this.currPos - 1, duration);
  816. },
  817. // Change to next gallery item
  818. // ===========================
  819. next: function(duration) {
  820. return this.jumpTo(this.currPos + 1, duration);
  821. },
  822. // Switch to selected gallery item
  823. // ===============================
  824. jumpTo: function(pos, duration) {
  825. var self = this,
  826. groupLen = self.group.length,
  827. firstRun,
  828. isMoved,
  829. loop,
  830. current,
  831. previous,
  832. slidePos,
  833. stagePos,
  834. prop,
  835. diff;
  836. if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) {
  837. return;
  838. }
  839. // Should loop?
  840. pos = parseInt(pos, 10);
  841. loop = self.current ? self.current.opts.loop : self.opts.loop;
  842. if (!loop && (pos < 0 || pos >= groupLen)) {
  843. return false;
  844. }
  845. // Check if opening for the first time; this helps to speed things up
  846. firstRun = self.firstRun = !Object.keys(self.slides).length;
  847. // Create slides
  848. previous = self.current;
  849. self.prevIndex = self.currIndex;
  850. self.prevPos = self.currPos;
  851. current = self.createSlide(pos);
  852. if (groupLen > 1) {
  853. if (loop || current.index < groupLen - 1) {
  854. self.createSlide(pos + 1);
  855. }
  856. if (loop || current.index > 0) {
  857. self.createSlide(pos - 1);
  858. }
  859. }
  860. self.current = current;
  861. self.currIndex = current.index;
  862. self.currPos = current.pos;
  863. self.trigger("beforeShow", firstRun);
  864. self.updateControls();
  865. // Validate duration length
  866. current.forcedDuration = undefined;
  867. if ($.isNumeric(duration)) {
  868. current.forcedDuration = duration;
  869. } else {
  870. duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"];
  871. }
  872. duration = parseInt(duration, 10);
  873. // Check if user has swiped the slides or if still animating
  874. isMoved = self.isMoved(current);
  875. // Make sure current slide is visible
  876. current.$slide.addClass("fancybox-slide--current");
  877. // Fresh start - reveal container, current slide and start loading content
  878. if (firstRun) {
  879. if (current.opts.animationEffect && duration) {
  880. self.$refs.container.css("transition-duration", duration + "ms");
  881. }
  882. self.$refs.container.addClass("fancybox-is-open").trigger("focus");
  883. // Attempt to load content into slide
  884. // This will later call `afterLoad` -> `revealContent`
  885. self.loadSlide(current);
  886. self.preload("image");
  887. return;
  888. }
  889. // Get actual slide/stage positions (before cleaning up)
  890. slidePos = $.fancybox.getTranslate(previous.$slide);
  891. stagePos = $.fancybox.getTranslate(self.$refs.stage);
  892. // Clean up all slides
  893. $.each(self.slides, function(index, slide) {
  894. $.fancybox.stop(slide.$slide, true);
  895. });
  896. if (previous.pos !== current.pos) {
  897. previous.isComplete = false;
  898. }
  899. previous.$slide.removeClass("fancybox-slide--complete fancybox-slide--current");
  900. // If slides are out of place, then animate them to correct position
  901. if (isMoved) {
  902. // Calculate horizontal swipe distance
  903. diff = slidePos.left - (previous.pos * slidePos.width + previous.pos * previous.opts.gutter);
  904. $.each(self.slides, function(index, slide) {
  905. slide.$slide.removeClass("fancybox-animated").removeClass(function(index, className) {
  906. return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" ");
  907. });
  908. // Make sure that each slide is in equal distance
  909. // This is mostly needed for freshly added slides, because they are not yet positioned
  910. var leftPos = slide.pos * slidePos.width + slide.pos * slide.opts.gutter;
  911. $.fancybox.setTranslate(slide.$slide, {top: 0, left: leftPos - stagePos.left + diff});
  912. if (slide.pos !== current.pos) {
  913. slide.$slide.addClass("fancybox-slide--" + (slide.pos > current.pos ? "next" : "previous"));
  914. }
  915. // Redraw to make sure that transition will start
  916. forceRedraw(slide.$slide);
  917. // Animate the slide
  918. $.fancybox.animate(
  919. slide.$slide,
  920. {
  921. top: 0,
  922. left: (slide.pos - current.pos) * slidePos.width + (slide.pos - current.pos) * slide.opts.gutter
  923. },
  924. duration,
  925. function() {
  926. slide.$slide
  927. .css({
  928. transform: "",
  929. opacity: ""
  930. })
  931. .removeClass("fancybox-slide--next fancybox-slide--previous");
  932. if (slide.pos === self.currPos) {
  933. self.complete();
  934. }
  935. }
  936. );
  937. });
  938. } else if (duration && current.opts.transitionEffect) {
  939. // Set transition effect for previously active slide
  940. prop = "fancybox-animated fancybox-fx-" + current.opts.transitionEffect;
  941. previous.$slide.addClass("fancybox-slide--" + (previous.pos > current.pos ? "next" : "previous"));
  942. $.fancybox.animate(
  943. previous.$slide,
  944. prop,
  945. duration,
  946. function() {
  947. previous.$slide.removeClass(prop).removeClass("fancybox-slide--next fancybox-slide--previous");
  948. },
  949. false
  950. );
  951. }
  952. if (current.isLoaded) {
  953. self.revealContent(current);
  954. } else {
  955. self.loadSlide(current);
  956. }
  957. self.preload("image");
  958. },
  959. // Create new "slide" element
  960. // These are gallery items that are actually added to DOM
  961. // =======================================================
  962. createSlide: function(pos) {
  963. var self = this,
  964. $slide,
  965. index;
  966. index = pos % self.group.length;
  967. index = index < 0 ? self.group.length + index : index;
  968. if (!self.slides[pos] && self.group[index]) {
  969. $slide = $('<div class="fancybox-slide"></div>').appendTo(self.$refs.stage);
  970. self.slides[pos] = $.extend(true, {}, self.group[index], {
  971. pos: pos,
  972. $slide: $slide,
  973. isLoaded: false
  974. });
  975. self.updateSlide(self.slides[pos]);
  976. }
  977. return self.slides[pos];
  978. },
  979. // Scale image to the actual size of the image;
  980. // x and y values should be relative to the slide
  981. // ==============================================
  982. scaleToActual: function(x, y, duration) {
  983. var self = this,
  984. current = self.current,
  985. $content = current.$content,
  986. canvasWidth = $.fancybox.getTranslate(current.$slide).width,
  987. canvasHeight = $.fancybox.getTranslate(current.$slide).height,
  988. newImgWidth = current.width,
  989. newImgHeight = current.height,
  990. imgPos,
  991. posX,
  992. posY,
  993. scaleX,
  994. scaleY;
  995. if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) {
  996. return;
  997. }
  998. self.isAnimating = true;
  999. $.fancybox.stop($content);
  1000. x = x === undefined ? canvasWidth * 0.5 : x;
  1001. y = y === undefined ? canvasHeight * 0.5 : y;
  1002. imgPos = $.fancybox.getTranslate($content);
  1003. imgPos.top -= $.fancybox.getTranslate(current.$slide).top;
  1004. imgPos.left -= $.fancybox.getTranslate(current.$slide).left;
  1005. scaleX = newImgWidth / imgPos.width;
  1006. scaleY = newImgHeight / imgPos.height;
  1007. // Get center position for original image
  1008. posX = canvasWidth * 0.5 - newImgWidth * 0.5;
  1009. posY = canvasHeight * 0.5 - newImgHeight * 0.5;
  1010. // Make sure image does not move away from edges
  1011. if (newImgWidth > canvasWidth) {
  1012. posX = imgPos.left * scaleX - (x * scaleX - x);
  1013. if (posX > 0) {
  1014. posX = 0;
  1015. }
  1016. if (posX < canvasWidth - newImgWidth) {
  1017. posX = canvasWidth - newImgWidth;
  1018. }
  1019. }
  1020. if (newImgHeight > canvasHeight) {
  1021. posY = imgPos.top * scaleY - (y * scaleY - y);
  1022. if (posY > 0) {
  1023. posY = 0;
  1024. }
  1025. if (posY < canvasHeight - newImgHeight) {
  1026. posY = canvasHeight - newImgHeight;
  1027. }
  1028. }
  1029. self.updateCursor(newImgWidth, newImgHeight);
  1030. $.fancybox.animate(
  1031. $content,
  1032. {
  1033. top: posY,
  1034. left: posX,
  1035. scaleX: scaleX,
  1036. scaleY: scaleY
  1037. },
  1038. duration || 366,
  1039. function() {
  1040. self.isAnimating = false;
  1041. }
  1042. );
  1043. // Stop slideshow
  1044. if (self.SlideShow && self.SlideShow.isActive) {
  1045. self.SlideShow.stop();
  1046. }
  1047. },
  1048. // Scale image to fit inside parent element
  1049. // ========================================
  1050. scaleToFit: function(duration) {
  1051. var self = this,
  1052. current = self.current,
  1053. $content = current.$content,
  1054. end;
  1055. if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) {
  1056. return;
  1057. }
  1058. self.isAnimating = true;
  1059. $.fancybox.stop($content);
  1060. end = self.getFitPos(current);
  1061. self.updateCursor(end.width, end.height);
  1062. $.fancybox.animate(
  1063. $content,
  1064. {
  1065. top: end.top,
  1066. left: end.left,
  1067. scaleX: end.width / $content.width(),
  1068. scaleY: end.height / $content.height()
  1069. },
  1070. duration || 366,
  1071. function() {
  1072. self.isAnimating = false;
  1073. }
  1074. );
  1075. },
  1076. // Calculate image size to fit inside viewport
  1077. // ===========================================
  1078. getFitPos: function(slide) {
  1079. var self = this,
  1080. $content = slide.$content,
  1081. $slide = slide.$slide,
  1082. width = slide.width || slide.opts.width,
  1083. height = slide.height || slide.opts.height,
  1084. maxWidth,
  1085. maxHeight,
  1086. minRatio,
  1087. aspectRatio,
  1088. rez = {};
  1089. if (!slide.isLoaded || !$content || !$content.length) {
  1090. return false;
  1091. }
  1092. maxWidth = $.fancybox.getTranslate(self.$refs.stage).width;
  1093. maxHeight = $.fancybox.getTranslate(self.$refs.stage).height;
  1094. maxWidth -=
  1095. parseFloat($slide.css("paddingLeft")) +
  1096. parseFloat($slide.css("paddingRight")) +
  1097. parseFloat($content.css("marginLeft")) +
  1098. parseFloat($content.css("marginRight"));
  1099. maxHeight -=
  1100. parseFloat($slide.css("paddingTop")) +
  1101. parseFloat($slide.css("paddingBottom")) +
  1102. parseFloat($content.css("marginTop")) +
  1103. parseFloat($content.css("marginBottom"));
  1104. if (!width || !height) {
  1105. width = maxWidth;
  1106. height = maxHeight;
  1107. }
  1108. minRatio = Math.min(1, maxWidth / width, maxHeight / height);
  1109. width = minRatio * width;
  1110. height = minRatio * height;
  1111. // Adjust width/height to precisely fit into container
  1112. if (width > maxWidth - 0.5) {
  1113. width = maxWidth;
  1114. }
  1115. if (height > maxHeight - 0.5) {
  1116. height = maxHeight;
  1117. }
  1118. if (slide.type === "image") {
  1119. rez.top = Math.floor((maxHeight - height) * 0.5) + parseFloat($slide.css("paddingTop"));
  1120. rez.left = Math.floor((maxWidth - width) * 0.5) + parseFloat($slide.css("paddingLeft"));
  1121. } else if (slide.contentType === "video") {
  1122. // Force aspect ratio for the video
  1123. // "I say the whole world must learn of our peaceful ways… by force!"
  1124. aspectRatio = slide.opts.width && slide.opts.height ? width / height : slide.opts.ratio || 16 / 9;
  1125. if (height > width / aspectRatio) {
  1126. height = width / aspectRatio;
  1127. } else if (width > height * aspectRatio) {
  1128. width = height * aspectRatio;
  1129. }
  1130. }
  1131. rez.width = width;
  1132. rez.height = height;
  1133. return rez;
  1134. },
  1135. // Update content size and position for all slides
  1136. // ==============================================
  1137. update: function(e) {
  1138. var self = this;
  1139. $.each(self.slides, function(key, slide) {
  1140. self.updateSlide(slide, e);
  1141. });
  1142. },
  1143. // Update slide content position and size
  1144. // ======================================
  1145. updateSlide: function(slide, e) {
  1146. var self = this,
  1147. $content = slide && slide.$content,
  1148. width = slide.width || slide.opts.width,
  1149. height = slide.height || slide.opts.height,
  1150. $slide = slide.$slide;
  1151. // First, prevent caption overlap, if needed
  1152. self.adjustCaption(slide);
  1153. // Then resize content to fit inside the slide
  1154. if ($content && (width || height || slide.contentType === "video") && !slide.hasError) {
  1155. $.fancybox.stop($content);
  1156. $.fancybox.setTranslate($content, self.getFitPos(slide));
  1157. if (slide.pos === self.currPos) {
  1158. self.isAnimating = false;
  1159. self.updateCursor();
  1160. }
  1161. }
  1162. // Then some adjustments
  1163. self.adjustLayout(slide);
  1164. if ($slide.length) {
  1165. $slide.trigger("refresh");
  1166. if (slide.pos === self.currPos) {
  1167. self.$refs.toolbar
  1168. .add(self.$refs.navigation.find(".fancybox-button--arrow_right"))
  1169. .toggleClass("compensate-for-scrollbar", $slide.get(0).scrollHeight > $slide.get(0).clientHeight);
  1170. }
  1171. }
  1172. self.trigger("onUpdate", slide, e);
  1173. },
  1174. // Horizontally center slide
  1175. // =========================
  1176. centerSlide: function(duration) {
  1177. var self = this,
  1178. current = self.current,
  1179. $slide = current.$slide;
  1180. if (self.isClosing || !current) {
  1181. return;
  1182. }
  1183. $slide.siblings().css({
  1184. transform: "",
  1185. opacity: ""
  1186. });
  1187. $slide
  1188. .parent()
  1189. .children()
  1190. .removeClass("fancybox-slide--previous fancybox-slide--next");
  1191. $.fancybox.animate(
  1192. $slide,
  1193. {
  1194. top: 0,
  1195. left: 0,
  1196. opacity: 1
  1197. },
  1198. duration === undefined ? 0 : duration,
  1199. function() {
  1200. // Clean up
  1201. $slide.css({
  1202. transform: "",
  1203. opacity: ""
  1204. });
  1205. if (!current.isComplete) {
  1206. self.complete();
  1207. }
  1208. },
  1209. false
  1210. );
  1211. },
  1212. // Check if current slide is moved (swiped)
  1213. // ========================================
  1214. isMoved: function(slide) {
  1215. var current = slide || this.current,
  1216. slidePos,
  1217. stagePos;
  1218. if (!current) {
  1219. return false;
  1220. }
  1221. stagePos = $.fancybox.getTranslate(this.$refs.stage);
  1222. slidePos = $.fancybox.getTranslate(current.$slide);
  1223. return (
  1224. !current.$slide.hasClass("fancybox-animated") &&
  1225. (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5)
  1226. );
  1227. },
  1228. // Update cursor style depending if content can be zoomed
  1229. // ======================================================
  1230. updateCursor: function(nextWidth, nextHeight) {
  1231. var self = this,
  1232. current = self.current,
  1233. $container = self.$refs.container,
  1234. canPan,
  1235. isZoomable;
  1236. if (!current || self.isClosing || !self.Guestures) {
  1237. return;
  1238. }
  1239. $container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan");
  1240. canPan = self.canPan(nextWidth, nextHeight);
  1241. isZoomable = canPan ? true : self.isZoomable();
  1242. $container.toggleClass("fancybox-is-zoomable", isZoomable);
  1243. $("[data-fancybox-zoom]").prop("disabled", !isZoomable);
  1244. if (canPan) {
  1245. $container.addClass("fancybox-can-pan");
  1246. } else if (
  1247. isZoomable &&
  1248. (current.opts.clickContent === "zoom" || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) == "zoom"))
  1249. ) {
  1250. $container.addClass("fancybox-can-zoomIn");
  1251. } else if (current.opts.touch && (current.opts.touch.vertical || self.group.length > 1) && current.contentType !== "video") {
  1252. $container.addClass("fancybox-can-swipe");
  1253. }
  1254. },
  1255. // Check if current slide is zoomable
  1256. // ==================================
  1257. isZoomable: function() {
  1258. var self = this,
  1259. current = self.current,
  1260. fitPos;
  1261. // Assume that slide is zoomable if:
  1262. // - image is still loading
  1263. // - actual size of the image is smaller than available area
  1264. if (current && !self.isClosing && current.type === "image" && !current.hasError) {
  1265. if (!current.isLoaded) {
  1266. return true;
  1267. }
  1268. fitPos = self.getFitPos(current);
  1269. if (fitPos && (current.width > fitPos.width || current.height > fitPos.height)) {
  1270. return true;
  1271. }
  1272. }
  1273. return false;
  1274. },
  1275. // Check if current image dimensions are smaller than actual
  1276. // =========================================================
  1277. isScaledDown: function(nextWidth, nextHeight) {
  1278. var self = this,
  1279. rez = false,
  1280. current = self.current,
  1281. $content = current.$content;
  1282. if (nextWidth !== undefined && nextHeight !== undefined) {
  1283. rez = nextWidth < current.width && nextHeight < current.height;
  1284. } else if ($content) {
  1285. rez = $.fancybox.getTranslate($content);
  1286. rez = rez.width < current.width && rez.height < current.height;
  1287. }
  1288. return rez;
  1289. },
  1290. // Check if image dimensions exceed parent element
  1291. // ===============================================
  1292. canPan: function(nextWidth, nextHeight) {
  1293. var self = this,
  1294. current = self.current,
  1295. pos = null,
  1296. rez = false;
  1297. if (current.type === "image" && (current.isComplete || (nextWidth && nextHeight)) && !current.hasError) {
  1298. rez = self.getFitPos(current);
  1299. if (nextWidth !== undefined && nextHeight !== undefined) {
  1300. pos = {width: nextWidth, height: nextHeight};
  1301. } else if (current.isComplete) {
  1302. pos = $.fancybox.getTranslate(current.$content);
  1303. }
  1304. if (pos && rez) {
  1305. rez = Math.abs(pos.width - rez.width) > 1.5 || Math.abs(pos.height - rez.height) > 1.5;
  1306. }
  1307. }
  1308. return rez;
  1309. },
  1310. // Load content into the slide
  1311. // ===========================
  1312. loadSlide: function(slide) {
  1313. var self = this,
  1314. type,
  1315. $slide,
  1316. ajaxLoad;
  1317. if (slide.isLoading || slide.isLoaded) {
  1318. return;
  1319. }
  1320. slide.isLoading = true;
  1321. if (self.trigger("beforeLoad", slide) === false) {
  1322. slide.isLoading = false;
  1323. return false;
  1324. }
  1325. type = slide.type;
  1326. $slide = slide.$slide;
  1327. $slide
  1328. .off("refresh")
  1329. .trigger("onReset")
  1330. .addClass(slide.opts.slideClass);
  1331. // Create content depending on the type
  1332. switch (type) {
  1333. case "image":
  1334. self.setImage(slide);
  1335. break;
  1336. case "iframe":
  1337. self.setIframe(slide);
  1338. break;
  1339. case "html":
  1340. self.setContent(slide,