Repository: fancyapps/fancybox Branch: master Commit: 504a80a6d080 Files: 25 Total size: 418.6 KB Directory structure: gitextract_9bdlpyyz/ ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── .gitignore ├── README.md ├── bower.json ├── dist/ │ ├── jquery.fancybox.css │ └── jquery.fancybox.js ├── docs/ │ └── index.html ├── gulpfile.js ├── package.json └── src/ ├── css/ │ ├── core.css │ ├── fullscreen.css │ ├── share.css │ ├── slideshow.css │ └── thumbs.css └── js/ ├── core.js ├── fullscreen.js ├── guestures.js ├── hash.js ├── media.js ├── share.js ├── slideshow.js ├── thumbs.js └── wheel.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: Describe your question here. title: '' labels: '' assignees: '' --- How do I ... ================================================ FILE: .gitignore ================================================ node_modules/* ================================================ FILE: README.md ================================================ # Archived This repository has been archived and is now part of https://github.com/fancyapps/ui # Fancybox jQuery lightbox script for displaying images, videos and more. Touch enabled, responsive and fully customizable. ## Quick start 1\. Add latest jQuery and Fancybox files ```html ``` 2\. Create links ```html ``` 3\. Enjoy! ## License Fancybox is licensed under the [GPLv3](http://choosealicense.com/licenses/gpl-3.0) license for all open source applications. A commercial license is required for all commercial applications (including sites, themes and apps you plan to sell). [Read more about Fancybox license](http://fancyapps.com/). ================================================ FILE: bower.json ================================================ { "name": "fancybox", "description": "Touch enabled, responsive and fully customizable jQuery lightbox script", "keywords": [ "touch", "responsive", "lightbox", "fancybox", "gallery", "jQuery", "plugin" ], "homepage": "http://fancyapps.com/fancybox/", "license": "GPL-3.0", "moduleType": "globals", "main": [ "dist/jquery.fancybox.min.css", "dist/jquery.fancybox.min.js" ], "dependencies": { "jquery": ">=1.9.0" } } ================================================ FILE: dist/jquery.fancybox.css ================================================ body.compensate-for-scrollbar { overflow: hidden; } .fancybox-active { height: auto; } .fancybox-is-hidden { left: -9999px; margin: 0; position: absolute !important; top: -9999px; visibility: hidden; } .fancybox-container { -webkit-backface-visibility: hidden; height: 100%; left: 0; outline: none; position: fixed; -webkit-tap-highlight-color: transparent; top: 0; -ms-touch-action: manipulation; touch-action: manipulation; transform: translateZ(0); width: 100%; z-index: 99992; } .fancybox-container * { box-sizing: border-box; } .fancybox-outer, .fancybox-inner, .fancybox-bg, .fancybox-stage { bottom: 0; left: 0; position: absolute; right: 0; top: 0; } .fancybox-outer { -webkit-overflow-scrolling: touch; overflow-y: auto; } .fancybox-bg { background: rgb(30, 30, 30); opacity: 0; transition-duration: inherit; transition-property: opacity; transition-timing-function: cubic-bezier(.47, 0, .74, .71); } .fancybox-is-open .fancybox-bg { opacity: .9; transition-timing-function: cubic-bezier(.22, .61, .36, 1); } .fancybox-infobar, .fancybox-toolbar, .fancybox-caption, .fancybox-navigation .fancybox-button { direction: ltr; opacity: 0; position: absolute; transition: opacity .25s ease, visibility 0s ease .25s; visibility: hidden; z-index: 99997; } .fancybox-show-infobar .fancybox-infobar, .fancybox-show-toolbar .fancybox-toolbar, .fancybox-show-caption .fancybox-caption, .fancybox-show-nav .fancybox-navigation .fancybox-button { opacity: 1; transition: opacity .25s ease 0s, visibility 0s ease 0s; visibility: visible; } .fancybox-infobar { color: #ccc; font-size: 13px; -webkit-font-smoothing: subpixel-antialiased; height: 44px; left: 0; line-height: 44px; min-width: 44px; mix-blend-mode: difference; padding: 0 10px; pointer-events: none; top: 0; -webkit-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .fancybox-toolbar { right: 0; top: 0; } .fancybox-stage { direction: ltr; overflow: visible; transform: translateZ(0); z-index: 99994; } .fancybox-is-open .fancybox-stage { overflow: hidden; } .fancybox-slide { -webkit-backface-visibility: hidden; /* Using without prefix would break IE11 */ display: none; height: 100%; left: 0; outline: none; overflow: auto; -webkit-overflow-scrolling: touch; padding: 44px; position: absolute; text-align: center; top: 0; transition-property: transform, opacity; white-space: normal; width: 100%; z-index: 99994; } .fancybox-slide::before { content: ''; display: inline-block; font-size: 0; height: 100%; vertical-align: middle; width: 0; } .fancybox-is-sliding .fancybox-slide, .fancybox-slide--previous, .fancybox-slide--current, .fancybox-slide--next { display: block; } .fancybox-slide--image { overflow: hidden; padding: 44px 0; } .fancybox-slide--image::before { display: none; } .fancybox-slide--html { padding: 6px; } .fancybox-content { background: #fff; display: inline-block; margin: 0; max-width: 100%; overflow: auto; -webkit-overflow-scrolling: touch; padding: 44px; position: relative; text-align: left; vertical-align: middle; } .fancybox-slide--image .fancybox-content { animation-timing-function: cubic-bezier(.5, 0, .14, 1); -webkit-backface-visibility: hidden; background: transparent; background-repeat: no-repeat; background-size: 100% 100%; left: 0; max-width: none; overflow: visible; padding: 0; position: absolute; top: 0; -ms-transform-origin: top left; transform-origin: top left; transition-property: transform, opacity; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; z-index: 99995; } .fancybox-can-zoomOut .fancybox-content { cursor: zoom-out; } .fancybox-can-zoomIn .fancybox-content { cursor: zoom-in; } .fancybox-can-swipe .fancybox-content, .fancybox-can-pan .fancybox-content { cursor: -webkit-grab; cursor: grab; } .fancybox-is-grabbing .fancybox-content { cursor: -webkit-grabbing; cursor: grabbing; } .fancybox-container [data-selectable='true'] { cursor: text; } .fancybox-image, .fancybox-spaceball { background: transparent; border: 0; height: 100%; left: 0; margin: 0; max-height: none; max-width: none; padding: 0; position: absolute; top: 0; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; width: 100%; } .fancybox-spaceball { z-index: 1; } .fancybox-slide--video .fancybox-content, .fancybox-slide--map .fancybox-content, .fancybox-slide--pdf .fancybox-content, .fancybox-slide--iframe .fancybox-content { height: 100%; overflow: visible; padding: 0; width: 100%; } .fancybox-slide--video .fancybox-content { background: #000; } .fancybox-slide--map .fancybox-content { background: #e5e3df; } .fancybox-slide--iframe .fancybox-content { background: #fff; } .fancybox-video, .fancybox-iframe { background: transparent; border: 0; display: block; height: 100%; margin: 0; overflow: hidden; padding: 0; width: 100%; } /* Fix iOS */ .fancybox-iframe { left: 0; position: absolute; top: 0; } .fancybox-error { background: #fff; cursor: default; max-width: 400px; padding: 40px; width: 100%; } .fancybox-error p { color: #444; font-size: 16px; line-height: 20px; margin: 0; padding: 0; } /* Buttons */ .fancybox-button { background: rgba(30, 30, 30, .6); border: 0; border-radius: 0; box-shadow: none; cursor: pointer; display: inline-block; height: 44px; margin: 0; padding: 10px; position: relative; transition: color .2s; vertical-align: top; visibility: inherit; width: 44px; } .fancybox-button, .fancybox-button:visited, .fancybox-button:link { color: #ccc; } .fancybox-button:hover { color: #fff; } .fancybox-button:focus { outline: none; } .fancybox-button.fancybox-focus { outline: 1px dotted; } .fancybox-button[disabled], .fancybox-button[disabled]:hover { color: #888; cursor: default; outline: none; } /* Fix IE11 */ .fancybox-button div { height: 100%; } .fancybox-button svg { display: block; height: 100%; overflow: visible; position: relative; width: 100%; } .fancybox-button svg path { fill: currentColor; stroke-width: 0; } .fancybox-button--play svg:nth-child(2), .fancybox-button--fsenter svg:nth-child(2) { display: none; } .fancybox-button--pause svg:nth-child(1), .fancybox-button--fsexit svg:nth-child(1) { display: none; } .fancybox-progress { background: #ff5268; height: 2px; left: 0; position: absolute; right: 0; top: 0; -ms-transform: scaleX(0); transform: scaleX(0); -ms-transform-origin: 0; transform-origin: 0; transition-property: transform; transition-timing-function: linear; z-index: 99998; } /* Close button on the top right corner of html content */ .fancybox-close-small { background: transparent; border: 0; border-radius: 0; color: #ccc; cursor: pointer; opacity: .8; padding: 8px; position: absolute; right: -12px; top: -44px; z-index: 401; } .fancybox-close-small:hover { color: #fff; opacity: 1; } .fancybox-slide--html .fancybox-close-small { color: currentColor; padding: 10px; right: 0; top: 0; } .fancybox-slide--image.fancybox-is-scaling .fancybox-content { overflow: hidden; } .fancybox-is-scaling .fancybox-close-small, .fancybox-is-zoomable.fancybox-can-pan .fancybox-close-small { display: none; } /* Navigation arrows */ .fancybox-navigation .fancybox-button { background-clip: content-box; height: 100px; opacity: 0; position: absolute; top: calc(50% - 50px); width: 70px; } .fancybox-navigation .fancybox-button div { padding: 7px; } .fancybox-navigation .fancybox-button--arrow_left { left: 0; left: env(safe-area-inset-left); padding: 31px 26px 31px 6px; } .fancybox-navigation .fancybox-button--arrow_right { padding: 31px 6px 31px 26px; right: 0; right: env(safe-area-inset-right); } /* Caption */ .fancybox-caption { background: linear-gradient(to top, rgba(0, 0, 0, .85) 0%, rgba(0, 0, 0, .3) 50%, rgba(0, 0, 0, .15) 65%, rgba(0, 0, 0, .075) 75.5%, rgba(0, 0, 0, .037) 82.85%, rgba(0, 0, 0, .019) 88%, rgba(0, 0, 0, 0) 100%); bottom: 0; color: #eee; font-size: 14px; font-weight: 400; left: 0; line-height: 1.5; padding: 75px 44px 25px 44px; pointer-events: none; right: 0; text-align: center; z-index: 99996; } @supports (padding: max(0px)) { .fancybox-caption { padding: 75px max(44px, env(safe-area-inset-right)) max(25px, env(safe-area-inset-bottom)) max(44px, env(safe-area-inset-left)); } } .fancybox-caption--separate { margin-top: -50px; } .fancybox-caption__body { max-height: 50vh; overflow: auto; pointer-events: all; } .fancybox-caption a, .fancybox-caption a:link, .fancybox-caption a:visited { color: #ccc; text-decoration: none; } .fancybox-caption a:hover { color: #fff; text-decoration: underline; } /* Loading indicator */ .fancybox-loading { animation: fancybox-rotate 1s linear infinite; background: transparent; border: 4px solid #888; border-bottom-color: #fff; border-radius: 50%; height: 50px; left: 50%; margin: -25px 0 0 -25px; opacity: .7; padding: 0; position: absolute; top: 50%; width: 50px; z-index: 99999; } @keyframes fancybox-rotate { 100% { transform: rotate(360deg); } } /* Transition effects */ .fancybox-animated { transition-timing-function: cubic-bezier(0, 0, .25, 1); } /* transitionEffect: slide */ .fancybox-fx-slide.fancybox-slide--previous { opacity: 0; transform: translate3d(-100%, 0, 0); } .fancybox-fx-slide.fancybox-slide--next { opacity: 0; transform: translate3d(100%, 0, 0); } .fancybox-fx-slide.fancybox-slide--current { opacity: 1; transform: translate3d(0, 0, 0); } /* transitionEffect: fade */ .fancybox-fx-fade.fancybox-slide--previous, .fancybox-fx-fade.fancybox-slide--next { opacity: 0; transition-timing-function: cubic-bezier(.19, 1, .22, 1); } .fancybox-fx-fade.fancybox-slide--current { opacity: 1; } /* transitionEffect: zoom-in-out */ .fancybox-fx-zoom-in-out.fancybox-slide--previous { opacity: 0; transform: scale3d(1.5, 1.5, 1.5); } .fancybox-fx-zoom-in-out.fancybox-slide--next { opacity: 0; transform: scale3d(.5, .5, .5); } .fancybox-fx-zoom-in-out.fancybox-slide--current { opacity: 1; transform: scale3d(1, 1, 1); } /* transitionEffect: rotate */ .fancybox-fx-rotate.fancybox-slide--previous { opacity: 0; -ms-transform: rotate(-360deg); transform: rotate(-360deg); } .fancybox-fx-rotate.fancybox-slide--next { opacity: 0; -ms-transform: rotate(360deg); transform: rotate(360deg); } .fancybox-fx-rotate.fancybox-slide--current { opacity: 1; -ms-transform: rotate(0deg); transform: rotate(0deg); } /* transitionEffect: circular */ .fancybox-fx-circular.fancybox-slide--previous { opacity: 0; transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0); } .fancybox-fx-circular.fancybox-slide--next { opacity: 0; transform: scale3d(0, 0, 0) translate3d(100%, 0, 0); } .fancybox-fx-circular.fancybox-slide--current { opacity: 1; transform: scale3d(1, 1, 1) translate3d(0, 0, 0); } /* transitionEffect: tube */ .fancybox-fx-tube.fancybox-slide--previous { transform: translate3d(-100%, 0, 0) scale(.1) skew(-10deg); } .fancybox-fx-tube.fancybox-slide--next { transform: translate3d(100%, 0, 0) scale(.1) skew(10deg); } .fancybox-fx-tube.fancybox-slide--current { transform: translate3d(0, 0, 0) scale(1); } /* Styling for Small-Screen Devices */ @media all and (max-height: 576px) { .fancybox-slide { padding-left: 6px; padding-right: 6px; } .fancybox-slide--image { padding: 6px 0; } .fancybox-close-small { right: -6px; } .fancybox-slide--image .fancybox-close-small { background: #4e4e4e; color: #f2f4f6; height: 36px; opacity: 1; padding: 6px; right: 0; top: 0; width: 36px; } .fancybox-caption { padding-left: 12px; padding-right: 12px; } @supports (padding: max(0px)) { .fancybox-caption { padding-left: max(12px, env(safe-area-inset-left)); padding-right: max(12px, env(safe-area-inset-right)); } } } /* Share */ .fancybox-share { background: #f4f4f4; border-radius: 3px; max-width: 90%; padding: 30px; text-align: center; } .fancybox-share h1 { color: #222; font-size: 35px; font-weight: 700; margin: 0 0 20px 0; } .fancybox-share p { margin: 0; padding: 0; } .fancybox-share__button { border: 0; border-radius: 3px; display: inline-block; font-size: 14px; font-weight: 700; line-height: 40px; margin: 0 5px 10px 5px; min-width: 130px; padding: 0 15px; text-decoration: none; transition: all .2s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; white-space: nowrap; } .fancybox-share__button:visited, .fancybox-share__button:link { color: #fff; } .fancybox-share__button:hover { text-decoration: none; } .fancybox-share__button--fb { background: #3b5998; } .fancybox-share__button--fb:hover { background: #344e86; } .fancybox-share__button--pt { background: #bd081d; } .fancybox-share__button--pt:hover { background: #aa0719; } .fancybox-share__button--tw { background: #1da1f2; } .fancybox-share__button--tw:hover { background: #0d95e8; } .fancybox-share__button svg { height: 25px; margin-right: 7px; position: relative; top: -1px; vertical-align: middle; width: 25px; } .fancybox-share__button svg path { fill: #fff; } .fancybox-share__input { background: transparent; border: 0; border-bottom: 1px solid #d7d7d7; border-radius: 0; color: #5d5b5b; font-size: 14px; margin: 10px 0 0 0; outline: none; padding: 10px 15px; width: 100%; } /* Thumbs */ .fancybox-thumbs { background: #ddd; bottom: 0; display: none; margin: 0; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; padding: 2px 2px 4px 2px; position: absolute; right: 0; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); top: 0; width: 212px; z-index: 99995; } .fancybox-thumbs-x { overflow-x: auto; overflow-y: hidden; } .fancybox-show-thumbs .fancybox-thumbs { display: block; } .fancybox-show-thumbs .fancybox-inner { right: 212px; } .fancybox-thumbs__list { font-size: 0; height: 100%; list-style: none; margin: 0; overflow-x: hidden; overflow-y: auto; padding: 0; position: absolute; position: relative; white-space: nowrap; width: 100%; } .fancybox-thumbs-x .fancybox-thumbs__list { overflow: hidden; } .fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar { width: 7px; } .fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-track { background: #fff; border-radius: 10px; box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); } .fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 10px; } .fancybox-thumbs__list a { -webkit-backface-visibility: hidden; backface-visibility: hidden; background-color: rgba(0, 0, 0, .1); background-position: center center; background-repeat: no-repeat; background-size: cover; cursor: pointer; float: left; height: 75px; margin: 2px; max-height: calc(100% - 8px); max-width: calc(50% - 4px); outline: none; overflow: hidden; padding: 0; position: relative; -webkit-tap-highlight-color: transparent; width: 100px; } .fancybox-thumbs__list a::before { border: 6px solid #ff5268; bottom: 0; content: ''; left: 0; opacity: 0; position: absolute; right: 0; top: 0; transition: all .2s cubic-bezier(.25, .46, .45, .94); z-index: 99991; } .fancybox-thumbs__list a:focus::before { opacity: .5; } .fancybox-thumbs__list a.fancybox-thumbs-active::before { opacity: 1; } /* Styling for Small-Screen Devices */ @media all and (max-width: 576px) { .fancybox-thumbs { width: 110px; } .fancybox-show-thumbs .fancybox-inner { right: 110px; } .fancybox-thumbs__list a { max-width: calc(100% - 10px); } } ================================================ FILE: dist/jquery.fancybox.js ================================================ // ================================================== // fancyBox v3.5.7 // // Licensed GPLv3 for open source use // or fancyBox Commercial License for commercial use // // http://fancyapps.com/fancybox/ // Copyright 2019 fancyApps // // ================================================== (function (window, document, $, undefined) { "use strict"; window.console = window.console || { info: function (stuff) {} }; // If there's no jQuery, fancyBox can't work // ========================================= if (!$) { return; } // Check if fancyBox is already initialized // ======================================== if ($.fn.fancybox) { console.info("fancyBox already initialized"); return; } // Private default settings // ======================== var defaults = { // Close existing modals // Set this to false if you do not need to stack multiple instances closeExisting: false, // Enable infinite gallery navigation loop: false, // Horizontal space between slides gutter: 50, // Enable keyboard navigation keyboard: true, // Should allow caption to overlap the content preventCaptionOverlap: true, // Should display navigation arrows at the screen edges arrows: true, // Should display counter at the top left corner infobar: true, // Should display close button (using `btnTpl.smallBtn` template) over the content // Can be true, false, "auto" // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items smallBtn: "auto", // Should display toolbar (buttons at the top) // Can be true, false, "auto" // If "auto" - will be automatically hidden if "smallBtn" is enabled toolbar: "auto", // What buttons should appear in the top right corner. // Buttons will be created using templates from `btnTpl` option // and they will be placed into toolbar (class="fancybox-toolbar"` element) buttons: [ "zoom", //"share", "slideShow", //"fullScreen", //"download", "thumbs", "close" ], // Detect "idle" time in seconds idleTime: 3, // Disable right-click and use simple image protection for images protect: false, // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc modal: false, image: { // Wait for images to load before displaying // true - wait for image to load and then display; // false - display thumbnail and load the full-sized image over top, // requires predefined image dimensions (`data-width` and `data-height` attributes) preload: false }, ajax: { // Object containing settings for ajax request settings: { // This helps to indicate that request comes from the modal // Feel free to change naming data: { fancybox: true } } }, iframe: { // Iframe template tpl: '', // Preload iframe before displaying it // This allows to calculate iframe content width and height // (note: Due to "Same Origin Policy", you can't get cross domain data). preload: true, // Custom CSS styling for iframe wrapping element // You can use this to set custom iframe dimensions css: {}, // Iframe tag attributes attr: { scrolling: "auto" } }, // For HTML5 video only video: { tpl: '", format: "", // custom video format autoStart: true }, // Default content type if cannot be detected automatically defaultType: "image", // Open/close animation type // Possible values: // false - disable // "zoom" - zoom images from/to thumbnail // "fade" // "zoom-in-out" // animationEffect: "zoom", // Duration in ms for open/close animation animationDuration: 366, // Should image change opacity while zooming // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios zoomOpacity: "auto", // Transition effect between slides // // Possible values: // false - disable // "fade' // "slide' // "circular' // "tube' // "zoom-in-out' // "rotate' // transitionEffect: "fade", // Duration in ms for transition animation transitionDuration: 366, // Custom CSS class for slide element slideClass: "", // Custom CSS class for layout baseClass: "", // Base template for layout baseTpl: '", // Loading indicator template spinnerTpl: '
', // Error message template errorTpl: '

{{ERROR}}

', btnTpl: { download: '' + '' + "", zoom: '", close: '", // Arrows arrowLeft: '", arrowRight: '", // This small close button will be appended to your html/inline/ajax content by default, // if "smallBtn" option is not set to false smallBtn: '" }, // Container is injected into this element parentEl: "body", // Hide browser vertical scrollbars; use at your own risk hideScrollbar: true, // Focus handling // ============== // Try to focus on the first focusable element after opening autoFocus: true, // Put focus back to active element after closing backFocus: true, // Do not let user to focus on element outside modal content trapFocus: true, // Module specific options // ======================= fullScreen: { autoStart: false }, // Set `touch: false` to disable panning/swiping touch: { vertical: true, // Allow to drag content vertically momentum: true // Continue movement after releasing mouse/touch when panning }, // Hash value when initializing manually, // set `false` to disable hash change hash: null, // Customize or add new media types // Example: /* media : { youtube : { params : { autoplay : 0 } } } */ media: {}, slideShow: { autoStart: false, speed: 3000 }, thumbs: { autoStart: false, // Display thumbnails on opening hideOnClose: true, // Hide thumbnail grid when closing animation starts parentEl: ".fancybox-container", // Container is injected into this element axis: "y" // Vertical (y) or horizontal (x) scrolling }, // Use mousewheel to navigate gallery // If 'auto' - enabled for images only wheel: "auto", // Callbacks //========== // See Documentation/API/Events for more information // Example: /* afterShow: function( instance, current ) { console.info( 'Clicked element:' ); console.info( current.opts.$orig ); } */ onInit: $.noop, // When instance has been initialized beforeLoad: $.noop, // Before the content of a slide is being loaded afterLoad: $.noop, // When the content of a slide is done loading beforeShow: $.noop, // Before open animation starts afterShow: $.noop, // When content is done loading and animating beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close. afterClose: $.noop, // After instance has been closed onActivate: $.noop, // When instance is brought to front onDeactivate: $.noop, // When other instance has been activated // Interaction // =========== // Use options below to customize taken action when user clicks or double clicks on the fancyBox area, // each option can be string or method that returns value. // // Possible values: // "close" - close instance // "next" - move to next gallery item // "nextOrClose" - move to next gallery item or close if gallery has only one item // "toggleControls" - show/hide controls // "zoom" - zoom image (if loaded) // false - do nothing // Clicked on the content clickContent: function (current, event) { return current.type === "image" ? "zoom" : false; }, // Clicked on the slide clickSlide: "close", // Clicked on the background (backdrop) element; // if you have not changed the layout, then most likely you need to use `clickSlide` option clickOutside: "close", // Same as previous two, but for double click dblclickContent: false, dblclickSlide: false, dblclickOutside: false, // Custom options when mobile device is detected // ============================================= mobile: { preventCaptionOverlap: false, idleTime: false, clickContent: function (current, event) { return current.type === "image" ? "toggleControls" : false; }, clickSlide: function (current, event) { return current.type === "image" ? "toggleControls" : "close"; }, dblclickContent: function (current, event) { return current.type === "image" ? "zoom" : false; }, dblclickSlide: function (current, event) { return current.type === "image" ? "zoom" : false; } }, // Internationalization // ==================== lang: "en", i18n: { en: { CLOSE: "Close", NEXT: "Next", PREV: "Previous", ERROR: "The requested content cannot be loaded.
Please try again later.", PLAY_START: "Start slideshow", PLAY_STOP: "Pause slideshow", FULL_SCREEN: "Full screen", THUMBS: "Thumbnails", DOWNLOAD: "Download", SHARE: "Share", ZOOM: "Zoom" }, de: { CLOSE: "Schließen", NEXT: "Weiter", PREV: "Zurück", ERROR: "Die angeforderten Daten konnten nicht geladen werden.
Bitte versuchen Sie es später nochmal.", PLAY_START: "Diaschau starten", PLAY_STOP: "Diaschau beenden", FULL_SCREEN: "Vollbild", THUMBS: "Vorschaubilder", DOWNLOAD: "Herunterladen", SHARE: "Teilen", ZOOM: "Vergrößern" } } }; // Few useful variables and methods // ================================ var $W = $(window); var $D = $(document); var called = 0; // Check if an object is a jQuery object and not a native JavaScript object // ======================================================================== var isQuery = function (obj) { return obj && obj.hasOwnProperty && obj instanceof $; }; // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" // =============================================================================== var requestAFrame = (function () { return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || // if all else fails, use setTimeout function (callback) { return window.setTimeout(callback, 1000 / 60); } ); })(); var cancelAFrame = (function () { return ( window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function (id) { window.clearTimeout(id); } ); })(); // Detect the supported transition-end event property name // ======================================================= var transitionEnd = (function () { var el = document.createElement("fakeelement"), t; var transitions = { transition: "transitionend", OTransition: "oTransitionEnd", MozTransition: "transitionend", WebkitTransition: "webkitTransitionEnd" }; for (t in transitions) { if (el.style[t] !== undefined) { return transitions[t]; } } return "transitionend"; })(); // Force redraw on an element. // This helps in cases where the browser doesn't redraw an updated element properly // ================================================================================ var forceRedraw = function ($el) { return $el && $el.length && $el[0].offsetHeight; }; // Exclude array (`buttons`) options from deep merging // =================================================== var mergeOpts = function (opts1, opts2) { var rez = $.extend(true, {}, opts1, opts2); $.each(opts2, function (key, value) { if ($.isArray(value)) { rez[key] = value; } }); return rez; }; // How much of an element is visible in viewport // ============================================= var inViewport = function (elem) { var elemCenter, rez; if (!elem || elem.ownerDocument !== document) { return false; } $(".fancybox-container").css("pointer-events", "none"); elemCenter = { x: elem.getBoundingClientRect().left + elem.offsetWidth / 2, y: elem.getBoundingClientRect().top + elem.offsetHeight / 2 }; rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem; $(".fancybox-container").css("pointer-events", ""); return rez; }; // Class definition // ================ var FancyBox = function (content, opts, index) { var self = this; self.opts = mergeOpts({ index: index }, $.fancybox.defaults); if ($.isPlainObject(opts)) { self.opts = mergeOpts(self.opts, opts); } if ($.fancybox.isMobile) { self.opts = mergeOpts(self.opts, self.opts.mobile); } self.id = self.opts.id || ++called; self.currIndex = parseInt(self.opts.index, 10) || 0; self.prevIndex = null; self.prevPos = null; self.currPos = 0; self.firstRun = true; // All group items self.group = []; // Existing slides (for current, next and previous gallery items) self.slides = {}; // Create group elements self.addContent(content); if (!self.group.length) { return; } self.init(); }; $.extend(FancyBox.prototype, { // Create DOM structure // ==================== init: function () { var self = this, firstItem = self.group[self.currIndex], firstItemOpts = firstItem.opts, $container, buttonStr; if (firstItemOpts.closeExisting) { $.fancybox.close(true); } // Hide scrollbars // =============== $("body").addClass("fancybox-active"); if ( !$.fancybox.getInstance() && firstItemOpts.hideScrollbar !== false && !$.fancybox.isMobile && document.body.scrollHeight > window.innerHeight ) { $("head").append( '" ); $("body").addClass("compensate-for-scrollbar"); } // Build html markup and set references // ==================================== // Build html code for buttons and insert into main template buttonStr = ""; $.each(firstItemOpts.buttons, function (index, value) { buttonStr += firstItemOpts.btnTpl[value] || ""; }); // Create markup from base template, it will be initially hidden to // avoid unnecessary work like painting while initializing is not complete $container = $( self.translate( self, firstItemOpts.baseTpl .replace("{{buttons}}", buttonStr) .replace("{{arrows}}", firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight) ) ) .attr("id", "fancybox-container-" + self.id) .addClass(firstItemOpts.baseClass) .data("FancyBox", self) .appendTo(firstItemOpts.parentEl); // Create object holding references to jQuery wrapped nodes self.$refs = { container: $container }; ["bg", "inner", "infobar", "toolbar", "stage", "caption", "navigation"].forEach(function (item) { self.$refs[item] = $container.find(".fancybox-" + item); }); self.trigger("onInit"); // Enable events, deactive previous instances self.activate(); // Build slides, load and reveal content self.jumpTo(self.currIndex); }, // Simple i18n support - replaces object keys found in template // with corresponding values // ============================================================ translate: function (obj, str) { var arr = obj.opts.i18n[obj.opts.lang] || obj.opts.i18n.en; return str.replace(/\{\{(\w+)\}\}/g, function (match, n) { return arr[n] === undefined ? match : arr[n]; }); }, // Populate current group with fresh content // Check if each object has valid type and content // =============================================== addContent: function (content) { var self = this, items = $.makeArray(content), thumbs; $.each(items, function (i, item) { var obj = {}, opts = {}, $item, type, found, src, srcParts; // Step 1 - Make sure we have an object // ==================================== if ($.isPlainObject(item)) { // We probably have manual usage here, something like // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) obj = item; opts = item.opts || item; } else if ($.type(item) === "object" && $(item).length) { // Here we probably have jQuery collection returned by some selector $item = $(item); // Support attributes like `data-options='{"touch" : false}'` and `data-touch='false'` opts = $item.data() || {}; opts = $.extend(true, {}, opts, opts.options); // Here we store clicked element opts.$orig = $item; obj.src = self.opts.src || opts.src || $item.attr("href"); // Assume that simple syntax is used, for example: // `$.fancybox.open( $("#test"), {} );` if (!obj.type && !obj.src) { obj.type = "inline"; obj.src = item; } } else { // Assume we have a simple html code, for example: // $.fancybox.open( '

Hi!

' ); obj = { type: "html", src: item + "" }; } // Each gallery object has full collection of options obj.opts = $.extend(true, {}, self.opts, opts); // Do not merge buttons array if ($.isArray(opts.buttons)) { obj.opts.buttons = opts.buttons; } if ($.fancybox.isMobile && obj.opts.mobile) { obj.opts = mergeOpts(obj.opts, obj.opts.mobile); } // Step 2 - Make sure we have content type, if not - try to guess // ============================================================== type = obj.type || obj.opts.type; src = obj.src || ""; if (!type && src) { if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) { type = "video"; if (!obj.opts.video.format) { obj.opts.video.format = "video/" + (found[1] === "ogv" ? "ogg" : found[1]); } } else if (src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) { type = "image"; } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) { type = "iframe"; obj = $.extend(true, obj, { contentType: "pdf", opts: { iframe: { preload: false } } }); } else if (src.charAt(0) === "#") { type = "inline"; } } if (type) { obj.type = type; } else { self.trigger("objectNeedsType", obj); } if (!obj.contentType) { obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type; } // Step 3 - Some adjustments // ========================= obj.index = self.group.length; if (obj.opts.smallBtn == "auto") { obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1; } if (obj.opts.toolbar === "auto") { obj.opts.toolbar = !obj.opts.smallBtn; } // Find thumbnail image, check if exists and if is in the viewport obj.$thumb = obj.opts.$thumb || null; if (obj.opts.$trigger && obj.index === self.opts.index) { obj.$thumb = obj.opts.$trigger.find("img:first"); if (obj.$thumb.length) { obj.opts.$orig = obj.opts.$trigger; } } if (!(obj.$thumb && obj.$thumb.length) && obj.opts.$orig) { obj.$thumb = obj.opts.$orig.find("img:first"); } if (obj.$thumb && !obj.$thumb.length) { obj.$thumb = null; } obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null); // "caption" is a "special" option, it can be used to customize caption per gallery item if ($.type(obj.opts.caption) === "function") { obj.opts.caption = obj.opts.caption.apply(item, [self, obj]); } if ($.type(self.opts.caption) === "function") { obj.opts.caption = self.opts.caption.apply(item, [self, obj]); } // Make sure we have caption as a string or jQuery object if (!(obj.opts.caption instanceof $)) { obj.opts.caption = obj.opts.caption === undefined ? "" : obj.opts.caption + ""; } // Check if url contains "filter" used to filter the content // Example: "ajax.html #something" if (obj.type === "ajax") { srcParts = src.split(/\s+/, 2); if (srcParts.length > 1) { obj.src = srcParts.shift(); obj.opts.filter = srcParts.shift(); } } // Hide all buttons and disable interactivity for modal items if (obj.opts.modal) { obj.opts = $.extend(true, obj.opts, { trapFocus: true, // Remove buttons infobar: 0, toolbar: 0, smallBtn: 0, // Disable keyboard navigation keyboard: 0, // Disable some modules slideShow: 0, fullScreen: 0, thumbs: 0, touch: 0, // Disable click event handlers clickContent: false, clickSlide: false, clickOutside: false, dblclickContent: false, dblclickSlide: false, dblclickOutside: false }); } // Step 4 - Add processed object to group // ====================================== self.group.push(obj); }); // Update controls if gallery is already opened if (Object.keys(self.slides).length) { self.updateControls(); // Update thumbnails, if needed thumbs = self.Thumbs; if (thumbs && thumbs.isActive) { thumbs.create(); thumbs.focus(); } } }, // Attach an event handler functions for: // - navigation buttons // - browser scrolling, resizing; // - focusing // - keyboard // - detecting inactivity // ====================================== addEvents: function () { var self = this; self.removeEvents(); // Make navigation elements clickable // ================================== self.$refs.container .on("click.fb-close", "[data-fancybox-close]", function (e) { e.stopPropagation(); e.preventDefault(); self.close(e); }) .on("touchstart.fb-prev click.fb-prev", "[data-fancybox-prev]", function (e) { e.stopPropagation(); e.preventDefault(); self.previous(); }) .on("touchstart.fb-next click.fb-next", "[data-fancybox-next]", function (e) { e.stopPropagation(); e.preventDefault(); self.next(); }) .on("click.fb", "[data-fancybox-zoom]", function (e) { // Click handler for zoom button self[self.isScaledDown() ? "scaleToActual" : "scaleToFit"](); }); // Handle page scrolling and browser resizing // ========================================== $W.on("orientationchange.fb resize.fb", function (e) { if (e && e.originalEvent && e.originalEvent.type === "resize") { if (self.requestId) { cancelAFrame(self.requestId); } self.requestId = requestAFrame(function () { self.update(e); }); } else { if (self.current && self.current.type === "iframe") { self.$refs.stage.hide(); } setTimeout( function () { self.$refs.stage.show(); self.update(e); }, $.fancybox.isMobile ? 600 : 250 ); } }); $D.on("keydown.fb", function (e) { var instance = $.fancybox ? $.fancybox.getInstance() : null, current = instance.current, keycode = e.keyCode || e.which; // Trap keyboard focus inside of the modal // ======================================= if (keycode == 9) { if (current.opts.trapFocus) { self.focus(e); } return; } // Enable keyboard navigation // ========================== if (!current.opts.keyboard || e.ctrlKey || e.altKey || e.shiftKey || $(e.target).is("input,textarea,video,audio,select")) { return; } // Backspace and Esc keys if (keycode === 8 || keycode === 27) { e.preventDefault(); self.close(e); return; } // Left arrow and Up arrow if (keycode === 37 || keycode === 38) { e.preventDefault(); self.previous(); return; } // Righ arrow and Down arrow if (keycode === 39 || keycode === 40) { e.preventDefault(); self.next(); return; } self.trigger("afterKeydown", e, keycode); }); // Hide controls after some inactivity period if (self.group[self.currIndex].opts.idleTime) { self.idleSecondsCounter = 0; $D.on( "mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle", function (e) { self.idleSecondsCounter = 0; if (self.isIdle) { self.showControls(); } self.isIdle = false; } ); self.idleInterval = window.setInterval(function () { self.idleSecondsCounter++; if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) { self.isIdle = true; self.idleSecondsCounter = 0; self.hideControls(); } }, 1000); } }, // Remove events added by the core // =============================== removeEvents: function () { var self = this; $W.off("orientationchange.fb resize.fb"); $D.off("keydown.fb .fb-idle"); this.$refs.container.off(".fb-close .fb-prev .fb-next"); if (self.idleInterval) { window.clearInterval(self.idleInterval); self.idleInterval = null; } }, // Change to previous gallery item // =============================== previous: function (duration) { return this.jumpTo(this.currPos - 1, duration); }, // Change to next gallery item // =========================== next: function (duration) { return this.jumpTo(this.currPos + 1, duration); }, // Switch to selected gallery item // =============================== jumpTo: function (pos, duration) { var self = this, groupLen = self.group.length, firstRun, isMoved, loop, current, previous, slidePos, stagePos, prop, diff; if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) { return; } // Should loop? pos = parseInt(pos, 10); loop = self.current ? self.current.opts.loop : self.opts.loop; if (!loop && (pos < 0 || pos >= groupLen)) { return false; } // Check if opening for the first time; this helps to speed things up firstRun = self.firstRun = !Object.keys(self.slides).length; // Create slides previous = self.current; self.prevIndex = self.currIndex; self.prevPos = self.currPos; current = self.createSlide(pos); if (groupLen > 1) { if (loop || current.index < groupLen - 1) { self.createSlide(pos + 1); } if (loop || current.index > 0) { self.createSlide(pos - 1); } } self.current = current; self.currIndex = current.index; self.currPos = current.pos; self.trigger("beforeShow", firstRun); self.updateControls(); // Validate duration length current.forcedDuration = undefined; if ($.isNumeric(duration)) { current.forcedDuration = duration; } else { duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"]; } duration = parseInt(duration, 10); // Check if user has swiped the slides or if still animating isMoved = self.isMoved(current); // Make sure current slide is visible current.$slide.addClass("fancybox-slide--current"); // Fresh start - reveal container, current slide and start loading content if (firstRun) { if (current.opts.animationEffect && duration) { self.$refs.container.css("transition-duration", duration + "ms"); } self.$refs.container.addClass("fancybox-is-open").trigger("focus"); // Attempt to load content into slide // This will later call `afterLoad` -> `revealContent` self.loadSlide(current); self.preload("image"); return; } // Get actual slide/stage positions (before cleaning up) slidePos = $.fancybox.getTranslate(previous.$slide); stagePos = $.fancybox.getTranslate(self.$refs.stage); // Clean up all slides $.each(self.slides, function (index, slide) { $.fancybox.stop(slide.$slide, true); }); if (previous.pos !== current.pos) { previous.isComplete = false; } previous.$slide.removeClass("fancybox-slide--complete fancybox-slide--current"); // If slides are out of place, then animate them to correct position if (isMoved) { // Calculate horizontal swipe distance diff = slidePos.left - (previous.pos * slidePos.width + previous.pos * previous.opts.gutter); $.each(self.slides, function (index, slide) { slide.$slide.removeClass("fancybox-animated").removeClass(function (index, className) { return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" "); }); // Make sure that each slide is in equal distance // This is mostly needed for freshly added slides, because they are not yet positioned var leftPos = slide.pos * slidePos.width + slide.pos * slide.opts.gutter; $.fancybox.setTranslate(slide.$slide, { top: 0, left: leftPos - stagePos.left + diff }); if (slide.pos !== current.pos) { slide.$slide.addClass("fancybox-slide--" + (slide.pos > current.pos ? "next" : "previous")); } // Redraw to make sure that transition will start forceRedraw(slide.$slide); // Animate the slide $.fancybox.animate( slide.$slide, { top: 0, left: (slide.pos - current.pos) * slidePos.width + (slide.pos - current.pos) * slide.opts.gutter }, duration, function () { slide.$slide .css({ transform: "", opacity: "" }) .removeClass("fancybox-slide--next fancybox-slide--previous"); if (slide.pos === self.currPos) { self.complete(); } } ); }); } else if (duration && current.opts.transitionEffect) { // Set transition effect for previously active slide prop = "fancybox-animated fancybox-fx-" + current.opts.transitionEffect; previous.$slide.addClass("fancybox-slide--" + (previous.pos > current.pos ? "next" : "previous")); $.fancybox.animate( previous.$slide, prop, duration, function () { previous.$slide.removeClass(prop).removeClass("fancybox-slide--next fancybox-slide--previous"); }, false ); } if (current.isLoaded) { self.revealContent(current); } else { self.loadSlide(current); } self.preload("image"); }, // Create new "slide" element // These are gallery items that are actually added to DOM // ======================================================= createSlide: function (pos) { var self = this, $slide, index; index = pos % self.group.length; index = index < 0 ? self.group.length + index : index; if (!self.slides[pos] && self.group[index]) { $slide = $('
').appendTo(self.$refs.stage); self.slides[pos] = $.extend(true, {}, self.group[index], { pos: pos, $slide: $slide, isLoaded: false }); self.updateSlide(self.slides[pos]); } return self.slides[pos]; }, // Scale image to the actual size of the image; // x and y values should be relative to the slide // ============================================== scaleToActual: function (x, y, duration) { var self = this, current = self.current, $content = current.$content, canvasWidth = $.fancybox.getTranslate(current.$slide).width, canvasHeight = $.fancybox.getTranslate(current.$slide).height, newImgWidth = current.width, newImgHeight = current.height, imgPos, posX, posY, scaleX, scaleY; if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { return; } self.isAnimating = true; $.fancybox.stop($content); x = x === undefined ? canvasWidth * 0.5 : x; y = y === undefined ? canvasHeight * 0.5 : y; imgPos = $.fancybox.getTranslate($content); imgPos.top -= $.fancybox.getTranslate(current.$slide).top; imgPos.left -= $.fancybox.getTranslate(current.$slide).left; scaleX = newImgWidth / imgPos.width; scaleY = newImgHeight / imgPos.height; // Get center position for original image posX = canvasWidth * 0.5 - newImgWidth * 0.5; posY = canvasHeight * 0.5 - newImgHeight * 0.5; // Make sure image does not move away from edges if (newImgWidth > canvasWidth) { posX = imgPos.left * scaleX - (x * scaleX - x); if (posX > 0) { posX = 0; } if (posX < canvasWidth - newImgWidth) { posX = canvasWidth - newImgWidth; } } if (newImgHeight > canvasHeight) { posY = imgPos.top * scaleY - (y * scaleY - y); if (posY > 0) { posY = 0; } if (posY < canvasHeight - newImgHeight) { posY = canvasHeight - newImgHeight; } } self.updateCursor(newImgWidth, newImgHeight); $.fancybox.animate( $content, { top: posY, left: posX, scaleX: scaleX, scaleY: scaleY }, duration || 366, function () { self.isAnimating = false; } ); // Stop slideshow if (self.SlideShow && self.SlideShow.isActive) { self.SlideShow.stop(); } }, // Scale image to fit inside parent element // ======================================== scaleToFit: function (duration) { var self = this, current = self.current, $content = current.$content, end; if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { return; } self.isAnimating = true; $.fancybox.stop($content); end = self.getFitPos(current); self.updateCursor(end.width, end.height); $.fancybox.animate( $content, { top: end.top, left: end.left, scaleX: end.width / $content.width(), scaleY: end.height / $content.height() }, duration || 366, function () { self.isAnimating = false; } ); }, // Calculate image size to fit inside viewport // =========================================== getFitPos: function (slide) { var self = this, $content = slide.$content, $slide = slide.$slide, width = slide.width || slide.opts.width, height = slide.height || slide.opts.height, maxWidth, maxHeight, minRatio, aspectRatio, rez = {}; if (!slide.isLoaded || !$content || !$content.length) { return false; } maxWidth = $.fancybox.getTranslate(self.$refs.stage).width; maxHeight = $.fancybox.getTranslate(self.$refs.stage).height; maxWidth -= parseFloat($slide.css("paddingLeft")) + parseFloat($slide.css("paddingRight")) + parseFloat($content.css("marginLeft")) + parseFloat($content.css("marginRight")); maxHeight -= parseFloat($slide.css("paddingTop")) + parseFloat($slide.css("paddingBottom")) + parseFloat($content.css("marginTop")) + parseFloat($content.css("marginBottom")); if (!width || !height) { width = maxWidth; height = maxHeight; } minRatio = Math.min(1, maxWidth / width, maxHeight / height); width = minRatio * width; height = minRatio * height; // Adjust width/height to precisely fit into container if (width > maxWidth - 0.5) { width = maxWidth; } if (height > maxHeight - 0.5) { height = maxHeight; } if (slide.type === "image") { rez.top = Math.floor((maxHeight - height) * 0.5) + parseFloat($slide.css("paddingTop")); rez.left = Math.floor((maxWidth - width) * 0.5) + parseFloat($slide.css("paddingLeft")); } else if (slide.contentType === "video") { // Force aspect ratio for the video // "I say the whole world must learn of our peaceful ways… by force!" aspectRatio = slide.opts.width && slide.opts.height ? width / height : slide.opts.ratio || 16 / 9; if (height > width / aspectRatio) { height = width / aspectRatio; } else if (width > height * aspectRatio) { width = height * aspectRatio; } } rez.width = width; rez.height = height; return rez; }, // Update content size and position for all slides // ============================================== update: function (e) { var self = this; $.each(self.slides, function (key, slide) { self.updateSlide(slide, e); }); }, // Update slide content position and size // ====================================== updateSlide: function (slide, e) { var self = this, $content = slide && slide.$content, width = slide.width || slide.opts.width, height = slide.height || slide.opts.height, $slide = slide.$slide; // First, prevent caption overlap, if needed self.adjustCaption(slide); // Then resize content to fit inside the slide if ($content && (width || height || slide.contentType === "video") && !slide.hasError) { $.fancybox.stop($content); $.fancybox.setTranslate($content, self.getFitPos(slide)); if (slide.pos === self.currPos) { self.isAnimating = false; self.updateCursor(); } } // Then some adjustments self.adjustLayout(slide); if ($slide.length) { $slide.trigger("refresh"); if (slide.pos === self.currPos) { self.$refs.toolbar .add(self.$refs.navigation.find(".fancybox-button--arrow_right")) .toggleClass("compensate-for-scrollbar", $slide.get(0).scrollHeight > $slide.get(0).clientHeight); } } self.trigger("onUpdate", slide, e); }, // Horizontally center slide // ========================= centerSlide: function (duration) { var self = this, current = self.current, $slide = current.$slide; if (self.isClosing || !current) { return; } $slide.siblings().css({ transform: "", opacity: "" }); $slide .parent() .children() .removeClass("fancybox-slide--previous fancybox-slide--next"); $.fancybox.animate( $slide, { top: 0, left: 0, opacity: 1 }, duration === undefined ? 0 : duration, function () { // Clean up $slide.css({ transform: "", opacity: "" }); if (!current.isComplete) { self.complete(); } }, false ); }, // Check if current slide is moved (swiped) // ======================================== isMoved: function (slide) { var current = slide || this.current, slidePos, stagePos; if (!current) { return false; } stagePos = $.fancybox.getTranslate(this.$refs.stage); slidePos = $.fancybox.getTranslate(current.$slide); return ( !current.$slide.hasClass("fancybox-animated") && (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5) ); }, // Update cursor style depending if content can be zoomed // ====================================================== updateCursor: function (nextWidth, nextHeight) { var self = this, current = self.current, $container = self.$refs.container, canPan, isZoomable; if (!current || self.isClosing || !self.Guestures) { return; } $container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan"); canPan = self.canPan(nextWidth, nextHeight); isZoomable = canPan ? true : self.isZoomable(); $container.toggleClass("fancybox-is-zoomable", isZoomable); $("[data-fancybox-zoom]").prop("disabled", !isZoomable); if (canPan) { $container.addClass("fancybox-can-pan"); } else if ( isZoomable && (current.opts.clickContent === "zoom" || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) == "zoom")) ) { $container.addClass("fancybox-can-zoomIn"); } else if (current.opts.touch && (current.opts.touch.vertical || self.group.length > 1) && current.contentType !== "video") { $container.addClass("fancybox-can-swipe"); } }, // Check if current slide is zoomable // ================================== isZoomable: function () { var self = this, current = self.current, fitPos; // Assume that slide is zoomable if: // - image is still loading // - actual size of the image is smaller than available area if (current && !self.isClosing && current.type === "image" && !current.hasError) { if (!current.isLoaded) { return true; } fitPos = self.getFitPos(current); if (fitPos && (current.width > fitPos.width || current.height > fitPos.height)) { return true; } } return false; }, // Check if current image dimensions are smaller than actual // ========================================================= isScaledDown: function (nextWidth, nextHeight) { var self = this, rez = false, current = self.current, $content = current.$content; if (nextWidth !== undefined && nextHeight !== undefined) { rez = nextWidth < current.width && nextHeight < current.height; } else if ($content) { rez = $.fancybox.getTranslate($content); rez = rez.width < current.width && rez.height < current.height; } return rez; }, // Check if image dimensions exceed parent element // =============================================== canPan: function (nextWidth, nextHeight) { var self = this, current = self.current, pos = null, rez = false; if (current.type === "image" && (current.isComplete || (nextWidth && nextHeight)) && !current.hasError) { rez = self.getFitPos(current); if (nextWidth !== undefined && nextHeight !== undefined) { pos = { width: nextWidth, height: nextHeight }; } else if (current.isComplete) { pos = $.fancybox.getTranslate(current.$content); } if (pos && rez) { rez = Math.abs(pos.width - rez.width) > 1.5 || Math.abs(pos.height - rez.height) > 1.5; } } return rez; }, // Load content into the slide // =========================== loadSlide: function (slide) { var self = this, type, $slide, ajaxLoad; if (slide.isLoading || slide.isLoaded) { return; } slide.isLoading = true; if (self.trigger("beforeLoad", slide) === false) { slide.isLoading = false; return false; } type = slide.type; $slide = slide.$slide; $slide .off("refresh") .trigger("onReset") .addClass(slide.opts.slideClass); // Create content depending on the type switch (type) { case "image": self.setImage(slide); break; case "iframe": self.setIframe(slide); break; case "html": self.setContent(slide, slide.src || slide.content); break; case "video": self.setContent( slide, slide.opts.video.tpl .replace(/\{\{src\}\}/gi, slide.src) .replace("{{format}}", slide.opts.videoFormat || slide.opts.video.format || "") .replace("{{poster}}", slide.thumb || "") ); break; case "inline": if ($(slide.src).length) { self.setContent(slide, $(slide.src)); } else { self.setError(slide); } break; case "ajax": self.showLoading(slide); ajaxLoad = $.ajax( $.extend({}, slide.opts.ajax.settings, { url: slide.src, success: function (data, textStatus) { if (textStatus === "success") { self.setContent(slide, data); } }, error: function (jqXHR, textStatus) { if (jqXHR && textStatus !== "abort") { self.setError(slide); } } }) ); $slide.one("onReset", function () { ajaxLoad.abort(); }); break; default: self.setError(slide); break; } return true; }, // Use thumbnail image, if possible // ================================ setImage: function (slide) { var self = this, ghost; // Check if need to show loading icon setTimeout(function () { var $img = slide.$image; if (!self.isClosing && slide.isLoading && (!$img || !$img.length || !$img[0].complete) && !slide.hasError) { self.showLoading(slide); } }, 50); //Check if image has srcset self.checkSrcset(slide); // This will be wrapper containing both ghost and actual image slide.$content = $('
') .addClass("fancybox-is-hidden") .appendTo(slide.$slide.addClass("fancybox-slide--image")); // If we have a thumbnail, we can display it while actual image is loading // Users will not stare at black screen and actual image will appear gradually if (slide.opts.preload !== false && slide.opts.width && slide.opts.height && slide.thumb) { slide.width = slide.opts.width; slide.height = slide.opts.height; ghost = document.createElement("img"); ghost.onerror = function () { $(this).remove(); slide.$ghost = null; }; ghost.onload = function () { self.afterLoad(slide); }; slide.$ghost = $(ghost) .addClass("fancybox-image") .appendTo(slide.$content) .attr("src", slide.thumb); } // Start loading actual image self.setBigImage(slide); }, // Check if image has srcset and get the source // ============================================ checkSrcset: function (slide) { var srcset = slide.opts.srcset || slide.opts.image.srcset, found, temp, pxRatio, windowWidth; // If we have "srcset", then we need to find first matching "src" value. // This is necessary, because when you set an src attribute, the browser will preload the image // before any javascript or even CSS is applied. if (srcset) { pxRatio = window.devicePixelRatio || 1; windowWidth = window.innerWidth * pxRatio; temp = srcset.split(",").map(function (el) { var ret = {}; el.trim() .split(/\s+/) .forEach(function (el, i) { var value = parseInt(el.substring(0, el.length - 1), 10); if (i === 0) { return (ret.url = el); } if (value) { ret.value = value; ret.postfix = el[el.length - 1]; } }); return ret; }); // Sort by value temp.sort(function (a, b) { return a.value - b.value; }); // Ok, now we have an array of all srcset values for (var j = 0; j < temp.length; j++) { var el = temp[j]; if ((el.postfix === "w" && el.value >= windowWidth) || (el.postfix === "x" && el.value >= pxRatio)) { found = el; break; } } // If not found, take the last one if (!found && temp.length) { found = temp[temp.length - 1]; } if (found) { slide.src = found.url; // If we have default width/height values, we can calculate height for matching source if (slide.width && slide.height && found.postfix == "w") { slide.height = (slide.width / slide.height) * found.value; slide.width = found.value; } slide.opts.srcset = srcset; } } }, // Create full-size image // ====================== setBigImage: function (slide) { var self = this, img = document.createElement("img"), $img = $(img); slide.$image = $img .one("error", function () { self.setError(slide); }) .one("load", function () { var sizes; if (!slide.$ghost) { self.resolveImageSlideSize(slide, this.naturalWidth, this.naturalHeight); self.afterLoad(slide); } if (self.isClosing) { return; } if (slide.opts.srcset) { sizes = slide.opts.sizes; if (!sizes || sizes === "auto") { sizes = (slide.width / slide.height > 1 && $W.width() / $W.height() > 1 ? "100" : Math.round((slide.width / slide.height) * 100)) + "vw"; } $img.attr("sizes", sizes).attr("srcset", slide.opts.srcset); } // Hide temporary image after some delay if (slide.$ghost) { setTimeout(function () { if (slide.$ghost && !self.isClosing) { slide.$ghost.hide(); } }, Math.min(300, Math.max(1000, slide.height / 1600))); } self.hideLoading(slide); }) .addClass("fancybox-image") .attr("src", slide.src) .appendTo(slide.$content); if ((img.complete || img.readyState == "complete") && $img.naturalWidth && $img.naturalHeight) { $img.trigger("load"); } else if (img.error) { $img.trigger("error"); } }, // Computes the slide size from image size and maxWidth/maxHeight // ============================================================== resolveImageSlideSize: function (slide, imgWidth, imgHeight) { var maxWidth = parseInt(slide.opts.width, 10), maxHeight = parseInt(slide.opts.height, 10); // Sets the default values from the image slide.width = imgWidth; slide.height = imgHeight; if (maxWidth > 0) { slide.width = maxWidth; slide.height = Math.floor((maxWidth * imgHeight) / imgWidth); } if (maxHeight > 0) { slide.width = Math.floor((maxHeight * imgWidth) / imgHeight); slide.height = maxHeight; } }, // Create iframe wrapper, iframe and bindings // ========================================== setIframe: function (slide) { var self = this, opts = slide.opts.iframe, $slide = slide.$slide, $iframe; slide.$content = $('
') .css(opts.css) .appendTo($slide); $slide.addClass("fancybox-slide--" + slide.contentType); slide.$iframe = $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime())) .attr(opts.attr) .appendTo(slide.$content); if (opts.preload) { self.showLoading(slide); // Unfortunately, it is not always possible to determine if iframe is successfully loaded // (due to browser security policy) $iframe.on("load.fb error.fb", function (e) { this.isReady = 1; slide.$slide.trigger("refresh"); self.afterLoad(slide); }); // Recalculate iframe content size // =============================== $slide.on("refresh.fb", function () { var $content = slide.$content, frameWidth = opts.css.width, frameHeight = opts.css.height, $contents, $body; if ($iframe[0].isReady !== 1) { return; } try { $contents = $iframe.contents(); $body = $contents.find("body"); } catch (ignore) {} // Calculate content dimensions, if it is accessible if ($body && $body.length && $body.children().length) { // Avoid scrolling to top (if multiple instances) $slide.css("overflow", "visible"); $content.css({ width: "100%", "max-width": "100%", height: "9999px" }); if (frameWidth === undefined) { frameWidth = Math.ceil(Math.max($body[0].clientWidth, $body.outerWidth(true))); } $content.css("width", frameWidth ? frameWidth : "").css("max-width", ""); if (frameHeight === undefined) { frameHeight = Math.ceil(Math.max($body[0].clientHeight, $body.outerHeight(true))); } $content.css("height", frameHeight ? frameHeight : ""); $slide.css("overflow", "auto"); } $content.removeClass("fancybox-is-hidden"); }); } else { self.afterLoad(slide); } $iframe.attr("src", slide.src); // Remove iframe if closing or changing gallery item $slide.one("onReset", function () { // This helps IE not to throw errors when closing try { $(this) .find("iframe") .hide() .unbind() .attr("src", "//about:blank"); } catch (ignore) {} $(this) .off("refresh.fb") .empty(); slide.isLoaded = false; slide.isRevealed = false; }); }, // Wrap and append content to the slide // ====================================== setContent: function (slide, content) { var self = this; if (self.isClosing) { return; } self.hideLoading(slide); if (slide.$content) { $.fancybox.stop(slide.$content); } slide.$slide.empty(); // If content is a jQuery object, then it will be moved to the slide. // The placeholder is created so we will know where to put it back. if (isQuery(content) && content.parent().length) { // Make sure content is not already moved to fancyBox if (content.hasClass("fancybox-content") || content.parent().hasClass("fancybox-content")) { content.parents(".fancybox-slide").trigger("onReset"); } // Create temporary element marking original place of the content slide.$placeholder = $("
") .hide() .insertAfter(content); // Make sure content is visible content.css("display", "inline-block"); } else if (!slide.hasError) { // If content is just a plain text, try to convert it to html if ($.type(content) === "string") { content = $("
") .append($.trim(content)) .contents(); } // If "filter" option is provided, then filter content if (slide.opts.filter) { content = $("
") .html(content) .find(slide.opts.filter); } } slide.$slide.one("onReset", function () { // Pause all html5 video/audio $(this) .find("video,audio") .trigger("pause"); // Put content back if (slide.$placeholder) { slide.$placeholder.after(content.removeClass("fancybox-content").hide()).remove(); slide.$placeholder = null; } // Remove custom close button if (slide.$smallBtn) { slide.$smallBtn.remove(); slide.$smallBtn = null; } // Remove content and mark slide as not loaded if (!slide.hasError) { $(this).empty(); slide.isLoaded = false; slide.isRevealed = false; } }); $(content).appendTo(slide.$slide); if ($(content).is("video,audio")) { $(content).addClass("fancybox-video"); $(content).wrap("
"); slide.contentType = "video"; slide.opts.width = slide.opts.width || $(content).attr("width"); slide.opts.height = slide.opts.height || $(content).attr("height"); } slide.$content = slide.$slide .children() .filter("div,form,main,video,audio,article,.fancybox-content") .first(); slide.$content.siblings().hide(); // Re-check if there is a valid content // (in some cases, ajax response can contain various elements or plain text) if (!slide.$content.length) { slide.$content = slide.$slide .wrapInner("
") .children() .first(); } slide.$content.addClass("fancybox-content"); slide.$slide.addClass("fancybox-slide--" + slide.contentType); self.afterLoad(slide); }, // Display error message // ===================== setError: function (slide) { slide.hasError = true; slide.$slide .trigger("onReset") .removeClass("fancybox-slide--" + slide.contentType) .addClass("fancybox-slide--error"); slide.contentType = "html"; this.setContent(slide, this.translate(slide, slide.opts.errorTpl)); if (slide.pos === this.currPos) { this.isAnimating = false; } }, // Show loading icon inside the slide // ================================== showLoading: function (slide) { var self = this; slide = slide || self.current; if (slide && !slide.$spinner) { slide.$spinner = $(self.translate(self, self.opts.spinnerTpl)) .appendTo(slide.$slide) .hide() .fadeIn("fast"); } }, // Remove loading icon from the slide // ================================== hideLoading: function (slide) { var self = this; slide = slide || self.current; if (slide && slide.$spinner) { slide.$spinner.stop().remove(); delete slide.$spinner; } }, // Adjustments after slide content has been loaded // =============================================== afterLoad: function (slide) { var self = this; if (self.isClosing) { return; } slide.isLoading = false; slide.isLoaded = true; self.trigger("afterLoad", slide); self.hideLoading(slide); // Add small close button if (slide.opts.smallBtn && (!slide.$smallBtn || !slide.$smallBtn.length)) { slide.$smallBtn = $(self.translate(slide, slide.opts.btnTpl.smallBtn)).appendTo(slide.$content); } // Disable right click if (slide.opts.protect && slide.$content && !slide.hasError) { slide.$content.on("contextmenu.fb", function (e) { if (e.button == 2) { e.preventDefault(); } return true; }); // Add fake element on top of the image // This makes a bit harder for user to select image if (slide.type === "image") { $('
').appendTo(slide.$content); } } self.adjustCaption(slide); self.adjustLayout(slide); if (slide.pos === self.currPos) { self.updateCursor(); } self.revealContent(slide); }, // Prevent caption overlap, // fix css inconsistency across browsers // ===================================== adjustCaption: function (slide) { var self = this, current = slide || self.current, caption = current.opts.caption, preventOverlap = current.opts.preventCaptionOverlap, $caption = self.$refs.caption, $clone, captionH = false; $caption.toggleClass("fancybox-caption--separate", preventOverlap); if (preventOverlap && caption && caption.length) { if (current.pos !== self.currPos) { $clone = $caption.clone().appendTo($caption.parent()); $clone .children() .eq(0) .empty() .html(caption); captionH = $clone.outerHeight(true); $clone.empty().remove(); } else if (self.$caption) { captionH = self.$caption.outerHeight(true); } current.$slide.css("padding-bottom", captionH || ""); } }, // Simple hack to fix inconsistency across browsers, described here (affects Edge, too): // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 // ==================================================================================== adjustLayout: function (slide) { var self = this, current = slide || self.current, scrollHeight, marginBottom, inlinePadding, actualPadding; if (current.isLoaded && current.opts.disableLayoutFix !== true) { current.$content.css("margin-bottom", ""); // If we would always set margin-bottom for the content, // then it would potentially break vertical align if (current.$content.outerHeight() > current.$slide.height() + 0.5) { inlinePadding = current.$slide[0].style["padding-bottom"]; actualPadding = current.$slide.css("padding-bottom"); if (parseFloat(actualPadding) > 0) { scrollHeight = current.$slide[0].scrollHeight; current.$slide.css("padding-bottom", 0); if (Math.abs(scrollHeight - current.$slide[0].scrollHeight) < 1) { marginBottom = actualPadding; } current.$slide.css("padding-bottom", inlinePadding); } } current.$content.css("margin-bottom", marginBottom); } }, // Make content visible // This method is called right after content has been loaded or // user navigates gallery and transition should start // ============================================================ revealContent: function (slide) { var self = this, $slide = slide.$slide, end = false, start = false, isMoved = self.isMoved(slide), isRevealed = slide.isRevealed, effect, effectClassName, duration, opacity; slide.isRevealed = true; effect = slide.opts[self.firstRun ? "animationEffect" : "transitionEffect"]; duration = slide.opts[self.firstRun ? "animationDuration" : "transitionDuration"]; duration = parseInt(slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10); if (isMoved || slide.pos !== self.currPos || !duration) { effect = false; } // Check if can zoom if (effect === "zoom") { if (slide.pos === self.currPos && duration && slide.type === "image" && !slide.hasError && (start = self.getThumbPos(slide))) { end = self.getFitPos(slide); } else { effect = "fade"; } } // Zoom animation // ============== if (effect === "zoom") { self.isAnimating = true; end.scaleX = end.width / start.width; end.scaleY = end.height / start.height; // Check if we need to animate opacity opacity = slide.opts.zoomOpacity; if (opacity == "auto") { opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1; } if (opacity) { start.opacity = 0.1; end.opacity = 1; } // Draw image at start position $.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start); forceRedraw(slide.$content); // Start animation $.fancybox.animate(slide.$content, end, duration, function () { self.isAnimating = false; self.complete(); }); return; } self.updateSlide(slide); // Simply show content if no effect // ================================ if (!effect) { slide.$content.removeClass("fancybox-is-hidden"); if (!isRevealed && isMoved && slide.type === "image" && !slide.hasError) { slide.$content.hide().fadeIn("fast"); } if (slide.pos === self.currPos) { self.complete(); } return; } // Prepare for CSS transiton // ========================= $.fancybox.stop($slide); //effectClassName = "fancybox-animated fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-fx-" + effect; effectClassName = "fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-animated fancybox-fx-" + effect; $slide.addClass(effectClassName).removeClass("fancybox-slide--current"); //.addClass(effectClassName); slide.$content.removeClass("fancybox-is-hidden"); // Force reflow forceRedraw($slide); if (slide.type !== "image") { slide.$content.hide().show(0); } $.fancybox.animate( $slide, "fancybox-slide--current", duration, function () { $slide.removeClass(effectClassName).css({ transform: "", opacity: "" }); if (slide.pos === self.currPos) { self.complete(); } }, true ); }, // Check if we can and have to zoom from thumbnail //================================================ getThumbPos: function (slide) { var rez = false, $thumb = slide.$thumb, thumbPos, btw, brw, bbw, blw; if (!$thumb || !inViewport($thumb[0])) { return false; } thumbPos = $.fancybox.getTranslate($thumb); btw = parseFloat($thumb.css("border-top-width") || 0); brw = parseFloat($thumb.css("border-right-width") || 0); bbw = parseFloat($thumb.css("border-bottom-width") || 0); blw = parseFloat($thumb.css("border-left-width") || 0); rez = { top: thumbPos.top + btw, left: thumbPos.left + blw, width: thumbPos.width - brw - blw, height: thumbPos.height - btw - bbw, scaleX: 1, scaleY: 1 }; return thumbPos.width > 0 && thumbPos.height > 0 ? rez : false; }, // Final adjustments after current gallery item is moved to position // and it`s content is loaded // ================================================================== complete: function () { var self = this, current = self.current, slides = {}, $el; if (self.isMoved() || !current.isLoaded) { return; } if (!current.isComplete) { current.isComplete = true; current.$slide.siblings().trigger("onReset"); self.preload("inline"); // Trigger any CSS transiton inside the slide forceRedraw(current.$slide); current.$slide.addClass("fancybox-slide--complete"); // Remove unnecessary slides $.each(self.slides, function (key, slide) { if (slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1) { slides[slide.pos] = slide; } else if (slide) { $.fancybox.stop(slide.$slide); slide.$slide.off().remove(); } }); self.slides = slides; } self.isAnimating = false; self.updateCursor(); self.trigger("afterShow"); // Autoplay first html5 video/audio if (!!current.opts.video.autoStart) { current.$slide .find("video,audio") .filter(":visible:first") .trigger("play") .one("ended", function () { if (Document.exitFullscreen) { Document.exitFullscreen(); } else if (this.webkitExitFullscreen) { this.webkitExitFullscreen(); } self.next(); }); } // Try to focus on the first focusable element if (current.opts.autoFocus && current.contentType === "html") { // Look for the first input with autofocus attribute $el = current.$content.find("input[autofocus]:enabled:visible:first"); if ($el.length) { $el.trigger("focus"); } else { self.focus(null, true); } } // Avoid jumping current.$slide.scrollTop(0).scrollLeft(0); }, // Preload next and previous slides // ================================ preload: function (type) { var self = this, prev, next; if (self.group.length < 2) { return; } next = self.slides[self.currPos + 1]; prev = self.slides[self.currPos - 1]; if (prev && prev.type === type) { self.loadSlide(prev); } if (next && next.type === type) { self.loadSlide(next); } }, // Try to find and focus on the first focusable element // ==================================================== focus: function (e, firstRun) { var self = this, focusableStr = [ "a[href]", "area[href]", 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', "select:not([disabled]):not([aria-hidden])", "textarea:not([disabled]):not([aria-hidden])", "button:not([disabled]):not([aria-hidden])", "iframe", "object", "embed", "video", "audio", "[contenteditable]", '[tabindex]:not([tabindex^="-"])' ].join(","), focusableItems, focusedItemIndex; if (self.isClosing) { return; } if (e || !self.current || !self.current.isComplete) { // Focus on any element inside fancybox focusableItems = self.$refs.container.find("*:visible"); } else { // Focus inside current slide focusableItems = self.current.$slide.find("*:visible" + (firstRun ? ":not(.fancybox-close-small)" : "")); } focusableItems = focusableItems.filter(focusableStr).filter(function () { return $(this).css("visibility") !== "hidden" && !$(this).hasClass("disabled"); }); if (focusableItems.length) { focusedItemIndex = focusableItems.index(document.activeElement); if (e && e.shiftKey) { // Back tab if (focusedItemIndex < 0 || focusedItemIndex == 0) { e.preventDefault(); focusableItems.eq(focusableItems.length - 1).trigger("focus"); } } else { // Outside or Forward tab if (focusedItemIndex < 0 || focusedItemIndex == focusableItems.length - 1) { if (e) { e.preventDefault(); } focusableItems.eq(0).trigger("focus"); } } } else { self.$refs.container.trigger("focus"); } }, // Activates current instance - brings container to the front and enables keyboard, // notifies other instances about deactivating // ================================================================================= activate: function () { var self = this; // Deactivate all instances $(".fancybox-container").each(function () { var instance = $(this).data("FancyBox"); // Skip self and closing instances if (instance && instance.id !== self.id && !instance.isClosing) { instance.trigger("onDeactivate"); instance.removeEvents(); instance.isVisible = false; } }); self.isVisible = true; if (self.current || self.isIdle) { self.update(); self.updateControls(); } self.trigger("onActivate"); self.addEvents(); }, // Start closing procedure // This will start "zoom-out" animation if needed and clean everything up afterwards // ================================================================================= close: function (e, d) { var self = this, current = self.current, effect, duration, $content, domRect, opacity, start, end; var done = function () { self.cleanUp(e); }; if (self.isClosing) { return false; } self.isClosing = true; // If beforeClose callback prevents closing, make sure content is centered if (self.trigger("beforeClose", e) === false) { self.isClosing = false; requestAFrame(function () { self.update(); }); return false; } // Remove all events // If there are multiple instances, they will be set again by "activate" method self.removeEvents(); $content = current.$content; effect = current.opts.animationEffect; duration = $.isNumeric(d) ? d : effect ? current.opts.animationDuration : 0; current.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"); if (e !== true) { $.fancybox.stop(current.$slide); } else { effect = false; } // Remove other slides current.$slide .siblings() .trigger("onReset") .remove(); // Trigger animations if (duration) { self.$refs.container .removeClass("fancybox-is-open") .addClass("fancybox-is-closing") .css("transition-duration", duration + "ms"); } // Clean up self.hideLoading(current); self.hideControls(true); self.updateCursor(); // Check if possible to zoom-out if ( effect === "zoom" && !($content && duration && current.type === "image" && !self.isMoved() && !current.hasError && (end = self.getThumbPos(current))) ) { effect = "fade"; } if (effect === "zoom") { $.fancybox.stop($content); domRect = $.fancybox.getTranslate($content); start = { top: domRect.top, left: domRect.left, scaleX: domRect.width / end.width, scaleY: domRect.height / end.height, width: end.width, height: end.height }; // Check if we need to animate opacity opacity = current.opts.zoomOpacity; if (opacity == "auto") { opacity = Math.abs(current.width / current.height - end.width / end.height) > 0.1; } if (opacity) { end.opacity = 0; } $.fancybox.setTranslate($content, start); forceRedraw($content); $.fancybox.animate($content, end, duration, done); return true; } if (effect && duration) { $.fancybox.animate( current.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"), "fancybox-animated fancybox-fx-" + effect, duration, done ); } else { // If skip animation if (e === true) { setTimeout(done, duration); } else { done(); } } return true; }, // Final adjustments after removing the instance // ============================================= cleanUp: function (e) { var self = this, instance, $focus = self.current.opts.$orig, x, y; self.current.$slide.trigger("onReset"); self.$refs.container.empty().remove(); self.trigger("afterClose", e); // Place back focus if (!!self.current.opts.backFocus) { if (!$focus || !$focus.length || !$focus.is(":visible")) { $focus = self.$trigger; } if ($focus && $focus.length) { x = window.scrollX; y = window.scrollY; $focus.trigger("focus"); $("html, body") .scrollTop(y) .scrollLeft(x); } } self.current = null; // Check if there are other instances instance = $.fancybox.getInstance(); if (instance) { instance.activate(); } else { $("body").removeClass("fancybox-active compensate-for-scrollbar"); $("#fancybox-style-noscroll").remove(); } }, // Call callback and trigger an event // ================================== trigger: function (name, slide) { var args = Array.prototype.slice.call(arguments, 1), self = this, obj = slide && slide.opts ? slide : self.current, rez; if (obj) { args.unshift(obj); } else { obj = self; } args.unshift(self); if ($.isFunction(obj.opts[name])) { rez = obj.opts[name].apply(obj, args); } if (rez === false) { return rez; } if (name === "afterClose" || !self.$refs) { $D.trigger(name + ".fb", args); } else { self.$refs.container.trigger(name + ".fb", args); } }, // Update infobar values, navigation button states and reveal caption // ================================================================== updateControls: function () { var self = this, current = self.current, index = current.index, $container = self.$refs.container, $caption = self.$refs.caption, caption = current.opts.caption; // Recalculate content dimensions current.$slide.trigger("refresh"); // Set caption if (caption && caption.length) { self.$caption = $caption; $caption .children() .eq(0) .html(caption); } else { self.$caption = null; } if (!self.hasHiddenControls && !self.isIdle) { self.showControls(); } // Update info and navigation elements $container.find("[data-fancybox-count]").html(self.group.length); $container.find("[data-fancybox-index]").html(index + 1); $container.find("[data-fancybox-prev]").prop("disabled", !current.opts.loop && index <= 0); $container.find("[data-fancybox-next]").prop("disabled", !current.opts.loop && index >= self.group.length - 1); if (current.type === "image") { // Re-enable buttons; update download button source $container .find("[data-fancybox-zoom]") .show() .end() .find("[data-fancybox-download]") .attr("href", current.opts.image.src || current.src) .show(); } else if (current.opts.toolbar) { $container.find("[data-fancybox-download],[data-fancybox-zoom]").hide(); } // Make sure focus is not on disabled button/element if ($(document.activeElement).is(":hidden,[disabled]")) { self.$refs.container.trigger("focus"); } }, // Hide toolbar and caption // ======================== hideControls: function (andCaption) { var self = this, arr = ["infobar", "toolbar", "nav"]; if (andCaption || !self.current.opts.preventCaptionOverlap) { arr.push("caption"); } this.$refs.container.removeClass( arr .map(function (i) { return "fancybox-show-" + i; }) .join(" ") ); this.hasHiddenControls = true; }, showControls: function () { var self = this, opts = self.current ? self.current.opts : self.opts, $container = self.$refs.container; self.hasHiddenControls = false; self.idleSecondsCounter = 0; $container .toggleClass("fancybox-show-toolbar", !!(opts.toolbar && opts.buttons)) .toggleClass("fancybox-show-infobar", !!(opts.infobar && self.group.length > 1)) .toggleClass("fancybox-show-caption", !!self.$caption) .toggleClass("fancybox-show-nav", !!(opts.arrows && self.group.length > 1)) .toggleClass("fancybox-is-modal", !!opts.modal); }, // Toggle toolbar and caption // ========================== toggleControls: function () { if (this.hasHiddenControls) { this.showControls(); } else { this.hideControls(); } } }); $.fancybox = { version: "3.5.7", defaults: defaults, // Get current instance and execute a command. // // Examples of usage: // // $instance = $.fancybox.getInstance(); // $.fancybox.getInstance().jumpTo( 1 ); // $.fancybox.getInstance( 'jumpTo', 1 ); // $.fancybox.getInstance( function() { // console.info( this.currIndex ); // }); // ====================================================== getInstance: function (command) { var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"), args = Array.prototype.slice.call(arguments, 1); if (instance instanceof FancyBox) { if ($.type(command) === "string") { instance[command].apply(instance, args); } else if ($.type(command) === "function") { command.apply(instance, args); } return instance; } return false; }, // Create new instance // =================== open: function (items, opts, index) { return new FancyBox(items, opts, index); }, // Close current or all instances // ============================== close: function (all) { var instance = this.getInstance(); if (instance) { instance.close(); // Try to find and close next instance if (all === true) { this.close(all); } } }, // Close all instances and unbind all events // ========================================= destroy: function () { this.close(true); $D.add("body").off("click.fb-start", "**"); }, // Try to detect mobile devices // ============================ isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), // Detect if 'translate3d' support is available // ============================================ use3d: (function () { var div = document.createElement("div"); return ( window.getComputedStyle && window.getComputedStyle(div) && window.getComputedStyle(div).getPropertyValue("transform") && !(document.documentMode && document.documentMode < 11) ); })(), // Helper function to get current visual state of an element // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] // ===================================================================== getTranslate: function ($el) { var domRect; if (!$el || !$el.length) { return false; } domRect = $el[0].getBoundingClientRect(); return { top: domRect.top || 0, left: domRect.left || 0, width: domRect.width, height: domRect.height, opacity: parseFloat($el.css("opacity")) }; }, // Shortcut for setting "translate3d" properties for element // Can set be used to set opacity, too // ======================================================== setTranslate: function ($el, props) { var str = "", css = {}; if (!$el || !props) { return; } if (props.left !== undefined || props.top !== undefined) { str = (props.left === undefined ? $el.position().left : props.left) + "px, " + (props.top === undefined ? $el.position().top : props.top) + "px"; if (this.use3d) { str = "translate3d(" + str + ", 0px)"; } else { str = "translate(" + str + ")"; } } if (props.scaleX !== undefined && props.scaleY !== undefined) { str += " scale(" + props.scaleX + ", " + props.scaleY + ")"; } else if (props.scaleX !== undefined) { str += " scaleX(" + props.scaleX + ")"; } if (str.length) { css.transform = str; } if (props.opacity !== undefined) { css.opacity = props.opacity; } if (props.width !== undefined) { css.width = props.width; } if (props.height !== undefined) { css.height = props.height; } return $el.css(css); }, // Simple CSS transition handler // ============================= animate: function ($el, to, duration, callback, leaveAnimationName) { var self = this, from; if ($.isFunction(duration)) { callback = duration; duration = null; } self.stop($el); from = self.getTranslate($el); $el.on(transitionEnd, function (e) { // Skip events from child elements and z-index change if (e && e.originalEvent && (!$el.is(e.originalEvent.target) || e.originalEvent.propertyName == "z-index")) { return; } self.stop($el); if ($.isNumeric(duration)) { $el.css("transition-duration", ""); } if ($.isPlainObject(to)) { if (to.scaleX !== undefined && to.scaleY !== undefined) { self.setTranslate($el, { top: to.top, left: to.left, width: from.width * to.scaleX, height: from.height * to.scaleY, scaleX: 1, scaleY: 1 }); } } else if (leaveAnimationName !== true) { $el.removeClass(to); } if ($.isFunction(callback)) { callback(e); } }); if ($.isNumeric(duration)) { $el.css("transition-duration", duration + "ms"); } // Start animation by changing CSS properties or class name if ($.isPlainObject(to)) { if (to.scaleX !== undefined && to.scaleY !== undefined) { delete to.width; delete to.height; if ($el.parent().hasClass("fancybox-slide--image")) { $el.parent().addClass("fancybox-is-scaling"); } } $.fancybox.setTranslate($el, to); } else { $el.addClass(to); } // Make sure that `transitionend` callback gets fired $el.data( "timer", setTimeout(function () { $el.trigger(transitionEnd); }, duration + 33) ); }, stop: function ($el, callCallback) { if ($el && $el.length) { clearTimeout($el.data("timer")); if (callCallback) { $el.trigger(transitionEnd); } $el.off(transitionEnd).css("transition-duration", ""); $el.parent().removeClass("fancybox-is-scaling"); } } }; // Default click handler for "fancyboxed" links // ============================================ function _run(e, opts) { var items = [], index = 0, $target, value, instance; // Avoid opening multiple times if (e && e.isDefaultPrevented()) { return; } e.preventDefault(); opts = opts || {}; if (e && e.data) { opts = mergeOpts(e.data.options, opts); } $target = opts.$target || $(e.currentTarget).trigger("blur"); instance = $.fancybox.getInstance(); if (instance && instance.$trigger && instance.$trigger.is($target)) { return; } if (opts.selector) { items = $(opts.selector); } else { // Get all related items and find index for clicked one value = $target.attr("data-fancybox") || ""; if (value) { items = e.data ? e.data.items : []; items = items.length ? items.filter('[data-fancybox="' + value + '"]') : $('[data-fancybox="' + value + '"]'); } else { items = [$target]; } } index = $(items).index($target); // Sometimes current item can not be found if (index < 0) { index = 0; } instance = $.fancybox.open(items, opts, index); // Save last active element instance.$trigger = $target; } // Create a jQuery plugin // ====================== $.fn.fancybox = function (options) { var selector; options = options || {}; selector = options.selector || false; if (selector) { // Use body element instead of document so it executes first $("body") .off("click.fb-start", selector) .on("click.fb-start", selector, { options: options }, _run); } else { this.off("click.fb-start").on( "click.fb-start", { items: this, options: options }, _run ); } return this; }; // Self initializing plugin for all elements having `data-fancybox` attribute // ========================================================================== $D.on("click.fb-start", "[data-fancybox]", _run); // Enable "trigger elements" // ========================= $D.on("click.fb-start", "[data-fancybox-trigger]", function (e) { $('[data-fancybox="' + $(this).attr("data-fancybox-trigger") + '"]') .eq($(this).attr("data-fancybox-index") || 0) .trigger("click.fb-start", { $trigger: $(this) }); }); // Track focus event for better accessibility styling // ================================================== (function () { var buttonStr = ".fancybox-button", focusStr = "fancybox-focus", $pressed = null; $D.on("mousedown mouseup focus blur", buttonStr, function (e) { switch (e.type) { case "mousedown": $pressed = $(this); break; case "mouseup": $pressed = null; break; case "focusin": $(buttonStr).removeClass(focusStr); if (!$(this).is($pressed) && !$(this).is("[disabled]")) { $(this).addClass(focusStr); } break; case "focusout": $(buttonStr).removeClass(focusStr); break; } }); })(); })(window, document, jQuery); // ========================================================================== // // Media // Adds additional media type support // // ========================================================================== (function ($) { "use strict"; // Object containing properties for each media type var defaults = { youtube: { matcher: /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i, params: { autoplay: 1, autohide: 1, fs: 1, rel: 0, hd: 1, wmode: "transparent", enablejsapi: 1, html5: 1 }, paramPlace: 8, type: "iframe", url: "https://www.youtube-nocookie.com/embed/$4", thumb: "https://img.youtube.com/vi/$4/hqdefault.jpg" }, vimeo: { matcher: /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/, params: { autoplay: 1, hd: 1, show_title: 1, show_byline: 1, show_portrait: 0, fullscreen: 1 }, paramPlace: 3, type: "iframe", url: "//player.vimeo.com/video/$2" }, instagram: { matcher: /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, type: "image", url: "//$1/p/$2/media/?size=l" }, // Examples: // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 // https://www.google.com/maps/@37.7852006,-122.4146355,14.65z // https://www.google.com/maps/@52.2111123,2.9237542,6.61z?hl=en // https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 gmap_place: { matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i, type: "iframe", url: function (rez) { return ( "//maps.google." + rez[2] + "/?ll=" + (rez[9] ? rez[9] + "&z=" + Math.floor(rez[10]) + (rez[12] ? rez[12].replace(/^\//, "&") : "") : rez[12] + "").replace(/\?/, "&") + "&output=" + (rez[12] && rez[12].indexOf("layer=c") > 0 ? "svembed" : "embed") ); } }, // Examples: // https://www.google.com/maps/search/Empire+State+Building/ // https://www.google.com/maps/search/?api=1&query=centurylink+field // https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393 gmap_search: { matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i, type: "iframe", url: function (rez) { return "//maps.google." + rez[2] + "/maps?q=" + rez[5].replace("query=", "q=").replace("api=1", "") + "&output=embed"; } } }; // Formats matching url to final form var format = function (url, rez, params) { if (!url) { return; } params = params || ""; if ($.type(params) === "object") { params = $.param(params, true); } $.each(rez, function (key, value) { url = url.replace("$" + key, value || ""); }); if (params.length) { url += (url.indexOf("?") > 0 ? "&" : "?") + params; } return url; }; $(document).on("objectNeedsType.fb", function (e, instance, item) { var url = item.src || "", type = false, media, thumb, rez, params, urlParams, paramObj, provider; media = $.extend(true, {}, defaults, item.opts.media); // Look for any matching media type $.each(media, function (providerName, providerOpts) { rez = url.match(providerOpts.matcher); if (!rez) { return; } type = providerOpts.type; provider = providerName; paramObj = {}; if (providerOpts.paramPlace && rez[providerOpts.paramPlace]) { urlParams = rez[providerOpts.paramPlace]; if (urlParams[0] == "?") { urlParams = urlParams.substring(1); } urlParams = urlParams.split("&"); for (var m = 0; m < urlParams.length; ++m) { var p = urlParams[m].split("=", 2); if (p.length == 2) { paramObj[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); } } } params = $.extend(true, {}, providerOpts.params, item.opts[providerName], paramObj); url = $.type(providerOpts.url) === "function" ? providerOpts.url.call(this, rez, params, item) : format(providerOpts.url, rez, params); thumb = $.type(providerOpts.thumb) === "function" ? providerOpts.thumb.call(this, rez, params, item) : format(providerOpts.thumb, rez); if (providerName === "youtube") { url = url.replace(/&t=((\d+)m)?(\d+)s/, function (match, p1, m, s) { return "&start=" + ((m ? parseInt(m, 10) * 60 : 0) + parseInt(s, 10)); }); } else if (providerName === "vimeo") { url = url.replace("&%23", "#"); } return false; }); // If it is found, then change content type and update the url if (type) { if (!item.opts.thumb && !(item.opts.$thumb && item.opts.$thumb.length)) { item.opts.thumb = thumb; } if (type === "iframe") { item.opts = $.extend(true, item.opts, { iframe: { preload: false, attr: { scrolling: "no" } } }); } $.extend(item, { type: type, src: url, origSrc: item.src, contentSource: provider, contentType: type === "image" ? "image" : provider == "gmap_place" || provider == "gmap_search" ? "map" : "video" }); } else if (url) { item.type = item.opts.defaultType; } }); // Load YouTube/Video API on request to detect when video finished playing var VideoAPILoader = { youtube: { src: "https://www.youtube.com/iframe_api", class: "YT", loading: false, loaded: false }, vimeo: { src: "https://player.vimeo.com/api/player.js", class: "Vimeo", loading: false, loaded: false }, load: function (vendor) { var _this = this, script; if (this[vendor].loaded) { setTimeout(function () { _this.done(vendor); }); return; } if (this[vendor].loading) { return; } this[vendor].loading = true; script = document.createElement("script"); script.type = "text/javascript"; script.src = this[vendor].src; if (vendor === "youtube") { window.onYouTubeIframeAPIReady = function () { _this[vendor].loaded = true; _this.done(vendor); }; } else { script.onload = function () { _this[vendor].loaded = true; _this.done(vendor); }; } document.body.appendChild(script); }, done: function (vendor) { var instance, $el, player; if (vendor === "youtube") { delete window.onYouTubeIframeAPIReady; } instance = $.fancybox.getInstance(); if (instance) { $el = instance.current.$content.find("iframe"); if (vendor === "youtube" && YT !== undefined && YT) { player = new YT.Player($el.attr("id"), { events: { onStateChange: function (e) { if (e.data == 0) { instance.next(); } } } }); } else if (vendor === "vimeo" && Vimeo !== undefined && Vimeo) { player = new Vimeo.Player($el); player.on("ended", function () { instance.next(); }); } } } }; $(document).on({ "afterShow.fb": function (e, instance, current) { if (instance.group.length > 1 && (current.contentSource === "youtube" || current.contentSource === "vimeo")) { VideoAPILoader.load(current.contentSource); } } }); })(jQuery); // ========================================================================== // // Guestures // Adds touch guestures, handles click and tap events // // ========================================================================== (function (window, document, $) { "use strict"; var requestAFrame = (function () { return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || // if all else fails, use setTimeout function (callback) { return window.setTimeout(callback, 1000 / 60); } ); })(); var cancelAFrame = (function () { return ( window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function (id) { window.clearTimeout(id); } ); })(); var getPointerXY = function (e) { var result = []; e = e.originalEvent || e || window.e; e = e.touches && e.touches.length ? e.touches : e.changedTouches && e.changedTouches.length ? e.changedTouches : [e]; for (var key in e) { if (e[key].pageX) { result.push({ x: e[key].pageX, y: e[key].pageY }); } else if (e[key].clientX) { result.push({ x: e[key].clientX, y: e[key].clientY }); } } return result; }; var distance = function (point2, point1, what) { if (!point1 || !point2) { return 0; } if (what === "x") { return point2.x - point1.x; } else if (what === "y") { return point2.y - point1.y; } return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)); }; var isClickable = function ($el) { if ( $el.is('a,area,button,[role="button"],input,label,select,summary,textarea,video,audio,iframe') || $.isFunction($el.get(0).onclick) || $el.data("selectable") ) { return true; } // Check for attributes like data-fancybox-next or data-fancybox-close for (var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++) { if (atts[i].nodeName.substr(0, 14) === "data-fancybox-") { return true; } } return false; }; var hasScrollbars = function (el) { var overflowY = window.getComputedStyle(el)["overflow-y"], overflowX = window.getComputedStyle(el)["overflow-x"], vertical = (overflowY === "scroll" || overflowY === "auto") && el.scrollHeight > el.clientHeight, horizontal = (overflowX === "scroll" || overflowX === "auto") && el.scrollWidth > el.clientWidth; return vertical || horizontal; }; var isScrollable = function ($el) { var rez = false; while (true) { rez = hasScrollbars($el.get(0)); if (rez) { break; } $el = $el.parent(); if (!$el.length || $el.hasClass("fancybox-stage") || $el.is("body")) { break; } } return rez; }; var Guestures = function (instance) { var self = this; self.instance = instance; self.$bg = instance.$refs.bg; self.$stage = instance.$refs.stage; self.$container = instance.$refs.container; self.destroy(); self.$container.on("touchstart.fb.touch mousedown.fb.touch", $.proxy(self, "ontouchstart")); }; Guestures.prototype.destroy = function () { var self = this; self.$container.off(".fb.touch"); $(document).off(".fb.touch"); if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } if (self.tapped) { clearTimeout(self.tapped); self.tapped = null; } }; Guestures.prototype.ontouchstart = function (e) { var self = this, $target = $(e.target), instance = self.instance, current = instance.current, $slide = current.$slide, $content = current.$content, isTouchDevice = e.type == "touchstart"; // Do not respond to both (touch and mouse) events if (isTouchDevice) { self.$container.off("mousedown.fb.touch"); } // Ignore right click if (e.originalEvent && e.originalEvent.button == 2) { return; } // Ignore taping on links, buttons, input elements if (!$slide.length || !$target.length || isClickable($target) || isClickable($target.parent())) { return; } // Ignore clicks on the scrollbar if (!$target.is("img") && e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left) { return; } // Ignore clicks while zooming or closing if (!current || instance.isAnimating || current.$slide.hasClass("fancybox-animated")) { e.stopPropagation(); e.preventDefault(); return; } self.realPoints = self.startPoints = getPointerXY(e); if (!self.startPoints.length) { return; } // Allow other scripts to catch touch event if "touch" is set to false if (current.touch) { e.stopPropagation(); } self.startEvent = e; self.canTap = true; self.$target = $target; self.$content = $content; self.opts = current.opts.touch; self.isPanning = false; self.isSwiping = false; self.isZooming = false; self.isScrolling = false; self.canPan = instance.canPan(); self.startTime = new Date().getTime(); self.distanceX = self.distanceY = self.distance = 0; self.canvasWidth = Math.round($slide[0].clientWidth); self.canvasHeight = Math.round($slide[0].clientHeight); self.contentLastPos = null; self.contentStartPos = $.fancybox.getTranslate(self.$content) || { top: 0, left: 0 }; self.sliderStartPos = $.fancybox.getTranslate($slide); // Since position will be absolute, but we need to make it relative to the stage self.stagePos = $.fancybox.getTranslate(instance.$refs.stage); self.sliderStartPos.top -= self.stagePos.top; self.sliderStartPos.left -= self.stagePos.left; self.contentStartPos.top -= self.stagePos.top; self.contentStartPos.left -= self.stagePos.left; $(document) .off(".fb.touch") .on(isTouchDevice ? "touchend.fb.touch touchcancel.fb.touch" : "mouseup.fb.touch mouseleave.fb.touch", $.proxy(self, "ontouchend")) .on(isTouchDevice ? "touchmove.fb.touch" : "mousemove.fb.touch", $.proxy(self, "ontouchmove")); if ($.fancybox.isMobile) { document.addEventListener("scroll", self.onscroll, true); } // Skip if clicked outside the sliding area if (!(self.opts || self.canPan) || !($target.is(self.$stage) || self.$stage.find($target).length)) { if ($target.is(".fancybox-image")) { e.preventDefault(); } if (!($.fancybox.isMobile && $target.parents(".fancybox-caption").length)) { return; } } self.isScrollable = isScrollable($target) || isScrollable($target.parent()); // Check if element is scrollable and try to prevent default behavior (scrolling) if (!($.fancybox.isMobile && self.isScrollable)) { e.preventDefault(); } // One finger or mouse click - swipe or pan an image if (self.startPoints.length === 1 || current.hasError) { if (self.canPan) { $.fancybox.stop(self.$content); self.isPanning = true; } else { self.isSwiping = true; } self.$container.addClass("fancybox-is-grabbing"); } // Two fingers - zoom image if (self.startPoints.length === 2 && current.type === "image" && (current.isLoaded || current.$ghost)) { self.canTap = false; self.isSwiping = false; self.isPanning = false; self.isZooming = true; $.fancybox.stop(self.$content); self.centerPointStartX = (self.startPoints[0].x + self.startPoints[1].x) * 0.5 - $(window).scrollLeft(); self.centerPointStartY = (self.startPoints[0].y + self.startPoints[1].y) * 0.5 - $(window).scrollTop(); self.percentageOfImageAtPinchPointX = (self.centerPointStartX - self.contentStartPos.left) / self.contentStartPos.width; self.percentageOfImageAtPinchPointY = (self.centerPointStartY - self.contentStartPos.top) / self.contentStartPos.height; self.startDistanceBetweenFingers = distance(self.startPoints[0], self.startPoints[1]); } }; Guestures.prototype.onscroll = function (e) { var self = this; self.isScrolling = true; document.removeEventListener("scroll", self.onscroll, true); }; Guestures.prototype.ontouchmove = function (e) { var self = this; // Make sure user has not released over iframe or disabled element if (e.originalEvent.buttons !== undefined && e.originalEvent.buttons === 0) { self.ontouchend(e); return; } if (self.isScrolling) { self.canTap = false; return; } self.newPoints = getPointerXY(e); if (!(self.opts || self.canPan) || !self.newPoints.length || !self.newPoints.length) { return; } if (!(self.isSwiping && self.isSwiping === true)) { e.preventDefault(); } self.distanceX = distance(self.newPoints[0], self.startPoints[0], "x"); self.distanceY = distance(self.newPoints[0], self.startPoints[0], "y"); self.distance = distance(self.newPoints[0], self.startPoints[0]); // Skip false ontouchmove events (Chrome) if (self.distance > 0) { if (self.isSwiping) { self.onSwipe(e); } else if (self.isPanning) { self.onPan(); } else if (self.isZooming) { self.onZoom(); } } }; Guestures.prototype.onSwipe = function (e) { var self = this, instance = self.instance, swiping = self.isSwiping, left = self.sliderStartPos.left || 0, angle; // If direction is not yet determined if (swiping === true) { // We need at least 10px distance to correctly calculate an angle if (Math.abs(self.distance) > 10) { self.canTap = false; if (instance.group.length < 2 && self.opts.vertical) { self.isSwiping = "y"; } else if (instance.isDragging || self.opts.vertical === false || (self.opts.vertical === "auto" && $(window).width() > 800)) { self.isSwiping = "x"; } else { angle = Math.abs((Math.atan2(self.distanceY, self.distanceX) * 180) / Math.PI); self.isSwiping = angle > 45 && angle < 135 ? "y" : "x"; } if (self.isSwiping === "y" && $.fancybox.isMobile && self.isScrollable) { self.isScrolling = true; return; } instance.isDragging = self.isSwiping; // Reset points to avoid jumping, because we dropped first swipes to calculate the angle self.startPoints = self.newPoints; $.each(instance.slides, function (index, slide) { var slidePos, stagePos; $.fancybox.stop(slide.$slide); slidePos = $.fancybox.getTranslate(slide.$slide); stagePos = $.fancybox.getTranslate(instance.$refs.stage); slide.$slide .css({ transform: "", opacity: "", "transition-duration": "" }) .removeClass("fancybox-animated") .removeClass(function (index, className) { return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" "); }); if (slide.pos === instance.current.pos) { self.sliderStartPos.top = slidePos.top - stagePos.top; self.sliderStartPos.left = slidePos.left - stagePos.left; } $.fancybox.setTranslate(slide.$slide, { top: slidePos.top - stagePos.top, left: slidePos.left - stagePos.left }); }); // Stop slideshow if (instance.SlideShow && instance.SlideShow.isActive) { instance.SlideShow.stop(); } } return; } // Sticky edges if (swiping == "x") { if ( self.distanceX > 0 && (self.instance.group.length < 2 || (self.instance.current.index === 0 && !self.instance.current.opts.loop)) ) { left = left + Math.pow(self.distanceX, 0.8); } else if ( self.distanceX < 0 && (self.instance.group.length < 2 || (self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop)) ) { left = left - Math.pow(-self.distanceX, 0.8); } else { left = left + self.distanceX; } } self.sliderLastPos = { top: swiping == "x" ? 0 : self.sliderStartPos.top + self.distanceY, left: left }; if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.requestId = requestAFrame(function () { if (self.sliderLastPos) { $.each(self.instance.slides, function (index, slide) { var pos = slide.pos - self.instance.currPos; $.fancybox.setTranslate(slide.$slide, { top: self.sliderLastPos.top, left: self.sliderLastPos.left + pos * self.canvasWidth + pos * slide.opts.gutter }); }); self.$container.addClass("fancybox-is-sliding"); } }); }; Guestures.prototype.onPan = function () { var self = this; // Prevent accidental movement (sometimes, when tapping casually, finger can move a bit) if (distance(self.newPoints[0], self.realPoints[0]) < ($.fancybox.isMobile ? 10 : 5)) { self.startPoints = self.newPoints; return; } self.canTap = false; self.contentLastPos = self.limitMovement(); if (self.requestId) { cancelAFrame(self.requestId); } self.requestId = requestAFrame(function () { $.fancybox.setTranslate(self.$content, self.contentLastPos); }); }; // Make panning sticky to the edges Guestures.prototype.limitMovement = function () { var self = this; var canvasWidth = self.canvasWidth; var canvasHeight = self.canvasHeight; var distanceX = self.distanceX; var distanceY = self.distanceY; var contentStartPos = self.contentStartPos; var currentOffsetX = contentStartPos.left; var currentOffsetY = contentStartPos.top; var currentWidth = contentStartPos.width; var currentHeight = contentStartPos.height; var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY, newOffsetX, newOffsetY; if (currentWidth > canvasWidth) { newOffsetX = currentOffsetX + distanceX; } else { newOffsetX = currentOffsetX; } newOffsetY = currentOffsetY + distanceY; // Slow down proportionally to traveled distance minTranslateX = Math.max(0, canvasWidth * 0.5 - currentWidth * 0.5); minTranslateY = Math.max(0, canvasHeight * 0.5 - currentHeight * 0.5); maxTranslateX = Math.min(canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5); maxTranslateY = Math.min(canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5); // -> if (distanceX > 0 && newOffsetX > minTranslateX) { newOffsetX = minTranslateX - 1 + Math.pow(-minTranslateX + currentOffsetX + distanceX, 0.8) || 0; } // <- if (distanceX < 0 && newOffsetX < maxTranslateX) { newOffsetX = maxTranslateX + 1 - Math.pow(maxTranslateX - currentOffsetX - distanceX, 0.8) || 0; } // \/ if (distanceY > 0 && newOffsetY > minTranslateY) { newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8) || 0; } // /\ if (distanceY < 0 && newOffsetY < maxTranslateY) { newOffsetY = maxTranslateY + 1 - Math.pow(maxTranslateY - currentOffsetY - distanceY, 0.8) || 0; } return { top: newOffsetY, left: newOffsetX }; }; Guestures.prototype.limitPosition = function (newOffsetX, newOffsetY, newWidth, newHeight) { var self = this; var canvasWidth = self.canvasWidth; var canvasHeight = self.canvasHeight; if (newWidth > canvasWidth) { newOffsetX = newOffsetX > 0 ? 0 : newOffsetX; newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX; } else { // Center horizontally newOffsetX = Math.max(0, canvasWidth / 2 - newWidth / 2); } if (newHeight > canvasHeight) { newOffsetY = newOffsetY > 0 ? 0 : newOffsetY; newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY; } else { // Center vertically newOffsetY = Math.max(0, canvasHeight / 2 - newHeight / 2); } return { top: newOffsetY, left: newOffsetX }; }; Guestures.prototype.onZoom = function () { var self = this; // Calculate current distance between points to get pinch ratio and new width and height var contentStartPos = self.contentStartPos; var currentWidth = contentStartPos.width; var currentHeight = contentStartPos.height; var currentOffsetX = contentStartPos.left; var currentOffsetY = contentStartPos.top; var endDistanceBetweenFingers = distance(self.newPoints[0], self.newPoints[1]); var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; var newWidth = Math.floor(currentWidth * pinchRatio); var newHeight = Math.floor(currentHeight * pinchRatio); // This is the translation due to pinch-zooming var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; // Point between the two touches var centerPointEndX = (self.newPoints[0].x + self.newPoints[1].x) / 2 - $(window).scrollLeft(); var centerPointEndY = (self.newPoints[0].y + self.newPoints[1].y) / 2 - $(window).scrollTop(); // And this is the translation due to translation of the centerpoint // between the two fingers var translateFromTranslatingX = centerPointEndX - self.centerPointStartX; var translateFromTranslatingY = centerPointEndY - self.centerPointStartY; // The new offset is the old/current one plus the total translation var newOffsetX = currentOffsetX + (translateFromZoomingX + translateFromTranslatingX); var newOffsetY = currentOffsetY + (translateFromZoomingY + translateFromTranslatingY); var newPos = { top: newOffsetY, left: newOffsetX, scaleX: pinchRatio, scaleY: pinchRatio }; self.canTap = false; self.newWidth = newWidth; self.newHeight = newHeight; self.contentLastPos = newPos; if (self.requestId) { cancelAFrame(self.requestId); } self.requestId = requestAFrame(function () { $.fancybox.setTranslate(self.$content, self.contentLastPos); }); }; Guestures.prototype.ontouchend = function (e) { var self = this; var swiping = self.isSwiping; var panning = self.isPanning; var zooming = self.isZooming; var scrolling = self.isScrolling; self.endPoints = getPointerXY(e); self.dMs = Math.max(new Date().getTime() - self.startTime, 1); self.$container.removeClass("fancybox-is-grabbing"); $(document).off(".fb.touch"); document.removeEventListener("scroll", self.onscroll, true); if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.isSwiping = false; self.isPanning = false; self.isZooming = false; self.isScrolling = false; self.instance.isDragging = false; if (self.canTap) { return self.onTap(e); } self.speed = 100; // Speed in px/ms self.velocityX = (self.distanceX / self.dMs) * 0.5; self.velocityY = (self.distanceY / self.dMs) * 0.5; if (panning) { self.endPanning(); } else if (zooming) { self.endZooming(); } else { self.endSwiping(swiping, scrolling); } return; }; Guestures.prototype.endSwiping = function (swiping, scrolling) { var self = this, ret = false, len = self.instance.group.length, distanceX = Math.abs(self.distanceX), canAdvance = swiping == "x" && len > 1 && ((self.dMs > 130 && distanceX > 10) || distanceX > 50), speedX = 300; self.sliderLastPos = null; // Close if swiped vertically / navigate if horizontally if (swiping == "y" && !scrolling && Math.abs(self.distanceY) > 50) { // Continue vertical movement $.fancybox.animate( self.instance.current.$slide, { top: self.sliderStartPos.top + self.distanceY + self.velocityY * 150, opacity: 0 }, 200 ); ret = self.instance.close(true, 250); } else if (canAdvance && self.distanceX > 0) { ret = self.instance.previous(speedX); } else if (canAdvance && self.distanceX < 0) { ret = self.instance.next(speedX); } if (ret === false && (swiping == "x" || swiping == "y")) { self.instance.centerSlide(200); } self.$container.removeClass("fancybox-is-sliding"); }; // Limit panning from edges // ======================== Guestures.prototype.endPanning = function () { var self = this, newOffsetX, newOffsetY, newPos; if (!self.contentLastPos) { return; } if (self.opts.momentum === false || self.dMs > 350) { newOffsetX = self.contentLastPos.left; newOffsetY = self.contentLastPos.top; } else { // Continue movement newOffsetX = self.contentLastPos.left + self.velocityX * 500; newOffsetY = self.contentLastPos.top + self.velocityY * 500; } newPos = self.limitPosition(newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height); newPos.width = self.contentStartPos.width; newPos.height = self.contentStartPos.height; $.fancybox.animate(self.$content, newPos, 366); }; Guestures.prototype.endZooming = function () { var self = this; var current = self.instance.current; var newOffsetX, newOffsetY, newPos, reset; var newWidth = self.newWidth; var newHeight = self.newHeight; if (!self.contentLastPos) { return; } newOffsetX = self.contentLastPos.left; newOffsetY = self.contentLastPos.top; reset = { top: newOffsetY, left: newOffsetX, width: newWidth, height: newHeight, scaleX: 1, scaleY: 1 }; // Reset scalex/scaleY values; this helps for perfomance and does not break animation $.fancybox.setTranslate(self.$content, reset); if (newWidth < self.canvasWidth && newHeight < self.canvasHeight) { self.instance.scaleToFit(150); } else if (newWidth > current.width || newHeight > current.height) { self.instance.scaleToActual(self.centerPointStartX, self.centerPointStartY, 150); } else { newPos = self.limitPosition(newOffsetX, newOffsetY, newWidth, newHeight); $.fancybox.animate(self.$content, newPos, 150); } }; Guestures.prototype.onTap = function (e) { var self = this; var $target = $(e.target); var instance = self.instance; var current = instance.current; var endPoints = (e && getPointerXY(e)) || self.startPoints; var tapX = endPoints[0] ? endPoints[0].x - $(window).scrollLeft() - self.stagePos.left : 0; var tapY = endPoints[0] ? endPoints[0].y - $(window).scrollTop() - self.stagePos.top : 0; var where; var process = function (prefix) { var action = current.opts[prefix]; if ($.isFunction(action)) { action = action.apply(instance, [current, e]); } if (!action) { return; } switch (action) { case "close": instance.close(self.startEvent); break; case "toggleControls": instance.toggleControls(); break; case "next": instance.next(); break; case "nextOrClose": if (instance.group.length > 1) { instance.next(); } else { instance.close(self.startEvent); } break; case "zoom": if (current.type == "image" && (current.isLoaded || current.$ghost)) { if (instance.canPan()) { instance.scaleToFit(); } else if (instance.isScaledDown()) { instance.scaleToActual(tapX, tapY); } else if (instance.group.length < 2) { instance.close(self.startEvent); } } break; } }; // Ignore right click if (e.originalEvent && e.originalEvent.button == 2) { return; } // Skip if clicked on the scrollbar if (!$target.is("img") && tapX > $target[0].clientWidth + $target.offset().left) { return; } // Check where is clicked if ($target.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container")) { where = "Outside"; } else if ($target.is(".fancybox-slide")) { where = "Slide"; } else if ( instance.current.$content && instance.current.$content .find($target) .addBack() .filter($target).length ) { where = "Content"; } else { return; } // Check if this is a double tap if (self.tapped) { // Stop previously created single tap clearTimeout(self.tapped); self.tapped = null; // Skip if distance between taps is too big if (Math.abs(tapX - self.tapX) > 50 || Math.abs(tapY - self.tapY) > 50) { return this; } // OK, now we assume that this is a double-tap process("dblclick" + where); } else { // Single tap will be processed if user has not clicked second time within 300ms // or there is no need to wait for double-tap self.tapX = tapX; self.tapY = tapY; if (current.opts["dblclick" + where] && current.opts["dblclick" + where] !== current.opts["click" + where]) { self.tapped = setTimeout(function () { self.tapped = null; if (!instance.isAnimating) { process("click" + where); } }, 500); } else { process("click" + where); } } return this; }; $(document) .on("onActivate.fb", function (e, instance) { if (instance && !instance.Guestures) { instance.Guestures = new Guestures(instance); } }) .on("beforeClose.fb", function (e, instance) { if (instance && instance.Guestures) { instance.Guestures.destroy(); } }); })(window, document, jQuery); // ========================================================================== // // SlideShow // Enables slideshow functionality // // Example of usage: // $.fancybox.getInstance().SlideShow.start() // // ========================================================================== (function (document, $) { "use strict"; $.extend(true, $.fancybox.defaults, { btnTpl: { slideShow: '" }, slideShow: { autoStart: false, speed: 3000, progress: true } }); var SlideShow = function (instance) { this.instance = instance; this.init(); }; $.extend(SlideShow.prototype, { timer: null, isActive: false, $button: null, init: function () { var self = this, instance = self.instance, opts = instance.group[instance.currIndex].opts.slideShow; self.$button = instance.$refs.toolbar.find("[data-fancybox-play]").on("click", function () { self.toggle(); }); if (instance.group.length < 2 || !opts) { self.$button.hide(); } else if (opts.progress) { self.$progress = $('
').appendTo(instance.$refs.inner); } }, set: function (force) { var self = this, instance = self.instance, current = instance.current; // Check if reached last element if (current && (force === true || current.opts.loop || instance.currIndex < instance.group.length - 1)) { if (self.isActive && current.contentType !== "video") { if (self.$progress) { $.fancybox.animate(self.$progress.show(), { scaleX: 1 }, current.opts.slideShow.speed); } self.timer = setTimeout(function () { if (!instance.current.opts.loop && instance.current.index == instance.group.length - 1) { instance.jumpTo(0); } else { instance.next(); } }, current.opts.slideShow.speed); } } else { self.stop(); instance.idleSecondsCounter = 0; instance.showControls(); } }, clear: function () { var self = this; clearTimeout(self.timer); self.timer = null; if (self.$progress) { self.$progress.removeAttr("style").hide(); } }, start: function () { var self = this, current = self.instance.current; if (current) { self.$button .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_STOP) .removeClass("fancybox-button--play") .addClass("fancybox-button--pause"); self.isActive = true; if (current.isComplete) { self.set(true); } self.instance.trigger("onSlideShowChange", true); } }, stop: function () { var self = this, current = self.instance.current; self.clear(); self.$button .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_START) .removeClass("fancybox-button--pause") .addClass("fancybox-button--play"); self.isActive = false; self.instance.trigger("onSlideShowChange", false); if (self.$progress) { self.$progress.removeAttr("style").hide(); } }, toggle: function () { var self = this; if (self.isActive) { self.stop(); } else { self.start(); } } }); $(document).on({ "onInit.fb": function (e, instance) { if (instance && !instance.SlideShow) { instance.SlideShow = new SlideShow(instance); } }, "beforeShow.fb": function (e, instance, current, firstRun) { var SlideShow = instance && instance.SlideShow; if (firstRun) { if (SlideShow && current.opts.slideShow.autoStart) { SlideShow.start(); } } else if (SlideShow && SlideShow.isActive) { SlideShow.clear(); } }, "afterShow.fb": function (e, instance, current) { var SlideShow = instance && instance.SlideShow; if (SlideShow && SlideShow.isActive) { SlideShow.set(); } }, "afterKeydown.fb": function (e, instance, current, keypress, keycode) { var SlideShow = instance && instance.SlideShow; // "P" or Spacebar if (SlideShow && current.opts.slideShow && (keycode === 80 || keycode === 32) && !$(document.activeElement).is("button,a,input")) { keypress.preventDefault(); SlideShow.toggle(); } }, "beforeClose.fb onDeactivate.fb": function (e, instance) { var SlideShow = instance && instance.SlideShow; if (SlideShow) { SlideShow.stop(); } } }); // Page Visibility API to pause slideshow when window is not active $(document).on("visibilitychange", function () { var instance = $.fancybox.getInstance(), SlideShow = instance && instance.SlideShow; if (SlideShow && SlideShow.isActive) { if (document.hidden) { SlideShow.clear(); } else { SlideShow.set(); } } }); })(document, jQuery); // ========================================================================== // // FullScreen // Adds fullscreen functionality // // ========================================================================== (function (document, $) { "use strict"; // Collection of methods supported by user browser var fn = (function () { var fnMap = [ ["requestFullscreen", "exitFullscreen", "fullscreenElement", "fullscreenEnabled", "fullscreenchange", "fullscreenerror"], // new WebKit [ "webkitRequestFullscreen", "webkitExitFullscreen", "webkitFullscreenElement", "webkitFullscreenEnabled", "webkitfullscreenchange", "webkitfullscreenerror" ], // old WebKit (Safari 5.1) [ "webkitRequestFullScreen", "webkitCancelFullScreen", "webkitCurrentFullScreenElement", "webkitCancelFullScreen", "webkitfullscreenchange", "webkitfullscreenerror" ], [ "mozRequestFullScreen", "mozCancelFullScreen", "mozFullScreenElement", "mozFullScreenEnabled", "mozfullscreenchange", "mozfullscreenerror" ], ["msRequestFullscreen", "msExitFullscreen", "msFullscreenElement", "msFullscreenEnabled", "MSFullscreenChange", "MSFullscreenError"] ]; var ret = {}; for (var i = 0; i < fnMap.length; i++) { var val = fnMap[i]; if (val && val[1] in document) { for (var j = 0; j < val.length; j++) { ret[fnMap[0][j]] = val[j]; } return ret; } } return false; })(); if (fn) { var FullScreen = { request: function (elem) { elem = elem || document.documentElement; elem[fn.requestFullscreen](elem.ALLOW_KEYBOARD_INPUT); }, exit: function () { document[fn.exitFullscreen](); }, toggle: function (elem) { elem = elem || document.documentElement; if (this.isFullscreen()) { this.exit(); } else { this.request(elem); } }, isFullscreen: function () { return Boolean(document[fn.fullscreenElement]); }, enabled: function () { return Boolean(document[fn.fullscreenEnabled]); } }; $.extend(true, $.fancybox.defaults, { btnTpl: { fullScreen: '" }, fullScreen: { autoStart: false } }); $(document).on(fn.fullscreenchange, function () { var isFullscreen = FullScreen.isFullscreen(), instance = $.fancybox.getInstance(); if (instance) { // If image is zooming, then force to stop and reposition properly if (instance.current && instance.current.type === "image" && instance.isAnimating) { instance.isAnimating = false; instance.update(true, true, 0); if (!instance.isComplete) { instance.complete(); } } instance.trigger("onFullscreenChange", isFullscreen); instance.$refs.container.toggleClass("fancybox-is-fullscreen", isFullscreen); instance.$refs.toolbar .find("[data-fancybox-fullscreen]") .toggleClass("fancybox-button--fsenter", !isFullscreen) .toggleClass("fancybox-button--fsexit", isFullscreen); } }); } $(document).on({ "onInit.fb": function (e, instance) { var $container; if (!fn) { instance.$refs.toolbar.find("[data-fancybox-fullscreen]").remove(); return; } if (instance && instance.group[instance.currIndex].opts.fullScreen) { $container = instance.$refs.container; $container.on("click.fb-fullscreen", "[data-fancybox-fullscreen]", function (e) { e.stopPropagation(); e.preventDefault(); FullScreen.toggle(); }); if (instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true) { FullScreen.request(); } // Expose API instance.FullScreen = FullScreen; } else if (instance) { instance.$refs.toolbar.find("[data-fancybox-fullscreen]").hide(); } }, "afterKeydown.fb": function (e, instance, current, keypress, keycode) { // "F" if (instance && instance.FullScreen && keycode === 70) { keypress.preventDefault(); instance.FullScreen.toggle(); } }, "beforeClose.fb": function (e, instance) { if (instance && instance.FullScreen && instance.$refs.container.hasClass("fancybox-is-fullscreen")) { FullScreen.exit(); } } }); })(document, jQuery); // ========================================================================== // // Thumbs // Displays thumbnails in a grid // // ========================================================================== (function (document, $) { "use strict"; var CLASS = "fancybox-thumbs", CLASS_ACTIVE = CLASS + "-active"; // Make sure there are default values $.fancybox.defaults = $.extend( true, { btnTpl: { thumbs: '" }, thumbs: { autoStart: false, // Display thumbnails on opening hideOnClose: true, // Hide thumbnail grid when closing animation starts parentEl: ".fancybox-container", // Container is injected into this element axis: "y" // Vertical (y) or horizontal (x) scrolling } }, $.fancybox.defaults ); var FancyThumbs = function (instance) { this.init(instance); }; $.extend(FancyThumbs.prototype, { $button: null, $grid: null, $list: null, isVisible: false, isActive: false, init: function (instance) { var self = this, group = instance.group, enabled = 0; self.instance = instance; self.opts = group[instance.currIndex].opts.thumbs; instance.Thumbs = self; self.$button = instance.$refs.toolbar.find("[data-fancybox-thumbs]"); // Enable thumbs if at least two group items have thumbnails for (var i = 0, len = group.length; i < len; i++) { if (group[i].thumb) { enabled++; } if (enabled > 1) { break; } } if (enabled > 1 && !!self.opts) { self.$button.removeAttr("style").on("click", function () { self.toggle(); }); self.isActive = true; } else { self.$button.hide(); } }, create: function () { var self = this, instance = self.instance, parentEl = self.opts.parentEl, list = [], src; if (!self.$grid) { // Create main element self.$grid = $('
').appendTo( instance.$refs.container .find(parentEl) .addBack() .filter(parentEl) ); // Add "click" event that performs gallery navigation self.$grid.on("click", "a", function () { instance.jumpTo($(this).attr("data-index")); }); } // Build the list if (!self.$list) { self.$list = $('
').appendTo(self.$grid); } $.each(instance.group, function (i, item) { src = item.thumb; if (!src && item.type === "image") { src = item.src; } list.push( '" ); }); self.$list[0].innerHTML = list.join(""); if (self.opts.axis === "x") { // Set fixed width for list element to enable horizontal scrolling self.$list.width( parseInt(self.$grid.css("padding-right"), 10) + instance.group.length * self.$list .children() .eq(0) .outerWidth(true) ); } }, focus: function (duration) { var self = this, $list = self.$list, $grid = self.$grid, thumb, thumbPos; if (!self.instance.current) { return; } thumb = $list .children() .removeClass(CLASS_ACTIVE) .filter('[data-index="' + self.instance.current.index + '"]') .addClass(CLASS_ACTIVE); thumbPos = thumb.position(); // Check if need to scroll to make current thumb visible if (self.opts.axis === "y" && (thumbPos.top < 0 || thumbPos.top > $list.height() - thumb.outerHeight())) { $list.stop().animate({ scrollTop: $list.scrollTop() + thumbPos.top }, duration ); } else if ( self.opts.axis === "x" && (thumbPos.left < $grid.scrollLeft() || thumbPos.left > $grid.scrollLeft() + ($grid.width() - thumb.outerWidth())) ) { $list .parent() .stop() .animate({ scrollLeft: thumbPos.left }, duration ); } }, update: function () { var that = this; that.instance.$refs.container.toggleClass("fancybox-show-thumbs", this.isVisible); if (that.isVisible) { if (!that.$grid) { that.create(); } that.instance.trigger("onThumbsShow"); that.focus(0); } else if (that.$grid) { that.instance.trigger("onThumbsHide"); } // Update content position that.instance.update(); }, hide: function () { this.isVisible = false; this.update(); }, show: function () { this.isVisible = true; this.update(); }, toggle: function () { this.isVisible = !this.isVisible; this.update(); } }); $(document).on({ "onInit.fb": function (e, instance) { var Thumbs; if (instance && !instance.Thumbs) { Thumbs = new FancyThumbs(instance); if (Thumbs.isActive && Thumbs.opts.autoStart === true) { Thumbs.show(); } } }, "beforeShow.fb": function (e, instance, item, firstRun) { var Thumbs = instance && instance.Thumbs; if (Thumbs && Thumbs.isVisible) { Thumbs.focus(firstRun ? 0 : 250); } }, "afterKeydown.fb": function (e, instance, current, keypress, keycode) { var Thumbs = instance && instance.Thumbs; // "G" if (Thumbs && Thumbs.isActive && keycode === 71) { keypress.preventDefault(); Thumbs.toggle(); } }, "beforeClose.fb": function (e, instance) { var Thumbs = instance && instance.Thumbs; if (Thumbs && Thumbs.isVisible && Thumbs.opts.hideOnClose !== false) { Thumbs.$grid.hide(); } } }); })(document, jQuery); //// ========================================================================== // // Share // Displays simple form for sharing current url // // ========================================================================== (function (document, $) { "use strict"; $.extend(true, $.fancybox.defaults, { btnTpl: { share: '" }, share: { url: function (instance, item) { return ( (!instance.currentHash && !(item.type === "inline" || item.type === "html") ? item.origSrc || item.src : false) || window.location ); }, tpl: '
' + "

{{SHARE}}

" + "

" + '' + '' + "Facebook" + "" + '' + '' + "Twitter" + "" + '' + '' + "Pinterest" + "" + "

" + '

' + "
" } }); function escapeHtml(string) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", "`": "`", "=": "=" }; return String(string).replace(/[&<>"'`=\/]/g, function (s) { return entityMap[s]; }); } $(document).on("click", "[data-fancybox-share]", function () { var instance = $.fancybox.getInstance(), current = instance.current || null, url, tpl; if (!current) { return; } if ($.type(current.opts.share.url) === "function") { url = current.opts.share.url.apply(current, [instance, current]); } tpl = current.opts.share.tpl .replace(/\{\{media\}\}/g, current.type === "image" ? encodeURIComponent(current.src) : "") .replace(/\{\{url\}\}/g, encodeURIComponent(url)) .replace(/\{\{url_raw\}\}/g, escapeHtml(url)) .replace(/\{\{descr\}\}/g, instance.$caption ? encodeURIComponent(instance.$caption.text()) : ""); $.fancybox.open({ src: instance.translate(instance, tpl), type: "html", opts: { touch: false, animationEffect: false, afterLoad: function (shareInstance, shareCurrent) { // Close self if parent instance is closing instance.$refs.container.one("beforeClose.fb", function () { shareInstance.close(null, 0); }); // Opening links in a popup window shareCurrent.$content.find(".fancybox-share__button").click(function () { window.open(this.href, "Share", "width=550, height=450"); return false; }); }, mobile: { autoFocus: false } } }); }); })(document, jQuery); // ========================================================================== // // Hash // Enables linking to each modal // // ========================================================================== (function (window, document, $) { "use strict"; // Simple $.escapeSelector polyfill (for jQuery prior v3) if (!$.escapeSelector) { $.escapeSelector = function (sel) { var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; var fcssescape = function (ch, asCodePoint) { if (asCodePoint) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if (ch === "\0") { return "\uFFFD"; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " "; } // Other potentially-special ASCII characters get backslash-escaped return "\\" + ch; }; return (sel + "").replace(rcssescape, fcssescape); }; } // Get info about gallery name and current index from url function parseUrl() { var hash = window.location.hash.substr(1), rez = hash.split("-"), index = rez.length > 1 && /^\+?\d+$/.test(rez[rez.length - 1]) ? parseInt(rez.pop(-1), 10) || 1 : 1, gallery = rez.join("-"); return { hash: hash, /* Index is starting from 1 */ index: index < 1 ? 1 : index, gallery: gallery }; } // Trigger click evnt on links to open new fancyBox instance function triggerFromUrl(url) { if (url.gallery !== "") { // If we can find element matching 'data-fancybox' atribute, // then triggering click event should start fancyBox $("[data-fancybox='" + $.escapeSelector(url.gallery) + "']") .eq(url.index - 1) .focus() .trigger("click.fb-start"); } } // Get gallery name from current instance function getGalleryID(instance) { var opts, ret; if (!instance) { return false; } opts = instance.current ? instance.current.opts : instance.opts; ret = opts.hash || (opts.$orig ? opts.$orig.data("fancybox") || opts.$orig.data("fancybox-trigger") : ""); return ret === "" ? false : ret; } // Start when DOM becomes ready $(function () { // Check if user has disabled this module if ($.fancybox.defaults.hash === false) { return; } // Update hash when opening/closing fancyBox $(document).on({ "onInit.fb": function (e, instance) { var url, gallery; if (instance.group[instance.currIndex].opts.hash === false) { return; } url = parseUrl(); gallery = getGalleryID(instance); // Make sure gallery start index matches index from hash if (gallery && url.gallery && gallery == url.gallery) { instance.currIndex = url.index - 1; } }, "beforeShow.fb": function (e, instance, current, firstRun) { var gallery; if (!current || current.opts.hash === false) { return; } // Check if need to update window hash gallery = getGalleryID(instance); if (!gallery) { return; } // Variable containing last hash value set by fancyBox // It will be used to determine if fancyBox needs to close after hash change is detected instance.currentHash = gallery + (instance.group.length > 1 ? "-" + (current.index + 1) : ""); // If current hash is the same (this instance most likely is opened by hashchange), then do nothing if (window.location.hash === "#" + instance.currentHash) { return; } if (firstRun && !instance.origHash) { instance.origHash = window.location.hash; } if (instance.hashTimer) { clearTimeout(instance.hashTimer); } // Update hash instance.hashTimer = setTimeout(function () { if ("replaceState" in window.history) { window.history[firstRun ? "pushState" : "replaceState"]({}, document.title, window.location.pathname + window.location.search + "#" + instance.currentHash ); if (firstRun) { instance.hasCreatedHistory = true; } } else { window.location.hash = instance.currentHash; } instance.hashTimer = null; }, 300); }, "beforeClose.fb": function (e, instance, current) { if (!current || current.opts.hash === false) { return; } clearTimeout(instance.hashTimer); // Goto previous history entry if (instance.currentHash && instance.hasCreatedHistory) { window.history.back(); } else if (instance.currentHash) { if ("replaceState" in window.history) { window.history.replaceState({}, document.title, window.location.pathname + window.location.search + (instance.origHash || "")); } else { window.location.hash = instance.origHash; } } instance.currentHash = null; } }); // Check if need to start/close after url has changed $(window).on("hashchange.fb", function () { var url = parseUrl(), fb = null; // Find last fancyBox instance that has "hash" $.each( $(".fancybox-container") .get() .reverse(), function (index, value) { var tmp = $(value).data("FancyBox"); if (tmp && tmp.currentHash) { fb = tmp; return false; } } ); if (fb) { // Now, compare hash values if (fb.currentHash !== url.gallery + "-" + url.index && !(url.index === 1 && fb.currentHash == url.gallery)) { fb.currentHash = null; fb.close(); } } else if (url.gallery !== "") { triggerFromUrl(url); } }); // Check current hash and trigger click event on matching element to start fancyBox, if needed setTimeout(function () { if (!$.fancybox.getInstance()) { triggerFromUrl(parseUrl()); } }, 50); }); })(window, document, jQuery); // ========================================================================== // // Wheel // Basic mouse weheel support for gallery navigation // // ========================================================================== (function (document, $) { "use strict"; var prevTime = new Date().getTime(); $(document).on({ "onInit.fb": function (e, instance, current) { instance.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll", function (e) { var current = instance.current, currTime = new Date().getTime(); if (instance.group.length < 2 || current.opts.wheel === false || (current.opts.wheel === "auto" && current.type !== "image")) { return; } e.preventDefault(); e.stopPropagation(); if (current.$slide.hasClass("fancybox-animated")) { return; } e = e.originalEvent || e; if (currTime - prevTime < 250) { return; } prevTime = currTime; instance[(-e.deltaY || -e.deltaX || e.wheelDelta || -e.detail) < 0 ? "next" : "previous"](); }); } }); })(document, jQuery); ================================================ FILE: docs/index.html ================================================ fancybox3 · Documentation

Introduction

Get started with fancybox, probably the world’s most popular lightbox script.

Dependencies

jQuery 3+ is preferred, but fancybox works with jQuery 1.9.1+ and jQuery 2+

Compatibility

fancybox includes support for touch gestures and even supports pinch gestures for zooming. It is perfectly suited for both mobile and desktop browsers.

fancybox has been tested in following browsers/devices:

  • Chrome
  • Firefox
  • IE10/11
  • Edge
  • iOS Safari
  • Android 7.0 Tablet

Setup

You can install fancybox by linking .css and .js files to your html file. Make sure you also load the jQuery library. Below is a basic HTML template to use as an example:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>My page</title>

	<!-- CSS -->
	<link rel="stylesheet" type="text/css" href="jquery.fancybox.min.css">
</head>
<body>

	<!-- Your HTML content goes here -->

	<!-- JS -->
	<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
	<script src="jquery.fancybox.min.js"></script>
</body>
</html>

Download fancybox

Download the latest version of fancybox on GitHub.
Or just link directly to fancybox files on cdnjs - https://cdnjs.com/libraries/fancybox.

Package Managers

fancybox is also available on npm and Bower.

# NPM
npm install @fancyapps/fancybox --save

# Bower
bower install fancybox --save

Important

  • Make sure you add the jQuery library before the fancybox JS file
  • If you already have jQuery on your page, you shouldn't include it second time
  • Do not include both fancybox.js and fancybox.min.js files
  • Some functionality (ajax, iframes, etc) will not work when you're opening local file directly on your browser, the code must be running on a web server

How to Use

Initialize with data attributes

The most basic way to use fancybox is by adding the data-fancybox attribute to your element. This will automatically bind click event that will start fancybox. Use href or data-src attribute to specify source of your content. Example:

<a href="image.jpg" data-fancybox data-caption="Caption for single image">
	<img src="thumbnail.jpg" alt="" />
</a>

View demo on CodePen

If you have a group of items, you can use the same attribute data-fancybox value for each of them to create a gallery. Each group should have a unique value. Example:

<a href="image_1.jpg" data-fancybox="gallery" data-caption="Caption #1">
	<img src="thumbnail_1.jpg" alt="" />
</a>

<a href="image_2.jpg" data-fancybox="gallery" data-caption="Caption #2">
	<img src="thumbnail_2.jpg" alt="" />
</a>

View demo on CodePen

If you choose this method, default settings will be applied. See options section for examples how to customize by changing defaults, using data-options attribute or by initializing with JavaScript.

Info Sometimes you have multiple links pointing to the same source and that creates duplicates in the gallery. To avoid that, simply use data-fancybox-trigger attribute with the same value used for data-fancybox attribute for your other links. Optionally, use data-fancybox-index attribute to specify index of starting element:

<a data-fancybox-trigger="gallery" href="javascript:;">
    <img src="thumbnail_1.jpg" alt="" />
</a>

View demo on CodePen

Initialize with JavaScript

Select your elements with a jQuery selector (you can use any valid selector) and call the fancybox method:

$('[data-fancybox="gallery"]').fancybox({
	// Options will go here
});

View demo on CodePen

Info Sometimes you might need to bind fancybox to dynamically added elements. Use selector option to attach click event listener for elements that exist now or in the future. All selected items will be automatically grouped in the gallery. Example:

$().fancybox({
    selector : '.imglist a:visible'
});

View demo on CodePen

Use with Javascript

You can also open and close fancybox programmatically. Here are a couple of examples, visit API section for more information and demos.

Display simple message:

$.fancybox.open('<div class="message"><h2>Hello!</h2><p>You are awesome!</p></div>');

View demo on CodePen

Display iframed page:

$.fancybox.open({
	src  : 'link-to-your-page.html',
	type : 'iframe',
	opts : {
		afterShow : function( instance, current ) {
			console.info( 'done!' );
		}
	}
});

View demo on CodePen

Important

fancybox attempts to automatically detect the type of content based on the given url. If it cannot be detected, the type can also be set manually using data-type attribute (or type option). Example:

<a href="images.php?id=123" data-type="image" data-caption="Caption">
	Show image
</a>

Media types

fancybox is designed to display images, video, iframes and any HTML content. For your convenience, there is a built in support for inline content and ajax.

Images

The standard way of using fancybox is with a number of thumbnail images that link to larger images:

<a href="image.jpg" data-fancybox="images" data-caption="My caption">
	<img src="thumbnail.jpg" alt="" />
</a>

View demo on CodePen

By default, fancybox fully preloads an image before displaying it. You can choose to display the image right away. It will render and show the full size image while the data is being received. To do so, some attributes are necessary:

  • data-width   - the real width of the image
  • data-height - the real height of the image
<a href="image.jpg" data-fancybox="images" data-width="2048" data-height="1365">
    <img src="thumbnail.jpg" />
</a>

View demo on CodePen

You can also use these width and height properties to control size of the image. This can be used to make images look sharper on retina displays. Example:

$('[data-fancybox="images"]').fancybox({
    afterLoad : function(instance, current) {
        var pixelRatio = window.devicePixelRatio || 1;

        if ( pixelRatio > 1.5 ) {
            current.width  = current.width  / pixelRatio;
            current.height = current.height / pixelRatio;
        }
    }
});

View demo on CodePen

fancybox supports "srcset" so it can display different images based on viewport width. You can use this to improve download times for mobile users and over time save bandwidth. Example:

<a href="medium.jpg" data-fancybox="images" data-srcset="large.jpg 1600w, medium.jpg 1200w, small.jpg 640w">
	<img src="thumbnail.jpg" />
</a>

View demo on CodePen

It is also possible to protect images from downloading by right-click. While this does not protect from truly determined users, it should discourage the vast majority from ripping off your files. Optionally, put the watermark over image.

$('[data-fancybox]').fancybox({
	protect: true
});

View demo on CodePen

Video

YouTube and Vimeo videos can be used with fancybox by just providing the page URL. Link to MP4 video directly or use trigger element to display hidden <video> element.

Use data-width and data-height attributes to customize video dimensions and data-ratio for the aspect ratio.

<a data-fancybox href="https://www.youtube.com/watch?v=_sI_Ps7JSEk">
    YouTube video
</a>

<a data-fancybox href="https://vimeo.com/191947042">
    Vimeo video
</a>

<a data-fancybox data-width="640" data-height="360" href="video.mp4">
    Direct link to MP4 video
</a>

<a data-fancybox href="#myVideo">
    HTML5 video element
</a>

<video width="640" height="320" controls id="myVideo" style="display:none;">
    <source src="https://www.html5rocks.com/en/tutorials/video/basics/Chrome_ImF.mp4" type="video/mp4">
    <source src="https://www.html5rocks.com/en/tutorials/video/basics/Chrome_ImF.webm" type="video/webm">
    <source src="https://www.html5rocks.com/en/tutorials/video/basics/Chrome_ImF.ogv" type="video/ogg">
    Your browser doesn't support HTML5 video tag.
</video>

View demo on CodePen

Controlling YouTube & Vimeo video via URL parameters:

<a data-fancybox href="https://www.youtube.com/watch?v=_sI_Ps7JSEk&amp;autoplay=1&amp;rel=0&amp;controls=0&amp;showinfo=0">
    YouTube video - hide controls and info
</a>

<a data-fancybox href="https://vimeo.com/191947042?color=f00">
    Vimeo video - custom color
</a>

View demo on CodePen

Via JavaScript:

$('[data-fancybox]').fancybox({
    youtube : {
        controls : 0,
        showinfo : 0
    },
    vimeo : {
        color : 'f00'
    }
});

View demo on CodePen

Iframe

If you need to display content from another page, add data-fancybox and data-type="iframe" attributes to your link. This would create <iframe> element that allows to embed an entire web document inside the modal.

<a data-fancybox data-type="iframe" data-src="http://codepen.io/fancyapps/full/jyEGGG/" href="javascript:;">
	Webpage
</a>

<a data-fancybox data-type="iframe" data-src="https://mozilla.github.io/pdf.js/web/viewer.html" href="javascript:;">
    Sample PDF file 
</a>

View demo on CodePen

If you have not disabled iframe preloading (using preload option), the script will atempt to calculate content dimensions and will adjust width/height of <iframe> to fit with content in it. Keep in mind, that due to same origin policy, there are some limitations.

This example will disable iframe preloading and will display small close button next to iframe instead of the toolbar:

$('[data-fancybox]').fancybox({
	toolbar  : false,
	smallBtn : true,
	iframe : {
		preload : false
	}
})

View demo on CodePen

Iframe dimensions can be controlled by CSS:

.fancybox-slide--iframe .fancybox-content {
    width  : 800px;
    height : 600px;
    max-width  : 80%;
    max-height : 80%;
    margin: 0;
}

These CSS rules can be overridden by JS, if needed:

$("[data-fancybox]").fancybox({
    iframe : {
        css : {
            width : '600px'
        }
    }
});

How to access and control fancybox in parent window from inside an iframe:

// Close current fancybox instance
parent.jQuery.fancybox.getInstance().close();

// Adjust iframe height according to the contents
parent.jQuery.fancybox.getInstance().update();

Inline

fancybox can be used to display any HTML element on the page. First, create a hidden element with unique ID:

<div style="display: none;" id="hidden-content">
	<h2>Hello</h2>
	<p>You are awesome.</p>
</div>

Then simply create a link having data-src attribute that matches ID of the element you want to open (preceded by a hash mark (#); in this example - #hidden-content):

<a data-fancybox data-src="#hidden-content" href="javascript:;">
	Trigger the fancybox
</a>

View demo on CodePen

The script will append small close button (if you have not disabled by smallBtn:false) and will not apply any styles except for centering. Therefore you can easily set custom dimensions using CSS.

Info If necessary, you can make your element (and similarly any other html content) scrollable by adding additional wrapping element and some CSS - view demo on CodePen.

Ajax

To load your content via AJAX, you need to add a data-type="ajax" attribute to your link:

<a data-fancybox data-type="ajax" data-src="my_page.com/path/to/ajax/" href="javascript:;">
	AJAX content
</a>

View demo on CodePen

Additionally it is possible to define a selector with the data-filter attribute to show only a part of the response. The selector can be any string, that is a valid jQuery selector:

<a data-fancybox data-type="ajax" data-src="my_page.com/path/to/ajax/" data-filter="#two" href="javascript:;">
	AJAX content
</a>

View demo on CodePen

Options

Quick reference for all default options as defined in the source:

var defaults = {
  // Close existing modals
  // Set this to false if you do not need to stack multiple instances
  closeExisting: false,

  // Enable infinite gallery navigation
  loop: false,

  // Horizontal space between slides
  gutter: 50,

  // Enable keyboard navigation
  keyboard: true,

  // Should allow caption to overlap the content
  preventCaptionOverlap: true,

  // Should display navigation arrows at the screen edges
  arrows: true,

  // Should display counter at the top left corner
  infobar: true,

  // Should display close button (using `btnTpl.smallBtn` template) over the content
  // Can be true, false, "auto"
  // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items
  smallBtn: "auto",

  // Should display toolbar (buttons at the top)
  // Can be true, false, "auto"
  // If "auto" - will be automatically hidden if "smallBtn" is enabled
  toolbar: "auto",

  // What buttons should appear in the top right corner.
  // Buttons will be created using templates from `btnTpl` option
  // and they will be placed into toolbar (class="fancybox-toolbar"` element)
  buttons: [
    "zoom",
    //"share",
    "slideShow",
    //"fullScreen",
    //"download",
    "thumbs",
    "close"
  ],

  // Detect "idle" time in seconds
  idleTime: 3,

  // Disable right-click and use simple image protection for images
  protect: false,

  // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc
  modal: false,

  image: {
    // Wait for images to load before displaying
    //   true  - wait for image to load and then display;
    //   false - display thumbnail and load the full-sized image over top,
    //           requires predefined image dimensions (`data-width` and `data-height` attributes)
    preload: false
  },

  ajax: {
    // Object containing settings for ajax request
    settings: {
      // This helps to indicate that request comes from the modal
      // Feel free to change naming
      data: {
        fancybox: true
      }
    }
  },

  iframe: {
    // Iframe template
    tpl:
      '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" allowfullscreen allow="autoplay; fullscreen" src=""></iframe>',

    // Preload iframe before displaying it
    // This allows to calculate iframe content width and height
    // (note: Due to "Same Origin Policy", you can't get cross domain data).
    preload: true,

    // Custom CSS styling for iframe wrapping element
    // You can use this to set custom iframe dimensions
    css: {},

    // Iframe tag attributes
    attr: {
      scrolling: "auto"
    }
  },

  // For HTML5 video only
  video: {
    tpl:
      '<video class="fancybox-video" controls controlsList="nodownload" poster="{{poster}}">' +
      '<source src="{{src}}" type="{{format}}" />' +
      'Sorry, your browser doesn\'t support embedded videos, <a href="{{src}}">download</a> and watch with your favorite video player!' +
      "</video>",
    format: "", // custom video format
    autoStart: true
  },

  // Default content type if cannot be detected automatically
  defaultType: "image",

  // Open/close animation type
  // Possible values:
  //   false            - disable
  //   "zoom"           - zoom images from/to thumbnail
  //   "fade"
  //   "zoom-in-out"
  //
  animationEffect: "zoom",

  // Duration in ms for open/close animation
  animationDuration: 366,

  // Should image change opacity while zooming
  // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios
  zoomOpacity: "auto",

  // Transition effect between slides
  //
  // Possible values:
  //   false            - disable
  //   "fade'
  //   "slide'
  //   "circular'
  //   "tube'
  //   "zoom-in-out'
  //   "rotate'
  //
  transitionEffect: "fade",

  // Duration in ms for transition animation
  transitionDuration: 366,

  // Custom CSS class for slide element
  slideClass: "",

  // Custom CSS class for layout
  baseClass: "",

  // Base template for layout
  baseTpl:
    '<div class="fancybox-container" role="dialog" tabindex="-1">' +
    '<div class="fancybox-bg"></div>' +
    '<div class="fancybox-inner">' +
    '<div class="fancybox-infobar"><span data-fancybox-index></span>&nbsp;/&nbsp;<span data-fancybox-count></span></div>' +
    '<div class="fancybox-toolbar">{{buttons}}</div>' +
    '<div class="fancybox-navigation">{{arrows}}</div>' +
    '<div class="fancybox-stage"></div>' +
    '<div class="fancybox-caption"><div class=""fancybox-caption__body"></div></div>' +
    '</div>' +
    '</div>',

  // Loading indicator template
  spinnerTpl: '<div class="fancybox-loading"></div>',

  // Error message template
  errorTpl: '<div class="fancybox-error"><p>{{ERROR}}</p></div>',

  btnTpl: {
    download:
      '<a download data-fancybox-download class="fancybox-button fancybox-button--download" title="{{DOWNLOAD}}" href="javascript:;">' +
      '<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>' +
      "</a>",

    zoom:
      '<button data-fancybox-zoom class="fancybox-button fancybox-button--zoom" title="{{ZOOM}}">' +
      '<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>' +
      "</button>",

    close:
      '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}">' +
      '<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>' +
      "</button>",

    // Arrows
    arrowLeft:
      '<button data-fancybox-prev class="fancybox-button fancybox-button--arrow_left" title="{{PREV}}">' +
      '<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>' +
      "</button>",

    arrowRight:
      '<button data-fancybox-next class="fancybox-button fancybox-button--arrow_right" title="{{NEXT}}">' +
      '<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>' +
      "</button>",

    // This small close button will be appended to your html/inline/ajax content by default,
    // if "smallBtn" option is not set to false
    smallBtn:
      '<button type="button" data-fancybox-close class="fancybox-button fancybox-close-small" title="{{CLOSE}}">' +
      '<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>' +
      "</button>"
  },

  // Container is injected into this element
  parentEl: "body",

  // Hide browser vertical scrollbars; use at your own risk
  hideScrollbar: true,

  // Focus handling
  // ==============

  // Try to focus on the first focusable element after opening
  autoFocus: true,

  // Put focus back to active element after closing
  backFocus: true,

  // Do not let user to focus on element outside modal content
  trapFocus: true,

  // Module specific options
  // =======================

  fullScreen: {
    autoStart: false
  },

  // Set `touch: false` to disable panning/swiping
  touch: {
    vertical: true, // Allow to drag content vertically
    momentum: true // Continue movement after releasing mouse/touch when panning
  },

  // Hash value when initializing manually,
  // set `false` to disable hash change
  hash: null,

  // Customize or add new media types
  // Example:
  /*
    media : {
      youtube : {
        params : {
          autoplay : 0
        }
      }
    }
  */
  media: {},

  slideShow: {
    autoStart: false,
    speed: 3000
  },

  thumbs: {
    autoStart: false, // Display thumbnails on opening
    hideOnClose: true, // Hide thumbnail grid when closing animation starts
    parentEl: ".fancybox-container", // Container is injected into this element
    axis: "y" // Vertical (y) or horizontal (x) scrolling
  },

  // Use mousewheel to navigate gallery
  // If 'auto' - enabled for images only
  wheel: "auto",

  // Callbacks
  //==========

  // See Documentation/API/Events for more information
  // Example:
  /*
    afterShow: function( instance, current ) {
      console.info( 'Clicked element:' );
      console.info( current.opts.$orig );
    }
  */

  onInit: $.noop, // When instance has been initialized

  beforeLoad: $.noop, // Before the content of a slide is being loaded
  afterLoad: $.noop, // When the content of a slide is done loading

  beforeShow: $.noop, // Before open animation starts
  afterShow: $.noop, // When content is done loading and animating

  beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close.
  afterClose: $.noop, // After instance has been closed

  onActivate: $.noop, // When instance is brought to front
  onDeactivate: $.noop, // When other instance has been activated

  // Interaction
  // ===========

  // Use options below to customize taken action when user clicks or double clicks on the fancyBox area,
  // each option can be string or method that returns value.
  //
  // Possible values:
  //   "close"           - close instance
  //   "next"            - move to next gallery item
  //   "nextOrClose"     - move to next gallery item or close if gallery has only one item
  //   "toggleControls"  - show/hide controls
  //   "zoom"            - zoom image (if loaded)
  //   false             - do nothing

  // Clicked on the content
  clickContent: function(current, event) {
    return current.type === "image" ? "zoom" : false;
  },

  // Clicked on the slide
  clickSlide: "close",

  // Clicked on the background (backdrop) element;
  // if you have not changed the layout, then most likely you need to use `clickSlide` option
  clickOutside: "close",

  // Same as previous two, but for double click
  dblclickContent: false,
  dblclickSlide: false,
  dblclickOutside: false,

  // Custom options when mobile device is detected
  // =============================================

  mobile: {
    preventCaptionOverlap: false,
    idleTime: false,
    clickContent: function(current, event) {
      return current.type === "image" ? "toggleControls" : false;
    },
    clickSlide: function(current, event) {
      return current.type === "image" ? "toggleControls" : "close";
    },
    dblclickContent: function(current, event) {
      return current.type === "image" ? "zoom" : false;
    },
    dblclickSlide: function(current, event) {
      return current.type === "image" ? "zoom" : false;
    }
  },

  // Internationalization
  // ====================

  lang: "en",
  i18n: {
    en: {
      CLOSE: "Close",
      NEXT: "Next",
      PREV: "Previous",
      ERROR: "The requested content cannot be loaded. <br/> Please try again later.",
      PLAY_START: "Start slideshow",
      PLAY_STOP: "Pause slideshow",
      FULL_SCREEN: "Full screen",
      THUMBS: "Thumbnails",
      DOWNLOAD: "Download",
      SHARE: "Share",
      ZOOM: "Zoom"
    },
    de: {
      CLOSE: "Schliessen",
      NEXT: "Weiter",
      PREV: "Zurück",
      ERROR: "Die angeforderten Daten konnten nicht geladen werden. <br/> Bitte versuchen Sie es später nochmal.",
      PLAY_START: "Diaschau starten",
      PLAY_STOP: "Diaschau beenden",
      FULL_SCREEN: "Vollbild",
      THUMBS: "Vorschaubilder",
      DOWNLOAD: "Herunterladen",
      SHARE: "Teilen",
      ZOOM: "Maßstab"
    }
  }
};

Set instance options by passing a valid object to fancybox() method:

$('[data-fancybox="gallery"]').fancybox({
	thumbs : {
		autoStart : true
	}
});

Plugin options / defaults are exposed in $.fancybox.defaults namespace so you can easily adjust them globally:

$.fancybox.defaults.animationEffect = "fade";

Custom options for each element individually can be set by adding a data-options attribute to the element. This attribute should contain the properly formatted JSON object (remember, strings should be wrapped in double quotes).

It is also possible to quickly set any option using parameterized name of the selected option, for example, animationEffect would be data-animation-effect:

<a data-fancybox data-options='{"caption" : "My caption", "src" : "https://codepen.io/about/", "type" : "iframe"}' href="javascript:;" class="btn btn-primary">
    Example #1
</a>

<a data-fancybox data-animation-effect="false" href="https://source.unsplash.com/0JYgd2QuMfw/1500x1000" class="btn btn-primary">
    Example #2
</a>

View demo on CodePen

API

The fancybox API offers a couple of methods to control fancybox. This gives you the ability to extend the plugin and to integrate it with other web application components.

Core methods

Core methods are methods which affect/handle instances:

// Start new fancybox instance
$.fancybox.open( items, opts, index );

// Get refrence to currently active fancybox instance
$.fancybox.getInstance();

// Close currently active fancybox instance (pass `true` to close all instances) 
$.fancybox.close();

// Close all instances and unbind all events
$.fancybox.destroy();

Starting fancybox

When creating group objects manually, each item should follow this pattern:

{
	src  : '' // Source of the content
	type : '' // Content type: image|inline|ajax|iframe|html (optional)
	opts : {} // Object containing item options (optional)
}

Example of opening image gallery programmatically:

$.fancybox.open([
	{
		src  : '1_b.jpg',
		opts : {
			caption : 'First caption',
			thumb   : '1_s.jpg'
		}
	},
	{
		src  : '2_b.jpg',
		opts : {
			caption : 'Second caption',
			thumb   : '2_s.jpg'
		}
	}
], {
	loop : false
});

View demo on CodePen

It is also possible to pass only one object. Example of opening inline content:

$.fancybox.open({
	src  : '#hidden-content',
	type : 'inline',
	opts : {
		afterShow : function( instance, current ) {
			console.info( 'done!' );
		}
	}
});

View demo on CodePen

If you wish to quickly display some html content (for example, a message), then you can use a simpler syntax. Do not forget to use a wrapping element around your content.

$.fancybox.open('<div class="message"><h2>Hello!</h2><p>You are awesome!</p></div>');

View demo on CodePen

Group items can be collection of jQuery objects, too. This can be used, for example, to display group of inline elements:

$('#test').on('click', function() {
  $.fancybox.open( $('.inline-gallery'), {
    touch: false
  });
});

View demo on CodePen

Instance methods

In order to use these methods, you need an instance of the plugin's object. There are 3 common ways to get the reference.

1) Using API method to get currently active instance:

var instance = $.fancybox.getInstance();

2) While starting fancybox programmatically:

var instance = $.fancybox.open(
	// Your content and options
);

3) From within the callback - first argument is always a reference to current instance:

$('[data-fancybox="gallery"]').fancybox({
	afterShow : function( instance, current ) {
		console.info( instance );
	}
});

Once you have a reference to fancybox instance the following methods are available:

// Go to next gallery item
instance.next( duration );

// Go to previous gallery item
instance.previous( duration );

// Switch to selected gallery item
instance.jumpTo( index, duration );

// Check if current image dimensions are smaller than actual
instance.isScaledDown();

// Scale image to the actual size of the image
instance.scaleToActual( x, y, duration );

// Check if image dimensions exceed parent element
instance.canPan();

// Scale image to fit inside parent element
instance.scaleToFit( duration );

// Update position and content of all slides
instance.update();

// Update slide position and scale content to fit
instance.updateSlide( slide );

// Update infobar values, navigation button states and reveal caption
instance.updateControls( force );

// Load custom content into the slide
instance.setContent( slide, content );

// Show loading icon inside the slide
instance.showLoading( slide );

// Remove loading icon from the slide
instance.hideLoading( slide );

// Try to find and focus on the first focusable element
instance.focus();

// Activates current instance, brings it to the front
instance.activate();

// Close instance
instance.close();

You can also do something like this:

$.fancybox.getInstance().jumpTo(1);

or simply:

$.fancybox.getInstance('jumpTo', 1);

Events

fancybox fires several events:

beforeLoad   : Before the content of a slide is being loaded
afterLoad    : When the content of a slide is done loading

beforeShow   : Before open animation starts
afterShow    : When content is done loading and animating

beforeClose  : Before the instance attempts to close. Return false to cancel the close.
afterClose   : After instance has been closed

onInit       : When instance has been initialized
onActivate   : When instance is brought to front
onDeactivate : When other instance has been activated

Event callbacks can be set as function properties of the options object passed to fancybox initialization function:

<script type="text/javascript">
	$("[data-fancybox]").fancybox({
		afterShow: function( instance, slide ) {

			// Tip: Each event passes useful information within the event object:

			// Object containing references to interface elements
			// (background, buttons, caption, etc)
			// console.info( instance.$refs );

			// Current slide options
			// console.info( slide.opts );

			// Clicked element
			// console.info( slide.opts.$orig );

			// Reference to DOM element of the slide
			// console.info( slide.$slide );

		}
	});
</script>

Each callback receives two parameters - current fancybox instance and current gallery object, if exists.

It is also possible to attach event handler for all instances. To prevent interfering with other scripts, these events have been namespaced to .fb. These handlers receive 3 parameters - event, current fancybox instance and current gallery object.

Here is an example of binding to the afterShow event:

$(document).on('afterShow.fb', function( e, instance, slide ) {
	// Your code goes here
});

If you wish to prevent closing of the modal (for example, after form submit), you can use beforeClose callback. Simply return false:

beforeClose : function( instance, current, e ) {
	if ( $('#my-field').val() == '' ) {
		return false;
	}
}

Modules

fancybox code is split into several files (modules) that extend core functionality. You can build your own fancybox version by excluding unnecessary modules, if needed. Each one has their own js and/or css files.

Some modules can be customized and controlled programmatically. List of all possible options:

fullScreen: {
  autoStart: false
},

touch : {
  vertical : true,  // Allow to drag content vertically
  momentum : true   // Continuous movement when panning
},

// Hash value when initializing manually,
// set `false` to disable hash change
hash : null,

// Customize or add new media types
// Example:
/*
media : {
  youtube : {
    params : {
      autoplay : 0
    }
  }
}
*/
media : {},

slideShow : {
  autoStart : false,
  speed     : 4000
},

thumbs : {
  autoStart   : false,                  // Display thumbnails on opening
  hideOnClose : true,                   // Hide thumbnail grid when closing animation starts
  parentEl    : '.fancybox-container',  // Container is injected into this element
  axis        : 'y'                     // Vertical (y) or horizontal (x) scrolling
},

share: {
  url: function(instance, item) {
    return (
      (!instance.currentHash && !(item.type === "inline" || item.type === "html") ? item.origSrc || item.src : false) || window.location
    );
  },
  tpl:
    '<div class="fancybox-share">' +
    "<h1>{{SHARE}}</h1>" +
    "<p>" +
    '<a class="fancybox-share__button fancybox-share__button--fb" href="https://www.facebook.com/sharer/sharer.php?u={{url}}">' +
    '<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>' +
    "<span>Facebook</span>" +
    "</a>" +
    '<a class="fancybox-share__button fancybox-share__button--tw" href="https://twitter.com/intent/tweet?url={{url}}&text={{descr}}">' +
    '<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>' +
    "<span>Twitter</span>" +
    "</a>" +
    '<a class="fancybox-share__button fancybox-share__button--pt" href="https://www.pinterest.com/pin/create/button/?url={{url}}&description={{descr}}&media={{media}}">' +
    '<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>' +
    "<span>Pinterest</span>" +
    "</a>" +
    "</p>" +
    '<p><input class="fancybox-share__input" type="text" value="{{url_raw}}" /></p>' +
    "</div>"
}

Couple of examples

Show thumbnails on start:

$('[data-fancybox="images"]').fancybox({
	thumbs : {
		autoStart : true
	}
});

View demo on CodePen

Customize share url if displaying hidden video element:

$('[data-fancybox="test-share-url"]').fancybox({
    buttons : ['share', 'close'],
    hash : false,
    share : {
        url : function( instance, item ) {
            if (item.type === 'inline' && item.contentType === 'video') {
                return item.$content.find('source:first').attr('src');
            }

            return item.src;
        }
    }
});

View demo on CodePen

If you would inspect fancybox instance object, you would find that same keys ar captialized - these are references for each module object. Also, you would notice that fancybox uses common naming convention to prefix jQuery objects with $.

This is how you, for example, can access thumbnail grid element:

$.fancybox.getInstance().Thumbs.$grid

This example shows how to call method that toggles thumbnails:

$.fancybox.getInstance().Thumbs.toggle();

List of available methods:

Thumbs.focus()
Thumbs.update();
Thumbs.hide();
Thumbs.show();
Thumbs.toggle();

FullScreen.request( elem );
FullScreen.exit();
FullScreen.toggle( elem );
FullScreen.isFullscreen();
FullScreen.enabled();

SlideShow.start();
SlideShow.stop();
SlideShow.toggle();

If you wish to disable hash module, use this snippet (after including JS file):

$.fancybox.defaults.hash = false;

FAQ

#1 Opening/closing causes fixed element to jump

Simply add compensate-for-scrollbar CSS class to your fixed positioned elements. Example of using Bootstrap navbar component:

<nav class="navbar navbar-inverse navbar-fixed-top compensate-for-scrollbar">
	<div class="container">
		..
	</div>
</nav>

View demo on CodePen

The script measures width of the scrollbar and creates compensate-for-scrollbar CSS class that uses this value for margin-right property. Therefore, if your element has width:100%, you should positon it using left and right properties instead. Example:

.navbar {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
}

#2 How to customize caption

You can use caption option that accepts a function and is called for each group element. Example of appending image download link:

$( '[data-fancybox="images"]' ).fancybox({
    caption : function( instance, item ) {
        var caption = $(this).data('caption') || '';

        if ( item.type === 'image' ) {
            caption = (caption.length ? caption + '<br />' : '') + '<a href="' + item.src + '">Download image</a>' ;
        }

        return caption;
    }
});

View demo on CodePen

Add current image index and image count (the total number of images in the gallery) right in the caption:

$( '[data-fancybox="images"]' ).fancybox({
    infobar : false,
    caption : function( instance, item ) {
        var caption = $(this).data('caption') || '';

        return ( caption.length ? caption + '<br />' : '' ) + 'Image <span data-fancybox-index></span> of <span data-fancybox-count></span>';
    }
});

View demo on CodePen

Inside caption method, this refers to the clicked element. Example of using different source for caption:

$( '[data-fancybox]' ).fancybox({
	caption : function( instance, item ) {
		return $(this).find('figcaption').html();
	}
});

View demo on CodePen

#3 How to create custom button in the toolbar

Example of creating reusable button:

// Create template for the button
$.fancybox.defaults.btnTpl.fb = '<button data-fancybox-fb class="fancybox-button fancybox-button--fb" title="Facebook">' +
    '<svg viewBox="0 0 24 24">' +
        '<path d="M22.676 0H1.324C.594 0 0 .593 0 1.324v21.352C0 23.408.593 24 1.324 24h11.494v-9.294h-3.13v-3.62h3.13V8.41c0-3.1 1.894-4.785 4.66-4.785 1.324 0 2.463.097 2.795.14v3.24h-1.92c-1.5 0-1.793.722-1.793 1.772v2.31h3.584l-.465 3.63h-3.12V24h6.115c.733 0 1.325-.592 1.325-1.324V1.324C24 .594 23.408 0 22.676 0"/>' +
    '</svg>' +
'</button>';

// Make button clickable using event delegation
$('body').on('click', '[data-fancybox-fb]', function() {
    window.open("https://www.facebook.com/sharer/sharer.php?u="+encodeURIComponent(window.location.href)+"&t="+encodeURIComponent(document.title), '','left=0,top=0,width=600,height=300,menubar=no,toolbar=no,resizable=yes,scrollbars=yes');
});

// Customize buttons
$( '[data-fancybox="images"]' ).fancybox({
    buttons : [
        'fb',
        'close'
    ]
});

View demo on CodePen

#4 How to reposition thumbnail grid

There is currenty no JS option to change thumbnail grid position. But fancybox is designed so that you can use CSS to change position or dimension for each block (e.g., content area, caption or thumbnail grid). This gives you freedom to completely change the look and feel of the modal window, if needed. View demo on CodePen

#5 How to disable touch gestures/swiping

When you want to make your content selectable or clickable, you have two options:

  • disable touch gestures completely by setting touch:false
  • add data-selectable="true" attribute to your html element

View demo on CodePen

#6 Slider/carousel add's cloned duplicate items

If you are combining fancybox with slider/carousel script and that script clones items to enable infinite navigation, then duplicated items will appear in the gallery. To avoid that - 1) initialise fancybox on all items except cloned; 2) add custom click event on cloned items and trigger click event on corresponding "real" item. Here is an example using Slick slider:

// Init fancybox
// =============
var selector = '.slick-slide:not(.slick-cloned)';

// Skip cloned elements
$().fancybox({
  selector : selector,
  backFocus : false
});

// Attach custom click event on cloned elements, 
// trigger click event on corresponding link
$(document).on('click', '.slick-cloned', function(e) {
  $(selector)
    .eq( ( $(e.currentTarget).attr("data-slick-index") || 0) % $(selector).length )
    .trigger("click.fb-start", {
      $trigger: $(this)
    });

  return false;
});

// Init Slick
// ==========
$(".main-slider").slick({
  slidesToShow   : 3,
  infinite : true,
  dots     : false,
  arrows   : false
});

View demo on CodePen

Back to Top

================================================ FILE: gulpfile.js ================================================ var gulp = require("gulp"), concat = require("gulp-concat"), uglify = require("gulp-uglify"), rename = require("gulp-rename"), cssnano = require("gulp-cssnano"), autoprefixer = require("gulp-autoprefixer"), header = require("gulp-header"), replace = require("gulp-replace"); var pkg = require("./package.json"); var banner = [ "// ==================================================", "// fancyBox v${pkg.version}", "//", "// Licensed GPLv3 for open source use", "// or fancyBox Commercial License for commercial use", "//", "// http://fancyapps.com/fancybox/", "// Copyright ${new Date().getFullYear()} fancyApps", "//", "// ==================================================", "" ].join("\n"); // Concatenate & Minify JS gulp.task("scripts", function() { return gulp .src([ "src/js/core.js", "src/js/media.js", "src/js/guestures.js", "src/js/slideshow.js", "src/js/fullscreen.js", "src/js/thumbs.js", "src/js/hash.js", "src/js/wheel.js" ]) .pipe(concat("jquery.fancybox.js")) .pipe(replace(/({fancybox-version})/g, pkg.version)) .pipe(header(banner, {pkg: pkg})) .pipe(gulp.dest("dist")) .pipe(rename({suffix: ".min"})) .pipe(uglify()) .pipe(header(banner, {pkg: pkg})) .pipe(gulp.dest("dist")); }); // Compile CSS gulp.task("css", function() { return gulp .src("src/css/*.css") // Gets all files src/css .pipe( autoprefixer({ browsers: ["last 5 versions"], cascade: false }) ) .pipe(concat("jquery.fancybox.css")) .pipe(gulp.dest("dist")) .pipe(rename({suffix: ".min"})) .pipe(cssnano({zindex: false})) .pipe(gulp.dest("dist")); }); // Default Task gulp.task("default", ["scripts", "css"]); ================================================ FILE: package.json ================================================ { "name": "@fancyapps/fancybox", "description": "Touch enabled, responsive and fully customizable jQuery lightbox script", "version": "3.5.7", "homepage": "https://fancyapps.com/fancybox/3/", "main": "dist/jquery.fancybox.js", "style": "dist/jquery.fancybox.css", "author": "fancyApps", "license": "GPL-3.0", "repository": { "type": "git", "url": "git+https://github.com/fancyapps/fancybox.git" }, "peerDependencies": { "jquery": ">=1.9.0" }, "devDependencies": { "del": "^2.2.2", "gulp": "^3.9.1", "gulp-autoprefixer": "^3.1.1", "gulp-concat": "^2.6.1", "gulp-cssnano": "^2.1.2", "gulp-header": "^1.8.8", "gulp-jshint": "^2.0.4", "gulp-livereload": "^3.8.1", "gulp-notify": "^2.2.0", "gulp-rename": "^1.2.2", "gulp-replace": "^0.5.4", "gulp-uglify": "^2.0.0", "gulp-util": "^3.0.8", "jshint": "^2.9.4" }, "keywords": [ "touch", "responsive", "lightbox", "fancybox", "gallery", "jQuery", "plugin" ], "bugs": { "url": "https://github.com/fancyapps/fancybox/issues" }, "directories": { "doc": "docs" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } } ================================================ FILE: src/css/core.css ================================================ body.compensate-for-scrollbar { overflow: hidden; } .fancybox-active { height: auto; } .fancybox-is-hidden { left: -9999px; margin: 0; position: absolute !important; top: -9999px; visibility: hidden; } .fancybox-container { -webkit-backface-visibility: hidden; height: 100%; left: 0; outline: none; position: fixed; -webkit-tap-highlight-color: transparent; top: 0; touch-action: manipulation; transform: translateZ(0); width: 100%; z-index: 99992; } .fancybox-container * { box-sizing: border-box; } .fancybox-outer, .fancybox-inner, .fancybox-bg, .fancybox-stage { bottom: 0; left: 0; position: absolute; right: 0; top: 0; } .fancybox-outer { -webkit-overflow-scrolling: touch; overflow-y: auto; } .fancybox-bg { background: rgb(30, 30, 30); opacity: 0; transition-duration: inherit; transition-property: opacity; transition-timing-function: cubic-bezier(.47, 0, .74, .71); } .fancybox-is-open .fancybox-bg { opacity: .9; transition-timing-function: cubic-bezier(.22, .61, .36, 1); } .fancybox-infobar, .fancybox-toolbar, .fancybox-caption, .fancybox-navigation .fancybox-button { direction: ltr; opacity: 0; position: absolute; transition: opacity .25s ease, visibility 0s ease .25s; visibility: hidden; z-index: 99997; } .fancybox-show-infobar .fancybox-infobar, .fancybox-show-toolbar .fancybox-toolbar, .fancybox-show-caption .fancybox-caption, .fancybox-show-nav .fancybox-navigation .fancybox-button { opacity: 1; transition: opacity .25s ease 0s, visibility 0s ease 0s; visibility: visible; } .fancybox-infobar { color: #ccc; font-size: 13px; -webkit-font-smoothing: subpixel-antialiased; height: 44px; left: 0; line-height: 44px; min-width: 44px; mix-blend-mode: difference; padding: 0 10px; pointer-events: none; top: 0; -webkit-touch-callout: none; user-select: none; } .fancybox-toolbar { right: 0; top: 0; } .fancybox-stage { direction: ltr; overflow: visible; transform: translateZ(0); z-index: 99994; } .fancybox-is-open .fancybox-stage { overflow: hidden; } .fancybox-slide { -webkit-backface-visibility: hidden; /* Using without prefix would break IE11 */ display: none; height: 100%; left: 0; outline: none; overflow: auto; -webkit-overflow-scrolling: touch; padding: 44px; position: absolute; text-align: center; top: 0; transition-property: transform, opacity; white-space: normal; width: 100%; z-index: 99994; } .fancybox-slide::before { content: ''; display: inline-block; font-size: 0; height: 100%; vertical-align: middle; width: 0; } .fancybox-is-sliding .fancybox-slide, .fancybox-slide--previous, .fancybox-slide--current, .fancybox-slide--next { display: block; } .fancybox-slide--image { overflow: hidden; padding: 44px 0; } .fancybox-slide--image::before { display: none; } .fancybox-slide--html { padding: 6px; } .fancybox-content { background: #fff; display: inline-block; margin: 0; max-width: 100%; overflow: auto; -webkit-overflow-scrolling: touch; padding: 44px; position: relative; text-align: left; vertical-align: middle; } .fancybox-slide--image .fancybox-content { animation-timing-function: cubic-bezier(.5, 0, .14, 1); -webkit-backface-visibility: hidden; background: transparent; background-repeat: no-repeat; background-size: 100% 100%; left: 0; max-width: none; overflow: visible; padding: 0; position: absolute; top: 0; transform-origin: top left; transition-property: transform, opacity; user-select: none; z-index: 99995; } .fancybox-can-zoomOut .fancybox-content { cursor: zoom-out; } .fancybox-can-zoomIn .fancybox-content { cursor: zoom-in; } .fancybox-can-swipe .fancybox-content, .fancybox-can-pan .fancybox-content { cursor: grab; } .fancybox-is-grabbing .fancybox-content { cursor: grabbing; } .fancybox-container [data-selectable='true'] { cursor: text; } .fancybox-image, .fancybox-spaceball { background: transparent; border: 0; height: 100%; left: 0; margin: 0; max-height: none; max-width: none; padding: 0; position: absolute; top: 0; user-select: none; width: 100%; } .fancybox-spaceball { z-index: 1; } .fancybox-slide--video .fancybox-content, .fancybox-slide--map .fancybox-content, .fancybox-slide--pdf .fancybox-content, .fancybox-slide--iframe .fancybox-content { height: 100%; overflow: visible; padding: 0; width: 100%; } .fancybox-slide--video .fancybox-content { background: #000; } .fancybox-slide--map .fancybox-content { background: #e5e3df; } .fancybox-slide--iframe .fancybox-content { background: #fff; } .fancybox-video, .fancybox-iframe { background: transparent; border: 0; display: block; height: 100%; margin: 0; overflow: hidden; padding: 0; width: 100%; } /* Fix iOS */ .fancybox-iframe { left: 0; position: absolute; top: 0; } .fancybox-error { background: #fff; cursor: default; max-width: 400px; padding: 40px; width: 100%; } .fancybox-error p { color: #444; font-size: 16px; line-height: 20px; margin: 0; padding: 0; } /* Buttons */ .fancybox-button { background: rgba(30, 30, 30, .6); border: 0; border-radius: 0; box-shadow: none; cursor: pointer; display: inline-block; height: 44px; margin: 0; padding: 10px; position: relative; transition: color .2s; vertical-align: top; visibility: inherit; width: 44px; } .fancybox-button, .fancybox-button:visited, .fancybox-button:link { color: #ccc; } .fancybox-button:hover { color: #fff; } .fancybox-button:focus { outline: none; } .fancybox-button.fancybox-focus { outline: 1px dotted; } .fancybox-button[disabled], .fancybox-button[disabled]:hover { color: #888; cursor: default; outline: none; } /* Fix IE11 */ .fancybox-button div { height: 100%; } .fancybox-button svg { display: block; height: 100%; overflow: visible; position: relative; width: 100%; } .fancybox-button svg path { fill: currentColor; stroke-width: 0; } .fancybox-button--play svg:nth-child(2), .fancybox-button--fsenter svg:nth-child(2) { display: none; } .fancybox-button--pause svg:nth-child(1), .fancybox-button--fsexit svg:nth-child(1) { display: none; } .fancybox-progress { background: #ff5268; height: 2px; left: 0; position: absolute; right: 0; top: 0; transform: scaleX(0); transform-origin: 0; transition-property: transform; transition-timing-function: linear; z-index: 99998; } /* Close button on the top right corner of html content */ .fancybox-close-small { background: transparent; border: 0; border-radius: 0; color: #ccc; cursor: pointer; opacity: .8; padding: 8px; position: absolute; right: -12px; top: -44px; z-index: 401; } .fancybox-close-small:hover { color: #fff; opacity: 1; } .fancybox-slide--html .fancybox-close-small { color: currentColor; padding: 10px; right: 0; top: 0; } .fancybox-slide--image.fancybox-is-scaling .fancybox-content { overflow: hidden; } .fancybox-is-scaling .fancybox-close-small, .fancybox-is-zoomable.fancybox-can-pan .fancybox-close-small { display: none; } /* Navigation arrows */ .fancybox-navigation .fancybox-button { background-clip: content-box; height: 100px; opacity: 0; position: absolute; top: calc(50% - 50px); width: 70px; } .fancybox-navigation .fancybox-button div { padding: 7px; } .fancybox-navigation .fancybox-button--arrow_left { left: 0; left: env(safe-area-inset-left); padding: 31px 26px 31px 6px; } .fancybox-navigation .fancybox-button--arrow_right { padding: 31px 6px 31px 26px; right: 0; right: env(safe-area-inset-right); } /* Caption */ .fancybox-caption { background: linear-gradient(to top, rgba(0, 0, 0, .85) 0%, rgba(0, 0, 0, .3) 50%, rgba(0, 0, 0, .15) 65%, rgba(0, 0, 0, .075) 75.5%, rgba(0, 0, 0, .037) 82.85%, rgba(0, 0, 0, .019) 88%, rgba(0, 0, 0, 0) 100%); bottom: 0; color: #eee; font-size: 14px; font-weight: 400; left: 0; line-height: 1.5; padding: 75px 44px 25px 44px; pointer-events: none; right: 0; text-align: center; z-index: 99996; } @supports (padding: max(0px)) { .fancybox-caption { padding: 75px max(44px, env(safe-area-inset-right)) max(25px, env(safe-area-inset-bottom)) max(44px, env(safe-area-inset-left)); } } .fancybox-caption--separate { margin-top: -50px; } .fancybox-caption__body { max-height: 50vh; overflow: auto; pointer-events: all; } .fancybox-caption a, .fancybox-caption a:link, .fancybox-caption a:visited { color: #ccc; text-decoration: none; } .fancybox-caption a:hover { color: #fff; text-decoration: underline; } /* Loading indicator */ .fancybox-loading { animation: fancybox-rotate 1s linear infinite; background: transparent; border: 4px solid #888; border-bottom-color: #fff; border-radius: 50%; height: 50px; left: 50%; margin: -25px 0 0 -25px; opacity: .7; padding: 0; position: absolute; top: 50%; width: 50px; z-index: 99999; } @keyframes fancybox-rotate { 100% { transform: rotate(360deg); } } /* Transition effects */ .fancybox-animated { transition-timing-function: cubic-bezier(0, 0, .25, 1); } /* transitionEffect: slide */ .fancybox-fx-slide.fancybox-slide--previous { opacity: 0; transform: translate3d(-100%, 0, 0); } .fancybox-fx-slide.fancybox-slide--next { opacity: 0; transform: translate3d(100%, 0, 0); } .fancybox-fx-slide.fancybox-slide--current { opacity: 1; transform: translate3d(0, 0, 0); } /* transitionEffect: fade */ .fancybox-fx-fade.fancybox-slide--previous, .fancybox-fx-fade.fancybox-slide--next { opacity: 0; transition-timing-function: cubic-bezier(.19, 1, .22, 1); } .fancybox-fx-fade.fancybox-slide--current { opacity: 1; } /* transitionEffect: zoom-in-out */ .fancybox-fx-zoom-in-out.fancybox-slide--previous { opacity: 0; transform: scale3d(1.5, 1.5, 1.5); } .fancybox-fx-zoom-in-out.fancybox-slide--next { opacity: 0; transform: scale3d(.5, .5, .5); } .fancybox-fx-zoom-in-out.fancybox-slide--current { opacity: 1; transform: scale3d(1, 1, 1); } /* transitionEffect: rotate */ .fancybox-fx-rotate.fancybox-slide--previous { opacity: 0; transform: rotate(-360deg); } .fancybox-fx-rotate.fancybox-slide--next { opacity: 0; transform: rotate(360deg); } .fancybox-fx-rotate.fancybox-slide--current { opacity: 1; transform: rotate(0deg); } /* transitionEffect: circular */ .fancybox-fx-circular.fancybox-slide--previous { opacity: 0; transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0); } .fancybox-fx-circular.fancybox-slide--next { opacity: 0; transform: scale3d(0, 0, 0) translate3d(100%, 0, 0); } .fancybox-fx-circular.fancybox-slide--current { opacity: 1; transform: scale3d(1, 1, 1) translate3d(0, 0, 0); } /* transitionEffect: tube */ .fancybox-fx-tube.fancybox-slide--previous { transform: translate3d(-100%, 0, 0) scale(.1) skew(-10deg); } .fancybox-fx-tube.fancybox-slide--next { transform: translate3d(100%, 0, 0) scale(.1) skew(10deg); } .fancybox-fx-tube.fancybox-slide--current { transform: translate3d(0, 0, 0) scale(1); } /* Styling for Small-Screen Devices */ @media all and (max-height: 576px) { .fancybox-slide { padding-left: 6px; padding-right: 6px; } .fancybox-slide--image { padding: 6px 0; } .fancybox-close-small { right: -6px; } .fancybox-slide--image .fancybox-close-small { background: #4e4e4e; color: #f2f4f6; height: 36px; opacity: 1; padding: 6px; right: 0; top: 0; width: 36px; } .fancybox-caption { padding-left: 12px; padding-right: 12px; } @supports (padding: max(0px)) { .fancybox-caption { padding-left: max(12px, env(safe-area-inset-left)); padding-right: max(12px, env(safe-area-inset-right)); } } } ================================================ FILE: src/css/fullscreen.css ================================================ /* Fullscreen */ .fancybox-button--fullscreen::before { width: 15px; height: 11px; left: calc(50% - 7px); top: calc(50% - 6px); border: 2px solid; background: none; } ================================================ FILE: src/css/share.css ================================================ /* Share */ .fancybox-share { background: #f4f4f4; border-radius: 3px; max-width: 90%; padding: 30px; text-align: center; } .fancybox-share h1 { color: #222; font-size: 35px; font-weight: 700; margin: 0 0 20px 0; } .fancybox-share p { margin: 0; padding: 0; } .fancybox-share__button { border: 0; border-radius: 3px; display: inline-block; font-size: 14px; font-weight: 700; line-height: 40px; margin: 0 5px 10px 5px; min-width: 130px; padding: 0 15px; text-decoration: none; transition: all .2s; user-select: none; white-space: nowrap; } .fancybox-share__button:visited, .fancybox-share__button:link { color: #fff; } .fancybox-share__button:hover { text-decoration: none; } .fancybox-share__button--fb { background: #3b5998; } .fancybox-share__button--fb:hover { background: #344e86; } .fancybox-share__button--pt { background: #bd081d; } .fancybox-share__button--pt:hover { background: #aa0719; } .fancybox-share__button--tw { background: #1da1f2; } .fancybox-share__button--tw:hover { background: #0d95e8; } .fancybox-share__button svg { height: 25px; margin-right: 7px; position: relative; top: -1px; vertical-align: middle; width: 25px; } .fancybox-share__button svg path { fill: #fff; } .fancybox-share__input { background: transparent; border: 0; border-bottom: 1px solid #d7d7d7; border-radius: 0; color: #5d5b5b; font-size: 14px; margin: 10px 0 0 0; outline: none; padding: 10px 15px; width: 100%; } ================================================ FILE: src/css/slideshow.css ================================================ /* Slideshow button */ .fancybox-button--play {} .fancybox-button--play::before, .fancybox-button--pause::before { top: calc(50% - 6px); left: calc(50% - 4px); background: transparent; } .fancybox-button--play::before { width: 0; height: 0; border-top: 6px inset transparent; border-bottom: 6px inset transparent; border-left: 10px solid; border-radius: 1px; } .fancybox-button--pause::before { width: 7px; height: 11px; border-style: solid; border-width: 0 2px 0 2px; } ================================================ FILE: src/css/thumbs.css ================================================ /* Thumbs */ .fancybox-thumbs { background: #ddd; bottom: 0; display: none; margin: 0; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; padding: 2px 2px 4px 2px; position: absolute; right: 0; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); top: 0; width: 212px; z-index: 99995; } .fancybox-thumbs-x { overflow-x: auto; overflow-y: hidden; } .fancybox-show-thumbs .fancybox-thumbs { display: block; } .fancybox-show-thumbs .fancybox-inner { right: 212px; } .fancybox-thumbs__list { font-size: 0; height: 100%; list-style: none; margin: 0; overflow-x: hidden; overflow-y: auto; padding: 0; position: absolute; position: relative; white-space: nowrap; width: 100%; } .fancybox-thumbs-x .fancybox-thumbs__list { overflow: hidden; } .fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar { width: 7px; } .fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-track { background: #fff; border-radius: 10px; box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); } .fancybox-thumbs-y .fancybox-thumbs__list::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 10px; } .fancybox-thumbs__list a { backface-visibility: hidden; background-color: rgba(0, 0, 0, .1); background-position: center center; background-repeat: no-repeat; background-size: cover; cursor: pointer; float: left; height: 75px; margin: 2px; max-height: calc(100% - 8px); max-width: calc(50% - 4px); outline: none; overflow: hidden; padding: 0; position: relative; -webkit-tap-highlight-color: transparent; width: 100px; } .fancybox-thumbs__list a::before { border: 6px solid #ff5268; bottom: 0; content: ''; left: 0; opacity: 0; position: absolute; right: 0; top: 0; transition: all .2s cubic-bezier(.25, .46, .45, .94); z-index: 99991; } .fancybox-thumbs__list a:focus::before { opacity: .5; } .fancybox-thumbs__list a.fancybox-thumbs-active::before { opacity: 1; } /* Styling for Small-Screen Devices */ @media all and (max-width: 576px) { .fancybox-thumbs { width: 110px; } .fancybox-show-thumbs .fancybox-inner { right: 110px; } .fancybox-thumbs__list a { max-width: calc(100% - 10px); } } ================================================ FILE: src/js/core.js ================================================ (function (window, document, $, undefined) { "use strict"; window.console = window.console || { info: function (stuff) {} }; // If there's no jQuery, fancyBox can't work // ========================================= if (!$) { return; } // Check if fancyBox is already initialized // ======================================== if ($.fn.fancybox) { console.info("fancyBox already initialized"); return; } // Private default settings // ======================== var defaults = { // Close existing modals // Set this to false if you do not need to stack multiple instances closeExisting: false, // Enable infinite gallery navigation loop: false, // Horizontal space between slides gutter: 50, // Enable keyboard navigation keyboard: true, // Should allow caption to overlap the content preventCaptionOverlap: true, // Should display navigation arrows at the screen edges arrows: true, // Should display counter at the top left corner infobar: true, // Should display close button (using `btnTpl.smallBtn` template) over the content // Can be true, false, "auto" // If "auto" - will be automatically enabled for "html", "inline" or "ajax" items smallBtn: "auto", // Should display toolbar (buttons at the top) // Can be true, false, "auto" // If "auto" - will be automatically hidden if "smallBtn" is enabled toolbar: "auto", // What buttons should appear in the top right corner. // Buttons will be created using templates from `btnTpl` option // and they will be placed into toolbar (class="fancybox-toolbar"` element) buttons: [ "zoom", //"share", "slideShow", //"fullScreen", //"download", "thumbs", "close" ], // Detect "idle" time in seconds idleTime: 3, // Disable right-click and use simple image protection for images protect: false, // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc modal: false, image: { // Wait for images to load before displaying // true - wait for image to load and then display; // false - display thumbnail and load the full-sized image over top, // requires predefined image dimensions (`data-width` and `data-height` attributes) preload: false }, ajax: { // Object containing settings for ajax request settings: { // This helps to indicate that request comes from the modal // Feel free to change naming data: { fancybox: true } } }, iframe: { // Iframe template tpl: '', // Preload iframe before displaying it // This allows to calculate iframe content width and height // (note: Due to "Same Origin Policy", you can't get cross domain data). preload: true, // Custom CSS styling for iframe wrapping element // You can use this to set custom iframe dimensions css: {}, // Iframe tag attributes attr: { scrolling: "auto" } }, // For HTML5 video only video: { tpl: '", format: "", // custom video format autoStart: true }, // Default content type if cannot be detected automatically defaultType: "image", // Open/close animation type // Possible values: // false - disable // "zoom" - zoom images from/to thumbnail // "fade" // "zoom-in-out" // animationEffect: "zoom", // Duration in ms for open/close animation animationDuration: 366, // Should image change opacity while zooming // If opacity is "auto", then opacity will be changed if image and thumbnail have different aspect ratios zoomOpacity: "auto", // Transition effect between slides // // Possible values: // false - disable // "fade' // "slide' // "circular' // "tube' // "zoom-in-out' // "rotate' // transitionEffect: "fade", // Duration in ms for transition animation transitionDuration: 366, // Custom CSS class for slide element slideClass: "", // Custom CSS class for layout baseClass: "", // Base template for layout baseTpl: '", // Loading indicator template spinnerTpl: '
', // Error message template errorTpl: '

{{ERROR}}

', btnTpl: { download: '' + '' + "", zoom: '", close: '", // Arrows arrowLeft: '", arrowRight: '", // This small close button will be appended to your html/inline/ajax content by default, // if "smallBtn" option is not set to false smallBtn: '" }, // Container is injected into this element parentEl: "body", // Hide browser vertical scrollbars; use at your own risk hideScrollbar: true, // Focus handling // ============== // Try to focus on the first focusable element after opening autoFocus: true, // Put focus back to active element after closing backFocus: true, // Do not let user to focus on element outside modal content trapFocus: true, // Module specific options // ======================= fullScreen: { autoStart: false }, // Set `touch: false` to disable panning/swiping touch: { vertical: true, // Allow to drag content vertically momentum: true // Continue movement after releasing mouse/touch when panning }, // Hash value when initializing manually, // set `false` to disable hash change hash: null, // Customize or add new media types // Example: /* media : { youtube : { params : { autoplay : 0 } } } */ media: {}, slideShow: { autoStart: false, speed: 3000 }, thumbs: { autoStart: false, // Display thumbnails on opening hideOnClose: true, // Hide thumbnail grid when closing animation starts parentEl: ".fancybox-container", // Container is injected into this element axis: "y" // Vertical (y) or horizontal (x) scrolling }, // Use mousewheel to navigate gallery // If 'auto' - enabled for images only wheel: "auto", // Callbacks //========== // See Documentation/API/Events for more information // Example: /* afterShow: function( instance, current ) { console.info( 'Clicked element:' ); console.info( current.opts.$orig ); } */ onInit: $.noop, // When instance has been initialized beforeLoad: $.noop, // Before the content of a slide is being loaded afterLoad: $.noop, // When the content of a slide is done loading beforeShow: $.noop, // Before open animation starts afterShow: $.noop, // When content is done loading and animating beforeClose: $.noop, // Before the instance attempts to close. Return false to cancel the close. afterClose: $.noop, // After instance has been closed onActivate: $.noop, // When instance is brought to front onDeactivate: $.noop, // When other instance has been activated // Interaction // =========== // Use options below to customize taken action when user clicks or double clicks on the fancyBox area, // each option can be string or method that returns value. // // Possible values: // "close" - close instance // "next" - move to next gallery item // "nextOrClose" - move to next gallery item or close if gallery has only one item // "toggleControls" - show/hide controls // "zoom" - zoom image (if loaded) // false - do nothing // Clicked on the content clickContent: function (current, event) { return current.type === "image" ? "zoom" : false; }, // Clicked on the slide clickSlide: "close", // Clicked on the background (backdrop) element; // if you have not changed the layout, then most likely you need to use `clickSlide` option clickOutside: "close", // Same as previous two, but for double click dblclickContent: false, dblclickSlide: false, dblclickOutside: false, // Custom options when mobile device is detected // ============================================= mobile: { preventCaptionOverlap: false, idleTime: false, clickContent: function (current, event) { return current.type === "image" ? "toggleControls" : false; }, clickSlide: function (current, event) { return current.type === "image" ? "toggleControls" : "close"; }, dblclickContent: function (current, event) { return current.type === "image" ? "zoom" : false; }, dblclickSlide: function (current, event) { return current.type === "image" ? "zoom" : false; } }, // Internationalization // ==================== lang: "en", i18n: { en: { CLOSE: "Close", NEXT: "Next", PREV: "Previous", ERROR: "The requested content cannot be loaded.
Please try again later.", PLAY_START: "Start slideshow", PLAY_STOP: "Pause slideshow", FULL_SCREEN: "Full screen", THUMBS: "Thumbnails", DOWNLOAD: "Download", SHARE: "Share", ZOOM: "Zoom" }, de: { CLOSE: "Schließen", NEXT: "Weiter", PREV: "Zurück", ERROR: "Die angeforderten Daten konnten nicht geladen werden.
Bitte versuchen Sie es später nochmal.", PLAY_START: "Diaschau starten", PLAY_STOP: "Diaschau beenden", FULL_SCREEN: "Vollbild", THUMBS: "Vorschaubilder", DOWNLOAD: "Herunterladen", SHARE: "Teilen", ZOOM: "Vergrößern" } } }; // Few useful variables and methods // ================================ var $W = $(window); var $D = $(document); var called = 0; // Check if an object is a jQuery object and not a native JavaScript object // ======================================================================== var isQuery = function (obj) { return obj && obj.hasOwnProperty && obj instanceof $; }; // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" // =============================================================================== var requestAFrame = (function () { return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || // if all else fails, use setTimeout function (callback) { return window.setTimeout(callback, 1000 / 60); } ); })(); var cancelAFrame = (function () { return ( window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function (id) { window.clearTimeout(id); } ); })(); // Detect the supported transition-end event property name // ======================================================= var transitionEnd = (function () { var el = document.createElement("fakeelement"), t; var transitions = { transition: "transitionend", OTransition: "oTransitionEnd", MozTransition: "transitionend", WebkitTransition: "webkitTransitionEnd" }; for (t in transitions) { if (el.style[t] !== undefined) { return transitions[t]; } } return "transitionend"; })(); // Force redraw on an element. // This helps in cases where the browser doesn't redraw an updated element properly // ================================================================================ var forceRedraw = function ($el) { return $el && $el.length && $el[0].offsetHeight; }; // Exclude array (`buttons`) options from deep merging // =================================================== var mergeOpts = function (opts1, opts2) { var rez = $.extend(true, {}, opts1, opts2); $.each(opts2, function (key, value) { if ($.isArray(value)) { rez[key] = value; } }); return rez; }; // How much of an element is visible in viewport // ============================================= var inViewport = function (elem) { var elemCenter, rez; if (!elem || elem.ownerDocument !== document) { return false; } $(".fancybox-container").css("pointer-events", "none"); elemCenter = { x: elem.getBoundingClientRect().left + elem.offsetWidth / 2, y: elem.getBoundingClientRect().top + elem.offsetHeight / 2 }; rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem; $(".fancybox-container").css("pointer-events", ""); return rez; }; // Class definition // ================ var FancyBox = function (content, opts, index) { var self = this; self.opts = mergeOpts({ index: index }, $.fancybox.defaults); if ($.isPlainObject(opts)) { self.opts = mergeOpts(self.opts, opts); } if ($.fancybox.isMobile) { self.opts = mergeOpts(self.opts, self.opts.mobile); } self.id = self.opts.id || ++called; self.currIndex = parseInt(self.opts.index, 10) || 0; self.prevIndex = null; self.prevPos = null; self.currPos = 0; self.firstRun = true; // All group items self.group = []; // Existing slides (for current, next and previous gallery items) self.slides = {}; // Create group elements self.addContent(content); if (!self.group.length) { return; } self.init(); }; $.extend(FancyBox.prototype, { // Create DOM structure // ==================== init: function () { var self = this, firstItem = self.group[self.currIndex], firstItemOpts = firstItem.opts, $container, buttonStr; if (firstItemOpts.closeExisting) { $.fancybox.close(true); } // Hide scrollbars // =============== $("body").addClass("fancybox-active"); if ( !$.fancybox.getInstance() && firstItemOpts.hideScrollbar !== false && !$.fancybox.isMobile && document.body.scrollHeight > window.innerHeight ) { $("head").append( '" ); $("body").addClass("compensate-for-scrollbar"); } // Build html markup and set references // ==================================== // Build html code for buttons and insert into main template buttonStr = ""; $.each(firstItemOpts.buttons, function (index, value) { buttonStr += firstItemOpts.btnTpl[value] || ""; }); // Create markup from base template, it will be initially hidden to // avoid unnecessary work like painting while initializing is not complete $container = $( self.translate( self, firstItemOpts.baseTpl .replace("{{buttons}}", buttonStr) .replace("{{arrows}}", firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight) ) ) .attr("id", "fancybox-container-" + self.id) .addClass(firstItemOpts.baseClass) .data("FancyBox", self) .appendTo(firstItemOpts.parentEl); // Create object holding references to jQuery wrapped nodes self.$refs = { container: $container }; ["bg", "inner", "infobar", "toolbar", "stage", "caption", "navigation"].forEach(function (item) { self.$refs[item] = $container.find(".fancybox-" + item); }); self.trigger("onInit"); // Enable events, deactive previous instances self.activate(); // Build slides, load and reveal content self.jumpTo(self.currIndex); }, // Simple i18n support - replaces object keys found in template // with corresponding values // ============================================================ translate: function (obj, str) { var arr = obj.opts.i18n[obj.opts.lang] || obj.opts.i18n.en; return str.replace(/\{\{(\w+)\}\}/g, function (match, n) { return arr[n] === undefined ? match : arr[n]; }); }, // Populate current group with fresh content // Check if each object has valid type and content // =============================================== addContent: function (content) { var self = this, items = $.makeArray(content), thumbs; $.each(items, function (i, item) { var obj = {}, opts = {}, $item, type, found, src, srcParts; // Step 1 - Make sure we have an object // ==================================== if ($.isPlainObject(item)) { // We probably have manual usage here, something like // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) obj = item; opts = item.opts || item; } else if ($.type(item) === "object" && $(item).length) { // Here we probably have jQuery collection returned by some selector $item = $(item); // Support attributes like `data-options='{"touch" : false}'` and `data-touch='false'` opts = $item.data() || {}; opts = $.extend(true, {}, opts, opts.options); // Here we store clicked element opts.$orig = $item; obj.src = self.opts.src || opts.src || $item.attr("href"); // Assume that simple syntax is used, for example: // `$.fancybox.open( $("#test"), {} );` if (!obj.type && !obj.src) { obj.type = "inline"; obj.src = item; } } else { // Assume we have a simple html code, for example: // $.fancybox.open( '

Hi!

' ); obj = { type: "html", src: item + "" }; } // Each gallery object has full collection of options obj.opts = $.extend(true, {}, self.opts, opts); // Do not merge buttons array if ($.isArray(opts.buttons)) { obj.opts.buttons = opts.buttons; } if ($.fancybox.isMobile && obj.opts.mobile) { obj.opts = mergeOpts(obj.opts, obj.opts.mobile); } // Step 2 - Make sure we have content type, if not - try to guess // ============================================================== type = obj.type || obj.opts.type; src = obj.src || ""; if (!type && src) { if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) { type = "video"; if (!obj.opts.video.format) { obj.opts.video.format = "video/" + (found[1] === "ogv" ? "ogg" : found[1]); } } else if (src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) { type = "image"; } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) { type = "iframe"; obj = $.extend(true, obj, { contentType: "pdf", opts: { iframe: { preload: false } } }); } else if (src.charAt(0) === "#") { type = "inline"; } } if (type) { obj.type = type; } else { self.trigger("objectNeedsType", obj); } if (!obj.contentType) { obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type; } // Step 3 - Some adjustments // ========================= obj.index = self.group.length; if (obj.opts.smallBtn == "auto") { obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1; } if (obj.opts.toolbar === "auto") { obj.opts.toolbar = !obj.opts.smallBtn; } // Find thumbnail image, check if exists and if is in the viewport obj.$thumb = obj.opts.$thumb || null; if (obj.opts.$trigger && obj.index === self.opts.index) { obj.$thumb = obj.opts.$trigger.find("img:first"); if (obj.$thumb.length) { obj.opts.$orig = obj.opts.$trigger; } } if (!(obj.$thumb && obj.$thumb.length) && obj.opts.$orig) { obj.$thumb = obj.opts.$orig.find("img:first"); } if (obj.$thumb && !obj.$thumb.length) { obj.$thumb = null; } obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null); // "caption" is a "special" option, it can be used to customize caption per gallery item if ($.type(obj.opts.caption) === "function") { obj.opts.caption = obj.opts.caption.apply(item, [self, obj]); } if ($.type(self.opts.caption) === "function") { obj.opts.caption = self.opts.caption.apply(item, [self, obj]); } // Make sure we have caption as a string or jQuery object if (!(obj.opts.caption instanceof $)) { obj.opts.caption = obj.opts.caption === undefined ? "" : obj.opts.caption + ""; } // Check if url contains "filter" used to filter the content // Example: "ajax.html #something" if (obj.type === "ajax") { srcParts = src.split(/\s+/, 2); if (srcParts.length > 1) { obj.src = srcParts.shift(); obj.opts.filter = srcParts.shift(); } } // Hide all buttons and disable interactivity for modal items if (obj.opts.modal) { obj.opts = $.extend(true, obj.opts, { trapFocus: true, // Remove buttons infobar: 0, toolbar: 0, smallBtn: 0, // Disable keyboard navigation keyboard: 0, // Disable some modules slideShow: 0, fullScreen: 0, thumbs: 0, touch: 0, // Disable click event handlers clickContent: false, clickSlide: false, clickOutside: false, dblclickContent: false, dblclickSlide: false, dblclickOutside: false }); } // Step 4 - Add processed object to group // ====================================== self.group.push(obj); }); // Update controls if gallery is already opened if (Object.keys(self.slides).length) { self.updateControls(); // Update thumbnails, if needed thumbs = self.Thumbs; if (thumbs && thumbs.isActive) { thumbs.create(); thumbs.focus(); } } }, // Attach an event handler functions for: // - navigation buttons // - browser scrolling, resizing; // - focusing // - keyboard // - detecting inactivity // ====================================== addEvents: function () { var self = this; self.removeEvents(); // Make navigation elements clickable // ================================== self.$refs.container .on("click.fb-close", "[data-fancybox-close]", function (e) { e.stopPropagation(); e.preventDefault(); self.close(e); }) .on("touchstart.fb-prev click.fb-prev", "[data-fancybox-prev]", function (e) { e.stopPropagation(); e.preventDefault(); self.previous(); }) .on("touchstart.fb-next click.fb-next", "[data-fancybox-next]", function (e) { e.stopPropagation(); e.preventDefault(); self.next(); }) .on("click.fb", "[data-fancybox-zoom]", function (e) { // Click handler for zoom button self[self.isScaledDown() ? "scaleToActual" : "scaleToFit"](); }); // Handle page scrolling and browser resizing // ========================================== $W.on("orientationchange.fb resize.fb", function (e) { if (e && e.originalEvent && e.originalEvent.type === "resize") { if (self.requestId) { cancelAFrame(self.requestId); } self.requestId = requestAFrame(function () { self.update(e); }); } else { if (self.current && self.current.type === "iframe") { self.$refs.stage.hide(); } setTimeout( function () { self.$refs.stage.show(); self.update(e); }, $.fancybox.isMobile ? 600 : 250 ); } }); $D.on("keydown.fb", function (e) { var instance = $.fancybox ? $.fancybox.getInstance() : null, current = instance.current, keycode = e.keyCode || e.which; // Trap keyboard focus inside of the modal // ======================================= if (keycode == 9) { if (current.opts.trapFocus) { self.focus(e); } return; } // Enable keyboard navigation // ========================== if (!current.opts.keyboard || e.ctrlKey || e.altKey || e.shiftKey || $(e.target).is("input,textarea,video,audio,select")) { return; } // Backspace and Esc keys if (keycode === 8 || keycode === 27) { e.preventDefault(); self.close(e); return; } // Left arrow and Up arrow if (keycode === 37 || keycode === 38) { e.preventDefault(); self.previous(); return; } // Righ arrow and Down arrow if (keycode === 39 || keycode === 40) { e.preventDefault(); self.next(); return; } self.trigger("afterKeydown", e, keycode); }); // Hide controls after some inactivity period if (self.group[self.currIndex].opts.idleTime) { self.idleSecondsCounter = 0; $D.on( "mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle", function (e) { self.idleSecondsCounter = 0; if (self.isIdle) { self.showControls(); } self.isIdle = false; } ); self.idleInterval = window.setInterval(function () { self.idleSecondsCounter++; if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) { self.isIdle = true; self.idleSecondsCounter = 0; self.hideControls(); } }, 1000); } }, // Remove events added by the core // =============================== removeEvents: function () { var self = this; $W.off("orientationchange.fb resize.fb"); $D.off("keydown.fb .fb-idle"); this.$refs.container.off(".fb-close .fb-prev .fb-next"); if (self.idleInterval) { window.clearInterval(self.idleInterval); self.idleInterval = null; } }, // Change to previous gallery item // =============================== previous: function (duration) { return this.jumpTo(this.currPos - 1, duration); }, // Change to next gallery item // =========================== next: function (duration) { return this.jumpTo(this.currPos + 1, duration); }, // Switch to selected gallery item // =============================== jumpTo: function (pos, duration) { var self = this, groupLen = self.group.length, firstRun, isMoved, loop, current, previous, slidePos, stagePos, prop, diff; if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) { return; } // Should loop? pos = parseInt(pos, 10); loop = self.current ? self.current.opts.loop : self.opts.loop; if (!loop && (pos < 0 || pos >= groupLen)) { return false; } // Check if opening for the first time; this helps to speed things up firstRun = self.firstRun = !Object.keys(self.slides).length; // Create slides previous = self.current; self.prevIndex = self.currIndex; self.prevPos = self.currPos; current = self.createSlide(pos); if (groupLen > 1) { if (loop || current.index < groupLen - 1) { self.createSlide(pos + 1); } if (loop || current.index > 0) { self.createSlide(pos - 1); } } self.current = current; self.currIndex = current.index; self.currPos = current.pos; self.trigger("beforeShow", firstRun); self.updateControls(); // Validate duration length current.forcedDuration = undefined; if ($.isNumeric(duration)) { current.forcedDuration = duration; } else { duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"]; } duration = parseInt(duration, 10); // Check if user has swiped the slides or if still animating isMoved = self.isMoved(current); // Make sure current slide is visible current.$slide.addClass("fancybox-slide--current"); // Fresh start - reveal container, current slide and start loading content if (firstRun) { if (current.opts.animationEffect && duration) { self.$refs.container.css("transition-duration", duration + "ms"); } self.$refs.container.addClass("fancybox-is-open").trigger("focus"); // Attempt to load content into slide // This will later call `afterLoad` -> `revealContent` self.loadSlide(current); self.preload("image"); return; } // Get actual slide/stage positions (before cleaning up) slidePos = $.fancybox.getTranslate(previous.$slide); stagePos = $.fancybox.getTranslate(self.$refs.stage); // Clean up all slides $.each(self.slides, function (index, slide) { $.fancybox.stop(slide.$slide, true); }); if (previous.pos !== current.pos) { previous.isComplete = false; } previous.$slide.removeClass("fancybox-slide--complete fancybox-slide--current"); // If slides are out of place, then animate them to correct position if (isMoved) { // Calculate horizontal swipe distance diff = slidePos.left - (previous.pos * slidePos.width + previous.pos * previous.opts.gutter); $.each(self.slides, function (index, slide) { slide.$slide.removeClass("fancybox-animated").removeClass(function (index, className) { return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" "); }); // Make sure that each slide is in equal distance // This is mostly needed for freshly added slides, because they are not yet positioned var leftPos = slide.pos * slidePos.width + slide.pos * slide.opts.gutter; $.fancybox.setTranslate(slide.$slide, { top: 0, left: leftPos - stagePos.left + diff }); if (slide.pos !== current.pos) { slide.$slide.addClass("fancybox-slide--" + (slide.pos > current.pos ? "next" : "previous")); } // Redraw to make sure that transition will start forceRedraw(slide.$slide); // Animate the slide $.fancybox.animate( slide.$slide, { top: 0, left: (slide.pos - current.pos) * slidePos.width + (slide.pos - current.pos) * slide.opts.gutter }, duration, function () { slide.$slide .css({ transform: "", opacity: "" }) .removeClass("fancybox-slide--next fancybox-slide--previous"); if (slide.pos === self.currPos) { self.complete(); } } ); }); } else if (duration && current.opts.transitionEffect) { // Set transition effect for previously active slide prop = "fancybox-animated fancybox-fx-" + current.opts.transitionEffect; previous.$slide.addClass("fancybox-slide--" + (previous.pos > current.pos ? "next" : "previous")); $.fancybox.animate( previous.$slide, prop, duration, function () { previous.$slide.removeClass(prop).removeClass("fancybox-slide--next fancybox-slide--previous"); }, false ); } if (current.isLoaded) { self.revealContent(current); } else { self.loadSlide(current); } self.preload("image"); }, // Create new "slide" element // These are gallery items that are actually added to DOM // ======================================================= createSlide: function (pos) { var self = this, $slide, index; index = pos % self.group.length; index = index < 0 ? self.group.length + index : index; if (!self.slides[pos] && self.group[index]) { $slide = $('
').appendTo(self.$refs.stage); self.slides[pos] = $.extend(true, {}, self.group[index], { pos: pos, $slide: $slide, isLoaded: false }); self.updateSlide(self.slides[pos]); } return self.slides[pos]; }, // Scale image to the actual size of the image; // x and y values should be relative to the slide // ============================================== scaleToActual: function (x, y, duration) { var self = this, current = self.current, $content = current.$content, canvasWidth = $.fancybox.getTranslate(current.$slide).width, canvasHeight = $.fancybox.getTranslate(current.$slide).height, newImgWidth = current.width, newImgHeight = current.height, imgPos, posX, posY, scaleX, scaleY; if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { return; } self.isAnimating = true; $.fancybox.stop($content); x = x === undefined ? canvasWidth * 0.5 : x; y = y === undefined ? canvasHeight * 0.5 : y; imgPos = $.fancybox.getTranslate($content); imgPos.top -= $.fancybox.getTranslate(current.$slide).top; imgPos.left -= $.fancybox.getTranslate(current.$slide).left; scaleX = newImgWidth / imgPos.width; scaleY = newImgHeight / imgPos.height; // Get center position for original image posX = canvasWidth * 0.5 - newImgWidth * 0.5; posY = canvasHeight * 0.5 - newImgHeight * 0.5; // Make sure image does not move away from edges if (newImgWidth > canvasWidth) { posX = imgPos.left * scaleX - (x * scaleX - x); if (posX > 0) { posX = 0; } if (posX < canvasWidth - newImgWidth) { posX = canvasWidth - newImgWidth; } } if (newImgHeight > canvasHeight) { posY = imgPos.top * scaleY - (y * scaleY - y); if (posY > 0) { posY = 0; } if (posY < canvasHeight - newImgHeight) { posY = canvasHeight - newImgHeight; } } self.updateCursor(newImgWidth, newImgHeight); $.fancybox.animate( $content, { top: posY, left: posX, scaleX: scaleX, scaleY: scaleY }, duration || 366, function () { self.isAnimating = false; } ); // Stop slideshow if (self.SlideShow && self.SlideShow.isActive) { self.SlideShow.stop(); } }, // Scale image to fit inside parent element // ======================================== scaleToFit: function (duration) { var self = this, current = self.current, $content = current.$content, end; if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { return; } self.isAnimating = true; $.fancybox.stop($content); end = self.getFitPos(current); self.updateCursor(end.width, end.height); $.fancybox.animate( $content, { top: end.top, left: end.left, scaleX: end.width / $content.width(), scaleY: end.height / $content.height() }, duration || 366, function () { self.isAnimating = false; } ); }, // Calculate image size to fit inside viewport // =========================================== getFitPos: function (slide) { var self = this, $content = slide.$content, $slide = slide.$slide, width = slide.width || slide.opts.width, height = slide.height || slide.opts.height, maxWidth, maxHeight, minRatio, aspectRatio, rez = {}; if (!slide.isLoaded || !$content || !$content.length) { return false; } maxWidth = $.fancybox.getTranslate(self.$refs.stage).width; maxHeight = $.fancybox.getTranslate(self.$refs.stage).height; maxWidth -= parseFloat($slide.css("paddingLeft")) + parseFloat($slide.css("paddingRight")) + parseFloat($content.css("marginLeft")) + parseFloat($content.css("marginRight")); maxHeight -= parseFloat($slide.css("paddingTop")) + parseFloat($slide.css("paddingBottom")) + parseFloat($content.css("marginTop")) + parseFloat($content.css("marginBottom")); if (!width || !height) { width = maxWidth; height = maxHeight; } minRatio = Math.min(1, maxWidth / width, maxHeight / height); width = minRatio * width; height = minRatio * height; // Adjust width/height to precisely fit into container if (width > maxWidth - 0.5) { width = maxWidth; } if (height > maxHeight - 0.5) { height = maxHeight; } if (slide.type === "image") { rez.top = Math.floor((maxHeight - height) * 0.5) + parseFloat($slide.css("paddingTop")); rez.left = Math.floor((maxWidth - width) * 0.5) + parseFloat($slide.css("paddingLeft")); } else if (slide.contentType === "video") { // Force aspect ratio for the video // "I say the whole world must learn of our peaceful ways… by force!" aspectRatio = slide.opts.width && slide.opts.height ? width / height : slide.opts.ratio || 16 / 9; if (height > width / aspectRatio) { height = width / aspectRatio; } else if (width > height * aspectRatio) { width = height * aspectRatio; } } rez.width = width; rez.height = height; return rez; }, // Update content size and position for all slides // ============================================== update: function (e) { var self = this; $.each(self.slides, function (key, slide) { self.updateSlide(slide, e); }); }, // Update slide content position and size // ====================================== updateSlide: function (slide, e) { var self = this, $content = slide && slide.$content, width = slide.width || slide.opts.width, height = slide.height || slide.opts.height, $slide = slide.$slide; // First, prevent caption overlap, if needed self.adjustCaption(slide); // Then resize content to fit inside the slide if ($content && (width || height || slide.contentType === "video") && !slide.hasError) { $.fancybox.stop($content); $.fancybox.setTranslate($content, self.getFitPos(slide)); if (slide.pos === self.currPos) { self.isAnimating = false; self.updateCursor(); } } // Then some adjustments self.adjustLayout(slide); if ($slide.length) { $slide.trigger("refresh"); if (slide.pos === self.currPos) { self.$refs.toolbar .add(self.$refs.navigation.find(".fancybox-button--arrow_right")) .toggleClass("compensate-for-scrollbar", $slide.get(0).scrollHeight > $slide.get(0).clientHeight); } } self.trigger("onUpdate", slide, e); }, // Horizontally center slide // ========================= centerSlide: function (duration) { var self = this, current = self.current, $slide = current.$slide; if (self.isClosing || !current) { return; } $slide.siblings().css({ transform: "", opacity: "" }); $slide .parent() .children() .removeClass("fancybox-slide--previous fancybox-slide--next"); $.fancybox.animate( $slide, { top: 0, left: 0, opacity: 1 }, duration === undefined ? 0 : duration, function () { // Clean up $slide.css({ transform: "", opacity: "" }); if (!current.isComplete) { self.complete(); } }, false ); }, // Check if current slide is moved (swiped) // ======================================== isMoved: function (slide) { var current = slide || this.current, slidePos, stagePos; if (!current) { return false; } stagePos = $.fancybox.getTranslate(this.$refs.stage); slidePos = $.fancybox.getTranslate(current.$slide); return ( !current.$slide.hasClass("fancybox-animated") && (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5) ); }, // Update cursor style depending if content can be zoomed // ====================================================== updateCursor: function (nextWidth, nextHeight) { var self = this, current = self.current, $container = self.$refs.container, canPan, isZoomable; if (!current || self.isClosing || !self.Guestures) { return; } $container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan"); canPan = self.canPan(nextWidth, nextHeight); isZoomable = canPan ? true : self.isZoomable(); $container.toggleClass("fancybox-is-zoomable", isZoomable); $("[data-fancybox-zoom]").prop("disabled", !isZoomable); if (canPan) { $container.addClass("fancybox-can-pan"); } else if ( isZoomable && (current.opts.clickContent === "zoom" || ($.isFunction(current.opts.clickContent) && current.opts.clickContent(current) == "zoom")) ) { $container.addClass("fancybox-can-zoomIn"); } else if (current.opts.touch && (current.opts.touch.vertical || self.group.length > 1) && current.contentType !== "video") { $container.addClass("fancybox-can-swipe"); } }, // Check if current slide is zoomable // ================================== isZoomable: function () { var self = this, current = self.current, fitPos; // Assume that slide is zoomable if: // - image is still loading // - actual size of the image is smaller than available area if (current && !self.isClosing && current.type === "image" && !current.hasError) { if (!current.isLoaded) { return true; } fitPos = self.getFitPos(current); if (fitPos && (current.width > fitPos.width || current.height > fitPos.height)) { return true; } } return false; }, // Check if current image dimensions are smaller than actual // ========================================================= isScaledDown: function (nextWidth, nextHeight) { var self = this, rez = false, current = self.current, $content = current.$content; if (nextWidth !== undefined && nextHeight !== undefined) { rez = nextWidth < current.width && nextHeight < current.height; } else if ($content) { rez = $.fancybox.getTranslate($content); rez = rez.width < current.width && rez.height < current.height; } return rez; }, // Check if image dimensions exceed parent element // =============================================== canPan: function (nextWidth, nextHeight) { var self = this, current = self.current, pos = null, rez = false; if (current.type === "image" && (current.isComplete || (nextWidth && nextHeight)) && !current.hasError) { rez = self.getFitPos(current); if (nextWidth !== undefined && nextHeight !== undefined) { pos = { width: nextWidth, height: nextHeight }; } else if (current.isComplete) { pos = $.fancybox.getTranslate(current.$content); } if (pos && rez) { rez = Math.abs(pos.width - rez.width) > 1.5 || Math.abs(pos.height - rez.height) > 1.5; } } return rez; }, // Load content into the slide // =========================== loadSlide: function (slide) { var self = this, type, $slide, ajaxLoad; if (slide.isLoading || slide.isLoaded) { return; } slide.isLoading = true; if (self.trigger("beforeLoad", slide) === false) { slide.isLoading = false; return false; } type = slide.type; $slide = slide.$slide; $slide .off("refresh") .trigger("onReset") .addClass(slide.opts.slideClass); // Create content depending on the type switch (type) { case "image": self.setImage(slide); break; case "iframe": self.setIframe(slide); break; case "html": self.setContent(slide, slide.src || slide.content); break; case "video": self.setContent( slide, slide.opts.video.tpl .replace(/\{\{src\}\}/gi, slide.src) .replace("{{format}}", slide.opts.videoFormat || slide.opts.video.format || "") .replace("{{poster}}", slide.thumb || "") ); break; case "inline": if ($(slide.src).length) { self.setContent(slide, $(slide.src)); } else { self.setError(slide); } break; case "ajax": self.showLoading(slide); ajaxLoad = $.ajax( $.extend({}, slide.opts.ajax.settings, { url: slide.src, success: function (data, textStatus) { if (textStatus === "success") { self.setContent(slide, data); } }, error: function (jqXHR, textStatus) { if (jqXHR && textStatus !== "abort") { self.setError(slide); } } }) ); $slide.one("onReset", function () { ajaxLoad.abort(); }); break; default: self.setError(slide); break; } return true; }, // Use thumbnail image, if possible // ================================ setImage: function (slide) { var self = this, ghost; // Check if need to show loading icon setTimeout(function () { var $img = slide.$image; if (!self.isClosing && slide.isLoading && (!$img || !$img.length || !$img[0].complete) && !slide.hasError) { self.showLoading(slide); } }, 50); //Check if image has srcset self.checkSrcset(slide); // This will be wrapper containing both ghost and actual image slide.$content = $('
') .addClass("fancybox-is-hidden") .appendTo(slide.$slide.addClass("fancybox-slide--image")); // If we have a thumbnail, we can display it while actual image is loading // Users will not stare at black screen and actual image will appear gradually if (slide.opts.preload !== false && slide.opts.width && slide.opts.height && slide.thumb) { slide.width = slide.opts.width; slide.height = slide.opts.height; ghost = document.createElement("img"); ghost.onerror = function () { $(this).remove(); slide.$ghost = null; }; ghost.onload = function () { self.afterLoad(slide); }; slide.$ghost = $(ghost) .addClass("fancybox-image") .appendTo(slide.$content) .attr("src", slide.thumb); } // Start loading actual image self.setBigImage(slide); }, // Check if image has srcset and get the source // ============================================ checkSrcset: function (slide) { var srcset = slide.opts.srcset || slide.opts.image.srcset, found, temp, pxRatio, windowWidth; // If we have "srcset", then we need to find first matching "src" value. // This is necessary, because when you set an src attribute, the browser will preload the image // before any javascript or even CSS is applied. if (srcset) { pxRatio = window.devicePixelRatio || 1; windowWidth = window.innerWidth * pxRatio; temp = srcset.split(",").map(function (el) { var ret = {}; el.trim() .split(/\s+/) .forEach(function (el, i) { var value = parseInt(el.substring(0, el.length - 1), 10); if (i === 0) { return (ret.url = el); } if (value) { ret.value = value; ret.postfix = el[el.length - 1]; } }); return ret; }); // Sort by value temp.sort(function (a, b) { return a.value - b.value; }); // Ok, now we have an array of all srcset values for (var j = 0; j < temp.length; j++) { var el = temp[j]; if ((el.postfix === "w" && el.value >= windowWidth) || (el.postfix === "x" && el.value >= pxRatio)) { found = el; break; } } // If not found, take the last one if (!found && temp.length) { found = temp[temp.length - 1]; } if (found) { slide.src = found.url; // If we have default width/height values, we can calculate height for matching source if (slide.width && slide.height && found.postfix == "w") { slide.height = (slide.width / slide.height) * found.value; slide.width = found.value; } slide.opts.srcset = srcset; } } }, // Create full-size image // ====================== setBigImage: function (slide) { var self = this, img = document.createElement("img"), $img = $(img); slide.$image = $img .one("error", function () { self.setError(slide); }) .one("load", function () { var sizes; if (!slide.$ghost) { self.resolveImageSlideSize(slide, this.naturalWidth, this.naturalHeight); self.afterLoad(slide); } if (self.isClosing) { return; } if (slide.opts.srcset) { sizes = slide.opts.sizes; if (!sizes || sizes === "auto") { sizes = (slide.width / slide.height > 1 && $W.width() / $W.height() > 1 ? "100" : Math.round((slide.width / slide.height) * 100)) + "vw"; } $img.attr("sizes", sizes).attr("srcset", slide.opts.srcset); } // Hide temporary image after some delay if (slide.$ghost) { setTimeout(function () { if (slide.$ghost && !self.isClosing) { slide.$ghost.hide(); } }, Math.min(300, Math.max(1000, slide.height / 1600))); } self.hideLoading(slide); }) .addClass("fancybox-image") .attr("src", slide.src) .appendTo(slide.$content); if ((img.complete || img.readyState == "complete") && $img.naturalWidth && $img.naturalHeight) { $img.trigger("load"); } else if (img.error) { $img.trigger("error"); } }, // Computes the slide size from image size and maxWidth/maxHeight // ============================================================== resolveImageSlideSize: function (slide, imgWidth, imgHeight) { var maxWidth = parseInt(slide.opts.width, 10), maxHeight = parseInt(slide.opts.height, 10); // Sets the default values from the image slide.width = imgWidth; slide.height = imgHeight; if (maxWidth > 0) { slide.width = maxWidth; slide.height = Math.floor((maxWidth * imgHeight) / imgWidth); } if (maxHeight > 0) { slide.width = Math.floor((maxHeight * imgWidth) / imgHeight); slide.height = maxHeight; } }, // Create iframe wrapper, iframe and bindings // ========================================== setIframe: function (slide) { var self = this, opts = slide.opts.iframe, $slide = slide.$slide, $iframe; slide.$content = $('
') .css(opts.css) .appendTo($slide); $slide.addClass("fancybox-slide--" + slide.contentType); slide.$iframe = $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime())) .attr(opts.attr) .appendTo(slide.$content); if (opts.preload) { self.showLoading(slide); // Unfortunately, it is not always possible to determine if iframe is successfully loaded // (due to browser security policy) $iframe.on("load.fb error.fb", function (e) { this.isReady = 1; slide.$slide.trigger("refresh"); self.afterLoad(slide); }); // Recalculate iframe content size // =============================== $slide.on("refresh.fb", function () { var $content = slide.$content, frameWidth = opts.css.width, frameHeight = opts.css.height, $contents, $body; if ($iframe[0].isReady !== 1) { return; } try { $contents = $iframe.contents(); $body = $contents.find("body"); } catch (ignore) {} // Calculate content dimensions, if it is accessible if ($body && $body.length && $body.children().length) { // Avoid scrolling to top (if multiple instances) $slide.css("overflow", "visible"); $content.css({ width: "100%", "max-width": "100%", height: "9999px" }); if (frameWidth === undefined) { frameWidth = Math.ceil(Math.max($body[0].clientWidth, $body.outerWidth(true))); } $content.css("width", frameWidth ? frameWidth : "").css("max-width", ""); if (frameHeight === undefined) { frameHeight = Math.ceil(Math.max($body[0].clientHeight, $body.outerHeight(true))); } $content.css("height", frameHeight ? frameHeight : ""); $slide.css("overflow", "auto"); } $content.removeClass("fancybox-is-hidden"); }); } else { self.afterLoad(slide); } $iframe.attr("src", slide.src); // Remove iframe if closing or changing gallery item $slide.one("onReset", function () { // This helps IE not to throw errors when closing try { $(this) .find("iframe") .hide() .unbind() .attr("src", "//about:blank"); } catch (ignore) {} $(this) .off("refresh.fb") .empty(); slide.isLoaded = false; slide.isRevealed = false; }); }, // Wrap and append content to the slide // ====================================== setContent: function (slide, content) { var self = this; if (self.isClosing) { return; } self.hideLoading(slide); if (slide.$content) { $.fancybox.stop(slide.$content); } slide.$slide.empty(); // If content is a jQuery object, then it will be moved to the slide. // The placeholder is created so we will know where to put it back. if (isQuery(content) && content.parent().length) { // Make sure content is not already moved to fancyBox if (content.hasClass("fancybox-content") || content.parent().hasClass("fancybox-content")) { content.parents(".fancybox-slide").trigger("onReset"); } // Create temporary element marking original place of the content slide.$placeholder = $("
") .hide() .insertAfter(content); // Make sure content is visible content.css("display", "inline-block"); } else if (!slide.hasError) { // If content is just a plain text, try to convert it to html if ($.type(content) === "string") { content = $("
") .append($.trim(content)) .contents(); } // If "filter" option is provided, then filter content if (slide.opts.filter) { content = $("
") .html(content) .find(slide.opts.filter); } } slide.$slide.one("onReset", function () { // Pause all html5 video/audio $(this) .find("video,audio") .trigger("pause"); // Put content back if (slide.$placeholder) { slide.$placeholder.after(content.removeClass("fancybox-content").hide()).remove(); slide.$placeholder = null; } // Remove custom close button if (slide.$smallBtn) { slide.$smallBtn.remove(); slide.$smallBtn = null; } // Remove content and mark slide as not loaded if (!slide.hasError) { $(this).empty(); slide.isLoaded = false; slide.isRevealed = false; } }); $(content).appendTo(slide.$slide); if ($(content).is("video,audio")) { $(content).addClass("fancybox-video"); $(content).wrap("
"); slide.contentType = "video"; slide.opts.width = slide.opts.width || $(content).attr("width"); slide.opts.height = slide.opts.height || $(content).attr("height"); } slide.$content = slide.$slide .children() .filter("div,form,main,video,audio,article,.fancybox-content") .first(); slide.$content.siblings().hide(); // Re-check if there is a valid content // (in some cases, ajax response can contain various elements or plain text) if (!slide.$content.length) { slide.$content = slide.$slide .wrapInner("
") .children() .first(); } slide.$content.addClass("fancybox-content"); slide.$slide.addClass("fancybox-slide--" + slide.contentType); self.afterLoad(slide); }, // Display error message // ===================== setError: function (slide) { slide.hasError = true; slide.$slide .trigger("onReset") .removeClass("fancybox-slide--" + slide.contentType) .addClass("fancybox-slide--error"); slide.contentType = "html"; this.setContent(slide, this.translate(slide, slide.opts.errorTpl)); if (slide.pos === this.currPos) { this.isAnimating = false; } }, // Show loading icon inside the slide // ================================== showLoading: function (slide) { var self = this; slide = slide || self.current; if (slide && !slide.$spinner) { slide.$spinner = $(self.translate(self, self.opts.spinnerTpl)) .appendTo(slide.$slide) .hide() .fadeIn("fast"); } }, // Remove loading icon from the slide // ================================== hideLoading: function (slide) { var self = this; slide = slide || self.current; if (slide && slide.$spinner) { slide.$spinner.stop().remove(); delete slide.$spinner; } }, // Adjustments after slide content has been loaded // =============================================== afterLoad: function (slide) { var self = this; if (self.isClosing) { return; } slide.isLoading = false; slide.isLoaded = true; self.trigger("afterLoad", slide); self.hideLoading(slide); // Add small close button if (slide.opts.smallBtn && (!slide.$smallBtn || !slide.$smallBtn.length)) { slide.$smallBtn = $(self.translate(slide, slide.opts.btnTpl.smallBtn)).appendTo(slide.$content); } // Disable right click if (slide.opts.protect && slide.$content && !slide.hasError) { slide.$content.on("contextmenu.fb", function (e) { if (e.button == 2) { e.preventDefault(); } return true; }); // Add fake element on top of the image // This makes a bit harder for user to select image if (slide.type === "image") { $('
').appendTo(slide.$content); } } self.adjustCaption(slide); self.adjustLayout(slide); if (slide.pos === self.currPos) { self.updateCursor(); } self.revealContent(slide); }, // Prevent caption overlap, // fix css inconsistency across browsers // ===================================== adjustCaption: function (slide) { var self = this, current = slide || self.current, caption = current.opts.caption, preventOverlap = current.opts.preventCaptionOverlap, $caption = self.$refs.caption, $clone, captionH = false; $caption.toggleClass("fancybox-caption--separate", preventOverlap); if (preventOverlap && caption && caption.length) { if (current.pos !== self.currPos) { $clone = $caption.clone().appendTo($caption.parent()); $clone .children() .eq(0) .empty() .html(caption); captionH = $clone.outerHeight(true); $clone.empty().remove(); } else if (self.$caption) { captionH = self.$caption.outerHeight(true); } current.$slide.css("padding-bottom", captionH || ""); } }, // Simple hack to fix inconsistency across browsers, described here (affects Edge, too): // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 // ==================================================================================== adjustLayout: function (slide) { var self = this, current = slide || self.current, scrollHeight, marginBottom, inlinePadding, actualPadding; if (current.isLoaded && current.opts.disableLayoutFix !== true) { current.$content.css("margin-bottom", ""); // If we would always set margin-bottom for the content, // then it would potentially break vertical align if (current.$content.outerHeight() > current.$slide.height() + 0.5) { inlinePadding = current.$slide[0].style["padding-bottom"]; actualPadding = current.$slide.css("padding-bottom"); if (parseFloat(actualPadding) > 0) { scrollHeight = current.$slide[0].scrollHeight; current.$slide.css("padding-bottom", 0); if (Math.abs(scrollHeight - current.$slide[0].scrollHeight) < 1) { marginBottom = actualPadding; } current.$slide.css("padding-bottom", inlinePadding); } } current.$content.css("margin-bottom", marginBottom); } }, // Make content visible // This method is called right after content has been loaded or // user navigates gallery and transition should start // ============================================================ revealContent: function (slide) { var self = this, $slide = slide.$slide, end = false, start = false, isMoved = self.isMoved(slide), isRevealed = slide.isRevealed, effect, effectClassName, duration, opacity; slide.isRevealed = true; effect = slide.opts[self.firstRun ? "animationEffect" : "transitionEffect"]; duration = slide.opts[self.firstRun ? "animationDuration" : "transitionDuration"]; duration = parseInt(slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10); if (isMoved || slide.pos !== self.currPos || !duration) { effect = false; } // Check if can zoom if (effect === "zoom") { if (slide.pos === self.currPos && duration && slide.type === "image" && !slide.hasError && (start = self.getThumbPos(slide))) { end = self.getFitPos(slide); } else { effect = "fade"; } } // Zoom animation // ============== if (effect === "zoom") { self.isAnimating = true; end.scaleX = end.width / start.width; end.scaleY = end.height / start.height; // Check if we need to animate opacity opacity = slide.opts.zoomOpacity; if (opacity == "auto") { opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1; } if (opacity) { start.opacity = 0.1; end.opacity = 1; } // Draw image at start position $.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start); forceRedraw(slide.$content); // Start animation $.fancybox.animate(slide.$content, end, duration, function () { self.isAnimating = false; self.complete(); }); return; } self.updateSlide(slide); // Simply show content if no effect // ================================ if (!effect) { slide.$content.removeClass("fancybox-is-hidden"); if (!isRevealed && isMoved && slide.type === "image" && !slide.hasError) { slide.$content.hide().fadeIn("fast"); } if (slide.pos === self.currPos) { self.complete(); } return; } // Prepare for CSS transiton // ========================= $.fancybox.stop($slide); //effectClassName = "fancybox-animated fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-fx-" + effect; effectClassName = "fancybox-slide--" + (slide.pos >= self.prevPos ? "next" : "previous") + " fancybox-animated fancybox-fx-" + effect; $slide.addClass(effectClassName).removeClass("fancybox-slide--current"); //.addClass(effectClassName); slide.$content.removeClass("fancybox-is-hidden"); // Force reflow forceRedraw($slide); if (slide.type !== "image") { slide.$content.hide().show(0); } $.fancybox.animate( $slide, "fancybox-slide--current", duration, function () { $slide.removeClass(effectClassName).css({ transform: "", opacity: "" }); if (slide.pos === self.currPos) { self.complete(); } }, true ); }, // Check if we can and have to zoom from thumbnail //================================================ getThumbPos: function (slide) { var rez = false, $thumb = slide.$thumb, thumbPos, btw, brw, bbw, blw; if (!$thumb || !inViewport($thumb[0])) { return false; } thumbPos = $.fancybox.getTranslate($thumb); btw = parseFloat($thumb.css("border-top-width") || 0); brw = parseFloat($thumb.css("border-right-width") || 0); bbw = parseFloat($thumb.css("border-bottom-width") || 0); blw = parseFloat($thumb.css("border-left-width") || 0); rez = { top: thumbPos.top + btw, left: thumbPos.left + blw, width: thumbPos.width - brw - blw, height: thumbPos.height - btw - bbw, scaleX: 1, scaleY: 1 }; return thumbPos.width > 0 && thumbPos.height > 0 ? rez : false; }, // Final adjustments after current gallery item is moved to position // and it`s content is loaded // ================================================================== complete: function () { var self = this, current = self.current, slides = {}, $el; if (self.isMoved() || !current.isLoaded) { return; } if (!current.isComplete) { current.isComplete = true; current.$slide.siblings().trigger("onReset"); self.preload("inline"); // Trigger any CSS transiton inside the slide forceRedraw(current.$slide); current.$slide.addClass("fancybox-slide--complete"); // Remove unnecessary slides $.each(self.slides, function (key, slide) { if (slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1) { slides[slide.pos] = slide; } else if (slide) { $.fancybox.stop(slide.$slide); slide.$slide.off().remove(); } }); self.slides = slides; } self.isAnimating = false; self.updateCursor(); self.trigger("afterShow"); // Autoplay first html5 video/audio if (!!current.opts.video.autoStart) { current.$slide .find("video,audio") .filter(":visible:first") .trigger("play") .one("ended", function () { if (Document.exitFullscreen) { Document.exitFullscreen(); } else if (this.webkitExitFullscreen) { this.webkitExitFullscreen(); } self.next(); }); } // Try to focus on the first focusable element if (current.opts.autoFocus && current.contentType === "html") { // Look for the first input with autofocus attribute $el = current.$content.find("input[autofocus]:enabled:visible:first"); if ($el.length) { $el.trigger("focus"); } else { self.focus(null, true); } } // Avoid jumping current.$slide.scrollTop(0).scrollLeft(0); }, // Preload next and previous slides // ================================ preload: function (type) { var self = this, prev, next; if (self.group.length < 2) { return; } next = self.slides[self.currPos + 1]; prev = self.slides[self.currPos - 1]; if (prev && prev.type === type) { self.loadSlide(prev); } if (next && next.type === type) { self.loadSlide(next); } }, // Try to find and focus on the first focusable element // ==================================================== focus: function (e, firstRun) { var self = this, focusableStr = [ "a[href]", "area[href]", 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', "select:not([disabled]):not([aria-hidden])", "textarea:not([disabled]):not([aria-hidden])", "button:not([disabled]):not([aria-hidden])", "iframe", "object", "embed", "video", "audio", "[contenteditable]", '[tabindex]:not([tabindex^="-"])' ].join(","), focusableItems, focusedItemIndex; if (self.isClosing) { return; } if (e || !self.current || !self.current.isComplete) { // Focus on any element inside fancybox focusableItems = self.$refs.container.find("*:visible"); } else { // Focus inside current slide focusableItems = self.current.$slide.find("*:visible" + (firstRun ? ":not(.fancybox-close-small)" : "")); } focusableItems = focusableItems.filter(focusableStr).filter(function () { return $(this).css("visibility") !== "hidden" && !$(this).hasClass("disabled"); }); if (focusableItems.length) { focusedItemIndex = focusableItems.index(document.activeElement); if (e && e.shiftKey) { // Back tab if (focusedItemIndex < 0 || focusedItemIndex == 0) { e.preventDefault(); focusableItems.eq(focusableItems.length - 1).trigger("focus"); } } else { // Outside or Forward tab if (focusedItemIndex < 0 || focusedItemIndex == focusableItems.length - 1) { if (e) { e.preventDefault(); } focusableItems.eq(0).trigger("focus"); } } } else { self.$refs.container.trigger("focus"); } }, // Activates current instance - brings container to the front and enables keyboard, // notifies other instances about deactivating // ================================================================================= activate: function () { var self = this; // Deactivate all instances $(".fancybox-container").each(function () { var instance = $(this).data("FancyBox"); // Skip self and closing instances if (instance && instance.id !== self.id && !instance.isClosing) { instance.trigger("onDeactivate"); instance.removeEvents(); instance.isVisible = false; } }); self.isVisible = true; if (self.current || self.isIdle) { self.update(); self.updateControls(); } self.trigger("onActivate"); self.addEvents(); }, // Start closing procedure // This will start "zoom-out" animation if needed and clean everything up afterwards // ================================================================================= close: function (e, d) { var self = this, current = self.current, effect, duration, $content, domRect, opacity, start, end; var done = function () { self.cleanUp(e); }; if (self.isClosing) { return false; } self.isClosing = true; // If beforeClose callback prevents closing, make sure content is centered if (self.trigger("beforeClose", e) === false) { self.isClosing = false; requestAFrame(function () { self.update(); }); return false; } // Remove all events // If there are multiple instances, they will be set again by "activate" method self.removeEvents(); $content = current.$content; effect = current.opts.animationEffect; duration = $.isNumeric(d) ? d : effect ? current.opts.animationDuration : 0; current.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"); if (e !== true) { $.fancybox.stop(current.$slide); } else { effect = false; } // Remove other slides current.$slide .siblings() .trigger("onReset") .remove(); // Trigger animations if (duration) { self.$refs.container .removeClass("fancybox-is-open") .addClass("fancybox-is-closing") .css("transition-duration", duration + "ms"); } // Clean up self.hideLoading(current); self.hideControls(true); self.updateCursor(); // Check if possible to zoom-out if ( effect === "zoom" && !($content && duration && current.type === "image" && !self.isMoved() && !current.hasError && (end = self.getThumbPos(current))) ) { effect = "fade"; } if (effect === "zoom") { $.fancybox.stop($content); domRect = $.fancybox.getTranslate($content); start = { top: domRect.top, left: domRect.left, scaleX: domRect.width / end.width, scaleY: domRect.height / end.height, width: end.width, height: end.height }; // Check if we need to animate opacity opacity = current.opts.zoomOpacity; if (opacity == "auto") { opacity = Math.abs(current.width / current.height - end.width / end.height) > 0.1; } if (opacity) { end.opacity = 0; } $.fancybox.setTranslate($content, start); forceRedraw($content); $.fancybox.animate($content, end, duration, done); return true; } if (effect && duration) { $.fancybox.animate( current.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"), "fancybox-animated fancybox-fx-" + effect, duration, done ); } else { // If skip animation if (e === true) { setTimeout(done, duration); } else { done(); } } return true; }, // Final adjustments after removing the instance // ============================================= cleanUp: function (e) { var self = this, instance, $focus = self.current.opts.$orig, x, y; self.current.$slide.trigger("onReset"); self.$refs.container.empty().remove(); self.trigger("afterClose", e); // Place back focus if (!!self.current.opts.backFocus) { if (!$focus || !$focus.length || !$focus.is(":visible")) { $focus = self.$trigger; } if ($focus && $focus.length) { x = window.scrollX; y = window.scrollY; $focus.trigger("focus"); $("html, body") .scrollTop(y) .scrollLeft(x); } } self.current = null; // Check if there are other instances instance = $.fancybox.getInstance(); if (instance) { instance.activate(); } else { $("body").removeClass("fancybox-active compensate-for-scrollbar"); $("#fancybox-style-noscroll").remove(); } }, // Call callback and trigger an event // ================================== trigger: function (name, slide) { var args = Array.prototype.slice.call(arguments, 1), self = this, obj = slide && slide.opts ? slide : self.current, rez; if (obj) { args.unshift(obj); } else { obj = self; } args.unshift(self); if ($.isFunction(obj.opts[name])) { rez = obj.opts[name].apply(obj, args); } if (rez === false) { return rez; } if (name === "afterClose" || !self.$refs) { $D.trigger(name + ".fb", args); } else { self.$refs.container.trigger(name + ".fb", args); } }, // Update infobar values, navigation button states and reveal caption // ================================================================== updateControls: function () { var self = this, current = self.current, index = current.index, $container = self.$refs.container, $caption = self.$refs.caption, caption = current.opts.caption; // Recalculate content dimensions current.$slide.trigger("refresh"); // Set caption if (caption && caption.length) { self.$caption = $caption; $caption .children() .eq(0) .html(caption); } else { self.$caption = null; } if (!self.hasHiddenControls && !self.isIdle) { self.showControls(); } // Update info and navigation elements $container.find("[data-fancybox-count]").html(self.group.length); $container.find("[data-fancybox-index]").html(index + 1); $container.find("[data-fancybox-prev]").prop("disabled", !current.opts.loop && index <= 0); $container.find("[data-fancybox-next]").prop("disabled", !current.opts.loop && index >= self.group.length - 1); if (current.type === "image") { // Re-enable buttons; update download button source $container .find("[data-fancybox-zoom]") .show() .end() .find("[data-fancybox-download]") .attr("href", current.opts.image.src || current.src) .show(); } else if (current.opts.toolbar) { $container.find("[data-fancybox-download],[data-fancybox-zoom]").hide(); } // Make sure focus is not on disabled button/element if ($(document.activeElement).is(":hidden,[disabled]")) { self.$refs.container.trigger("focus"); } }, // Hide toolbar and caption // ======================== hideControls: function (andCaption) { var self = this, arr = ["infobar", "toolbar", "nav"]; if (andCaption || !self.current.opts.preventCaptionOverlap) { arr.push("caption"); } this.$refs.container.removeClass( arr .map(function (i) { return "fancybox-show-" + i; }) .join(" ") ); this.hasHiddenControls = true; }, showControls: function () { var self = this, opts = self.current ? self.current.opts : self.opts, $container = self.$refs.container; self.hasHiddenControls = false; self.idleSecondsCounter = 0; $container .toggleClass("fancybox-show-toolbar", !!(opts.toolbar && opts.buttons)) .toggleClass("fancybox-show-infobar", !!(opts.infobar && self.group.length > 1)) .toggleClass("fancybox-show-caption", !!self.$caption) .toggleClass("fancybox-show-nav", !!(opts.arrows && self.group.length > 1)) .toggleClass("fancybox-is-modal", !!opts.modal); }, // Toggle toolbar and caption // ========================== toggleControls: function () { if (this.hasHiddenControls) { this.showControls(); } else { this.hideControls(); } } }); $.fancybox = { version: "{fancybox-version}", defaults: defaults, // Get current instance and execute a command. // // Examples of usage: // // $instance = $.fancybox.getInstance(); // $.fancybox.getInstance().jumpTo( 1 ); // $.fancybox.getInstance( 'jumpTo', 1 ); // $.fancybox.getInstance( function() { // console.info( this.currIndex ); // }); // ====================================================== getInstance: function (command) { var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"), args = Array.prototype.slice.call(arguments, 1); if (instance instanceof FancyBox) { if ($.type(command) === "string") { instance[command].apply(instance, args); } else if ($.type(command) === "function") { command.apply(instance, args); } return instance; } return false; }, // Create new instance // =================== open: function (items, opts, index) { return new FancyBox(items, opts, index); }, // Close current or all instances // ============================== close: function (all) { var instance = this.getInstance(); if (instance) { instance.close(); // Try to find and close next instance if (all === true) { this.close(all); } } }, // Close all instances and unbind all events // ========================================= destroy: function () { this.close(true); $D.add("body").off("click.fb-start", "**"); }, // Try to detect mobile devices // ============================ isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), // Detect if 'translate3d' support is available // ============================================ use3d: (function () { var div = document.createElement("div"); return ( window.getComputedStyle && window.getComputedStyle(div) && window.getComputedStyle(div).getPropertyValue("transform") && !(document.documentMode && document.documentMode < 11) ); })(), // Helper function to get current visual state of an element // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] // ===================================================================== getTranslate: function ($el) { var domRect; if (!$el || !$el.length) { return false; } domRect = $el[0].getBoundingClientRect(); return { top: domRect.top || 0, left: domRect.left || 0, width: domRect.width, height: domRect.height, opacity: parseFloat($el.css("opacity")) }; }, // Shortcut for setting "translate3d" properties for element // Can set be used to set opacity, too // ======================================================== setTranslate: function ($el, props) { var str = "", css = {}; if (!$el || !props) { return; } if (props.left !== undefined || props.top !== undefined) { str = (props.left === undefined ? $el.position().left : props.left) + "px, " + (props.top === undefined ? $el.position().top : props.top) + "px"; if (this.use3d) { str = "translate3d(" + str + ", 0px)"; } else { str = "translate(" + str + ")"; } } if (props.scaleX !== undefined && props.scaleY !== undefined) { str += " scale(" + props.scaleX + ", " + props.scaleY + ")"; } else if (props.scaleX !== undefined) { str += " scaleX(" + props.scaleX + ")"; } if (str.length) { css.transform = str; } if (props.opacity !== undefined) { css.opacity = props.opacity; } if (props.width !== undefined) { css.width = props.width; } if (props.height !== undefined) { css.height = props.height; } return $el.css(css); }, // Simple CSS transition handler // ============================= animate: function ($el, to, duration, callback, leaveAnimationName) { var self = this, from; if ($.isFunction(duration)) { callback = duration; duration = null; } self.stop($el); from = self.getTranslate($el); $el.on(transitionEnd, function (e) { // Skip events from child elements and z-index change if (e && e.originalEvent && (!$el.is(e.originalEvent.target) || e.originalEvent.propertyName == "z-index")) { return; } self.stop($el); if ($.isNumeric(duration)) { $el.css("transition-duration", ""); } if ($.isPlainObject(to)) { if (to.scaleX !== undefined && to.scaleY !== undefined) { self.setTranslate($el, { top: to.top, left: to.left, width: from.width * to.scaleX, height: from.height * to.scaleY, scaleX: 1, scaleY: 1 }); } } else if (leaveAnimationName !== true) { $el.removeClass(to); } if ($.isFunction(callback)) { callback(e); } }); if ($.isNumeric(duration)) { $el.css("transition-duration", duration + "ms"); } // Start animation by changing CSS properties or class name if ($.isPlainObject(to)) { if (to.scaleX !== undefined && to.scaleY !== undefined) { delete to.width; delete to.height; if ($el.parent().hasClass("fancybox-slide--image")) { $el.parent().addClass("fancybox-is-scaling"); } } $.fancybox.setTranslate($el, to); } else { $el.addClass(to); } // Make sure that `transitionend` callback gets fired $el.data( "timer", setTimeout(function () { $el.trigger(transitionEnd); }, duration + 33) ); }, stop: function ($el, callCallback) { if ($el && $el.length) { clearTimeout($el.data("timer")); if (callCallback) { $el.trigger(transitionEnd); } $el.off(transitionEnd).css("transition-duration", ""); $el.parent().removeClass("fancybox-is-scaling"); } } }; // Default click handler for "fancyboxed" links // ============================================ function _run(e, opts) { var items = [], index = 0, $target, value, instance; // Avoid opening multiple times if (e && e.isDefaultPrevented()) { return; } e.preventDefault(); opts = opts || {}; if (e && e.data) { opts = mergeOpts(e.data.options, opts); } $target = opts.$target || $(e.currentTarget).trigger("blur"); instance = $.fancybox.getInstance(); if (instance && instance.$trigger && instance.$trigger.is($target)) { return; } if (opts.selector) { items = $(opts.selector); } else { // Get all related items and find index for clicked one value = $target.attr("data-fancybox") || ""; if (value) { items = e.data ? e.data.items : []; items = items.length ? items.filter('[data-fancybox="' + value + '"]') : $('[data-fancybox="' + value + '"]'); } else { items = [$target]; } } index = $(items).index($target); // Sometimes current item can not be found if (index < 0) { index = 0; } instance = $.fancybox.open(items, opts, index); // Save last active element instance.$trigger = $target; } // Create a jQuery plugin // ====================== $.fn.fancybox = function (options) { var selector; options = options || {}; selector = options.selector || false; if (selector) { // Use body element instead of document so it executes first $("body") .off("click.fb-start", selector) .on("click.fb-start", selector, { options: options }, _run); } else { this.off("click.fb-start").on( "click.fb-start", { items: this, options: options }, _run ); } return this; }; // Self initializing plugin for all elements having `data-fancybox` attribute // ========================================================================== $D.on("click.fb-start", "[data-fancybox]", _run); // Enable "trigger elements" // ========================= $D.on("click.fb-start", "[data-fancybox-trigger]", function (e) { $('[data-fancybox="' + $(this).attr("data-fancybox-trigger") + '"]') .eq($(this).attr("data-fancybox-index") || 0) .trigger("click.fb-start", { $trigger: $(this) }); }); // Track focus event for better accessibility styling // ================================================== (function () { var buttonStr = ".fancybox-button", focusStr = "fancybox-focus", $pressed = null; $D.on("mousedown mouseup focus blur", buttonStr, function (e) { switch (e.type) { case "mousedown": $pressed = $(this); break; case "mouseup": $pressed = null; break; case "focusin": $(buttonStr).removeClass(focusStr); if (!$(this).is($pressed) && !$(this).is("[disabled]")) { $(this).addClass(focusStr); } break; case "focusout": $(buttonStr).removeClass(focusStr); break; } }); })(); })(window, document, jQuery); ================================================ FILE: src/js/fullscreen.js ================================================ // ========================================================================== // // FullScreen // Adds fullscreen functionality // // ========================================================================== (function (document, $) { "use strict"; // Collection of methods supported by user browser var fn = (function () { var fnMap = [ ["requestFullscreen", "exitFullscreen", "fullscreenElement", "fullscreenEnabled", "fullscreenchange", "fullscreenerror"], // new WebKit [ "webkitRequestFullscreen", "webkitExitFullscreen", "webkitFullscreenElement", "webkitFullscreenEnabled", "webkitfullscreenchange", "webkitfullscreenerror" ], // old WebKit (Safari 5.1) [ "webkitRequestFullScreen", "webkitCancelFullScreen", "webkitCurrentFullScreenElement", "webkitCancelFullScreen", "webkitfullscreenchange", "webkitfullscreenerror" ], [ "mozRequestFullScreen", "mozCancelFullScreen", "mozFullScreenElement", "mozFullScreenEnabled", "mozfullscreenchange", "mozfullscreenerror" ], ["msRequestFullscreen", "msExitFullscreen", "msFullscreenElement", "msFullscreenEnabled", "MSFullscreenChange", "MSFullscreenError"] ]; var ret = {}; for (var i = 0; i < fnMap.length; i++) { var val = fnMap[i]; if (val && val[1] in document) { for (var j = 0; j < val.length; j++) { ret[fnMap[0][j]] = val[j]; } return ret; } } return false; })(); if (fn) { var FullScreen = { request: function (elem) { elem = elem || document.documentElement; elem[fn.requestFullscreen](elem.ALLOW_KEYBOARD_INPUT); }, exit: function () { document[fn.exitFullscreen](); }, toggle: function (elem) { elem = elem || document.documentElement; if (this.isFullscreen()) { this.exit(); } else { this.request(elem); } }, isFullscreen: function () { return Boolean(document[fn.fullscreenElement]); }, enabled: function () { return Boolean(document[fn.fullscreenEnabled]); } }; $.extend(true, $.fancybox.defaults, { btnTpl: { fullScreen: '" }, fullScreen: { autoStart: false } }); $(document).on(fn.fullscreenchange, function () { var isFullscreen = FullScreen.isFullscreen(), instance = $.fancybox.getInstance(); if (instance) { // If image is zooming, then force to stop and reposition properly if (instance.current && instance.current.type === "image" && instance.isAnimating) { instance.isAnimating = false; instance.update(true, true, 0); if (!instance.isComplete) { instance.complete(); } } instance.trigger("onFullscreenChange", isFullscreen); instance.$refs.container.toggleClass("fancybox-is-fullscreen", isFullscreen); instance.$refs.toolbar .find("[data-fancybox-fullscreen]") .toggleClass("fancybox-button--fsenter", !isFullscreen) .toggleClass("fancybox-button--fsexit", isFullscreen); } }); } $(document).on({ "onInit.fb": function (e, instance) { var $container; if (!fn) { instance.$refs.toolbar.find("[data-fancybox-fullscreen]").remove(); return; } if (instance && instance.group[instance.currIndex].opts.fullScreen) { $container = instance.$refs.container; $container.on("click.fb-fullscreen", "[data-fancybox-fullscreen]", function (e) { e.stopPropagation(); e.preventDefault(); FullScreen.toggle(); }); if (instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true) { FullScreen.request(); } // Expose API instance.FullScreen = FullScreen; } else if (instance) { instance.$refs.toolbar.find("[data-fancybox-fullscreen]").hide(); } }, "afterKeydown.fb": function (e, instance, current, keypress, keycode) { // "F" if (instance && instance.FullScreen && keycode === 70) { keypress.preventDefault(); instance.FullScreen.toggle(); } }, "beforeClose.fb": function (e, instance) { if (instance && instance.FullScreen && instance.$refs.container.hasClass("fancybox-is-fullscreen")) { FullScreen.exit(); } } }); })(document, jQuery); ================================================ FILE: src/js/guestures.js ================================================ // ========================================================================== // // Guestures // Adds touch guestures, handles click and tap events // // ========================================================================== (function (window, document, $) { "use strict"; var requestAFrame = (function () { return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || // if all else fails, use setTimeout function (callback) { return window.setTimeout(callback, 1000 / 60); } ); })(); var cancelAFrame = (function () { return ( window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function (id) { window.clearTimeout(id); } ); })(); var getPointerXY = function (e) { var result = []; e = e.originalEvent || e || window.e; e = e.touches && e.touches.length ? e.touches : e.changedTouches && e.changedTouches.length ? e.changedTouches : [e]; for (var key in e) { if (e[key].pageX) { result.push({ x: e[key].pageX, y: e[key].pageY }); } else if (e[key].clientX) { result.push({ x: e[key].clientX, y: e[key].clientY }); } } return result; }; var distance = function (point2, point1, what) { if (!point1 || !point2) { return 0; } if (what === "x") { return point2.x - point1.x; } else if (what === "y") { return point2.y - point1.y; } return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)); }; var isClickable = function ($el) { if ( $el.is('a,area,button,[role="button"],input,label,select,summary,textarea,video,audio,iframe') || $.isFunction($el.get(0).onclick) || $el.data("selectable") ) { return true; } // Check for attributes like data-fancybox-next or data-fancybox-close for (var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++) { if (atts[i].nodeName.substr(0, 14) === "data-fancybox-") { return true; } } return false; }; var hasScrollbars = function (el) { var overflowY = window.getComputedStyle(el)["overflow-y"], overflowX = window.getComputedStyle(el)["overflow-x"], vertical = (overflowY === "scroll" || overflowY === "auto") && el.scrollHeight > el.clientHeight, horizontal = (overflowX === "scroll" || overflowX === "auto") && el.scrollWidth > el.clientWidth; return vertical || horizontal; }; var isScrollable = function ($el) { var rez = false; while (true) { rez = hasScrollbars($el.get(0)); if (rez) { break; } $el = $el.parent(); if (!$el.length || $el.hasClass("fancybox-stage") || $el.is("body")) { break; } } return rez; }; var Guestures = function (instance) { var self = this; self.instance = instance; self.$bg = instance.$refs.bg; self.$stage = instance.$refs.stage; self.$container = instance.$refs.container; self.destroy(); self.$container.on("touchstart.fb.touch mousedown.fb.touch", $.proxy(self, "ontouchstart")); }; Guestures.prototype.destroy = function () { var self = this; self.$container.off(".fb.touch"); $(document).off(".fb.touch"); if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } if (self.tapped) { clearTimeout(self.tapped); self.tapped = null; } }; Guestures.prototype.ontouchstart = function (e) { var self = this, $target = $(e.target), instance = self.instance, current = instance.current, $slide = current.$slide, $content = current.$content, isTouchDevice = e.type == "touchstart"; // Do not respond to both (touch and mouse) events if (isTouchDevice) { self.$container.off("mousedown.fb.touch"); } // Ignore right click if (e.originalEvent && e.originalEvent.button == 2) { return; } // Ignore taping on links, buttons, input elements if (!$slide.length || !$target.length || isClickable($target) || isClickable($target.parent())) { return; } // Ignore clicks on the scrollbar if (!$target.is("img") && e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left) { return; } // Ignore clicks while zooming or closing if (!current || instance.isAnimating || current.$slide.hasClass("fancybox-animated")) { e.stopPropagation(); e.preventDefault(); return; } self.realPoints = self.startPoints = getPointerXY(e); if (!self.startPoints.length) { return; } // Allow other scripts to catch touch event if "touch" is set to false if (current.touch) { e.stopPropagation(); } self.startEvent = e; self.canTap = true; self.$target = $target; self.$content = $content; self.opts = current.opts.touch; self.isPanning = false; self.isSwiping = false; self.isZooming = false; self.isScrolling = false; self.canPan = instance.canPan(); self.startTime = new Date().getTime(); self.distanceX = self.distanceY = self.distance = 0; self.canvasWidth = Math.round($slide[0].clientWidth); self.canvasHeight = Math.round($slide[0].clientHeight); self.contentLastPos = null; self.contentStartPos = $.fancybox.getTranslate(self.$content) || { top: 0, left: 0 }; self.sliderStartPos = $.fancybox.getTranslate($slide); // Since position will be absolute, but we need to make it relative to the stage self.stagePos = $.fancybox.getTranslate(instance.$refs.stage); self.sliderStartPos.top -= self.stagePos.top; self.sliderStartPos.left -= self.stagePos.left; self.contentStartPos.top -= self.stagePos.top; self.contentStartPos.left -= self.stagePos.left; $(document) .off(".fb.touch") .on(isTouchDevice ? "touchend.fb.touch touchcancel.fb.touch" : "mouseup.fb.touch mouseleave.fb.touch", $.proxy(self, "ontouchend")) .on(isTouchDevice ? "touchmove.fb.touch" : "mousemove.fb.touch", $.proxy(self, "ontouchmove")); if ($.fancybox.isMobile) { document.addEventListener("scroll", self.onscroll, true); } // Skip if clicked outside the sliding area if (!(self.opts || self.canPan) || !($target.is(self.$stage) || self.$stage.find($target).length)) { if ($target.is(".fancybox-image")) { e.preventDefault(); } if (!($.fancybox.isMobile && $target.parents(".fancybox-caption").length)) { return; } } self.isScrollable = isScrollable($target) || isScrollable($target.parent()); // Check if element is scrollable and try to prevent default behavior (scrolling) if (!($.fancybox.isMobile && self.isScrollable)) { e.preventDefault(); } // One finger or mouse click - swipe or pan an image if (self.startPoints.length === 1 || current.hasError) { if (self.canPan) { $.fancybox.stop(self.$content); self.isPanning = true; } else { self.isSwiping = true; } self.$container.addClass("fancybox-is-grabbing"); } // Two fingers - zoom image if (self.startPoints.length === 2 && current.type === "image" && (current.isLoaded || current.$ghost)) { self.canTap = false; self.isSwiping = false; self.isPanning = false; self.isZooming = true; $.fancybox.stop(self.$content); self.centerPointStartX = (self.startPoints[0].x + self.startPoints[1].x) * 0.5 - $(window).scrollLeft(); self.centerPointStartY = (self.startPoints[0].y + self.startPoints[1].y) * 0.5 - $(window).scrollTop(); self.percentageOfImageAtPinchPointX = (self.centerPointStartX - self.contentStartPos.left) / self.contentStartPos.width; self.percentageOfImageAtPinchPointY = (self.centerPointStartY - self.contentStartPos.top) / self.contentStartPos.height; self.startDistanceBetweenFingers = distance(self.startPoints[0], self.startPoints[1]); } }; Guestures.prototype.onscroll = function (e) { var self = this; self.isScrolling = true; document.removeEventListener("scroll", self.onscroll, true); }; Guestures.prototype.ontouchmove = function (e) { var self = this; // Make sure user has not released over iframe or disabled element if (e.originalEvent.buttons !== undefined && e.originalEvent.buttons === 0) { self.ontouchend(e); return; } if (self.isScrolling) { self.canTap = false; return; } self.newPoints = getPointerXY(e); if (!(self.opts || self.canPan) || !self.newPoints.length || !self.newPoints.length) { return; } if (!(self.isSwiping && self.isSwiping === true)) { e.preventDefault(); } self.distanceX = distance(self.newPoints[0], self.startPoints[0], "x"); self.distanceY = distance(self.newPoints[0], self.startPoints[0], "y"); self.distance = distance(self.newPoints[0], self.startPoints[0]); // Skip false ontouchmove events (Chrome) if (self.distance > 0) { if (self.isSwiping) { self.onSwipe(e); } else if (self.isPanning) { self.onPan(); } else if (self.isZooming) { self.onZoom(); } } }; Guestures.prototype.onSwipe = function (e) { var self = this, instance = self.instance, swiping = self.isSwiping, left = self.sliderStartPos.left || 0, angle; // If direction is not yet determined if (swiping === true) { // We need at least 10px distance to correctly calculate an angle if (Math.abs(self.distance) > 10) { self.canTap = false; if (instance.group.length < 2 && self.opts.vertical) { self.isSwiping = "y"; } else if (instance.isDragging || self.opts.vertical === false || (self.opts.vertical === "auto" && $(window).width() > 800)) { self.isSwiping = "x"; } else { angle = Math.abs((Math.atan2(self.distanceY, self.distanceX) * 180) / Math.PI); self.isSwiping = angle > 45 && angle < 135 ? "y" : "x"; } if (self.isSwiping === "y" && $.fancybox.isMobile && self.isScrollable) { self.isScrolling = true; return; } instance.isDragging = self.isSwiping; // Reset points to avoid jumping, because we dropped first swipes to calculate the angle self.startPoints = self.newPoints; $.each(instance.slides, function (index, slide) { var slidePos, stagePos; $.fancybox.stop(slide.$slide); slidePos = $.fancybox.getTranslate(slide.$slide); stagePos = $.fancybox.getTranslate(instance.$refs.stage); slide.$slide .css({ transform: "", opacity: "", "transition-duration": "" }) .removeClass("fancybox-animated") .removeClass(function (index, className) { return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" "); }); if (slide.pos === instance.current.pos) { self.sliderStartPos.top = slidePos.top - stagePos.top; self.sliderStartPos.left = slidePos.left - stagePos.left; } $.fancybox.setTranslate(slide.$slide, { top: slidePos.top - stagePos.top, left: slidePos.left - stagePos.left }); }); // Stop slideshow if (instance.SlideShow && instance.SlideShow.isActive) { instance.SlideShow.stop(); } } return; } // Sticky edges if (swiping == "x") { if ( self.distanceX > 0 && (self.instance.group.length < 2 || (self.instance.current.index === 0 && !self.instance.current.opts.loop)) ) { left = left + Math.pow(self.distanceX, 0.8); } else if ( self.distanceX < 0 && (self.instance.group.length < 2 || (self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop)) ) { left = left - Math.pow(-self.distanceX, 0.8); } else { left = left + self.distanceX; } } self.sliderLastPos = { top: swiping == "x" ? 0 : self.sliderStartPos.top + self.distanceY, left: left }; if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.requestId = requestAFrame(function () { if (self.sliderLastPos) { $.each(self.instance.slides, function (index, slide) { var pos = slide.pos - self.instance.currPos; $.fancybox.setTranslate(slide.$slide, { top: self.sliderLastPos.top, left: self.sliderLastPos.left + pos * self.canvasWidth + pos * slide.opts.gutter }); }); self.$container.addClass("fancybox-is-sliding"); } }); }; Guestures.prototype.onPan = function () { var self = this; // Prevent accidental movement (sometimes, when tapping casually, finger can move a bit) if (distance(self.newPoints[0], self.realPoints[0]) < ($.fancybox.isMobile ? 10 : 5)) { self.startPoints = self.newPoints; return; } self.canTap = false; self.contentLastPos = self.limitMovement(); if (self.requestId) { cancelAFrame(self.requestId); } self.requestId = requestAFrame(function () { $.fancybox.setTranslate(self.$content, self.contentLastPos); }); }; // Make panning sticky to the edges Guestures.prototype.limitMovement = function () { var self = this; var canvasWidth = self.canvasWidth; var canvasHeight = self.canvasHeight; var distanceX = self.distanceX; var distanceY = self.distanceY; var contentStartPos = self.contentStartPos; var currentOffsetX = contentStartPos.left; var currentOffsetY = contentStartPos.top; var currentWidth = contentStartPos.width; var currentHeight = contentStartPos.height; var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY, newOffsetX, newOffsetY; if (currentWidth > canvasWidth) { newOffsetX = currentOffsetX + distanceX; } else { newOffsetX = currentOffsetX; } newOffsetY = currentOffsetY + distanceY; // Slow down proportionally to traveled distance minTranslateX = Math.max(0, canvasWidth * 0.5 - currentWidth * 0.5); minTranslateY = Math.max(0, canvasHeight * 0.5 - currentHeight * 0.5); maxTranslateX = Math.min(canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5); maxTranslateY = Math.min(canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5); // -> if (distanceX > 0 && newOffsetX > minTranslateX) { newOffsetX = minTranslateX - 1 + Math.pow(-minTranslateX + currentOffsetX + distanceX, 0.8) || 0; } // <- if (distanceX < 0 && newOffsetX < maxTranslateX) { newOffsetX = maxTranslateX + 1 - Math.pow(maxTranslateX - currentOffsetX - distanceX, 0.8) || 0; } // \/ if (distanceY > 0 && newOffsetY > minTranslateY) { newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8) || 0; } // /\ if (distanceY < 0 && newOffsetY < maxTranslateY) { newOffsetY = maxTranslateY + 1 - Math.pow(maxTranslateY - currentOffsetY - distanceY, 0.8) || 0; } return { top: newOffsetY, left: newOffsetX }; }; Guestures.prototype.limitPosition = function (newOffsetX, newOffsetY, newWidth, newHeight) { var self = this; var canvasWidth = self.canvasWidth; var canvasHeight = self.canvasHeight; if (newWidth > canvasWidth) { newOffsetX = newOffsetX > 0 ? 0 : newOffsetX; newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX; } else { // Center horizontally newOffsetX = Math.max(0, canvasWidth / 2 - newWidth / 2); } if (newHeight > canvasHeight) { newOffsetY = newOffsetY > 0 ? 0 : newOffsetY; newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY; } else { // Center vertically newOffsetY = Math.max(0, canvasHeight / 2 - newHeight / 2); } return { top: newOffsetY, left: newOffsetX }; }; Guestures.prototype.onZoom = function () { var self = this; // Calculate current distance between points to get pinch ratio and new width and height var contentStartPos = self.contentStartPos; var currentWidth = contentStartPos.width; var currentHeight = contentStartPos.height; var currentOffsetX = contentStartPos.left; var currentOffsetY = contentStartPos.top; var endDistanceBetweenFingers = distance(self.newPoints[0], self.newPoints[1]); var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; var newWidth = Math.floor(currentWidth * pinchRatio); var newHeight = Math.floor(currentHeight * pinchRatio); // This is the translation due to pinch-zooming var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; // Point between the two touches var centerPointEndX = (self.newPoints[0].x + self.newPoints[1].x) / 2 - $(window).scrollLeft(); var centerPointEndY = (self.newPoints[0].y + self.newPoints[1].y) / 2 - $(window).scrollTop(); // And this is the translation due to translation of the centerpoint // between the two fingers var translateFromTranslatingX = centerPointEndX - self.centerPointStartX; var translateFromTranslatingY = centerPointEndY - self.centerPointStartY; // The new offset is the old/current one plus the total translation var newOffsetX = currentOffsetX + (translateFromZoomingX + translateFromTranslatingX); var newOffsetY = currentOffsetY + (translateFromZoomingY + translateFromTranslatingY); var newPos = { top: newOffsetY, left: newOffsetX, scaleX: pinchRatio, scaleY: pinchRatio }; self.canTap = false; self.newWidth = newWidth; self.newHeight = newHeight; self.contentLastPos = newPos; if (self.requestId) { cancelAFrame(self.requestId); } self.requestId = requestAFrame(function () { $.fancybox.setTranslate(self.$content, self.contentLastPos); }); }; Guestures.prototype.ontouchend = function (e) { var self = this; var swiping = self.isSwiping; var panning = self.isPanning; var zooming = self.isZooming; var scrolling = self.isScrolling; self.endPoints = getPointerXY(e); self.dMs = Math.max(new Date().getTime() - self.startTime, 1); self.$container.removeClass("fancybox-is-grabbing"); $(document).off(".fb.touch"); document.removeEventListener("scroll", self.onscroll, true); if (self.requestId) { cancelAFrame(self.requestId); self.requestId = null; } self.isSwiping = false; self.isPanning = false; self.isZooming = false; self.isScrolling = false; self.instance.isDragging = false; if (self.canTap) { return self.onTap(e); } self.speed = 100; // Speed in px/ms self.velocityX = (self.distanceX / self.dMs) * 0.5; self.velocityY = (self.distanceY / self.dMs) * 0.5; if (panning) { self.endPanning(); } else if (zooming) { self.endZooming(); } else { self.endSwiping(swiping, scrolling); } return; }; Guestures.prototype.endSwiping = function (swiping, scrolling) { var self = this, ret = false, len = self.instance.group.length, distanceX = Math.abs(self.distanceX), canAdvance = swiping == "x" && len > 1 && ((self.dMs > 130 && distanceX > 10) || distanceX > 50), speedX = 300; self.sliderLastPos = null; // Close if swiped vertically / navigate if horizontally if (swiping == "y" && !scrolling && Math.abs(self.distanceY) > 50) { // Continue vertical movement $.fancybox.animate( self.instance.current.$slide, { top: self.sliderStartPos.top + self.distanceY + self.velocityY * 150, opacity: 0 }, 200 ); ret = self.instance.close(true, 250); } else if (canAdvance && self.distanceX > 0) { ret = self.instance.previous(speedX); } else if (canAdvance && self.distanceX < 0) { ret = self.instance.next(speedX); } if (ret === false && (swiping == "x" || swiping == "y")) { self.instance.centerSlide(200); } self.$container.removeClass("fancybox-is-sliding"); }; // Limit panning from edges // ======================== Guestures.prototype.endPanning = function () { var self = this, newOffsetX, newOffsetY, newPos; if (!self.contentLastPos) { return; } if (self.opts.momentum === false || self.dMs > 350) { newOffsetX = self.contentLastPos.left; newOffsetY = self.contentLastPos.top; } else { // Continue movement newOffsetX = self.contentLastPos.left + self.velocityX * 500; newOffsetY = self.contentLastPos.top + self.velocityY * 500; } newPos = self.limitPosition(newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height); newPos.width = self.contentStartPos.width; newPos.height = self.contentStartPos.height; $.fancybox.animate(self.$content, newPos, 366); }; Guestures.prototype.endZooming = function () { var self = this; var current = self.instance.current; var newOffsetX, newOffsetY, newPos, reset; var newWidth = self.newWidth; var newHeight = self.newHeight; if (!self.contentLastPos) { return; } newOffsetX = self.contentLastPos.left; newOffsetY = self.contentLastPos.top; reset = { top: newOffsetY, left: newOffsetX, width: newWidth, height: newHeight, scaleX: 1, scaleY: 1 }; // Reset scalex/scaleY values; this helps for perfomance and does not break animation $.fancybox.setTranslate(self.$content, reset); if (newWidth < self.canvasWidth && newHeight < self.canvasHeight) { self.instance.scaleToFit(150); } else if (newWidth > current.width || newHeight > current.height) { self.instance.scaleToActual(self.centerPointStartX, self.centerPointStartY, 150); } else { newPos = self.limitPosition(newOffsetX, newOffsetY, newWidth, newHeight); $.fancybox.animate(self.$content, newPos, 150); } }; Guestures.prototype.onTap = function (e) { var self = this; var $target = $(e.target); var instance = self.instance; var current = instance.current; var endPoints = (e && getPointerXY(e)) || self.startPoints; var tapX = endPoints[0] ? endPoints[0].x - $(window).scrollLeft() - self.stagePos.left : 0; var tapY = endPoints[0] ? endPoints[0].y - $(window).scrollTop() - self.stagePos.top : 0; var where; var process = function (prefix) { var action = current.opts[prefix]; if ($.isFunction(action)) { action = action.apply(instance, [current, e]); } if (!action) { return; } switch (action) { case "close": instance.close(self.startEvent); break; case "toggleControls": instance.toggleControls(); break; case "next": instance.next(); break; case "nextOrClose": if (instance.group.length > 1) { instance.next(); } else { instance.close(self.startEvent); } break; case "zoom": if (current.type == "image" && (current.isLoaded || current.$ghost)) { if (instance.canPan()) { instance.scaleToFit(); } else if (instance.isScaledDown()) { instance.scaleToActual(tapX, tapY); } else if (instance.group.length < 2) { instance.close(self.startEvent); } } break; } }; // Ignore right click if (e.originalEvent && e.originalEvent.button == 2) { return; } // Skip if clicked on the scrollbar if (!$target.is("img") && tapX > $target[0].clientWidth + $target.offset().left) { return; } // Check where is clicked if ($target.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container")) { where = "Outside"; } else if ($target.is(".fancybox-slide")) { where = "Slide"; } else if ( instance.current.$content && instance.current.$content .find($target) .addBack() .filter($target).length ) { where = "Content"; } else { return; } // Check if this is a double tap if (self.tapped) { // Stop previously created single tap clearTimeout(self.tapped); self.tapped = null; // Skip if distance between taps is too big if (Math.abs(tapX - self.tapX) > 50 || Math.abs(tapY - self.tapY) > 50) { return this; } // OK, now we assume that this is a double-tap process("dblclick" + where); } else { // Single tap will be processed if user has not clicked second time within 300ms // or there is no need to wait for double-tap self.tapX = tapX; self.tapY = tapY; if (current.opts["dblclick" + where] && current.opts["dblclick" + where] !== current.opts["click" + where]) { self.tapped = setTimeout(function () { self.tapped = null; if (!instance.isAnimating) { process("click" + where); } }, 500); } else { process("click" + where); } } return this; }; $(document) .on("onActivate.fb", function (e, instance) { if (instance && !instance.Guestures) { instance.Guestures = new Guestures(instance); } }) .on("beforeClose.fb", function (e, instance) { if (instance && instance.Guestures) { instance.Guestures.destroy(); } }); })(window, document, jQuery); ================================================ FILE: src/js/hash.js ================================================ // ========================================================================== // // Hash // Enables linking to each modal // // ========================================================================== (function (window, document, $) { "use strict"; // Simple $.escapeSelector polyfill (for jQuery prior v3) if (!$.escapeSelector) { $.escapeSelector = function (sel) { var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; var fcssescape = function (ch, asCodePoint) { if (asCodePoint) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if (ch === "\0") { return "\uFFFD"; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice(0, -1) + "\\" + ch.charCodeAt(ch.length - 1).toString(16) + " "; } // Other potentially-special ASCII characters get backslash-escaped return "\\" + ch; }; return (sel + "").replace(rcssescape, fcssescape); }; } // Get info about gallery name and current index from url function parseUrl() { var hash = window.location.hash.substr(1), rez = hash.split("-"), index = rez.length > 1 && /^\+?\d+$/.test(rez[rez.length - 1]) ? parseInt(rez.pop(-1), 10) || 1 : 1, gallery = rez.join("-"); return { hash: hash, /* Index is starting from 1 */ index: index < 1 ? 1 : index, gallery: gallery }; } // Trigger click evnt on links to open new fancyBox instance function triggerFromUrl(url) { if (url.gallery !== "") { // If we can find element matching 'data-fancybox' atribute, // then triggering click event should start fancyBox $("[data-fancybox='" + $.escapeSelector(url.gallery) + "']") .eq(url.index - 1) .focus() .trigger("click.fb-start"); } } // Get gallery name from current instance function getGalleryID(instance) { var opts, ret; if (!instance) { return false; } opts = instance.current ? instance.current.opts : instance.opts; ret = opts.hash || (opts.$orig ? opts.$orig.data("fancybox") || opts.$orig.data("fancybox-trigger") : ""); return ret === "" ? false : ret; } // Start when DOM becomes ready $(function () { // Check if user has disabled this module if ($.fancybox.defaults.hash === false) { return; } // Update hash when opening/closing fancyBox $(document).on({ "onInit.fb": function (e, instance) { var url, gallery; if (instance.group[instance.currIndex].opts.hash === false) { return; } url = parseUrl(); gallery = getGalleryID(instance); // Make sure gallery start index matches index from hash if (gallery && url.gallery && gallery == url.gallery) { instance.currIndex = url.index - 1; } }, "beforeShow.fb": function (e, instance, current, firstRun) { var gallery; if (!current || current.opts.hash === false) { return; } // Check if need to update window hash gallery = getGalleryID(instance); if (!gallery) { return; } // Variable containing last hash value set by fancyBox // It will be used to determine if fancyBox needs to close after hash change is detected instance.currentHash = gallery + (instance.group.length > 1 ? "-" + (current.index + 1) : ""); // If current hash is the same (this instance most likely is opened by hashchange), then do nothing if (window.location.hash === "#" + instance.currentHash) { return; } if (firstRun && !instance.origHash) { instance.origHash = window.location.hash; } if (instance.hashTimer) { clearTimeout(instance.hashTimer); } // Update hash instance.hashTimer = setTimeout(function () { if ("replaceState" in window.history) { window.history[firstRun ? "pushState" : "replaceState"]({}, document.title, window.location.pathname + window.location.search + "#" + instance.currentHash ); if (firstRun) { instance.hasCreatedHistory = true; } } else { window.location.hash = instance.currentHash; } instance.hashTimer = null; }, 300); }, "beforeClose.fb": function (e, instance, current) { if (!current || current.opts.hash === false) { return; } clearTimeout(instance.hashTimer); // Goto previous history entry if (instance.currentHash && instance.hasCreatedHistory) { window.history.back(); } else if (instance.currentHash) { if ("replaceState" in window.history) { window.history.replaceState({}, document.title, window.location.pathname + window.location.search + (instance.origHash || "")); } else { window.location.hash = instance.origHash; } } instance.currentHash = null; } }); // Check if need to start/close after url has changed $(window).on("hashchange.fb", function () { var url = parseUrl(), fb = null; // Find last fancyBox instance that has "hash" $.each( $(".fancybox-container") .get() .reverse(), function (index, value) { var tmp = $(value).data("FancyBox"); if (tmp && tmp.currentHash) { fb = tmp; return false; } } ); if (fb) { // Now, compare hash values if (fb.currentHash !== url.gallery + "-" + url.index && !(url.index === 1 && fb.currentHash == url.gallery)) { fb.currentHash = null; fb.close(); } } else if (url.gallery !== "") { triggerFromUrl(url); } }); // Check current hash and trigger click event on matching element to start fancyBox, if needed setTimeout(function () { if (!$.fancybox.getInstance()) { triggerFromUrl(parseUrl()); } }, 50); }); })(window, document, jQuery); ================================================ FILE: src/js/media.js ================================================ // ========================================================================== // // Media // Adds additional media type support // // ========================================================================== (function ($) { "use strict"; // Object containing properties for each media type var defaults = { youtube: { matcher: /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i, params: { autoplay: 1, autohide: 1, fs: 1, rel: 0, hd: 1, wmode: "transparent", enablejsapi: 1, html5: 1 }, paramPlace: 8, type: "iframe", url: "https://www.youtube-nocookie.com/embed/$4", thumb: "https://img.youtube.com/vi/$4/hqdefault.jpg" }, vimeo: { matcher: /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/, params: { autoplay: 1, hd: 1, show_title: 1, show_byline: 1, show_portrait: 0, fullscreen: 1 }, paramPlace: 3, type: "iframe", url: "//player.vimeo.com/video/$2" }, instagram: { matcher: /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, type: "image", url: "//$1/p/$2/media/?size=l" }, // Examples: // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 // https://www.google.com/maps/@37.7852006,-122.4146355,14.65z // https://www.google.com/maps/@52.2111123,2.9237542,6.61z?hl=en // https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 gmap_place: { matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i, type: "iframe", url: function (rez) { return ( "//maps.google." + rez[2] + "/?ll=" + (rez[9] ? rez[9] + "&z=" + Math.floor(rez[10]) + (rez[12] ? rez[12].replace(/^\//, "&") : "") : rez[12] + "").replace(/\?/, "&") + "&output=" + (rez[12] && rez[12].indexOf("layer=c") > 0 ? "svembed" : "embed") ); } }, // Examples: // https://www.google.com/maps/search/Empire+State+Building/ // https://www.google.com/maps/search/?api=1&query=centurylink+field // https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393 gmap_search: { matcher: /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i, type: "iframe", url: function (rez) { return "//maps.google." + rez[2] + "/maps?q=" + rez[5].replace("query=", "q=").replace("api=1", "") + "&output=embed"; } } }; // Formats matching url to final form var format = function (url, rez, params) { if (!url) { return; } params = params || ""; if ($.type(params) === "object") { params = $.param(params, true); } $.each(rez, function (key, value) { url = url.replace("$" + key, value || ""); }); if (params.length) { url += (url.indexOf("?") > 0 ? "&" : "?") + params; } return url; }; $(document).on("objectNeedsType.fb", function (e, instance, item) { var url = item.src || "", type = false, media, thumb, rez, params, urlParams, paramObj, provider; media = $.extend(true, {}, defaults, item.opts.media); // Look for any matching media type $.each(media, function (providerName, providerOpts) { rez = url.match(providerOpts.matcher); if (!rez) { return; } type = providerOpts.type; provider = providerName; paramObj = {}; if (providerOpts.paramPlace && rez[providerOpts.paramPlace]) { urlParams = rez[providerOpts.paramPlace]; if (urlParams[0] == "?") { urlParams = urlParams.substring(1); } urlParams = urlParams.split("&"); for (var m = 0; m < urlParams.length; ++m) { var p = urlParams[m].split("=", 2); if (p.length == 2) { paramObj[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); } } } params = $.extend(true, {}, providerOpts.params, item.opts[providerName], paramObj); url = $.type(providerOpts.url) === "function" ? providerOpts.url.call(this, rez, params, item) : format(providerOpts.url, rez, params); thumb = $.type(providerOpts.thumb) === "function" ? providerOpts.thumb.call(this, rez, params, item) : format(providerOpts.thumb, rez); if (providerName === "youtube") { url = url.replace(/&t=((\d+)m)?(\d+)s/, function (match, p1, m, s) { return "&start=" + ((m ? parseInt(m, 10) * 60 : 0) + parseInt(s, 10)); }); } else if (providerName === "vimeo") { url = url.replace("&%23", "#"); } return false; }); // If it is found, then change content type and update the url if (type) { if (!item.opts.thumb && !(item.opts.$thumb && item.opts.$thumb.length)) { item.opts.thumb = thumb; } if (type === "iframe") { item.opts = $.extend(true, item.opts, { iframe: { preload: false, attr: { scrolling: "no" } } }); } $.extend(item, { type: type, src: url, origSrc: item.src, contentSource: provider, contentType: type === "image" ? "image" : provider == "gmap_place" || provider == "gmap_search" ? "map" : "video" }); } else if (url) { item.type = item.opts.defaultType; } }); // Load YouTube/Video API on request to detect when video finished playing var VideoAPILoader = { youtube: { src: "https://www.youtube.com/iframe_api", class: "YT", loading: false, loaded: false }, vimeo: { src: "https://player.vimeo.com/api/player.js", class: "Vimeo", loading: false, loaded: false }, load: function (vendor) { var _this = this, script; if (this[vendor].loaded) { setTimeout(function () { _this.done(vendor); }); return; } if (this[vendor].loading) { return; } this[vendor].loading = true; script = document.createElement("script"); script.type = "text/javascript"; script.src = this[vendor].src; if (vendor === "youtube") { window.onYouTubeIframeAPIReady = function () { _this[vendor].loaded = true; _this.done(vendor); }; } else { script.onload = function () { _this[vendor].loaded = true; _this.done(vendor); }; } document.body.appendChild(script); }, done: function (vendor) { var instance, $el, player; if (vendor === "youtube") { delete window.onYouTubeIframeAPIReady; } instance = $.fancybox.getInstance(); if (instance) { $el = instance.current.$content.find("iframe"); if (vendor === "youtube" && YT !== undefined && YT) { player = new YT.Player($el.attr("id"), { events: { onStateChange: function (e) { if (e.data == 0) { instance.next(); } } } }); } else if (vendor === "vimeo" && Vimeo !== undefined && Vimeo) { player = new Vimeo.Player($el); player.on("ended", function () { instance.next(); }); } } } }; $(document).on({ "afterShow.fb": function (e, instance, current) { if (instance.group.length > 1 && (current.contentSource === "youtube" || current.contentSource === "vimeo")) { VideoAPILoader.load(current.contentSource); } } }); })(jQuery); ================================================ FILE: src/js/share.js ================================================ //// ========================================================================== // // Share // Displays simple form for sharing current url // // ========================================================================== (function (document, $) { "use strict"; $.extend(true, $.fancybox.defaults, { btnTpl: { share: '" }, share: { url: function (instance, item) { return ( (!instance.currentHash && !(item.type === "inline" || item.type === "html") ? item.origSrc || item.src : false) || window.location ); }, tpl: '
' + "

{{SHARE}}

" + "

" + '' + '' + "Facebook" + "" + '' + '' + "Twitter" + "" + '' + '' + "Pinterest" + "" + "

" + '

' + "
" } }); function escapeHtml(string) { var entityMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", "`": "`", "=": "=" }; return String(string).replace(/[&<>"'`=\/]/g, function (s) { return entityMap[s]; }); } $(document).on("click", "[data-fancybox-share]", function () { var instance = $.fancybox.getInstance(), current = instance.current || null, url, tpl; if (!current) { return; } if ($.type(current.opts.share.url) === "function") { url = current.opts.share.url.apply(current, [instance, current]); } tpl = current.opts.share.tpl .replace(/\{\{media\}\}/g, current.type === "image" ? encodeURIComponent(current.src) : "") .replace(/\{\{url\}\}/g, encodeURIComponent(url)) .replace(/\{\{url_raw\}\}/g, escapeHtml(url)) .replace(/\{\{descr\}\}/g, instance.$caption ? encodeURIComponent(instance.$caption.text()) : ""); $.fancybox.open({ src: instance.translate(instance, tpl), type: "html", opts: { touch: false, animationEffect: false, afterLoad: function (shareInstance, shareCurrent) { // Close self if parent instance is closing instance.$refs.container.one("beforeClose.fb", function () { shareInstance.close(null, 0); }); // Opening links in a popup window shareCurrent.$content.find(".fancybox-share__button").click(function () { window.open(this.href, "Share", "width=550, height=450"); return false; }); }, mobile: { autoFocus: false } } }); }); })(document, jQuery); ================================================ FILE: src/js/slideshow.js ================================================ // ========================================================================== // // SlideShow // Enables slideshow functionality // // Example of usage: // $.fancybox.getInstance().SlideShow.start() // // ========================================================================== (function (document, $) { "use strict"; $.extend(true, $.fancybox.defaults, { btnTpl: { slideShow: '" }, slideShow: { autoStart: false, speed: 3000, progress: true } }); var SlideShow = function (instance) { this.instance = instance; this.init(); }; $.extend(SlideShow.prototype, { timer: null, isActive: false, $button: null, init: function () { var self = this, instance = self.instance, opts = instance.group[instance.currIndex].opts.slideShow; self.$button = instance.$refs.toolbar.find("[data-fancybox-play]").on("click", function () { self.toggle(); }); if (instance.group.length < 2 || !opts) { self.$button.hide(); } else if (opts.progress) { self.$progress = $('
').appendTo(instance.$refs.inner); } }, set: function (force) { var self = this, instance = self.instance, current = instance.current; // Check if reached last element if (current && (force === true || current.opts.loop || instance.currIndex < instance.group.length - 1)) { if (self.isActive && current.contentType !== "video") { if (self.$progress) { $.fancybox.animate(self.$progress.show(), { scaleX: 1 }, current.opts.slideShow.speed); } self.timer = setTimeout(function () { if (!instance.current.opts.loop && instance.current.index == instance.group.length - 1) { instance.jumpTo(0); } else { instance.next(); } }, current.opts.slideShow.speed); } } else { self.stop(); instance.idleSecondsCounter = 0; instance.showControls(); } }, clear: function () { var self = this; clearTimeout(self.timer); self.timer = null; if (self.$progress) { self.$progress.removeAttr("style").hide(); } }, start: function () { var self = this, current = self.instance.current; if (current) { self.$button .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_STOP) .removeClass("fancybox-button--play") .addClass("fancybox-button--pause"); self.isActive = true; if (current.isComplete) { self.set(true); } self.instance.trigger("onSlideShowChange", true); } }, stop: function () { var self = this, current = self.instance.current; self.clear(); self.$button .attr("title", (current.opts.i18n[current.opts.lang] || current.opts.i18n.en).PLAY_START) .removeClass("fancybox-button--pause") .addClass("fancybox-button--play"); self.isActive = false; self.instance.trigger("onSlideShowChange", false); if (self.$progress) { self.$progress.removeAttr("style").hide(); } }, toggle: function () { var self = this; if (self.isActive) { self.stop(); } else { self.start(); } } }); $(document).on({ "onInit.fb": function (e, instance) { if (instance && !instance.SlideShow) { instance.SlideShow = new SlideShow(instance); } }, "beforeShow.fb": function (e, instance, current, firstRun) { var SlideShow = instance && instance.SlideShow; if (firstRun) { if (SlideShow && current.opts.slideShow.autoStart) { SlideShow.start(); } } else if (SlideShow && SlideShow.isActive) { SlideShow.clear(); } }, "afterShow.fb": function (e, instance, current) { var SlideShow = instance && instance.SlideShow; if (SlideShow && SlideShow.isActive) { SlideShow.set(); } }, "afterKeydown.fb": function (e, instance, current, keypress, keycode) { var SlideShow = instance && instance.SlideShow; // "P" or Spacebar if (SlideShow && current.opts.slideShow && (keycode === 80 || keycode === 32) && !$(document.activeElement).is("button,a,input")) { keypress.preventDefault(); SlideShow.toggle(); } }, "beforeClose.fb onDeactivate.fb": function (e, instance) { var SlideShow = instance && instance.SlideShow; if (SlideShow) { SlideShow.stop(); } } }); // Page Visibility API to pause slideshow when window is not active $(document).on("visibilitychange", function () { var instance = $.fancybox.getInstance(), SlideShow = instance && instance.SlideShow; if (SlideShow && SlideShow.isActive) { if (document.hidden) { SlideShow.clear(); } else { SlideShow.set(); } } }); })(document, jQuery); ================================================ FILE: src/js/thumbs.js ================================================ // ========================================================================== // // Thumbs // Displays thumbnails in a grid // // ========================================================================== (function (document, $) { "use strict"; var CLASS = "fancybox-thumbs", CLASS_ACTIVE = CLASS + "-active"; // Make sure there are default values $.fancybox.defaults = $.extend( true, { btnTpl: { thumbs: '" }, thumbs: { autoStart: false, // Display thumbnails on opening hideOnClose: true, // Hide thumbnail grid when closing animation starts parentEl: ".fancybox-container", // Container is injected into this element axis: "y" // Vertical (y) or horizontal (x) scrolling } }, $.fancybox.defaults ); var FancyThumbs = function (instance) { this.init(instance); }; $.extend(FancyThumbs.prototype, { $button: null, $grid: null, $list: null, isVisible: false, isActive: false, init: function (instance) { var self = this, group = instance.group, enabled = 0; self.instance = instance; self.opts = group[instance.currIndex].opts.thumbs; instance.Thumbs = self; self.$button = instance.$refs.toolbar.find("[data-fancybox-thumbs]"); // Enable thumbs if at least two group items have thumbnails for (var i = 0, len = group.length; i < len; i++) { if (group[i].thumb) { enabled++; } if (enabled > 1) { break; } } if (enabled > 1 && !!self.opts) { self.$button.removeAttr("style").on("click", function () { self.toggle(); }); self.isActive = true; } else { self.$button.hide(); } }, create: function () { var self = this, instance = self.instance, parentEl = self.opts.parentEl, list = [], src; if (!self.$grid) { // Create main element self.$grid = $('
').appendTo( instance.$refs.container .find(parentEl) .addBack() .filter(parentEl) ); // Add "click" event that performs gallery navigation self.$grid.on("click", "a", function () { instance.jumpTo($(this).attr("data-index")); }); } // Build the list if (!self.$list) { self.$list = $('
').appendTo(self.$grid); } $.each(instance.group, function (i, item) { src = item.thumb; if (!src && item.type === "image") { src = item.src; } list.push( '" ); }); self.$list[0].innerHTML = list.join(""); if (self.opts.axis === "x") { // Set fixed width for list element to enable horizontal scrolling self.$list.width( parseInt(self.$grid.css("padding-right"), 10) + instance.group.length * self.$list .children() .eq(0) .outerWidth(true) ); } }, focus: function (duration) { var self = this, $list = self.$list, $grid = self.$grid, thumb, thumbPos; if (!self.instance.current) { return; } thumb = $list .children() .removeClass(CLASS_ACTIVE) .filter('[data-index="' + self.instance.current.index + '"]') .addClass(CLASS_ACTIVE); thumbPos = thumb.position(); // Check if need to scroll to make current thumb visible if (self.opts.axis === "y" && (thumbPos.top < 0 || thumbPos.top > $list.height() - thumb.outerHeight())) { $list.stop().animate({ scrollTop: $list.scrollTop() + thumbPos.top }, duration ); } else if ( self.opts.axis === "x" && (thumbPos.left < $grid.scrollLeft() || thumbPos.left > $grid.scrollLeft() + ($grid.width() - thumb.outerWidth())) ) { $list .parent() .stop() .animate({ scrollLeft: thumbPos.left }, duration ); } }, update: function () { var that = this; that.instance.$refs.container.toggleClass("fancybox-show-thumbs", this.isVisible); if (that.isVisible) { if (!that.$grid) { that.create(); } that.instance.trigger("onThumbsShow"); that.focus(0); } else if (that.$grid) { that.instance.trigger("onThumbsHide"); } // Update content position that.instance.update(); }, hide: function () { this.isVisible = false; this.update(); }, show: function () { this.isVisible = true; this.update(); }, toggle: function () { this.isVisible = !this.isVisible; this.update(); } }); $(document).on({ "onInit.fb": function (e, instance) { var Thumbs; if (instance && !instance.Thumbs) { Thumbs = new FancyThumbs(instance); if (Thumbs.isActive && Thumbs.opts.autoStart === true) { Thumbs.show(); } } }, "beforeShow.fb": function (e, instance, item, firstRun) { var Thumbs = instance && instance.Thumbs; if (Thumbs && Thumbs.isVisible) { Thumbs.focus(firstRun ? 0 : 250); } }, "afterKeydown.fb": function (e, instance, current, keypress, keycode) { var Thumbs = instance && instance.Thumbs; // "G" if (Thumbs && Thumbs.isActive && keycode === 71) { keypress.preventDefault(); Thumbs.toggle(); } }, "beforeClose.fb": function (e, instance) { var Thumbs = instance && instance.Thumbs; if (Thumbs && Thumbs.isVisible && Thumbs.opts.hideOnClose !== false) { Thumbs.$grid.hide(); } } }); })(document, jQuery); ================================================ FILE: src/js/wheel.js ================================================ // ========================================================================== // // Wheel // Basic mouse weheel support for gallery navigation // // ========================================================================== (function (document, $) { "use strict"; var prevTime = new Date().getTime(); $(document).on({ "onInit.fb": function (e, instance, current) { instance.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll", function (e) { var current = instance.current, currTime = new Date().getTime(); if (instance.group.length < 2 || current.opts.wheel === false || (current.opts.wheel === "auto" && current.type !== "image")) { return; } e.preventDefault(); e.stopPropagation(); if (current.$slide.hasClass("fancybox-animated")) { return; } e = e.originalEvent || e; if (currTime - prevTime < 250) { return; } prevTime = currTime; instance[(-e.deltaY || -e.deltaX || e.wheelDelta || -e.detail) < 0 ? "next" : "previous"](); }); } }); })(document, jQuery);