diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +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. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000..c5f92637 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,10 @@ +--- +name: Question +about: Describe your question here. +title: '' +labels: '' +assignees: '' + +--- + +How do I ... diff --git a/README.md b/README.md index 769bb031..9827157b 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,40 @@ -# fancyBox +# Archived -jQuery lightbox script for displaying images, videos and more. -Touch enabled, responsive and fully customizable. +This repository has been archived and is now part of https://github.com/fancyapps/ui -See the [project page](http://fancyapps.com/fancybox/3/) for documentation and a demonstration. - -Follow [@thefancyapps](//twitter.com/thefancyapps) for updates. +# 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 +1\. Add latest jQuery and Fancybox files ```html - + - + ``` - -2\. Create links +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. +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/fancybox/#license). - -## Bugs and feature requests - -If you find a bug, please report it [here on Github](https://github.com/fancyapps/fancybox/issues). - -Guidelines for bug reports: - -1. Use the GitHub issue search — check if the issue has already been reported. -2. Check if the issue has been fixed — try to reproduce it using the latest master or development branch in the repository. -3. Isolate the problem — create a reduced test case and a live example. You can use CodePen to fork any demo found on documentation to use it as a template. - -A good bug report shouldn't leave others needing to chase you up for more information. -Please try to be as detailed as possible in your report. - - -Feature requests are welcome. Please look for existing ones and use GitHub's "reactions" feature to vote. - -Please do not use the issue tracker for personal support requests - use Stack Overflow ([fancybox-3](http://stackoverflow.com/questions/tagged/fancybox-3) tag) instead. +[Read more about Fancybox license](http://fancyapps.com/). diff --git a/dist/jquery.fancybox.css b/dist/jquery.fancybox.css index 0ed07852..16b01254 100644 --- a/dist/jquery.fancybox.css +++ b/dist/jquery.fancybox.css @@ -1,740 +1,895 @@ -@charset "UTF-8"; -.fancybox-enabled { - overflow: hidden; } +body.compensate-for-scrollbar { + overflow: hidden; +} -.fancybox-enabled body { - overflow: visible; - height: 100%; } +.fancybox-active { + height: auto; +} .fancybox-is-hidden { - position: absolute; - top: -9999px; - left: -9999px; - visibility: hidden; } + left: -9999px; + margin: 0; + position: absolute !important; + top: -9999px; + visibility: hidden; +} .fancybox-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 99993; - -webkit-tap-highlight-color: transparent; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-transform: translateZ(0); - transform: translateZ(0); } - -/* Make sure that the first one is on the top */ -.fancybox-container ~ .fancybox-container { - z-index: 99992; } + -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 { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; } + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; +} .fancybox-outer { - overflow-y: auto; - -webkit-overflow-scrolling: touch; } + -webkit-overflow-scrolling: touch; + overflow-y: auto; +} .fancybox-bg { - background: #1e1e1e; - opacity: 0; - transition-duration: inherit; - transition-property: opacity; - transition-timing-function: cubic-bezier(0.47, 0, 0.74, 0.71); } + 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: 0.87; - transition-timing-function: cubic-bezier(0.22, 0.61, 0.36, 1); } + opacity: .9; + transition-timing-function: cubic-bezier(.22, .61, .36, 1); +} .fancybox-infobar, .fancybox-toolbar, -.fancybox-caption-wrap { - position: absolute; - direction: ltr; - z-index: 99997; - opacity: 0; - visibility: hidden; - transition: opacity .25s, visibility 0s linear .25s; - box-sizing: border-box; } +.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-wrap { - opacity: 1; - visibility: visible; - transition: opacity .25s, visibility 0s; } +.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 { - top: 0; - left: 50%; - margin-left: -79px; } - -.fancybox-infobar__body { - display: inline-block; - width: 70px; - line-height: 44px; - font-size: 13px; - font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; - text-align: center; - color: #ddd; - background-color: rgba(30, 30, 30, 0.7); - pointer-events: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; - -webkit-font-smoothing: subpixel-antialiased; } + 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 { - top: 0; - right: 0; } + right: 0; + top: 0; +} .fancybox-stage { - overflow: hidden; - direction: ltr; - z-index: 99994; - -webkit-transform: translate3d(0, 0, 0); } + direction: ltr; + overflow: visible; + transform: translateZ(0); + z-index: 99994; +} + +.fancybox-is-open .fancybox-stage { + overflow: hidden; +} .fancybox-slide { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - overflow: auto; - outline: none; - white-space: normal; - box-sizing: border-box; - text-align: center; - z-index: 99994; - -webkit-overflow-scrolling: touch; - display: none; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - transition-property: opacity, -webkit-transform; - transition-property: transform, opacity; - transition-property: transform, opacity, -webkit-transform; - -webkit-transform-style: preserve-3d; - transform-style: preserve-3d; } + -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; - vertical-align: middle; - height: 100%; - width: 0; } + 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; } + display: block; +} .fancybox-slide--image { - overflow: visible; } + overflow: hidden; + padding: 44px 0; +} .fancybox-slide--image::before { - display: none; } + display: none; +} -.fancybox-slide--video .fancybox-content, -.fancybox-slide--video iframe { - background: #000; } +.fancybox-slide--html { + padding: 6px; +} -.fancybox-slide--map .fancybox-content, -.fancybox-slide--map iframe { - background: #E5E3DF; } +.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--next { - z-index: 99995; } - -.fancybox-slide > * { - display: inline-block; - position: relative; - padding: 24px; - margin: 44px 0 44px; - border-width: 0; - vertical-align: middle; - text-align: left; - background-color: #fff; - overflow: auto; - box-sizing: border-box; } - -.fancybox-slide .fancybox-image-wrap { - position: absolute; - top: 0; - left: 0; - margin: 0; - padding: 0; - border: 0; - z-index: 99995; - background: transparent; - cursor: default; - overflow: visible; - -webkit-transform-origin: top left; - -ms-transform-origin: top left; - transform-origin: top left; - background-size: 100% 100%; - background-repeat: no-repeat; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; } - -.fancybox-can-zoomOut .fancybox-image-wrap { - cursor: -webkit-zoom-out; - cursor: zoom-out; } - -.fancybox-can-zoomIn .fancybox-image-wrap { - cursor: -webkit-zoom-in; - cursor: zoom-in; } - -.fancybox-can-drag .fancybox-image-wrap { - cursor: -webkit-grab; - cursor: grab; } - -.fancybox-is-dragging .fancybox-image-wrap { - cursor: -webkit-grabbing; - cursor: grabbing; } +.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 { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - border: 0; - max-width: none; - max-height: none; } + 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; } + z-index: 1; +} +.fancybox-slide--video .fancybox-content, +.fancybox-slide--map .fancybox-content, +.fancybox-slide--pdf .fancybox-content, .fancybox-slide--iframe .fancybox-content { - padding: 0; - width: 80%; - height: 80%; - max-width: calc(100% - 100px); - max-height: calc(100% - 88px); - overflow: visible; - background: #fff; } + 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 { - display: block; - margin: 0; - padding: 0; - border: 0; - width: 100%; - height: 100%; - background: #fff; } + 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 { - margin: 0; - padding: 40px; - width: 100%; - max-width: 380px; - background: #fff; - cursor: default; } + background: #fff; + cursor: default; + max-width: 400px; + padding: 40px; + width: 100%; +} .fancybox-error p { - margin: 0; - padding: 0; - color: #444; - font: 16px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; } + 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 { - position: absolute; - top: 0; - right: 0; - width: 44px; - height: 44px; - padding: 0; - margin: 0; - border: 0; - border-radius: 0; - outline: none; - background: transparent; - z-index: 10; - cursor: pointer; } - -.fancybox-close-small:after { - content: '×'; - position: absolute; - top: 5px; - right: 5px; - width: 30px; - height: 30px; - font: 20px/30px Arial,"Helvetica Neue",Helvetica,sans-serif; - color: #888; - font-weight: 300; - text-align: center; - border-radius: 50%; - border-width: 0; - background: #fff; - transition: background .25s; - box-sizing: border-box; - z-index: 2; } - -.fancybox-close-small:focus:after { - outline: 1px dotted #888; } - -.fancybox-close-small:hover:after { - color: #555; - background: #eee; } - -.fancybox-slide--iframe .fancybox-close-small { - top: 0; - right: -44px; } - -.fancybox-slide--iframe .fancybox-close-small:after { - background: transparent; - font-size: 35px; - color: #aaa; } - -.fancybox-slide--iframe .fancybox-close-small:hover:after { - color: #fff; } + 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-wrap { - bottom: 0; - left: 0; - right: 0; - padding: 60px 30px 0 30px; - background: linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.1) 20%, rgba(0, 0, 0, 0.2) 40%, rgba(0, 0, 0, 0.6) 80%, rgba(0, 0, 0, 0.8) 100%); - pointer-events: none; } .fancybox-caption { - padding: 30px 0; - border-top: 1px solid rgba(255, 255, 255, 0.4); - font-size: 14px; - font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; - color: #fff; - line-height: 20px; - -webkit-text-size-adjust: none; } + 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 button, -.fancybox-caption select { - pointer-events: all; } +.fancybox-caption a:link, +.fancybox-caption a:visited { + color: #ccc; + text-decoration: none; +} -.fancybox-caption a { - color: #fff; - text-decoration: underline; } - -/* Buttons */ -.fancybox-button { - display: inline-block; - position: relative; - margin: 0; - padding: 0; - border: 0; - width: 44px; - height: 44px; - line-height: 44px; - text-align: center; - background: transparent; - color: #ddd; - border-radius: 0; - cursor: pointer; - vertical-align: top; - outline: none; } - -.fancybox-button[disabled] { - cursor: default; - pointer-events: none; } - -.fancybox-infobar__body, .fancybox-button { - background: rgba(30, 30, 30, 0.6); } - -.fancybox-button:hover:not([disabled]) { - color: #fff; - background: rgba(0, 0, 0, 0.8); } - -.fancybox-button::before, -.fancybox-button::after { - content: ''; - pointer-events: none; - position: absolute; - background-color: currentColor; - color: currentColor; - opacity: 0.9; - box-sizing: border-box; - display: inline-block; } - -.fancybox-button[disabled]::before, -.fancybox-button[disabled]::after { - opacity: 0.3; } - -.fancybox-button--left::after, -.fancybox-button--right::after { - top: 18px; - width: 6px; - height: 6px; - background: transparent; - border-top: solid 2px currentColor; - border-right: solid 2px currentColor; } - -.fancybox-button--left::after { - left: 20px; - -webkit-transform: rotate(-135deg); - -ms-transform: rotate(-135deg); - transform: rotate(-135deg); } - -.fancybox-button--right::after { - right: 20px; - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); } - -.fancybox-button--left { - border-bottom-left-radius: 5px; } - -.fancybox-button--right { - border-bottom-right-radius: 5px; } - -.fancybox-button--close::before, .fancybox-button--close::after { - content: ''; - display: inline-block; - position: absolute; - height: 2px; - width: 16px; - top: calc(50% - 1px); - left: calc(50% - 8px); } - -.fancybox-button--close::before { - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); } - -.fancybox-button--close::after { - -webkit-transform: rotate(-45deg); - -ms-transform: rotate(-45deg); - transform: rotate(-45deg); } - -/* Navigation arrows */ -.fancybox-arrow { - position: absolute; - top: 50%; - margin: -50px 0 0 0; - height: 100px; - width: 54px; - padding: 0; - border: 0; - outline: none; - background: none; - cursor: pointer; - z-index: 99995; - opacity: 0; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - transition: opacity .25s; } - -.fancybox-arrow::after { - content: ''; - position: absolute; - top: 28px; - width: 44px; - height: 44px; - background-color: rgba(30, 30, 30, 0.8); - background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjQ4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSI0OCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgNGwtMS40MSAxLjQxTDE2LjE3IDExSDR2MmgxMi4xN2wtNS41OCA1LjU5TDEyIDIwbDgtOHoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-position: center center; - background-size: 24px 24px; } - -.fancybox-arrow--right { - right: 0; } - -.fancybox-arrow--left { - left: 0; - -webkit-transform: scaleX(-1); - -ms-transform: scaleX(-1); - transform: scaleX(-1); } - -.fancybox-arrow--right::after, -.fancybox-arrow--left::after { - left: 0; } - -.fancybox-show-nav .fancybox-arrow { - opacity: 0.6; } - -.fancybox-show-nav .fancybox-arrow[disabled] { - opacity: 0.3; } +.fancybox-caption a:hover { + color: #fff; + text-decoration: underline; +} /* Loading indicator */ + .fancybox-loading { - border: 6px solid rgba(100, 100, 100, 0.4); - border-top: 6px solid rgba(255, 255, 255, 0.6); - border-radius: 100%; - height: 50px; - width: 50px; - -webkit-animation: fancybox-rotate .8s infinite linear; - animation: fancybox-rotate .8s infinite linear; - background: transparent; - position: absolute; - top: 50%; - left: 50%; - margin-top: -25px; - margin-left: -25px; - z-index: 99999; } - -@-webkit-keyframes fancybox-rotate { - from { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - to { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); } } + 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 { - from { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - to { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); } } + 100% { + transform: rotate(360deg); + } +} /* Transition effects */ + .fancybox-animated { - transition-timing-function: cubic-bezier(0, 0, 0.25, 1); } + transition-timing-function: cubic-bezier(0, 0, .25, 1); +} /* transitionEffect: slide */ + .fancybox-fx-slide.fancybox-slide--previous { - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - opacity: 0; } + opacity: 0; + transform: translate3d(-100%, 0, 0); +} .fancybox-fx-slide.fancybox-slide--next { - -webkit-transform: translate3d(100%, 0, 0); - transform: translate3d(100%, 0, 0); - opacity: 0; } + opacity: 0; + transform: translate3d(100%, 0, 0); +} .fancybox-fx-slide.fancybox-slide--current { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - opacity: 1; } + 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(0.19, 1, 0.22, 1); } + opacity: 0; + transition-timing-function: cubic-bezier(.19, 1, .22, 1); +} .fancybox-fx-fade.fancybox-slide--current { - opacity: 1; } + opacity: 1; +} /* transitionEffect: zoom-in-out */ + .fancybox-fx-zoom-in-out.fancybox-slide--previous { - -webkit-transform: scale3d(1.5, 1.5, 1.5); - transform: scale3d(1.5, 1.5, 1.5); - opacity: 0; } + opacity: 0; + transform: scale3d(1.5, 1.5, 1.5); +} .fancybox-fx-zoom-in-out.fancybox-slide--next { - -webkit-transform: scale3d(0.5, 0.5, 0.5); - transform: scale3d(0.5, 0.5, 0.5); - opacity: 0; } + opacity: 0; + transform: scale3d(.5, .5, .5); +} .fancybox-fx-zoom-in-out.fancybox-slide--current { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); - opacity: 1; } + opacity: 1; + transform: scale3d(1, 1, 1); +} /* transitionEffect: rotate */ + .fancybox-fx-rotate.fancybox-slide--previous { - -webkit-transform: rotate(-360deg); - -ms-transform: rotate(-360deg); - transform: rotate(-360deg); - opacity: 0; } + opacity: 0; + -ms-transform: rotate(-360deg); + transform: rotate(-360deg); +} .fancybox-fx-rotate.fancybox-slide--next { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - opacity: 0; } + opacity: 0; + -ms-transform: rotate(360deg); + transform: rotate(360deg); +} .fancybox-fx-rotate.fancybox-slide--current { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - opacity: 1; } + opacity: 1; + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} /* transitionEffect: circular */ + .fancybox-fx-circular.fancybox-slide--previous { - -webkit-transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0); - transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0); - opacity: 0; } + opacity: 0; + transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0); +} .fancybox-fx-circular.fancybox-slide--next { - -webkit-transform: scale3d(0, 0, 0) translate3d(100%, 0, 0); - transform: scale3d(0, 0, 0) translate3d(100%, 0, 0); - opacity: 0; } + opacity: 0; + transform: scale3d(0, 0, 0) translate3d(100%, 0, 0); +} .fancybox-fx-circular.fancybox-slide--current { - -webkit-transform: scale3d(1, 1, 1) translate3d(0, 0, 0); - transform: scale3d(1, 1, 1) translate3d(0, 0, 0); - opacity: 1; } + opacity: 1; + transform: scale3d(1, 1, 1) translate3d(0, 0, 0); +} /* transitionEffect: tube */ + .fancybox-fx-tube.fancybox-slide--previous { - -webkit-transform: translate3d(-100%, 0, 0) scale(0.1) skew(-10deg); - transform: translate3d(-100%, 0, 0) scale(0.1) skew(-10deg); } + transform: translate3d(-100%, 0, 0) scale(.1) skew(-10deg); +} .fancybox-fx-tube.fancybox-slide--next { - -webkit-transform: translate3d(100%, 0, 0) scale(0.1) skew(10deg); - transform: translate3d(100%, 0, 0) scale(0.1) skew(10deg); } + transform: translate3d(100%, 0, 0) scale(.1) skew(10deg); +} .fancybox-fx-tube.fancybox-slide--current { - -webkit-transform: translate3d(0, 0, 0) scale(1); - transform: translate3d(0, 0, 0) scale(1); } + transform: translate3d(0, 0, 0) scale(1); +} /* Styling for Small-Screen Devices */ -@media all and (max-width: 800px) { - .fancybox-infobar { - left: 0; - margin-left: 0; } - .fancybox-button--left, - .fancybox-button--right { - display: none !important; } - .fancybox-caption { - padding: 20px 0; - margin: 0; } } - -/* Fullscreen */ -.fancybox-button--fullscreen::before { - width: 15px; - height: 11px; - left: calc(50% - 7px); - top: calc(50% - 6px); - border: 2px solid; - background: none; } - -/* Slideshow button */ -.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; } - +@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 { - display: none; } - -.fancybox-button--thumbs { - display: none; } - -@media all and (min-width: 800px) { - .fancybox-button--thumbs { - display: inline-block; } - .fancybox-button--thumbs span { - font-size: 23px; } - .fancybox-button--thumbs::before { - width: 3px; - height: 3px; - top: calc(50% - 2px); - left: calc(50% - 2px); - box-shadow: 0 -4px 0, -4px -4px 0, 4px -4px 0, 0 0 0 32px inset, -4px 0 0, 4px 0 0, 0 4px 0, -4px 4px 0, 4px 4px 0; } - .fancybox-thumbs { - position: absolute; - top: 0; - right: 0; + background: #ddd; bottom: 0; - left: auto; - width: 220px; + display: none; margin: 0; - padding: 5px 5px 0 0; - background: #fff; - word-break: normal; - -webkit-tap-highlight-color: transparent; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; - box-sizing: border-box; - z-index: 99995; } - .fancybox-show-thumbs .fancybox-thumbs { - display: block; } - .fancybox-show-thumbs .fancybox-inner { - right: 220px; } - .fancybox-thumbs > ul { - list-style: none; + padding: 2px 2px 4px 2px; position: absolute; - position: relative; - width: 100%; + 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; - padding: 0; overflow-x: hidden; overflow-y: auto; - font-size: 0; } - .fancybox-thumbs > ul > li { - float: left; - overflow: hidden; - max-width: 50%; padding: 0; - margin: 0; - width: 105px; - height: 75px; + 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; - border: 5px solid transparent; - border-top-width: 0; - border-right-width: 0; + overflow: hidden; + padding: 0; + position: relative; -webkit-tap-highlight-color: transparent; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - box-sizing: border-box; } - li.fancybox-thumbs-loading { - background: rgba(0, 0, 0, 0.1); } - .fancybox-thumbs > ul > li > img { - position: absolute; - top: 0; - left: 0; - min-width: 100%; - min-height: 100%; - max-width: none; - max-height: none; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } - .fancybox-thumbs > ul > li:before { + width: 100px; +} + +.fancybox-thumbs__list a::before { + border: 6px solid #ff5268; + bottom: 0; content: ''; + left: 0; + opacity: 0; position: absolute; - top: 0; right: 0; - bottom: 0; - left: 0; - border-radius: 2px; - border: 4px solid #4ea7f9; + top: 0; + transition: all .2s cubic-bezier(.25, .46, .45, .94); z-index: 99991; - opacity: 0; - transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); } - .fancybox-thumbs > ul > li.fancybox-thumbs-active:before { - opacity: 1; } } +} + +.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); + } +} \ No newline at end of file diff --git a/dist/jquery.fancybox.js b/dist/jquery.fancybox.js index eb47a7a7..806b2703 100644 --- a/dist/jquery.fancybox.js +++ b/dist/jquery.fancybox.js @@ -1,4916 +1,5632 @@ // ================================================== -// fancyBox v3.1.20 +// fancyBox v3.5.7 // // Licensed GPLv3 for open source use // or fancyBox Commercial License for commercial use // // http://fancyapps.com/fancybox/ -// Copyright 2017 fancyApps +// Copyright 2019 fancyApps // // ================================================== -;(function (window, document, $, undefined) { - 'use strict'; - - // If there's no jQuery, fancyBox can't work - // ========================================= +(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" + }; - if ( !$ ) { - return; + for (t in transitions) { + if (el.style[t] !== undefined) { + return transitions[t]; + } } - // Check if fancyBox is already initialized - // ======================================== + 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; + } + }); - if ( $.fn.fancybox ) { + return rez; + }; - $.error('fancyBox already initialized'); + // How much of an element is visible in viewport + // ============================================= - return; + var inViewport = function (elem) { + var elemCenter, rez; + + if (!elem || elem.ownerDocument !== document) { + return false; } - // Private default settings - // ======================== + $(".fancybox-container").css("pointer-events", "none"); - var defaults = { + elemCenter = { + x: elem.getBoundingClientRect().left + elem.offsetWidth / 2, + y: elem.getBoundingClientRect().top + elem.offsetHeight / 2 + }; - // Enable infinite gallery navigation - loop : false, + rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem; - // Space around image, ignored if zoomed-in or viewport smaller than 800px - margin : [44, 0], + $(".fancybox-container").css("pointer-events", ""); - // Horizontal space between slides - gutter : 50, + return rez; + }; - // Enable keyboard navigation - keyboard : true, + // Class definition + // ================ - // Should display navigation arrows at the screen edges - arrows : true, + var FancyBox = function (content, opts, index) { + var self = this; - // Should display infobar (counter and arrows at the top) - infobar : false, + self.opts = mergeOpts({ + index: index + }, $.fancybox.defaults); - // Should display toolbar (buttons at the top) - toolbar : true, + if ($.isPlainObject(opts)) { + self.opts = mergeOpts(self.opts, opts); + } - // 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 : [ - 'slideShow', - 'fullScreen', - 'thumbs', - 'close' - ], + if ($.fancybox.isMobile) { + self.opts = mergeOpts(self.opts, self.opts.mobile); + } - // Detect "idle" time in seconds - idleTime : 4, + self.id = self.opts.id || ++called; - // Should display buttons at top right corner of the content - // If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax' - // Use template from `btnTpl.smallBtn` for customization - smallBtn : 'auto', + self.currIndex = parseInt(self.opts.index, 10) || 0; + self.prevIndex = null; - // Disable right-click and use simple image protection for images - protect : false, + self.prevPos = null; + self.currPos = 0; - // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc - modal : false, + self.firstRun = true; - image : { + // All group items + self.group = []; - // Wait for images to load before displaying - // Requires predefined image dimensions - // If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found - preload : "auto", + // Existing slides (for current, next and previous gallery items) + self.slides = {}; - }, + // Create group elements + self.addContent(content); - ajax : { + if (!self.group.length) { + return; + } - // Object containing settings for ajax request - settings : { + 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 + "" + }; + } - // This helps to indicate that request comes from the modal - // Feel free to change naming - data : { - fancybox : true - } - } + // 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; + } - iframe : { + if ($.fancybox.isMobile && obj.opts.mobile) { + obj.opts = mergeOpts(obj.opts, obj.opts.mobile); + } - // Iframe template - tpl : '', + // Step 2 - Make sure we have content type, if not - try to guess + // ============================================================== - // 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, + type = obj.type || obj.opts.type; + src = obj.src || ""; - // Custom CSS styling for iframe wrapping element - // You can use this to set custom iframe dimensions - css : {}, + if (!type && src) { + if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) { + type = "video"; - // Iframe tag attributes - attr : { - scrolling : 'auto' + 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); + } - // 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 : { - slideShow : '', - fullScreen : '', - thumbs : '', - close : '', - - // This small close button will be appended to your html/inline/ajax content by default, - // if "smallBtn" option is not set to false - smallBtn : '' - }, + if (!obj.contentType) { + obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type; + } - // Container is injected into this element - parentEl : 'body', + // Step 3 - Some adjustments + // ========================= + obj.index = self.group.length; - // Focus handling - // ============== + if (obj.opts.smallBtn == "auto") { + obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1; + } - // Try to focus on the first focusable element after opening - autoFocus : true, + if (obj.opts.toolbar === "auto") { + obj.opts.toolbar = !obj.opts.smallBtn; + } - // Put focus back to active element after closing - backFocus : true, + // Find thumbnail image, check if exists and if is in the viewport + obj.$thumb = obj.opts.$thumb || null; - // Do not let user to focus on element outside modal content - trapFocus : true, + 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; + } + } - // Module specific options - // ======================= + if (!(obj.$thumb && obj.$thumb.length) && obj.opts.$orig) { + obj.$thumb = obj.opts.$orig.find("img:first"); + } - fullScreen : { - autoStart : false, - }, + if (obj.$thumb && !obj.$thumb.length) { + obj.$thumb = null; + } - touch : { - vertical : true, // Allow to drag content vertically - momentum : true // Continue movement after releasing mouse/touch when panning - }, + obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null); - // 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 - } - } + // "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]); } - */ - media : {}, - - slideShow : { - autoStart : false, - speed : 4000 - }, - thumbs : { - autoStart : false, // Display thumbnails on opening - hideOnClose : true // Hide thumbnail grid when closing animation starts - }, + if ($.type(self.opts.caption) === "function") { + obj.opts.caption = self.opts.caption.apply(item, [self, obj]); + } - // Callbacks - //========== + // 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 + ""; + } - // See Documentation/API/Events for more information - // Example: - /* - afterShow: function( instance, current ) { - console.info( 'Clicked element:' ); - console.info( current.opts.$orig ); - } - */ + // Check if url contains "filter" used to filter the content + // Example: "ajax.html #something" + if (obj.type === "ajax") { + srcParts = src.split(/\s+/, 2); - onInit : $.noop, // When instance has been initialized + if (srcParts.length > 1) { + obj.src = srcParts.shift(); - beforeLoad : $.noop, // Before the content of a slide is being loaded - afterLoad : $.noop, // When the content of a slide is done loading + obj.opts.filter = srcParts.shift(); + } + } - beforeShow : $.noop, // Before open animation starts - afterShow : $.noop, // When content is done loading and animating + // 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 + }); + } - beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close. - afterClose : $.noop, // After instance has been closed + // Step 4 - Add processed object to group + // ====================================== - onActivate : $.noop, // When instance is brought to front - onDeactivate : $.noop, // When other instance has been activated + self.group.push(obj); + }); + // Update controls if gallery is already opened + if (Object.keys(self.slides).length) { + self.updateControls(); - // Interaction - // =========== + // Update thumbnails, if needed + thumbs = self.Thumbs; - // 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 + if (thumbs && thumbs.isActive) { + thumbs.create(); - // Clicked on the content - clickContent : function( current, event ) { - return current.type === 'image' ? 'zoom' : false; - }, + 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(); + } - // Clicked on the slide - clickSlide : 'close', + setTimeout( + function () { + self.$refs.stage.show(); - // Clicked on the background (backdrop) element - clickOutside : 'close', + self.update(e); + }, + $.fancybox.isMobile ? 600 : 250 + ); + } + }); - // Same as previous two, but for double click - dblclickContent : false, - dblclickSlide : false, - dblclickOutside : false, + $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 + // ======================================= - // Custom options when mobile device is detected - // ============================================= + if (keycode == 9) { + if (current.opts.trapFocus) { + self.focus(e); + } - mobile : { - 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; - } - }, + return; + } + // Enable keyboard navigation + // ========================== - // 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' - }, - 'de' : { - CLOSE : 'Schliessen', - 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' - } + 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(); - // Few useful variables and methods - // ================================ + self.close(e); - var $W = $(window); - var $D = $(document); + return; + } - var called = 0; + // Left arrow and Up arrow + if (keycode === 37 || keycode === 38) { + e.preventDefault(); + self.previous(); - // Check if an object is a jQuery object and not a native JavaScript object - // ======================================================================== + return; + } - var isQuery = function ( obj ) { - return obj && obj.hasOwnProperty && obj instanceof $; - }; + // Righ arrow and Down arrow + if (keycode === 39 || keycode === 40) { + e.preventDefault(); + self.next(); - // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" - // =============================================================================== + return; + } - 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); - }; - })(); + self.trigger("afterKeydown", e, keycode); + }); + // Hide controls after some inactivity period + if (self.group[self.currIndex].opts.idleTime) { + self.idleSecondsCounter = 0; - // Detect the supported transition-end event property name - // ======================================================= + $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; - var transitionEnd = (function () { - var t, el = document.createElement("fakeelement"); + if (self.isIdle) { + self.showControls(); + } - var transitions = { - "transition" : "transitionend", - "OTransition" : "oTransitionEnd", - "MozTransition" : "transitionend", - "WebkitTransition": "webkitTransitionEnd" - }; + self.isIdle = false; + } + ); - for (t in transitions) { - if (el.style[t] !== undefined){ - return transitions[t]; - } - } - })(); + self.idleInterval = window.setInterval(function () { + self.idleSecondsCounter++; + if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) { + self.isIdle = true; + self.idleSecondsCounter = 0; - // Force redraw on an element. - // This helps in cases where the browser doesn't redraw an updated element properly. - // ================================================================================= + self.hideControls(); + } + }, 1000); + } + }, - var forceRedraw = function( $el ) { - return ( $el && $el.length && $el[0].offsetHeight ); - }; + // Remove events added by the core + // =============================== + removeEvents: function () { + var self = this; - // Class definition - // ================ + $W.off("orientationchange.fb resize.fb"); + $D.off("keydown.fb .fb-idle"); - var FancyBox = function( content, opts, index ) { - var self = this; + this.$refs.container.off(".fb-close .fb-prev .fb-next"); - self.opts = $.extend( true, { index : index }, defaults, opts || {} ); + if (self.idleInterval) { + window.clearInterval(self.idleInterval); - // Exclude buttons option from deep merging - if ( opts && $.isArray( opts.buttons ) ) { - self.opts.buttons = opts.buttons; - } + self.idleInterval = null; + } + }, - self.id = self.opts.id || ++called; - self.group = []; + // Change to previous gallery item + // =============================== - self.currIndex = parseInt( self.opts.index, 10 ) || 0; - self.prevIndex = null; + previous: function (duration) { + return this.jumpTo(this.currPos - 1, duration); + }, - self.prevPos = null; - self.currPos = 0; + // Change to next gallery item + // =========================== - self.firstRun = null; + next: function (duration) { + return this.jumpTo(this.currPos + 1, duration); + }, - // Create group elements from original item collection - self.createGroup( content ); + // Switch to selected gallery item + // =============================== - if ( !self.group.length ) { - return; - } + jumpTo: function (pos, duration) { + var self = this, + groupLen = self.group.length, + firstRun, + isMoved, + loop, + current, + previous, + slidePos, + stagePos, + prop, + diff; - // Save last active element and current scroll position - self.$lastFocus = $(document.activeElement).blur(); + if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) { + return; + } - // Collection of gallery objects - self.slides = {}; + // Should loop? + pos = parseInt(pos, 10); + loop = self.current ? self.current.opts.loop : self.opts.loop; - self.init( content ); + 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; - $.extend(FancyBox.prototype, { + // Create slides + previous = self.current; - // Create DOM structure - // ==================== + self.prevIndex = self.currIndex; + self.prevPos = self.currPos; - init : function() { - var self = this; + current = self.createSlide(pos); - var testWidth, $container, buttonStr; + if (groupLen > 1) { + if (loop || current.index < groupLen - 1) { + self.createSlide(pos + 1); + } - var firstItemOpts = self.group[ self.currIndex ].opts; + if (loop || current.index > 0) { + self.createSlide(pos - 1); + } + } - self.scrollTop = $D.scrollTop(); - self.scrollLeft = $D.scrollLeft(); + self.current = current; + self.currIndex = current.index; + self.currPos = current.pos; + self.trigger("beforeShow", firstRun); - // Hide scrollbars - // =============== + self.updateControls(); - if ( !$.fancybox.getInstance() && !$.fancybox.isMobile && $( 'body' ).css('overflow') !== 'hidden' ) { - testWidth = $( 'body' ).width(); + // Validate duration length + current.forcedDuration = undefined; - $( 'html' ).addClass( 'fancybox-enabled' ); + if ($.isNumeric(duration)) { + current.forcedDuration = duration; + } else { + duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"]; + } - // Compare body width after applying "overflow: hidden" - testWidth = $( 'body' ).width() - testWidth; + duration = parseInt(duration, 10); - // If width has changed - compensate missing scrollbars by adding right margin - if ( testWidth > 1 ) { - $( 'head' ).append( '' ); - } - } + // 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"); - // Build html markup and set references - // ==================================== + // 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"); + } - // Build html code for buttons and insert into main template - buttonStr = ''; + self.$refs.container.addClass("fancybox-is-open").trigger("focus"); - $.each( firstItemOpts.buttons, function( index, value ) { - buttonStr += ( firstItemOpts.btnTpl[ value ] || '' ); - }); + // Attempt to load content into slide + // This will later call `afterLoad` -> `revealContent` + self.loadSlide(current); - // 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 ) ) ) - .addClass( 'fancybox-is-hidden' ) - .attr('id', 'fancybox-container-' + self.id) - .addClass( firstItemOpts.baseClass ) - .data( 'FancyBox', self ) - .prependTo( firstItemOpts.parentEl ); - - // Create object holding references to jQuery wrapped nodes - self.$refs = { - container : $container - }; - - [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption' ].forEach(function(item) { - self.$refs[ item ] = $container.find( '.fancybox-' + item ); - }); + self.preload("image"); - // Check for redundant elements - if ( !firstItemOpts.arrows || self.group.length < 2 ) { - $container.find('.fancybox-navigation').remove(); - } + 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 ( !firstItemOpts.infobar ) { - self.$refs.infobar.remove(); + 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 + // ======================================================= - if ( !firstItemOpts.toolbar ) { - self.$refs.toolbar.remove(); - } + 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.trigger( 'onInit' ); + self.isAnimating = true; - // Bring to front and enable events - self.activate(); + $.fancybox.stop($content); - // Build slides, load and reveal content - self.jumpTo( self.currIndex ); - }, + x = x === undefined ? canvasWidth * 0.5 : x; + y = y === undefined ? canvasHeight * 0.5 : y; + imgPos = $.fancybox.getTranslate($content); - // Simple i18n support - replaces object keys found in template - // with corresponding values - // ============================================================ + imgPos.top -= $.fancybox.getTranslate(current.$slide).top; + imgPos.left -= $.fancybox.getTranslate(current.$slide).left; - translate : function( obj, str ) { - var arr = obj.opts.i18n[ obj.opts.lang ]; + scaleX = newImgWidth / imgPos.width; + scaleY = newImgHeight / imgPos.height; - return str.replace(/\{\{(\w+)\}\}/g, function(match, n) { - var value = arr[n]; + // Get center position for original image + posX = canvasWidth * 0.5 - newImgWidth * 0.5; + posY = canvasHeight * 0.5 - newImgHeight * 0.5; - if ( value === undefined ) { - return match; - } + // Make sure image does not move away from edges + if (newImgWidth > canvasWidth) { + posX = imgPos.left * scaleX - (x * scaleX - x); - return value; - }); - }, + if (posX > 0) { + posX = 0; + } - // Create array of gally item objects - // Check if each object has valid type and content - // =============================================== + if (posX < canvasWidth - newImgWidth) { + posX = canvasWidth - newImgWidth; + } + } - createGroup : function ( content ) { - var self = this; - var items = $.makeArray( content ); + if (newImgHeight > canvasHeight) { + posY = imgPos.top * scaleY - (y * scaleY - y); - $.each(items, function( i, item ) { - var obj = {}, - opts = {}, - data = [], - $item, - type, - src, - srcParts; + if (posY > 0) { + posY = 0; + } - // Step 1 - Make sure we have an object - // ==================================== + if (posY < canvasHeight - newImgHeight) { + posY = canvasHeight - newImgHeight; + } + } - if ( $.isPlainObject( item ) ) { + self.updateCursor(newImgWidth, newImgHeight); - // We probably have manual usage here, something like - // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) + $.fancybox.animate( + $content, { + top: posY, + left: posX, + scaleX: scaleX, + scaleY: scaleY + }, + duration || 366, + function () { + self.isAnimating = false; + } + ); - obj = item; - opts = item.opts || item; + // Stop slideshow + if (self.SlideShow && self.SlideShow.isActive) { + self.SlideShow.stop(); + } + }, - } else if ( $.type( item ) === 'object' && $( item ).length ) { + // Scale image to fit inside parent element + // ======================================== - // Here we propbably have jQuery collection returned by some selector + scaleToFit: function (duration) { + var self = this, + current = self.current, + $content = current.$content, + end; - $item = $( item ); - data = $item.data(); + if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { + return; + } - opts = 'options' in data ? data.options : {}; - opts = $.type( opts ) === 'object' ? opts : {}; + self.isAnimating = true; - obj.src = 'src' in data ? data.src : ( opts.src || $item.attr( 'href' ) ); + $.fancybox.stop($content); - [ 'width', 'height', 'thumb', 'type', 'filter' ].forEach(function(item) { - if ( item in data ) { - opts[ item ] = data[ item ]; - } - }); + end = self.getFitPos(current); - if ( 'srcset' in data ) { - opts.image = { srcset : data.srcset }; - } + self.updateCursor(end.width, end.height); - opts.$orig = $item; + $.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; + } + } - if ( !obj.type && !obj.src ) { - obj.type = 'inline'; - obj.src = item; - } + rez.width = width; + rez.height = height; - } else { + return rez; + }, - // Assume we have a simple html code, for example: - // $.fancybox.open( '

Hi!

' ); + // Update content size and position for all slides + // ============================================== - obj = { - type : 'html', - src : item + '' - }; + update: function (e) { + var self = this; - } + $.each(self.slides, function (key, slide) { + self.updateSlide(slide, e); + }); + }, - // Each gallery object has full collection of options - obj.opts = $.extend( true, {}, self.opts, opts ); + // Update slide content position and size + // ====================================== - if ( $.fancybox.isMobile ) { - obj.opts = $.extend( true, {}, obj.opts, obj.opts.mobile ); - } + 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); - // Step 2 - Make sure we have content type, if not - try to guess - // ============================================================== + // Then resize content to fit inside the slide + if ($content && (width || height || slide.contentType === "video") && !slide.hasError) { + $.fancybox.stop($content); - type = obj.type || obj.opts.type; - src = obj.src || ''; + $.fancybox.setTranslate($content, self.getFitPos(slide)); - if ( !type && src ) { - if ( src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i) ) { - type = 'image'; + if (slide.pos === self.currPos) { + self.isAnimating = false; - } else if ( src.match(/\.(pdf)((\?|#).*)?$/i) ) { - type = 'pdf'; + self.updateCursor(); + } + } - } else if ( src.charAt(0) === '#' ) { - type = 'inline'; - } - } + // Then some adjustments + self.adjustLayout(slide); - obj.type = type; + 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); + } + } - // Step 3 - Some adjustments - // ========================= + self.trigger("onUpdate", slide, e); + }, - obj.index = self.group.length; + // Horizontally center slide + // ========================= - // Check if $orig and $thumb objects exist - if ( obj.opts.$orig && !obj.opts.$orig.length ) { - delete obj.opts.$orig; - } + centerSlide: function (duration) { + var self = this, + current = self.current, + $slide = current.$slide; - if ( !obj.opts.$thumb && obj.opts.$orig ) { - obj.opts.$thumb = obj.opts.$orig.find( 'img:first' ); - } + 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 + ); + }, - if ( obj.opts.$thumb && !obj.opts.$thumb.length ) { - delete obj.opts.$thumb; - } + // Check if current slide is moved (swiped) + // ======================================== - // Caption is a "special" option, it can be passed as a method - if ( $.type( obj.opts.caption ) === 'function' ) { - obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] ); + isMoved: function (slide) { + var current = slide || this.current, + slidePos, + stagePos; - } else if ( 'caption' in data ) { - obj.opts.caption = data.caption; - } + if (!current) { + return false; + } - // Make sure we have caption as a string - obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + ''; + stagePos = $.fancybox.getTranslate(this.$refs.stage); + slidePos = $.fancybox.getTranslate(current.$slide); - // Check if url contains "filter" used to filter the content - // Example: "ajax.html #something" - if ( type === 'ajax' ) { - srcParts = src.split(/\s+/, 2); + return ( + !current.$slide.hasClass("fancybox-animated") && + (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5) + ); + }, - if ( srcParts.length > 1 ) { - obj.src = srcParts.shift(); + // Update cursor style depending if content can be zoomed + // ====================================================== - obj.opts.filter = srcParts.shift(); - } - } + updateCursor: function (nextWidth, nextHeight) { + var self = this, + current = self.current, + $container = self.$refs.container, + canPan, + isZoomable; - if ( obj.opts.smallBtn == 'auto' ) { + if (!current || self.isClosing || !self.Guestures) { + return; + } - if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) { - obj.opts.toolbar = false; - obj.opts.smallBtn = true; + $container.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan"); - } else { - obj.opts.smallBtn = false; - } + canPan = self.canPan(nextWidth, nextHeight); - } + isZoomable = canPan ? true : self.isZoomable(); - // If the type is "pdf", then simply load file into iframe - if ( type === 'pdf' ) { - obj.type = 'iframe'; + $container.toggleClass("fancybox-is-zoomable", isZoomable); - obj.opts.iframe.preload = false; - } + $("[data-fancybox-zoom]").prop("disabled", !isZoomable); - // Hide all buttons and disable interactivity for modal items - if ( obj.opts.modal ) { + 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"); + } + }, - obj.opts = $.extend(true, obj.opts, { - // Remove buttons - infobar : 0, - toolbar : 0, + // Check if current slide is zoomable + // ================================== - smallBtn : 0, + isZoomable: function () { + var self = this, + current = self.current, + fitPos; - // Disable keyboard navigation - keyboard : 0, + // 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; + } - // Disable some modules - slideShow : 0, - fullScreen : 0, - thumbs : 0, - touch : 0, + fitPos = self.getFitPos(current); - // Disable click event handlers - clickContent : false, - clickSlide : false, - clickOutside : false, - dblclickContent : false, - dblclickSlide : false, - dblclickOutside : false - }); + 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; + } + } - // Step 4 - Add processed object to group - // ====================================== + return rez; + }, - self.group.push( obj ); + // Load content into the slide + // =========================== - }); + loadSlide: function (slide) { + var self = this, + type, + $slide, + ajaxLoad; - }, + if (slide.isLoading || slide.isLoaded) { + return; + } + slide.isLoading = true; - // Attach an event handler functions for: - // - navigation buttons - // - browser scrolling, resizing; - // - focusing - // - keyboard - // - detect idle - // ====================================== + if (self.trigger("beforeLoad", slide) === false) { + slide.isLoading = false; - addEvents : function() { - var self = this; + return false; + } - self.removeEvents(); + type = slide.type; + $slide = slide.$slide; - // Make navigation elements clickable - self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function(e) { - e.stopPropagation(); - e.preventDefault(); + $slide + .off("refresh") + .trigger("onReset") + .addClass(slide.opts.slideClass); - self.close( e ); + // Create content depending on the type + switch (type) { + case "image": + self.setImage(slide); - }).on( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) { - e.stopPropagation(); - e.preventDefault(); + break; - self.previous(); + case "iframe": + self.setIframe(slide); - }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) { - e.stopPropagation(); - e.preventDefault(); + break; - self.next(); + 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 || "") + ); - // Handle page scrolling and browser resizing - $W.on('orientationchange.fb resize.fb', function(e) { + break; - if ( e && e.originalEvent && e.originalEvent.type === "resize" ) { + case "inline": + if ($(slide.src).length) { + self.setContent(slide, $(slide.src)); + } else { + self.setError(slide); + } - requestAFrame(function() { - self.update(); - }); + break; - } else { + case "ajax": + self.showLoading(slide); - self.$refs.stage.hide(); + 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); + } + } + }) + ); - setTimeout(function() { - self.$refs.stage.show(); + $slide.one("onReset", function () { + ajaxLoad.abort(); + }); - self.update(); - }, 500); + break; - } + default: + self.setError(slide); - }); + break; + } - // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal - // (a.k.a. "escaping the modal") - $D.on('focusin.fb', function(e) { - var instance = $.fancybox ? $.fancybox.getInstance() : null; + return true; + }, - if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) { - return; - } + // Use thumbnail image, if possible + // ================================ - if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) { - e.stopPropagation(); + setImage: function (slide) { + var self = this, + ghost; - instance.focus(); + // Check if need to show loading icon + setTimeout(function () { + var $img = slide.$image; - // Sometimes page gets scrolled, set it back - $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); - } - }); + 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); - // Enable keyboard navigation - $D.on('keydown.fb', function (e) { - var current = self.current, - keycode = e.keyCode || e.which; + // This will be wrapper containing both ghost and actual image + slide.$content = $('
') + .addClass("fancybox-is-hidden") + .appendTo(slide.$slide.addClass("fancybox-slide--image")); - if ( !current || !current.opts.keyboard ) { - return; - } + // 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; - if ( $(e.target).is('input') || $(e.target).is('textarea') ) { - return; - } + ghost = document.createElement("img"); - // Backspace and Esc keys - if ( keycode === 8 || keycode === 27 ) { - e.preventDefault(); + ghost.onerror = function () { + $(this).remove(); - self.close( e ); + slide.$ghost = null; + }; - return; - } + ghost.onload = function () { + self.afterLoad(slide); + }; - // Left arrow and Up arrow - if ( keycode === 37 || keycode === 38 ) { - e.preventDefault(); + slide.$ghost = $(ghost) + .addClass("fancybox-image") + .appendTo(slide.$content) + .attr("src", slide.thumb); + } - self.previous(); + // Start loading actual image + self.setBigImage(slide); + }, - return; - } + // 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]; + } + }); - // Righ arrow and Down arrow - if ( keycode === 39 || keycode === 40 ) { - e.preventDefault(); + return ret; + }); - self.next(); + // Sort by value + temp.sort(function (a, b) { + return a.value - b.value; + }); - return; - } + // Ok, now we have an array of all srcset values + for (var j = 0; j < temp.length; j++) { + var el = temp[j]; - self.trigger('afterKeydown', e, keycode); - }); + 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; - // Hide controls after some inactivity period - if ( self.group[ self.currIndex ].opts.idleTime ) { - self.idleSecondsCounter = 0; + // 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; + } - $D.on('mousemove.fb-idle mouseenter.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function() { - self.idleSecondsCounter = 0; + slide.opts.srcset = srcset; + } + } + }, + + // Create full-size image + // ====================== - if ( self.isIdle ) { - self.showControls(); - } + setBigImage: function (slide) { + var self = this, + img = document.createElement("img"), + $img = $(img); - self.isIdle = false; - }); + slide.$image = $img + .one("error", function () { + self.setError(slide); + }) + .one("load", function () { + var sizes; - self.idleInterval = window.setInterval(function() { + if (!slide.$ghost) { + self.resolveImageSlideSize(slide, this.naturalWidth, this.naturalHeight); - self.idleSecondsCounter++; + self.afterLoad(slide); + } - if ( self.idleSecondsCounter >= self.group[ self.currIndex ].opts.idleTime ) { - self.isIdle = true; - self.idleSecondsCounter = 0; + if (self.isClosing) { + return; + } - self.hideControls(); - } + if (slide.opts.srcset) { + sizes = slide.opts.sizes; - }, 1000); + 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))); + } - // Remove events added by the core - // =============================== + self.hideLoading(slide); + }) + .addClass("fancybox-image") + .attr("src", slide.src) + .appendTo(slide.$content); - removeEvents : function () { - var self = this; + if ((img.complete || img.readyState == "complete") && $img.naturalWidth && $img.naturalHeight) { + $img.trigger("load"); + } else if (img.error) { + $img.trigger("error"); + } + }, - $W.off( 'orientationchange.fb resize.fb' ); - $D.off( 'focusin.fb keydown.fb .fb-idle' ); + // Computes the slide size from image size and maxWidth/maxHeight + // ============================================================== - this.$refs.container.off( '.fb-close .fb-prev .fb-next' ); + resolveImageSlideSize: function (slide, imgWidth, imgHeight) { + var maxWidth = parseInt(slide.opts.width, 10), + maxHeight = parseInt(slide.opts.height, 10); - if ( self.idleInterval ) { - window.clearInterval( self.idleInterval ); + // Sets the default values from the image + slide.width = imgWidth; + slide.height = imgHeight; - self.idleInterval = null; - } - }, + 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; + } + }, - // Change to previous gallery item - // =============================== + // Create iframe wrapper, iframe and bindings + // ========================================== - previous : function( duration ) { - return this.jumpTo( this.currPos - 1, duration ); - }, + setIframe: function (slide) { + var self = this, + opts = slide.opts.iframe, + $slide = slide.$slide, + $iframe; + slide.$content = $('
') + .css(opts.css) + .appendTo($slide); - // Change to next gallery item - // =========================== + $slide.addClass("fancybox-slide--" + slide.contentType); - next : function( duration ) { - return this.jumpTo( this.currPos + 1, duration ); - }, + slide.$iframe = $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime())) + .attr(opts.attr) + .appendTo(slide.$content); + if (opts.preload) { + self.showLoading(slide); - // Switch to selected gallery item - // =============================== + // Unfortunately, it is not always possible to determine if iframe is successfully loaded + // (due to browser security policy) - jumpTo : function ( pos, duration, slide ) { - var self = this, - firstRun, - loop, - current, - previous, - canvasWidth, - currentPos, - transitionProps; + $iframe.on("load.fb error.fb", function (e) { + this.isReady = 1; - var groupLen = self.group.length; + slide.$slide.trigger("refresh"); - if ( self.isSliding || self.isClosing || ( self.isAnimating && self.firstRun ) ) { - return; - } + self.afterLoad(slide); + }); - pos = parseInt( pos, 10 ); - loop = self.current ? self.current.opts.loop : self.opts.loop; + // Recalculate iframe content size + // =============================== - if ( !loop && ( pos < 0 || pos >= groupLen ) ) { - return false; - } + $slide.on("refresh.fb", function () { + var $content = slide.$content, + frameWidth = opts.css.width, + frameHeight = opts.css.height, + $contents, + $body; - firstRun = self.firstRun = ( self.firstRun === null ); + 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 ( groupLen < 2 && !firstRun && !!self.isSliding ) { - return; + if (frameWidth === undefined) { + frameWidth = Math.ceil(Math.max($body[0].clientWidth, $body.outerWidth(true))); } - previous = self.current; + $content.css("width", frameWidth ? frameWidth : "").css("max-width", ""); - self.prevIndex = self.currIndex; - self.prevPos = self.currPos; + if (frameHeight === undefined) { + frameHeight = Math.ceil(Math.max($body[0].clientHeight, $body.outerHeight(true))); + } - // Create slides - current = self.createSlide( pos ); + $content.css("height", frameHeight ? frameHeight : ""); - if ( groupLen > 1 ) { - if ( loop || current.index > 0 ) { - self.createSlide( pos - 1 ); - } + $slide.css("overflow", "auto"); + } - if ( loop || current.index < groupLen - 1 ) { - self.createSlide( pos + 1 ); - } - } + $content.removeClass("fancybox-is-hidden"); + }); + } else { + self.afterLoad(slide); + } - self.current = current; - self.currIndex = current.index; - self.currPos = current.pos; + $iframe.attr("src", slide.src); - self.trigger( 'beforeShow', firstRun ); + // 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) {} - self.updateControls(); + $(this) + .off("refresh.fb") + .empty(); - currentPos = $.fancybox.getTranslate( current.$slide ); + slide.isLoaded = false; + slide.isRevealed = false; + }); + }, - current.isMoved = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' ); - current.forcedDuration = undefined; + // Wrap and append content to the slide + // ====================================== - if ( $.isNumeric( duration ) ) { - current.forcedDuration = duration; - } else { - duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ]; - } + setContent: function (slide, content) { + var self = this; + + if (self.isClosing) { + return; + } - duration = parseInt( duration, 10 ); + self.hideLoading(slide); - // Fresh start - reveal container, current slide and start loading content - if ( firstRun ) { + if (slide.$content) { + $.fancybox.stop(slide.$content); + } - if ( current.opts.animationEffect && duration ) { - self.$refs.container.css( 'transition-duration', duration + 'ms' ); - } + slide.$slide.empty(); - self.$refs.container.removeClass( 'fancybox-is-hidden' ); + // 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"); + } - forceRedraw( self.$refs.container ); + // 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(); + } - self.$refs.container.addClass( 'fancybox-is-open' ); + // If "filter" option is provided, then filter content + if (slide.opts.filter) { + content = $("
") + .html(content) + .find(slide.opts.filter); + } + } - // Make first slide visible (to display loading icon, if needed) - current.$slide.addClass( 'fancybox-slide--current' ); + slide.$slide.one("onReset", function () { + // Pause all html5 video/audio + $(this) + .find("video,audio") + .trigger("pause"); - self.loadSlide( current ); + // Put content back + if (slide.$placeholder) { + slide.$placeholder.after(content.removeClass("fancybox-content").hide()).remove(); - self.preload(); + slide.$placeholder = null; + } - return; - } + // Remove custom close button + if (slide.$smallBtn) { + slide.$smallBtn.remove(); - // Clean up - $.each(self.slides, function( index, slide ) { - $.fancybox.stop( slide.$slide ); - }); + slide.$smallBtn = null; + } - // Make current that slide is visible even if content is still loading - current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' ); + // Remove content and mark slide as not loaded + if (!slide.hasError) { + $(this).empty(); - // If slides have been dragged, animate them to correct position - if ( current.isMoved ) { - canvasWidth = Math.round( current.$slide.width() ); + slide.isLoaded = false; + slide.isRevealed = false; + } + }); - $.each(self.slides, function( index, slide ) { - var pos = slide.pos - current.pos; + $(content).appendTo(slide.$slide); - $.fancybox.animate( slide.$slide, { - top : 0, - left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ) - }, duration, function() { + if ($(content).is("video,audio")) { + $(content).addClass("fancybox-video"); - slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' ); + $(content).wrap("
"); - if ( slide.pos === self.currPos ) { - current.isMoved = false; + slide.contentType = "video"; - self.complete(); - } - }); - }); + slide.opts.width = slide.opts.width || $(content).attr("width"); + slide.opts.height = slide.opts.height || $(content).attr("height"); + } - } else { - self.$refs.stage.children().removeAttr( 'style' ); - } + slide.$content = slide.$slide + .children() + .filter("div,form,main,video,audio,article,.fancybox-content") + .first(); - // Start transition that reveals current content - // or wait when it will be loaded + slide.$content.siblings().hide(); - if ( current.isLoaded ) { - self.revealContent( current ); + // 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(); + } - } else { - self.loadSlide( current ); - } + slide.$content.addClass("fancybox-content"); - self.preload(); + slide.$slide.addClass("fancybox-slide--" + slide.contentType); - if ( previous.pos === current.pos ) { - return; - } + self.afterLoad(slide); + }, - // Handle previous slide - // ===================== + // Display error message + // ===================== - transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' ); + setError: function (slide) { + slide.hasError = true; - previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ); + slide.$slide + .trigger("onReset") + .removeClass("fancybox-slide--" + slide.contentType) + .addClass("fancybox-slide--error"); - previous.isComplete = false; + slide.contentType = "html"; - if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) { - return; - } + this.setContent(slide, this.translate(slide, slide.opts.errorTpl)); - if ( current.isMoved ) { - previous.$slide.addClass( transitionProps ); + if (slide.pos === this.currPos) { + this.isAnimating = false; + } + }, - } else { + // Show loading icon inside the slide + // ================================== - transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect; + showLoading: function (slide) { + var self = this; - $.fancybox.animate( previous.$slide, transitionProps, duration, function() { - previous.$slide.removeClass( transitionProps ).removeAttr( 'style' ); - }); + 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; - // Create new "slide" element - // These are gallery items that are actually added to DOM - // ======================================================= + slide = slide || self.current; - createSlide : function( pos ) { + if (slide && slide.$spinner) { + slide.$spinner.stop().remove(); - var self = this; - var $slide; - var index; + delete slide.$spinner; + } + }, - index = pos % self.group.length; - index = index < 0 ? self.group.length + index : index; + // Adjustments after slide content has been loaded + // =============================================== - if ( !self.slides[ pos ] && self.group[ index ] ) { - $slide = $('
').appendTo( self.$refs.stage ); + afterLoad: function (slide) { + var self = this; - self.slides[ pos ] = $.extend( true, {}, self.group[ index ], { - pos : pos, - $slide : $slide, - isLoaded : false, - }); + if (self.isClosing) { + return; + } - self.updateSlide( self.slides[ pos ] ); - } + slide.isLoading = false; + slide.isLoaded = true; - return self.slides[ pos ]; - }, + self.trigger("afterLoad", slide); + self.hideLoading(slide); - // Scale image to the actual size of the image - // =========================================== + // 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); + } - scaleToActual : function( x, y, duration ) { + // Disable right click + if (slide.opts.protect && slide.$content && !slide.hasError) { + slide.$content.on("contextmenu.fb", function (e) { + if (e.button == 2) { + e.preventDefault(); + } - var self = this; + return true; + }); - var current = self.current; - var $what = current.$content; + // 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); + } + } - var imgPos, posX, posY, scaleX, scaleY; + self.adjustCaption(slide); - var canvasWidth = parseInt( current.$slide.width(), 10 ); - var canvasHeight = parseInt( current.$slide.height(), 10 ); + self.adjustLayout(slide); - var newImgWidth = current.width; - var newImgHeight = current.height; + if (slide.pos === self.currPos) { + self.updateCursor(); + } - if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating) { - return; - } + self.revealContent(slide); + }, - $.fancybox.stop( $what ); + // Prevent caption overlap, + // fix css inconsistency across browsers + // ===================================== - self.isAnimating = true; + 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; - x = x === undefined ? canvasWidth * 0.5 : x; - y = y === undefined ? canvasHeight * 0.5 : y; + $caption.toggleClass("fancybox-caption--separate", preventOverlap); - imgPos = $.fancybox.getTranslate( $what ); + if (preventOverlap && caption && caption.length) { + if (current.pos !== self.currPos) { + $clone = $caption.clone().appendTo($caption.parent()); - scaleX = newImgWidth / imgPos.width; - scaleY = newImgHeight / imgPos.height; + $clone + .children() + .eq(0) + .empty() + .html(caption); - // Get center position for original image - posX = ( canvasWidth * 0.5 - newImgWidth * 0.5 ); - posY = ( canvasHeight * 0.5 - newImgHeight * 0.5 ); + captionH = $clone.outerHeight(true); - // Make sure image does not move away from edges - if ( newImgWidth > canvasWidth ) { - posX = imgPos.left * scaleX - ( ( x * scaleX ) - x ); + $clone.empty().remove(); + } else if (self.$caption) { + captionH = self.$caption.outerHeight(true); + } - if ( posX > 0 ) { - posX = 0; - } + current.$slide.css("padding-bottom", captionH || ""); + } + }, - if ( posX < canvasWidth - newImgWidth ) { - posX = canvasWidth - newImgWidth; - } - } + // Simple hack to fix inconsistency across browsers, described here (affects Edge, too): + // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 + // ==================================================================================== - if ( newImgHeight > canvasHeight) { - posY = imgPos.top * scaleY - ( ( y * scaleY ) - y ); + adjustLayout: function (slide) { + var self = this, + current = slide || self.current, + scrollHeight, + marginBottom, + inlinePadding, + actualPadding; - if ( posY > 0 ) { - posY = 0; - } + if (current.isLoaded && current.opts.disableLayoutFix !== true) { + current.$content.css("margin-bottom", ""); - if ( posY < canvasHeight - newImgHeight ) { - posY = canvasHeight - newImgHeight; - } - } + // 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"); - self.updateCursor( newImgWidth, newImgHeight ); + if (parseFloat(actualPadding) > 0) { + scrollHeight = current.$slide[0].scrollHeight; - $.fancybox.animate( $what, { - top : posY, - left : posX, - scaleX : scaleX, - scaleY : scaleY - }, duration || 330, function() { - self.isAnimating = false; - }); + current.$slide.css("padding-bottom", 0); - // Stop slideshow - if ( self.SlideShow && self.SlideShow.isActive ) { - self.SlideShow.stop(); + if (Math.abs(scrollHeight - current.$slide[0].scrollHeight) < 1) { + marginBottom = actualPadding; } - }, - - // Scale image to fit inside parent element - // ======================================== + current.$slide.css("padding-bottom", inlinePadding); + } + } - scaleToFit : function( duration ) { + 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"; + } + } - var self = this; + // Zoom animation + // ============== + if (effect === "zoom") { + self.isAnimating = true; - var current = self.current; - var $what = current.$content; - var end; + end.scaleX = end.width / start.width; + end.scaleY = end.height / start.height; - if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) { - return; - } + // Check if we need to animate opacity + opacity = slide.opts.zoomOpacity; - $.fancybox.stop( $what ); + if (opacity == "auto") { + opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1; + } - self.isAnimating = true; + if (opacity) { + start.opacity = 0.1; + end.opacity = 1; + } - end = self.getFitPos( current ); + // Draw image at start position + $.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start); - self.updateCursor( end.width, end.height ); + forceRedraw(slide.$content); - $.fancybox.animate( $what, { - top : end.top, - left : end.left, - scaleX : end.width / $what.width(), - scaleY : end.height / $what.height() - }, duration || 330, function() { - self.isAnimating = false; - }); + // Start animation + $.fancybox.animate(slide.$content, end, duration, function () { + self.isAnimating = false; - }, + self.complete(); + }); - // Calculate image size to fit inside viewport - // =========================================== + return; + } - getFitPos : function( slide ) { - var self = this; - var $what = slide.$content; + self.updateSlide(slide); - var imgWidth = slide.width; - var imgHeight = slide.height; + // Simply show content if no effect + // ================================ + if (!effect) { + slide.$content.removeClass("fancybox-is-hidden"); - var margin = slide.opts.margin; + if (!isRevealed && isMoved && slide.type === "image" && !slide.hasError) { + slide.$content.hide().fadeIn("fast"); + } - var canvasWidth, canvasHeight, minRatio, width, height; + if (slide.pos === self.currPos) { + self.complete(); + } - if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) { - return false; - } + return; + } - // Convert "margin to CSS style: [ top, right, bottom, left ] - if ( $.type( margin ) === "number" ) { - margin = [ margin, margin ]; - } + // Prepare for CSS transiton + // ========================= + $.fancybox.stop($slide); - if ( margin.length == 2 ) { - margin = [ margin[0], margin[1], margin[0], margin[1] ]; - } + //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; - if ( $W.width() < 800 ) { - margin = [ 0, 0, 0, 0 ]; - } + $slide.addClass(effectClassName).removeClass("fancybox-slide--current"); //.addClass(effectClassName); - // We can not use $slide width here, because it can have different diemensions while in transiton - canvasWidth = parseInt( self.$refs.stage.width(), 10 ) - ( margin[ 1 ] + margin[ 3 ] ); - canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] ); + slide.$content.removeClass("fancybox-is-hidden"); - minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight ); + // Force reflow + forceRedraw($slide); - width = Math.floor( minRatio * imgWidth ); - height = Math.floor( minRatio * imgHeight ); + if (slide.type !== "image") { + slide.$content.hide().show(0); + } - // Use floor rounding to make sure it really fits - return { - top : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ], - left : Math.floor( ( canvasWidth - width ) * 0.5 ) + margin[ 3 ], - width : width, - height : height - }; + $.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; - // Update position and content of all slides - // ========================================= - - update : function() { + current.$slide.siblings().trigger("onReset"); - var self = this; + self.preload("inline"); - $.each( self.slides, function( key, slide ) { - self.updateSlide( slide ); - }); + // 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); - // Update slide position and scale content to fit - // ============================================== + slide.$slide.off().remove(); + } + }); - updateSlide : function( slide ) { + self.slides = slides; + } - var self = this; - var $what = slide.$content; + self.isAnimating = false; - if ( $what && ( slide.width || slide.height ) ) { - $.fancybox.stop( $what ); + self.updateCursor(); - $.fancybox.setTranslate( $what, self.getFitPos( slide ) ); + self.trigger("afterShow"); - if ( slide.pos === self.currPos ) { - self.updateCursor(); - } + // 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(); } - slide.$slide.trigger( 'refresh' ); - - self.trigger( 'onUpdate', slide ); + 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"); - // Update cursor style depending if content can be zoomed - // ====================================================== + if ($el.length) { + $el.trigger("focus"); + } else { + self.focus(null, true); + } + } - updateCursor : function( nextWidth, nextHeight ) { + // Avoid jumping + current.$slide.scrollTop(0).scrollLeft(0); + }, - var self = this; - var isScaledDown; + // Preload next and previous slides + // ================================ - var $container = self.$refs.container.removeClass('fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut'); + preload: function (type) { + var self = this, + prev, + next; - if ( !self.current || self.isClosing ) { - return; + 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(); } - if ( self.isZoomable() ) { + focusableItems.eq(0).trigger("focus"); + } + } + } else { + self.$refs.container.trigger("focus"); + } + }, - $container.addClass( 'fancybox-is-zoomable' ); + // Activates current instance - brings container to the front and enables keyboard, + // notifies other instances about deactivating + // ================================================================================= - if ( nextWidth !== undefined && nextHeight !== undefined ) { - isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height; + activate: function () { + var self = this; - } else { - isScaledDown = self.isScaledDown(); - } + // Deactivate all instances + $(".fancybox-container").each(function () { + var instance = $(this).data("FancyBox"); - if ( isScaledDown ) { + // Skip self and closing instances + if (instance && instance.id !== self.id && !instance.isClosing) { + instance.trigger("onDeactivate"); - // If image is scaled down, then, obviously, it can be zoomed to full size - $container.addClass('fancybox-can-zoomIn'); + instance.removeEvents(); - } else { + instance.isVisible = false; + } + }); - if ( self.current.opts.touch ) { + self.isVisible = true; - // If image size ir largen than available available and touch module is not disable, - // then user can do panning - $container.addClass('fancybox-can-drag'); + if (self.current || self.isIdle) { + self.update(); - } else { - $container.addClass('fancybox-can-zoomOut'); - } + self.updateControls(); + } - } + self.trigger("onActivate"); - } else if ( self.current.opts.touch ) { - $container.addClass('fancybox-can-drag'); - } - - }, - - - // Check if current slide is zoomable - // ================================== - - isZoomable : function() { - - var self = this; - - var current = self.current; - var fitPos; - - if ( !current || self.isClosing ) { - return; - } - - // Assume that slide is zoomable if - // - image is loaded successfuly - // - click action is "zoom" - // - actual size of the image is smaller than available area - if ( current.type === 'image' && current.isLoaded && !current.hasError && - ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) === "zoom" ) ) - ) { - - fitPos = self.getFitPos( current ); - - if ( current.width > fitPos.width || current.height > fitPos.height ) { - return true; - } - - } - - return false; - - }, - - - // Check if current image dimensions are smaller than actual - // ========================================================= - - isScaledDown : function() { - - var self = this; - - var current = self.current; - var $what = current.$content; - - var rez = false; - - if ( $what ) { - rez = $.fancybox.getTranslate( $what ); - rez = rez.width < current.width || rez.height < current.height; - } - - return rez; - - }, - - - // Check if image dimensions exceed parent element - // =============================================== - - canPan : function() { - - var self = this; - - var current = self.current; - var $what = current.$content; - - var rez = false; - - if ( $what ) { - rez = self.getFitPos( current ); - rez = Math.abs( $what.width() - rez.width ) > 1 || Math.abs( $what.height() - rez.height ) > 1; - - } - - return rez; - - }, - - - // Load content into the slide - // =========================== - - loadSlide : function( slide ) { - - var self = this, type, $slide; - var ajaxLoad; - - if ( slide.isLoading ) { - return; - } - - if ( slide.isLoaded ) { - return; - } - - slide.isLoading = true; - - self.trigger( 'beforeLoad', slide ); - - type = slide.type; - $slide = slide.$slide; - - $slide - .off( 'refresh' ) - .trigger( 'onReset' ) - .addClass( 'fancybox-slide--' + ( type || 'unknown' ) ) - .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 '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; - var srcset = slide.opts.image.srcset; - - var found, temp, pxRatio, windowWidth; - - // If we have "srcset", then we need to find 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; - } - } - } - - // This will be wrapper containing both ghost and actual image - slide.$content = $('
') - .addClass( 'fancybox-is-hidden' ) - .appendTo( slide.$slide ); - - - // 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.opts.thumb || slide.opts.$thumb ) ) { - - slide.width = slide.opts.width; - slide.height = slide.opts.height; - - slide.$ghost = $('') - .one('error', function() { - - $(this).remove(); - - slide.$ghost = null; - - self.setBigImage( slide ); - - }) - .one('load', function() { - - self.afterLoad( slide ); - - self.setBigImage( slide ); - - }) - .addClass( 'fancybox-image' ) - .appendTo( slide.$content ) - .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) ); - - } else { - - self.setBigImage( slide ); - - } - - }, - - - // Create full-size image - // ====================== - - setBigImage : function ( slide ) { - var self = this; - var $img = $(''); - - slide.$image = $img - .one('error', function() { - - self.setError( slide ); - - }) - .one('load', function() { - - // Clear timeout that checks if loading icon needs to be displayed - clearTimeout( slide.timouts ); - - slide.timouts = null; - - if ( self.isClosing ) { - return; - } - - slide.width = this.naturalWidth; - slide.height = this.naturalHeight; - - if ( slide.opts.image.srcset ) { - $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset ); - } - - self.hideLoading( slide ); - - if ( slide.$ghost ) { - - slide.timouts = setTimeout(function() { - slide.timouts = null; - - slide.$ghost.hide(); - - }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) ); - - } else { - self.afterLoad( slide ); - } - - }) - .addClass( 'fancybox-image' ) - .attr('src', slide.src) - .appendTo( slide.$content ); - - if ( $img[0].complete ) { - $img.trigger( 'load' ); - - } else if( $img[0].error ) { - $img.trigger( 'error' ); - - } else { - - slide.timouts = setTimeout(function() { - if ( !$img[0].complete && !slide.hasError ) { - self.showLoading( slide ); - } - - }, 100); - - } - - }, - - - // 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 ); - - $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 $wrap = slide.$content, - $contents, - $body, - scrollWidth, - frameWidth, - frameHeight; - - if ( $iframe[0].isReady !== 1 ) { - return; - } - - // Check if content is accessible, - // it will fail if frame is not with the same origin - - try { - $contents = $iframe.contents(); - $body = $contents.find('body'); - - } catch (ignore) {} - - // Calculate dimensions for the wrapper - if ( $body && $body.length && !( opts.css.width !== undefined && opts.css.height !== undefined ) ) { - - scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth; - - frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) ); - frameHeight = Math.ceil( $body.outerHeight(true) ); - - // Resize wrapper to fit iframe content - $wrap.css({ - 'width' : opts.css.width === undefined ? frameWidth + ( $wrap.outerWidth() - $wrap.innerWidth() ) : opts.css.width, - 'height' : opts.css.height === undefined ? frameHeight + ( $wrap.outerHeight() - $wrap.innerHeight() ) : opts.css.height - }); - - } - - $wrap.removeClass( 'fancybox-is-hidden' ); - - }); - - } else { - - this.afterLoad( slide ); - - } - - $iframe.attr( 'src', slide.src ); - - if ( slide.opts.smallBtn === true ) { - slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) ); - } - - // 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().attr( 'src', '//about:blank' ); - - } catch ( ignore ) {} - - $( this ).empty(); - - slide.isLoaded = false; - - }); - - }, - - - // Wrap and append content to the slide - // ====================================== - - setContent : function ( slide, content ) { - - var self = this; - - if ( self.isClosing ) { - return; - } - - self.hideLoading( slide ); - - slide.$slide.empty(); - - if ( isQuery( content ) && content.parent().length ) { - - // 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 user is navigating gallery fast, then the content might be already inside fancyBox - // ===================================================================================== - - // Make sure content is not already moved to fancyBox - content.parent( '.fancybox-slide--inline' ).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 we have text node, then add wrapping element to make vertical alignment work - if ( content[0].nodeType === 3 ) { - content = $('
').html( content ); - } - } - - // If "filter" option is provided, then filter content - if ( slide.opts.filter ) { - content = $('
').html( content ).find( slide.opts.filter ); - } - - } - - slide.$slide.one('onReset', function () { - - // Put content back - if ( slide.$placeholder ) { - slide.$placeholder.after( 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.$content = $( content ).appendTo( slide.$slide ); - - if ( slide.opts.smallBtn && !slide.$smallBtn ) { - slide.$smallBtn = $( self.translate( slide, slide.opts.btnTpl.smallBtn ) ).appendTo( slide.$content ); - } - - this.afterLoad( slide ); - }, - - // Display error message - // ===================== - - setError : function ( slide ) { - - slide.hasError = true; - - slide.$slide.removeClass( 'fancybox-slide--' + slide.type ); - - this.setContent( slide, this.translate( slide, slide.opts.errorTpl ) ); - - }, - - - // Show loading icon inside the slide - // ================================== - - showLoading : function( slide ) { - - var self = this; - - slide = slide || self.current; - - if ( slide && !slide.$spinner ) { - slide.$spinner = $( self.opts.spinnerTpl ).appendTo( slide.$slide ); - } - - }, - - // Remove loading icon from the slide - // ================================== - - hideLoading : function( slide ) { - - var self = this; - - slide = slide || self.current; - - if ( slide && slide.$spinner ) { - slide.$spinner.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 ); - - if ( slide.opts.protect && slide.$content && !slide.hasError ) { - - // Disable right click - 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.revealContent( slide ); - - }, - - - // 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; - var $slide = slide.$slide; - - var effect, effectClassName, duration, opacity, end, start = false; - - effect = slide.opts[ self.firstRun ? 'animationEffect' : 'transitionEffect' ]; - duration = slide.opts[ self.firstRun ? 'animationDuration' : 'transitionDuration' ]; - - duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10 ); - - if ( slide.isMoved || slide.pos !== self.currPos || !duration ) { - effect = false; - } - - // Check if can zoom - if ( effect === 'zoom' && !( slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && ( start = self.getThumbPos( slide ) ) ) ) { - effect = 'fade'; - } - - - // Zoom animation - // ============== - - if ( effect === 'zoom' ) { - end = self.getFitPos( slide ); - - end.scaleX = Math.round( (end.width / start.width) * 100 ) / 100; - end.scaleY = Math.round( (end.height / start.height) * 100 ) / 100; - - delete end.width; - delete end.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.complete(); - }); - - return; - } - - - self.updateSlide( slide ); - - - // Simply show content - // =================== - - if ( !effect ) { - forceRedraw( $slide ); - - slide.$content.removeClass( 'fancybox-is-hidden' ); - - if ( slide.pos === self.currPos ) { - self.complete(); - } - - return; - } - - $.fancybox.stop( $slide ); - - effectClassName = 'fancybox-animated fancybox-slide--' + ( slide.pos > self.prevPos ? 'next' : 'previous' ) + ' fancybox-fx-' + effect; - - $slide.removeAttr( 'style' ).removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ).addClass( effectClassName ); - - slide.$content.removeClass( 'fancybox-is-hidden' ); - - //Force reflow for CSS3 transitions - forceRedraw( $slide ); - - $.fancybox.animate( $slide, 'fancybox-slide--current', duration, function(e) { - $slide.removeClass( effectClassName ).removeAttr( 'style' ); - - if ( slide.pos === self.currPos ) { - self.complete(); - } - - }, true); - - }, - - - // Check if we can and have to zoom from thumbnail - //================================================ - - getThumbPos : function( slide ) { - - var self = this; - var rez = false; - - // Check if element is inside the viewport by at least 1 pixel - var isElementVisible = function( $el ) { - var element = $el[0]; - - var elementRect = element.getBoundingClientRect(); - var parentRects = []; - - var visibleInAllParents; - - while ( element.parentElement !== null ) { - if ( $(element.parentElement).css('overflow') === 'hidden' || $(element.parentElement).css('overflow') === 'auto' ) { - parentRects.push(element.parentElement.getBoundingClientRect()); - } - - element = element.parentElement; - } - - visibleInAllParents = parentRects.every(function(parentRect){ - var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left); - var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top); - - return visiblePixelX > 0 && visiblePixelY > 0; - }); - - return visibleInAllParents && - elementRect.bottom > 0 && elementRect.right > 0 && - elementRect.left < $(window).width() && elementRect.top < $(window).height(); - }; - - var $thumb = slide.opts.$thumb; - var thumbPos = $thumb ? $thumb.offset() : 0; - var slidePos; - - if ( thumbPos && $thumb[0].ownerDocument === document && isElementVisible( $thumb ) ) { - slidePos = self.$refs.stage.offset(); - - rez = { - top : thumbPos.top - slidePos.top + parseFloat( $thumb.css( "border-top-width" ) || 0 ), - left : thumbPos.left - slidePos.left + parseFloat( $thumb.css( "border-left-width" ) || 0 ), - width : $thumb.width(), - height : $thumb.height(), - scaleX : 1, - scaleY : 1 - }; - } - - return rez; - }, - - - // Final adjustments after current gallery item is moved to position - // and it`s content is loaded - // ================================================================== - - complete : function() { - - var self = this; - - var current = self.current; - var slides = {}; - - if ( current.isMoved || !current.isLoaded || current.isComplete ) { - return; - } - - current.isComplete = true; - - current.$slide.siblings().trigger( 'onReset' ); - - // Trigger any CSS3 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.unbind().remove(); - } - }); - - self.slides = slides; - - self.updateCursor(); - - self.trigger( 'afterShow' ); - - // Try to focus on the first focusable element - if ( $( document.activeElement ).is( '[disabled]' ) || ( current.opts.autoFocus && !( current.type == 'image' || current.type === 'iframe' ) ) ) { - self.focus(); - } - - }, - - - // Preload next and previous slides - // ================================ - - preload : function() { - var self = this; - var next, prev; - - if ( self.group.length < 2 ) { - return; - } - - next = self.slides[ self.currPos + 1 ]; - prev = self.slides[ self.currPos - 1 ]; - - if ( next && next.type === 'image' ) { - self.loadSlide( next ); - } - - if ( prev && prev.type === 'image' ) { - self.loadSlide( prev ); - } - - }, - - - // Try to find and focus on the first focusable element - // ==================================================== - - focus : function() { - var current = this.current; - var $el; - - if ( this.isClosing ) { - return; - } - - // Skip for images and iframes - $el = current && current.isComplete ? current.$slide.find('button,:input,[tabindex],a').filter(':not([disabled]):visible:first') : null; - $el = $el && $el.length ? $el : this.$refs.container; - - $el.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.uid !== self.uid && !instance.isClosing) { - instance.trigger( 'onDeactivate' ); - } - - }); - - if ( self.current ) { - if ( self.$refs.container.index() > 0 ) { - self.$refs.container.prependTo( document.body ); - } - - 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; - var current = self.current; - - var effect, duration; - var $what, 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(); - - if ( current.timouts ) { - clearTimeout( current.timouts ); - } - - $what = current.$content; - effect = current.opts.animationEffect; - duration = $.isNumeric( d ) ? d : ( effect ? current.opts.animationDuration : 0 ); - - // Remove other slides - current.$slide.off( transitionEnd ).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated' ); - - current.$slide.siblings().trigger( 'onReset' ).remove(); - - // Trigger animations - if ( duration ) { - self.$refs.container.removeClass( 'fancybox-is-open' ).addClass( 'fancybox-is-closing' ); - } - - // Clean up - self.hideLoading( current ); - - self.hideControls(); - - self.updateCursor(); - - // Check if possible to zoom-out - if ( effect === 'zoom' && !( e !== true && $what && duration && current.type === 'image' && !current.hasError && ( end = self.getThumbPos( current ) ) ) ) { - effect = 'fade'; - } - - if ( effect === 'zoom' ) { - $.fancybox.stop( $what ); - - start = $.fancybox.getTranslate( $what ); - - start.width = start.width * start.scaleX; - start.height = start.height * start.scaleY; - - // 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; - } - - start.scaleX = start.width / end.width; - start.scaleY = start.height / end.height; - - start.width = end.width; - start.height = end.height; - - $.fancybox.setTranslate( current.$content, start ); - - $.fancybox.animate( current.$content, end, duration, done ); - - return true; - } - - if ( effect && duration ) { - - // If skip animation - if ( e === true ) { - setTimeout( done, duration ); - - } else { - $.fancybox.animate( current.$slide.removeClass( 'fancybox-slide--current' ), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done ); - } - - } else { - done(); - } - - return true; - }, - - - // Final adjustments after removing the instance - // ============================================= - - cleanUp : function( e ) { - var self = this, - instance; - - self.current.$slide.trigger( 'onReset' ); - - self.$refs.container.empty().remove(); - - self.trigger( 'afterClose', e ); - - // Place back focus - if ( self.$lastFocus && !!!self.current.focusBack ) { - self.$lastFocus.focus(); - } - - self.current = null; - - // Check if there are other instances - instance = $.fancybox.getInstance(); - - if ( instance ) { - instance.activate(); - - } else { - - $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); - - $( 'html' ).removeClass( 'fancybox-enabled' ); - - $( '#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' ) { - $D.trigger( name + '.fb', args ); - - } else { - self.$refs.container.trigger( name + '.fb', args ); - } - - }, - - - // Update infobar values, navigation button states and reveal caption - // ================================================================== - - updateControls : function ( force ) { - - var self = this; - - var current = self.current; - var index = current.index; - var opts = current.opts; - var caption = opts.caption; - var $caption = self.$refs.caption; - - // Recalculate content dimensions - current.$slide.trigger( 'refresh' ); - - self.$caption = caption && caption.length ? $caption.html( caption ) : null; - - if ( !self.isHiddenControls ) { - self.showControls(); - } - - // Update info and navigation elements - $('[data-fancybox-count]').html( self.group.length ); - $('[data-fancybox-index]').html( index + 1 ); - - $('[data-fancybox-prev]').prop('disabled', ( !opts.loop && index <= 0 ) ); - $('[data-fancybox-next]').prop('disabled', ( !opts.loop && index >= self.group.length - 1 ) ); - - }, - - // Hide toolbar and caption - // ======================== - - hideControls : function () { - - this.isHiddenControls = true; - - this.$refs.container.removeClass('fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav'); - - }, - - showControls : function() { - - var self = this; - var opts = self.current ? self.current.opts : self.opts; - var $container = self.$refs.container; - - self.isHiddenControls = 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-nav', !!( opts.arrows && self.group.length > 1 ) ) - .toggleClass('fancybox-is-modal', !!opts.modal ); - - if ( self.$caption ) { - $container.addClass( 'fancybox-show-caption '); - - } else { - $container.removeClass( 'fancybox-show-caption' ); - } - - }, - - - // Toggle toolbar and caption - // ========================== - - toggleControls : function() { - - if ( this.isHiddenControls ) { - this.showControls(); - - } else { - this.hideControls(); - } + self.addEvents(); + }, - }, - - - }); - - - $.fancybox = { - - version : "3.1.20", - 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"):first').data( 'FancyBox' ); - var 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(); - } - } - - }, - - // Close instances and unbind all events - // ============================== - - destroy : function() { - - this.close( true ); - - $D.off( 'click.fb-start' ); - - }, - - - // Try to detect mobile devices - // ============================ - - isMobile : document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent), - - - // Detect if 'translate3d' support is available - // ============================================ - - use3d : (function() { - var div = document.createElement('div'); - - return window.getComputedStyle && 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 matrix; - - if ( !$el || !$el.length ) { - return false; - } - - matrix = $el.eq( 0 ).css('transform'); - - if ( matrix && matrix.indexOf( 'matrix' ) !== -1 ) { - matrix = matrix.split('(')[1]; - matrix = matrix.split(')')[0]; - matrix = matrix.split(','); - } else { - matrix = []; - } - - if ( matrix.length ) { - - // If IE - if ( matrix.length > 10 ) { - matrix = [ matrix[13], matrix[12], matrix[0], matrix[5] ]; - - } else { - matrix = [ matrix[5], matrix[4], matrix[0], matrix[3]]; - } - - matrix = matrix.map(parseFloat); - - } else { - matrix = [ 0, 0, 1, 1 ]; - - var transRegex = /\.*translate\((.*)px,(.*)px\)/i; - var transRez = transRegex.exec( $el.eq( 0 ).attr('style') ); - - if ( transRez ) { - matrix[ 0 ] = parseFloat( transRez[2] ); - matrix[ 1 ] = parseFloat( transRez[1] ); - } - } - - return { - top : matrix[ 0 ], - left : matrix[ 1 ], - scaleX : matrix[ 2 ], - scaleY : matrix[ 3 ], - opacity : parseFloat( $el.css('opacity') ), - width : $el.width(), - height : $el.height() - }; - - }, - - - // Shortcut for setting "translate3d" properties for element - // Can set be used to set opacity, too - // ======================================================== - - setTranslate : function( $el, props ) { - var str = ''; - var 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 = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')'; - } - - 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 event = transitionEnd || 'transitionend'; - - if ( $.isFunction( duration ) ) { - callback = duration; - duration = null; - } - - if ( !$.isPlainObject( to ) ) { - $el.removeAttr('style'); - } - - $el.on( event, 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; - } - - $el.off( event ); - - if ( $.isPlainObject( to ) ) { - - if ( to.scaleX !== undefined && to.scaleY !== undefined ) { - $el.css( 'transition-duration', '0ms' ); - - to.width = $el.width() * to.scaleX; - to.height = $el.height() * to.scaleY; - - to.scaleX = 1; - to.scaleY = 1; - - $.fancybox.setTranslate( $el, to ); - } - - } else if ( leaveAnimationName !== true ) { - $el.removeClass( to ); - } - - if ( $.isFunction( callback ) ) { - callback( e ); - } - - }); - - if ( $.isNumeric( duration ) ) { - $el.css( 'transition-duration', duration + 'ms' ); - } - - if ( $.isPlainObject( to ) ) { - $.fancybox.setTranslate( $el, to ); - - } else { - $el.addClass( to ); - } - - $el.data("timer", setTimeout(function() { - $el.trigger( 'transitionend' ); - }, duration + 16)); - - }, - - stop : function( $el ) { - clearTimeout( $el.data("timer") ); - - $el.off( transitionEnd ); - } - - }; - - - // Default click handler for "fancyboxed" links - // ============================================ - - function _run( e ) { - var target = e.currentTarget, - opts = e.data ? e.data.options : {}, - items = e.data ? e.data.items : [], - value = $(target).attr( 'data-fancybox' ) || '', - index = 0; - - e.preventDefault(); - e.stopPropagation(); - - // Get all related items and find index for clicked one - if ( value ) { - items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' ); - index = items.index( target ); - - // Sometimes current item can not be found - // (for example, when slider clones items) - if ( index < 0 ) { - index = 0; - } - - } else { - items = [ target ]; - } - - $.fancybox.open( items, opts, index ); - } - - - // Create a jQuery plugin - // ====================== - - $.fn.fancybox = function (options) { - var selector; - - options = options || {}; - selector = options.selector || false; - - if ( selector ) { - - $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, { - items : $( selector ), - options : options - }, _run ); - - } else { - - this.off( 'click.fb-start' ).on( 'click.fb-start', { - items : this, - options : options - }, _run); - - } - - return this; - }; - - - // Self initializing plugin - // ======================== - - $D.on( 'click.fb-start', '[data-fancybox]', _run ); - -}( window, document, window.jQuery )); - -// ========================================================================== -// -// Media -// Adds additional media type support -// -// ========================================================================== -;(function ($) { - - 'use strict'; - - // 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; - }; - - // 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 : '//www.youtube.com/embed/$4', - thumb : '//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, - api : 1 - }, - paramPlace : 3, - type : 'iframe', - url : '//player.vimeo.com/video/$2' - }, - - metacafe : { - matcher : /metacafe.com\/watch\/(\d+)\/(.*)?/, - type : 'iframe', - url : '//www.metacafe.com/embed/$1/?ap=1' - }, - - dailymotion : { - matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, - params : { - additionalInfos : 0, - autoStart : 1 - }, - type : 'iframe', - url : '//www.dailymotion.com/embed/video/$1' - }, - - vine : { - matcher : /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/, - type : 'iframe', - url : '//vine.co/v/$1/embed/simple' - }, - - 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 - // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 - // https://www.google.lv/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 - google_maps : { - 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] ) + '&output=' + ( rez[12] && rez[12].indexOf('layer=c') > 0 ? 'svembed' : 'embed' ); - } - } - }; - - $(document).on('onInit.fb', function (e, instance) { - - $.each(instance.group, function( i, item ) { - - var url = item.src || '', - type = false, - media, - thumb, - rez, - params, - urlParams, - o, - provider; - - // Skip items that already have content type - if ( item.type ) { - return; - } - - media = $.extend( true, {}, defaults, item.opts.media ); - - // Look for any matching media type - $.each(media, function ( n, el ) { - rez = url.match(el.matcher); - o = {}; - provider = n; - - if (!rez) { - return; - } - - type = el.type; - - if ( el.paramPlace && rez[ el.paramPlace ] ) { - urlParams = rez[ el.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 ) { - o[ p[0] ] = decodeURIComponent( p[1].replace(/\+/g, " ") ); - } - } - } - - params = $.extend( true, {}, el.params, item.opts[ n ], o ); - - url = $.type(el.url) === "function" ? el.url.call(this, rez, params, item) : format(el.url, rez, params); - thumb = $.type(el.thumb) === "function" ? el.thumb.call(this, rez, params, item) : format(el.thumb, rez); - - if ( provider === 'vimeo' ) { - url = url.replace('&%23', '#'); - } - - return false; - }); - - // If it is found, then change content type and update the url - - if ( type ) { - item.src = url; - item.type = type; - - if ( !item.opts.thumb && !( item.opts.$thumb && item.opts.$thumb.length ) ) { - item.opts.thumb = thumb; - } - - if ( type === 'iframe' ) { - $.extend(true, item.opts, { - iframe : { - preload : false, - attr : { - scrolling : "no" - } - } - }); - - item.contentProvider = provider; - - item.opts.slideClass += ' fancybox-slide--' + ( provider == 'google_maps' ? 'map' : 'video' ); - } - - } else { - - // If no content type is found, then set it to `image` as fallback - item.type = 'image'; - } - - }); + // 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 + }; -}(window.jQuery)); + // Check if we need to animate opacity + opacity = current.opts.zoomOpacity; -// ========================================================================== -// -// 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 pointers = 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,button,input,select,textarea') || $.isFunction( $el.get(0).onclick ) ) { - 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; - } + if (opacity == "auto") { + opacity = Math.abs(current.width / current.height - end.width / end.height) > 0.1; } - return false; - }; - - var hasScrollbars = function( el ) { - var overflowY = window.getComputedStyle( el )['overflow-y']; - var overflowX = window.getComputedStyle( el )['overflow-x']; - - var vertical = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight; - var 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() { - this.$container.off( '.fb.touch' ); - }; - - Guestures.prototype.ontouchstart = function( e ) { - var self = this; - - var $target = $( e.target ); - var instance = self.instance; - var current = instance.current; - var $content = current.$content; - - var isTouchDevice = ( e.type == 'touchstart' ); - - // Do not respond to both events - if ( isTouchDevice ) { - self.$container.off( 'mousedown.fb.touch' ); - } - - // Ignore clicks while zooming or closing - if ( !current || self.instance.isAnimating || self.instance.isClosing ) { - e.stopPropagation(); - e.preventDefault(); - - return; - } - - // Ignore right click - if ( e.originalEvent && e.originalEvent.button == 2 ) { - return; - } - - // Ignore taping on links, buttons, input elements - if ( !$target.length || isClickable( $target ) || isClickable( $target.parent() ) ) { - return; - } - - // Ignore clicks on the scrollbar - if ( e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left ) { - return; - } - - self.startPoints = pointers( e ); - - // Prevent zooming if already swiping - if ( !self.startPoints || ( self.startPoints.length > 1 && instance.isSliding ) ) { - return; - } - - self.$target = $target; - self.$content = $content; - self.canTap = true; - - $(document).off( '.fb.touch' ); - - $(document).on( isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch', $.proxy(self, "ontouchend")); - $(document).on( isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch', $.proxy(self, "ontouchmove")); - - e.stopPropagation(); - - if ( !(instance.current.opts.touch || instance.canPan() ) || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) { - - // Prevent ghosting - if ( $target.is('img') ) { - e.preventDefault(); - } - - return; - } - - if ( !( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) ) { - e.preventDefault(); - } - - self.canvasWidth = Math.round( current.$slide[0].clientWidth ); - self.canvasHeight = Math.round( current.$slide[0].clientHeight ); - - self.startTime = new Date().getTime(); - self.distanceX = self.distanceY = self.distance = 0; - - self.isPanning = false; - self.isSwiping = false; - self.isZooming = false; - - self.sliderStartPos = self.sliderLastPos || { top: 0, left: 0 }; - self.contentStartPos = $.fancybox.getTranslate( self.$content ); - self.contentLastPos = null; - - if ( self.startPoints.length === 1 && !self.isZooming ) { - self.canTap = !instance.isSliding; - - if ( current.type === 'image' && ( self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1 ) ) { - - $.fancybox.stop( self.$content ); - - self.$content.css( 'transition-duration', '0ms' ); - - self.isPanning = true; - - } else { - - self.isSwiping = true; - } - - self.$container.addClass('fancybox-controls--isGrabbing'); - } - - if ( self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && ( current.isLoaded || current.$ghost ) ) { - self.isZooming = true; - - self.isSwiping = false; - self.isPanning = false; - - $.fancybox.stop( self.$content ); - - self.$content.css( 'transition-duration', '0ms' ); - - 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.ontouchmove = function( e ) { - - var self = this; - - self.newPoints = pointers( e ); - - if ( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) { - e.stopPropagation(); - - self.canTap = false; - - return; - } - - if ( !( self.instance.current.opts.touch || self.instance.canPan() ) || !self.newPoints || !self.newPoints.length ) { - return; - } - - 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.$target.is( self.$stage ) || self.$stage.find( self.$target ).length ) ) { - return; - } - - e.stopPropagation(); - e.preventDefault(); - - if ( self.isSwiping ) { - self.onSwipe(); - - } else if ( self.isPanning ) { - self.onPan(); - - } else if ( self.isZooming ) { - self.onZoom(); - } - - } + if (opacity) { + end.opacity = 0; + } - }; + $.fancybox.setTranslate($content, start); - Guestures.prototype.onSwipe = function() { + forceRedraw($content); - var self = this; + $.fancybox.animate($content, end, duration, done); - var swiping = self.isSwiping; - var left = self.sliderStartPos.left || 0; - var angle; + return true; + } - if ( swiping === 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(); + } + } - if ( Math.abs( self.distance ) > 10 ) { + return true; + }, - self.canTap = false; + // Final adjustments after removing the instance + // ============================================= - if ( self.instance.group.length < 2 && self.instance.opts.touch.vertical ) { - self.isSwiping = 'y'; + cleanUp: function (e) { + var self = this, + instance, + $focus = self.current.opts.$orig, + x, + y; - } else if ( self.instance.isSliding || self.instance.opts.touch.vertical === false || ( self.instance.opts.touch.vertical === 'auto' && $( window ).width() > 800 ) ) { - self.isSwiping = 'x'; + self.current.$slide.trigger("onReset"); - } else { - angle = Math.abs( Math.atan2( self.distanceY, self.distanceX ) * 180 / Math.PI ); + self.$refs.container.empty().remove(); - self.isSwiping = ( angle > 45 && angle < 135 ) ? 'y' : 'x'; - } + self.trigger("afterClose", e); - self.instance.isSliding = self.isSwiping; + // Place back focus + if (!!self.current.opts.backFocus) { + if (!$focus || !$focus.length || !$focus.is(":visible")) { + $focus = self.$trigger; + } - // Reset points to avoid jumping, because we dropped first swipes to calculate the angle - self.startPoints = self.newPoints; + if ($focus && $focus.length) { + x = window.scrollX; + y = window.scrollY; - $.each(self.instance.slides, function( index, slide ) { - $.fancybox.stop( slide.$slide ); + $focus.trigger("focus"); - slide.$slide.css( 'transition-duration', '0ms' ); + $("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 + // ======================== - slide.inTransition = false; + 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); + } - if ( slide.pos === self.instance.current.pos ) { - self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide ).left; - } - }); + return instance; + } - //self.instance.current.isMoved = true; + return false; + }, - // Stop slideshow - if ( self.instance.SlideShow && self.instance.SlideShow.isActive ) { - self.instance.SlideShow.stop(); - } - } + // Create new instance + // =================== - } else { + open: function (items, opts, index) { + return new FancyBox(items, opts, index); + }, - if ( swiping == 'x' ) { + // Close current or all instances + // ============================== - // Sticky edges - 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 ); + close: function (all) { + var instance = this.getInstance(); - } 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 ); + if (instance) { + instance.close(); - } else { - left = left + self.distanceX; - } + // Try to find and close next instance + if (all === true) { + this.close(all); + } + } + }, - } + // Close all instances and unbind all events + // ========================================= - self.sliderLastPos = { - top : swiping == 'x' ? 0 : self.sliderStartPos.top + self.distanceY, - left : left - }; + destroy: function () { + this.close(true); - if ( self.requestId ) { - cancelAFrame( self.requestId ); + $D.add("body").off("click.fb-start", "**"); + }, - self.requestId = null; - } + // Try to detect mobile devices + // ============================ - self.requestId = requestAFrame(function() { + isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), - if ( self.sliderLastPos ) { - $.each(self.instance.slides, function( index, slide ) { - var pos = slide.pos - self.instance.currPos; + // Detect if 'translate3d' support is available + // ============================================ - $.fancybox.setTranslate( slide.$slide, { - top : self.sliderLastPos.top, - left : self.sliderLastPos.left + ( pos * self.canvasWidth ) + ( pos * slide.opts.gutter ) - }); - }); + use3d: (function () { + var div = document.createElement("div"); - self.$container.addClass( 'fancybox-is-sliding' ); - } + 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; + } - Guestures.prototype.onPan = function() { + domRect = $el[0].getBoundingClientRect(); - var self = this; + return { + top: domRect.top || 0, + left: domRect.left || 0, + width: domRect.width, + height: domRect.height, + opacity: parseFloat($el.css("opacity")) + }; + }, - var newOffsetX, newOffsetY, newPos; + // Shortcut for setting "translate3d" properties for element + // Can set be used to set opacity, too + // ======================================================== - self.canTap = false; + setTranslate: function ($el, props) { + var str = "", + css = {}; - if ( self.contentStartPos.width > self.canvasWidth ) { - newOffsetX = self.contentStartPos.left + self.distanceX; + if (!$el || !props) { + return; + } - } else { - newOffsetX = self.contentStartPos.left; - } + 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"; - newOffsetY = self.contentStartPos.top + self.distanceY; + if (this.use3d) { + str = "translate3d(" + str + ", 0px)"; + } else { + str = "translate(" + str + ")"; + } + } - newPos = self.limitMovement( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); + if (props.scaleX !== undefined && props.scaleY !== undefined) { + str += " scale(" + props.scaleX + ", " + props.scaleY + ")"; + } else if (props.scaleX !== undefined) { + str += " scaleX(" + props.scaleX + ")"; + } - newPos.scaleX = self.contentStartPos.scaleX; - newPos.scaleY = self.contentStartPos.scaleY; + if (str.length) { + css.transform = str; + } - self.contentLastPos = newPos; + if (props.opacity !== undefined) { + css.opacity = props.opacity; + } - if ( self.requestId ) { - cancelAFrame( self.requestId ); + if (props.width !== undefined) { + css.width = props.width; + } - self.requestId = null; - } + if (props.height !== undefined) { + css.height = props.height; + } - self.requestId = requestAFrame(function() { - $.fancybox.setTranslate( self.$content, self.contentLastPos ); - }); - }; + return $el.css(css); + }, - // Make panning sticky to the edges - Guestures.prototype.limitMovement = function( newOffsetX, newOffsetY, newWidth, newHeight ) { + // Simple CSS transition handler + // ============================= - var self = this; + animate: function ($el, to, duration, callback, leaveAnimationName) { + var self = this, + from; - var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY; + if ($.isFunction(duration)) { + callback = duration; + duration = null; + } - var canvasWidth = self.canvasWidth; - var canvasHeight = self.canvasHeight; + self.stop($el); - var currentOffsetX = self.contentStartPos.left; - var currentOffsetY = self.contentStartPos.top; + from = self.getTranslate($el); - var distanceX = self.distanceX; - var distanceY = self.distanceY; + $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; + } - // Slow down proportionally to traveled distance + self.stop($el); - minTranslateX = Math.max(0, canvasWidth * 0.5 - newWidth * 0.5 ); - minTranslateY = Math.max(0, canvasHeight * 0.5 - newHeight * 0.5 ); + if ($.isNumeric(duration)) { + $el.css("transition-duration", ""); + } - maxTranslateX = Math.min( canvasWidth - newWidth, canvasWidth * 0.5 - newWidth * 0.5 ); - maxTranslateY = Math.min( canvasHeight - newHeight, canvasHeight * 0.5 - newHeight * 0.5 ); + 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 ( newWidth > canvasWidth ) { + if ($.isFunction(callback)) { + callback(e); + } + }); - // -> - if ( distanceX > 0 && newOffsetX > minTranslateX ) { - newOffsetX = minTranslateX - 1 + Math.pow( -minTranslateX + currentOffsetX + distanceX, 0.8 ) || 0; - } + if ($.isNumeric(duration)) { + $el.css("transition-duration", duration + "ms"); + } - // <- - if ( distanceX < 0 && newOffsetX < maxTranslateX ) { - newOffsetX = maxTranslateX + 1 - Math.pow( maxTranslateX - currentOffsetX - distanceX, 0.8 ) || 0; - } + // 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"); + } + } - if ( newHeight > canvasHeight ) { + $.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); + } - // \/ - if ( distanceY > 0 && newOffsetY > minTranslateY ) { - newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8 ) || 0; - } + $el.off(transitionEnd).css("transition-duration", ""); - // /\ - if ( distanceY < 0 && newOffsetY < maxTranslateY ) { - newOffsetY = maxTranslateY + 1 - Math.pow ( maxTranslateY - currentOffsetY - distanceY, 0.8 ) || 0; - } + $el.parent().removeClass("fancybox-is-scaling"); + } + } + }; - } + // Default click handler for "fancyboxed" links + // ============================================ - return { - top : newOffsetY, - left : newOffsetX - }; + function _run(e, opts) { + var items = [], + index = 0, + $target, + value, + instance; - }; + // Avoid opening multiple times + if (e && e.isDefaultPrevented()) { + return; + } + e.preventDefault(); - Guestures.prototype.limitPosition = function( newOffsetX, newOffsetY, newWidth, newHeight ) { + opts = opts || {}; - var self = this; + if (e && e.data) { + opts = mergeOpts(e.data.options, opts); + } - var canvasWidth = self.canvasWidth; - var canvasHeight = self.canvasHeight; + $target = opts.$target || $(e.currentTarget).trigger("blur"); + instance = $.fancybox.getInstance(); - if ( newWidth > canvasWidth ) { - newOffsetX = newOffsetX > 0 ? 0 : newOffsetX; - newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX; + if (instance && instance.$trigger && instance.$trigger.is($target)) { + return; + } - } else { + 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]; + } + } - // Center horizontally - newOffsetX = Math.max( 0, canvasWidth / 2 - newWidth / 2 ); + index = $(items).index($target); - } + // Sometimes current item can not be found + if (index < 0) { + index = 0; + } - if ( newHeight > canvasHeight ) { - newOffsetY = newOffsetY > 0 ? 0 : newOffsetY; - newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY; + 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 + ); + } - } else { + 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"; + } + } + }; - // Center vertically - newOffsetY = Math.max( 0, canvasHeight / 2 - newHeight / 2 ); + // Formats matching url to final form + var format = function (url, rez, params) { + if (!url) { + return; + } - } + params = params || ""; - return { - top : newOffsetY, - left : newOffsetX - }; + if ($.type(params) === "object") { + params = $.param(params, true); + } - }; + $.each(rez, function (key, value) { + url = url.replace("$" + key, value || ""); + }); - Guestures.prototype.onZoom = function() { + if (params.length) { + url += (url.indexOf("?") > 0 ? "&" : "?") + params; + } - var self = this; + return url; + }; - // Calculate current distance between points to get pinch ratio and new width and height + $(document).on("objectNeedsType.fb", function (e, instance, item) { + var url = item.src || "", + type = false, + media, + thumb, + rez, + params, + urlParams, + paramObj, + provider; - var currentWidth = self.contentStartPos.width; - var currentHeight = self.contentStartPos.height; + media = $.extend(true, {}, defaults, item.opts.media); - var currentOffsetX = self.contentStartPos.left; - var currentOffsetY = self.contentStartPos.top; + // Look for any matching media type + $.each(media, function (providerName, providerOpts) { + rez = url.match(providerOpts.matcher); - var endDistanceBetweenFingers = distance( self.newPoints[0], self.newPoints[1] ); + if (!rez) { + return; + } - var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; + type = providerOpts.type; + provider = providerName; + paramObj = {}; - var newWidth = Math.floor( currentWidth * pinchRatio ); - var newHeight = Math.floor( currentHeight * pinchRatio ); + if (providerOpts.paramPlace && rez[providerOpts.paramPlace]) { + urlParams = rez[providerOpts.paramPlace]; - // This is the translation due to pinch-zooming - var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; - var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; + if (urlParams[0] == "?") { + urlParams = urlParams.substring(1); + } - //Point between the two touches + urlParams = urlParams.split("&"); - 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(); + for (var m = 0; m < urlParams.length; ++m) { + var p = urlParams[m].split("=", 2); - // And this is the translation due to translation of the centerpoint - // between the two fingers + if (p.length == 2) { + paramObj[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); + } + } + } - var translateFromTranslatingX = centerPointEndX - self.centerPointStartX; - var translateFromTranslatingY = centerPointEndY - self.centerPointStartY; + params = $.extend(true, {}, providerOpts.params, item.opts[providerName], paramObj); - // The new offset is the old/current one plus the total translation + url = + $.type(providerOpts.url) === "function" ? providerOpts.url.call(this, rez, params, item) : format(providerOpts.url, rez, params); - var newOffsetX = currentOffsetX + ( translateFromZoomingX + translateFromTranslatingX ); - var newOffsetY = currentOffsetY + ( translateFromZoomingY + translateFromTranslatingY ); + thumb = + $.type(providerOpts.thumb) === "function" ? providerOpts.thumb.call(this, rez, params, item) : format(providerOpts.thumb, rez); - var newPos = { - top : newOffsetY, - left : newOffsetX, - scaleX : self.contentStartPos.scaleX * pinchRatio, - scaleY : self.contentStartPos.scaleY * pinchRatio - }; + 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", "#"); + } - self.canTap = false; + return false; + }); - self.newWidth = newWidth; - self.newHeight = newHeight; + // If it is found, then change content type and update the url - self.contentLastPos = newPos; + if (type) { + if (!item.opts.thumb && !(item.opts.$thumb && item.opts.$thumb.length)) { + item.opts.thumb = thumb; + } - if ( self.requestId ) { - cancelAFrame( self.requestId ); + 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; + } - self.requestId = null; - } + if (this[vendor].loading) { + return; + } - self.requestId = requestAFrame(function() { - $.fancybox.setTranslate( self.$content, self.contentLastPos ); - }); + this[vendor].loading = true; - }; + script = document.createElement("script"); + script.type = "text/javascript"; + script.src = this[vendor].src; - Guestures.prototype.ontouchend = function( e ) { + if (vendor === "youtube") { + window.onYouTubeIframeAPIReady = function () { + _this[vendor].loaded = true; + _this.done(vendor); + }; + } else { + script.onload = function () { + _this[vendor].loaded = true; + _this.done(vendor); + }; + } - var self = this; - var dMs = Math.max( (new Date().getTime() ) - self.startTime, 1); + document.body.appendChild(script); + }, + done: function (vendor) { + var instance, $el, player; - var swiping = self.isSwiping; - var panning = self.isPanning; - var zooming = self.isZooming; + if (vendor === "youtube") { + delete window.onYouTubeIframeAPIReady; + } - self.endPoints = pointers( e ); + instance = $.fancybox.getInstance(); - self.$container.removeClass( 'fancybox-controls--isGrabbing' ); + if (instance) { + $el = instance.current.$content.find("iframe"); - $(document).off( '.fb.touch' ); + 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); - if ( self.requestId ) { - cancelAFrame( self.requestId ); + player.on("ended", function () { + instance.next(); + }); + } + } + } + }; - self.requestId = null; - } + $(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 + }); + } + } - self.isSwiping = false; - self.isPanning = false; - self.isZooming = false; + return result; + }; - if ( self.canTap ) { - return self.onTap( e ); - } + var distance = function (point2, point1, what) { + if (!point1 || !point2) { + return 0; + } - self.speed = 366; + if (what === "x") { + return point2.x - point1.x; + } else if (what === "y") { + return point2.y - point1.y; + } - // Speed in px/ms - self.velocityX = self.distanceX / dMs * 0.5; - self.velocityY = self.distanceY / dMs * 0.5; + return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)); + }; - self.speedX = Math.max( self.speed * 0.5, Math.min( self.speed * 1.5, ( 1 / Math.abs( self.velocityX ) ) * self.speed ) ); + 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; + } - if ( panning ) { - self.endPanning(); + // 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; + } + } - } else if ( zooming ) { - self.endZooming(); + return false; + }; - } else { - self.endSwiping( swiping ); - } + 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; - }; + return vertical || horizontal; + }; - Guestures.prototype.endSwiping = function( swiping ) { + var isScrollable = function ($el) { + var rez = false; - var self = this; - var ret = false; + while (true) { + rez = hasScrollbars($el.get(0)); - self.instance.isSliding = false; - self.sliderLastPos = null; + if (rez) { + break; + } - // Close if swiped vertically / navigate if horizontally - if ( swiping == 'y' && Math.abs( self.distanceY ) > 50 ) { + $el = $el.parent(); - // Continue vertical movement - $.fancybox.animate( self.instance.current.$slide, { - top : self.sliderStartPos.top + self.distanceY + ( self.velocityY * 150 ), - opacity : 0 - }, 150 ); + if (!$el.length || $el.hasClass("fancybox-stage") || $el.is("body")) { + break; + } + } - ret = self.instance.close( true, 300 ); + return rez; + }; - } else if ( swiping == 'x' && self.distanceX > 50 && self.instance.group.length > 1 ) { - ret = self.instance.previous( self.speedX ); + var Guestures = function (instance) { + var self = this; - } else if ( swiping == 'x' && self.distanceX < -50 && self.instance.group.length > 1 ) { - ret = self.instance.next( self.speedX ); - } + self.instance = instance; - if ( ret === false && ( swiping == 'x' || swiping == 'y' ) ) { - self.instance.jumpTo( self.instance.current.index, 150 ); - } + self.$bg = instance.$refs.bg; + self.$stage = instance.$refs.stage; + self.$container = instance.$refs.container; - self.$container.removeClass( 'fancybox-is-sliding' ); + self.destroy(); - }; + self.$container.on("touchstart.fb.touch mousedown.fb.touch", $.proxy(self, "ontouchstart")); + }; - // Limit panning from edges - // ======================== + Guestures.prototype.destroy = function () { + var self = this; - Guestures.prototype.endPanning = function() { + self.$container.off(".fb.touch"); - var self = this; - var newOffsetX, newOffsetY, newPos; + $(document).off(".fb.touch"); - if ( !self.contentLastPos ) { - return; - } + if (self.requestId) { + cancelAFrame(self.requestId); + self.requestId = null; + } - if ( self.instance.current.opts.touch.momentum === false ) { - newOffsetX = self.contentLastPos.left; - newOffsetY = self.contentLastPos.top; + 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"); + } - } else { + // Ignore right click + if (e.originalEvent && e.originalEvent.button == 2) { + return; + } - // Continue movement - newOffsetX = self.contentLastPos.left + ( self.velocityX * self.speed ); - newOffsetY = self.contentLastPos.top + ( self.velocityY * self.speed ); - } + // 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; + } - newPos = self.limitPosition( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); + // Ignore clicks while zooming or closing + if (!current || instance.isAnimating || current.$slide.hasClass("fancybox-animated")) { + e.stopPropagation(); + e.preventDefault(); - newPos.width = self.contentStartPos.width; - newPos.height = self.contentStartPos.height; + return; + } - $.fancybox.animate( self.$content, newPos, 330 ); - }; + self.realPoints = self.startPoints = getPointerXY(e); + if (!self.startPoints.length) { + return; + } - Guestures.prototype.endZooming = function() { + // Allow other scripts to catch touch event if "touch" is set to false + if (current.touch) { + e.stopPropagation(); + } - var self = this; + self.startEvent = e; - var current = self.instance.current; + self.canTap = true; + self.$target = $target; + self.$content = $content; + self.opts = current.opts.touch; - var newOffsetX, newOffsetY, newPos, reset; + self.isPanning = false; + self.isSwiping = false; + self.isZooming = false; + self.isScrolling = false; + self.canPan = instance.canPan(); - var newWidth = self.newWidth; - var newHeight = self.newHeight; + self.startTime = new Date().getTime(); + self.distanceX = self.distanceY = self.distance = 0; - if ( !self.contentLastPos ) { - return; - } + self.canvasWidth = Math.round($slide[0].clientWidth); + self.canvasHeight = Math.round($slide[0].clientHeight); - newOffsetX = self.contentLastPos.left; - newOffsetY = self.contentLastPos.top; + self.contentLastPos = null; + self.contentStartPos = $.fancybox.getTranslate(self.$content) || { + top: 0, + left: 0 + }; + self.sliderStartPos = $.fancybox.getTranslate($slide); - reset = { - top : newOffsetY, - left : newOffsetX, - width : newWidth, - height : newHeight, - scaleX : 1, - scaleY : 1 - }; + // Since position will be absolute, but we need to make it relative to the stage + self.stagePos = $.fancybox.getTranslate(instance.$refs.stage); - // Reset scalex/scaleY values; this helps for perfomance and does not break animation - $.fancybox.setTranslate( self.$content, reset ); + self.sliderStartPos.top -= self.stagePos.top; + self.sliderStartPos.left -= self.stagePos.left; - if ( newWidth < self.canvasWidth && newHeight < self.canvasHeight ) { - self.instance.scaleToFit( 150 ); + self.contentStartPos.top -= self.stagePos.top; + self.contentStartPos.left -= self.stagePos.left; - } else if ( newWidth > current.width || newHeight > current.height ) { - self.instance.scaleToActual( self.centerPointStartX, self.centerPointStartY, 150 ); + $(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")); - } else { + if ($.fancybox.isMobile) { + document.addEventListener("scroll", self.onscroll, true); + } - newPos = self.limitPosition( newOffsetX, newOffsetY, newWidth, newHeight ); + // 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(); + } - // Switch from scale() to width/height or animation will not work correctly - $.fancybox.setTranslate( self.content, $.fancybox.getTranslate( self.$content ) ); + if (!($.fancybox.isMobile && $target.parents(".fancybox-caption").length)) { + return; + } + } - $.fancybox.animate( self.$content, newPos, 150 ); - } + 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(); + } - Guestures.prototype.onTap = function(e) { - var self = this; - var $target = $( e.target ); + // One finger or mouse click - swipe or pan an image + if (self.startPoints.length === 1 || current.hasError) { + if (self.canPan) { + $.fancybox.stop(self.$content); - var instance = self.instance; - var current = instance.current; + self.isPanning = true; + } else { + self.isSwiping = true; + } - var endPoints = ( e && pointers( e ) ) || self.startPoints; + self.$container.addClass("fancybox-is-grabbing"); + } - var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0; - var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top : 0; + // 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; - var where; + self.isZooming = true; - var process = function ( prefix ) { + $.fancybox.stop(self.$content); - var action = current.opts[ prefix ]; + 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(); - if ( $.isFunction( action ) ) { - action = action.apply( instance, [ current, e ] ); - } + self.percentageOfImageAtPinchPointX = (self.centerPointStartX - self.contentStartPos.left) / self.contentStartPos.width; + self.percentageOfImageAtPinchPointY = (self.centerPointStartY - self.contentStartPos.top) / self.contentStartPos.height; - if ( !action) { - return; - } + self.startDistanceBetweenFingers = distance(self.startPoints[0], self.startPoints[1]); + } + }; - switch ( action ) { + Guestures.prototype.onscroll = function (e) { + var self = this; - case "close" : + self.isScrolling = true; - instance.close( self.startEvent ); + document.removeEventListener("scroll", self.onscroll, true); + }; - break; + Guestures.prototype.ontouchmove = function (e) { + var self = this; - case "toggleControls" : + // Make sure user has not released over iframe or disabled element + if (e.originalEvent.buttons !== undefined && e.originalEvent.buttons === 0) { + self.ontouchend(e); + return; + } - instance.toggleControls( true ); + if (self.isScrolling) { + self.canTap = false; + return; + } - break; + self.newPoints = getPointerXY(e); - case "next" : + if (!(self.opts || self.canPan) || !self.newPoints.length || !self.newPoints.length) { + return; + } - instance.next(); + if (!(self.isSwiping && self.isSwiping === true)) { + e.preventDefault(); + } - break; + self.distanceX = distance(self.newPoints[0], self.startPoints[0], "x"); + self.distanceY = distance(self.newPoints[0], self.startPoints[0], "y"); - case "nextOrClose" : + self.distance = distance(self.newPoints[0], self.startPoints[0]); - if ( instance.group.length > 1 ) { - instance.next(); + // 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); - } else { - instance.close( self.startEvent ); - } + self.isSwiping = angle > 45 && angle < 135 ? "y" : "x"; + } - break; + if (self.isSwiping === "y" && $.fancybox.isMobile && self.isScrollable) { + self.isScrolling = true; - case "zoom" : + return; + } - if ( current.type == 'image' && ( current.isLoaded || current.$ghost ) ) { + instance.isDragging = self.isSwiping; - if ( instance.canPan() ) { - instance.scaleToFit(); + // Reset points to avoid jumping, because we dropped first swipes to calculate the angle + self.startPoints = self.newPoints; - } else if ( instance.isScaledDown() ) { - instance.scaleToActual( tapX, tapY ); + $.each(instance.slides, function (index, slide) { + var slidePos, stagePos; - } else if ( instance.group.length < 2 ) { - instance.close( self.startEvent ); - } - } + $.fancybox.stop(slide.$slide); - break; - } + 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(" "); + }); - // Ignore right click - if ( e.originalEvent && e.originalEvent.button == 2 ) { - return; - } + if (slide.pos === instance.current.pos) { + self.sliderStartPos.top = slidePos.top - stagePos.top; + self.sliderStartPos.left = slidePos.left - stagePos.left; + } - // Skip if current slide is not in the center - if ( instance.isSliding ) { - return; - } + $.fancybox.setTranslate(slide.$slide, { + top: slidePos.top - stagePos.top, + left: slidePos.left - stagePos.left + }); + }); - // Skip if clicked on the scrollbar - if ( tapX > $target[0].clientWidth + $target.offset().left ) { - return; - } + // Stop slideshow + if (instance.SlideShow && instance.SlideShow.isActive) { + instance.SlideShow.stop(); + } + } - // Check where is clicked - if ( $target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container' ) ) { - where = 'Outside'; + return; + } - } else if ( $target.is( '.fancybox-slide' ) ) { - where = 'Slide'; + // 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; + } + } - } else if ( instance.current.$content && instance.current.$content.has( e.target ).length ) { - where = 'Content'; + self.sliderLastPos = { + top: swiping == "x" ? 0 : self.sliderStartPos.top + self.distanceY, + left: left + }; - } else { - return; - } + if (self.requestId) { + cancelAFrame(self.requestId); - // Check if this is a double tap - if ( self.tapped ) { + self.requestId = null; + } - // Stop previously created single tap - clearTimeout( self.tapped ); - self.tapped = null; + self.requestId = requestAFrame(function () { + if (self.sliderLastPos) { + $.each(self.instance.slides, function (index, slide) { + var pos = slide.pos - self.instance.currPos; - // Skip if distance between taps is too big - if ( Math.abs( tapX - self.tapX ) > 50 || Math.abs( tapY - self.tapY ) > 50 || instance.isSliding ) { - return this; - } + $.fancybox.setTranslate(slide.$slide, { + top: self.sliderLastPos.top, + left: self.sliderLastPos.left + pos * self.canvasWidth + pos * slide.opts.gutter + }); + }); - // OK, now we assume that this is a double-tap - process( 'dblclick' + where ); + self.$container.addClass("fancybox-is-sliding"); + } + }); + }; - } else { + Guestures.prototype.onPan = function () { + var self = this; - // 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; + // 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; + } - if ( current.opts[ 'dblclick' + where ] && current.opts[ 'dblclick' + where ] !== current.opts[ 'click' + where ] ) { - self.tapped = setTimeout(function() { - self.tapped = null; + self.canTap = false; - process( 'click' + where ); + self.contentLastPos = self.limitMovement(); - }, 300); + if (self.requestId) { + cancelAFrame(self.requestId); + } - } else { - process( 'click' + where ); - } + self.requestId = requestAFrame(function () { + $.fancybox.setTranslate(self.$content, self.contentLastPos); + }); + }; - } + // Make panning sticky to the edges + Guestures.prototype.limitMovement = function () { + var self = this; - return this; - }; + var canvasWidth = self.canvasWidth; + var canvasHeight = self.canvasHeight; - $(document).on('onActivate.fb', function (e, instance) { - if ( instance && !instance.Guestures ) { - instance.Guestures = new Guestures( instance ); - } - }); + var distanceX = self.distanceX; + var distanceY = self.distanceY; - $(document).on('beforeClose.fb', function (e, instance) { - if ( instance && instance.Guestures ) { - instance.Guestures.destroy(); - } - }); + var contentStartPos = self.contentStartPos; + var currentOffsetX = contentStartPos.left; + var currentOffsetY = contentStartPos.top; -}(window, document, window.jQuery)); + var currentWidth = contentStartPos.width; + var currentHeight = contentStartPos.height; -// ========================================================================== -// -// SlideShow -// Enables slideshow functionality -// -// Example of usage: -// $.fancybox.getInstance().SlideShow.start() -// -// ========================================================================== -;(function (document, $) { - 'use strict'; + var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY, newOffsetX, newOffsetY; - var SlideShow = function( instance ) { - this.instance = instance; - this.init(); - }; + if (currentWidth > canvasWidth) { + newOffsetX = currentOffsetX + distanceX; + } else { + newOffsetX = currentOffsetX; + } - $.extend( SlideShow.prototype, { - timer : null, - isActive : false, - $button : null, - speed : 3000, + newOffsetY = currentOffsetY + distanceY; - init : function() { - var self = this; + // 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); - self.$button = self.instance.$refs.toolbar.find('[data-fancybox-play]').on('click', function() { - self.toggle(); - }); + maxTranslateX = Math.min(canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5); + maxTranslateY = Math.min(canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5); - if ( self.instance.group.length < 2 || !self.instance.group[ self.instance.currIndex ].opts.slideShow ) { - self.$button.hide(); - } - }, + // -> + if (distanceX > 0 && newOffsetX > minTranslateX) { + newOffsetX = minTranslateX - 1 + Math.pow(-minTranslateX + currentOffsetX + distanceX, 0.8) || 0; + } - set : function() { - var self = this; + // <- + if (distanceX < 0 && newOffsetX < maxTranslateX) { + newOffsetX = maxTranslateX + 1 - Math.pow(maxTranslateX - currentOffsetX - distanceX, 0.8) || 0; + } - // Check if reached last element - if ( self.instance && self.instance.current && (self.instance.current.opts.loop || self.instance.currIndex < self.instance.group.length - 1 )) { - self.timer = setTimeout(function() { - self.instance.next(); + // \/ + if (distanceY > 0 && newOffsetY > minTranslateY) { + newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8) || 0; + } - }, self.instance.current.opts.slideShow.speed || self.speed); + // /\ + if (distanceY < 0 && newOffsetY < maxTranslateY) { + newOffsetY = maxTranslateY + 1 - Math.pow(maxTranslateY - currentOffsetY - distanceY, 0.8) || 0; + } - } else { - self.stop(); - self.instance.idleSecondsCounter = 0; - self.instance.showControls(); - } + return { + top: newOffsetY, + left: newOffsetX + }; + }; - }, + Guestures.prototype.limitPosition = function (newOffsetX, newOffsetY, newWidth, newHeight) { + var self = this; - clear : function() { - var self = this; + var canvasWidth = self.canvasWidth; + var canvasHeight = self.canvasHeight; - clearTimeout( self.timer ); + 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); + } - self.timer = null; - }, + 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); + } - start : function() { - var self = this; - var current = self.instance.current; + return { + top: newOffsetY, + left: newOffsetX + }; + }; - if ( self.instance && current && ( current.opts.loop || current.index < self.instance.group.length - 1 )) { + Guestures.prototype.onZoom = function () { + var self = this; - self.isActive = true; + // Calculate current distance between points to get pinch ratio and new width and height + var contentStartPos = self.contentStartPos; - self.$button - .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_STOP ) - .addClass( 'fancybox-button--pause' ); + var currentWidth = contentStartPos.width; + var currentHeight = contentStartPos.height; - if ( current.isComplete ) { - self.set(); - } - } - }, + var currentOffsetX = contentStartPos.left; + var currentOffsetY = contentStartPos.top; - stop : function() { - var self = this; - var current = self.instance.current; + var endDistanceBetweenFingers = distance(self.newPoints[0], self.newPoints[1]); - self.clear(); + var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; - self.$button - .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_START ) - .removeClass( 'fancybox-button--pause' ); + var newWidth = Math.floor(currentWidth * pinchRatio); + var newHeight = Math.floor(currentHeight * pinchRatio); - self.isActive = false; - }, + // This is the translation due to pinch-zooming + var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; + var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; - toggle : function() { - var self = this; + // 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(); - if ( self.isActive ) { - self.stop(); + // 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; - } else { - self.start(); - } - } + // 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 + }; - $(document).on({ - 'onInit.fb' : function(e, instance) { - if ( instance && !instance.SlideShow ) { - instance.SlideShow = new SlideShow( instance ); - } - }, + self.canTap = false; - 'beforeShow.fb' : function(e, instance, current, firstRun) { - var SlideShow = instance && instance.SlideShow; + self.newWidth = newWidth; + self.newHeight = newHeight; - if ( firstRun ) { + self.contentLastPos = newPos; - if ( SlideShow && current.opts.slideShow.autoStart ) { - SlideShow.start(); - } + if (self.requestId) { + cancelAFrame(self.requestId); + } - } else if ( SlideShow && SlideShow.isActive ) { - SlideShow.clear(); - } - }, + self.requestId = requestAFrame(function () { + $.fancybox.setTranslate(self.$content, self.contentLastPos); + }); + }; - 'afterShow.fb' : function(e, instance, current) { - var SlideShow = instance && instance.SlideShow; + Guestures.prototype.ontouchend = function (e) { + var self = this; - if ( SlideShow && SlideShow.isActive ) { - SlideShow.set(); - } - }, + var swiping = self.isSwiping; + var panning = self.isPanning; + var zooming = self.isZooming; + var scrolling = self.isScrolling; - 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { - var SlideShow = instance && instance.SlideShow; + self.endPoints = getPointerXY(e); + self.dMs = Math.max(new Date().getTime() - self.startTime, 1); - // "P" or Spacebar - if ( SlideShow && current.opts.slideShow && ( keycode === 80 || keycode === 32 ) && !$(document.activeElement).is( 'button,a,input' ) ) { - keypress.preventDefault(); + self.$container.removeClass("fancybox-is-grabbing"); - SlideShow.toggle(); - } - }, + $(document).off(".fb.touch"); - 'beforeClose.fb onDeactivate.fb' : function(e, instance) { - var SlideShow = instance && instance.SlideShow; + document.removeEventListener("scroll", self.onscroll, true); - if ( SlideShow ) { - SlideShow.stop(); - } - } - }); + if (self.requestId) { + cancelAFrame(self.requestId); - // Page Visibility API to pause slideshow when window is not active - $(document).on("visibilitychange", function() { - var instance = $.fancybox.getInstance(); - var SlideShow = instance && instance.SlideShow; + self.requestId = null; + } - if ( SlideShow && SlideShow.isActive ) { - if ( document.hidden ) { - SlideShow.clear(); + self.isSwiping = false; + self.isPanning = false; + self.isZooming = false; + self.isScrolling = false; - } else { - SlideShow.set(); - } - } - }); + self.instance.isDragging = false; -}(document, window.jQuery)); + if (self.canTap) { + return self.onTap(e); + } -// ========================================================================== -// -// 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' + self.speed = 100; - ], - [ - 'mozRequestFullScreen', - 'mozCancelFullScreen', - 'mozFullScreenElement', - 'mozFullScreenEnabled', - 'mozfullscreenchange', - 'mozfullscreenerror' - ], - [ - 'msRequestFullscreen', - 'msExitFullscreen', - 'msFullscreenElement', - 'msFullscreenEnabled', - 'MSFullscreenChange', - 'MSFullscreenError' - ] - ]; + // Speed in px/ms + self.velocityX = (self.distanceX / self.dMs) * 0.5; + self.velocityY = (self.distanceY / self.dMs) * 0.5; - var val; - var ret = {}; - var i, j; + if (panning) { + self.endPanning(); + } else if (zooming) { + self.endZooming(); + } else { + self.endSwiping(swiping, scrolling); + } - for ( i = 0; i < fnMap.length; i++ ) { - val = fnMap[ i ]; + 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 ( val && val[ 1 ] in document ) { - for ( j = 0; j < val.length; j++ ) { - ret[ fnMap[ 0 ][ j ] ] = val[ j ]; - } + if (ret === false && (swiping == "x" || swiping == "y")) { + self.instance.centerSlide(200); + } - return ret; - } - } + self.$container.removeClass("fancybox-is-sliding"); + }; - return false; - })(); + // Limit panning from edges + // ======================== + Guestures.prototype.endPanning = function () { + var self = this, + newOffsetX, + newOffsetY, + newPos; - // If browser does not have Full Screen API, then simply unset default button template and stop - if ( !fn ) { - $.fancybox.defaults.btnTpl.fullScreen = false; + if (!self.contentLastPos) { + return; + } - 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; + } - var FullScreen = { - request : function ( elem ) { + newPos = self.limitPosition(newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height); - elem = elem || document.documentElement; + newPos.width = self.contentStartPos.width; + newPos.height = self.contentStartPos.height; - elem[ fn.requestFullscreen ]( elem.ALLOW_KEYBOARD_INPUT ); + $.fancybox.animate(self.$content, newPos, 366); + }; - }, - exit : function () { + Guestures.prototype.endZooming = function () { + var self = this; - document[ fn.exitFullscreen ](); + var current = self.instance.current; - }, - toggle : function ( elem ) { + var newOffsetX, newOffsetY, newPos, reset; - elem = elem || document.documentElement; + var newWidth = self.newWidth; + var newHeight = self.newHeight; - if ( this.isFullscreen() ) { - this.exit(); + if (!self.contentLastPos) { + return; + } - } else { - this.request( elem ); - } + newOffsetX = self.contentLastPos.left; + newOffsetY = self.contentLastPos.top; - }, - isFullscreen : function() { + reset = { + top: newOffsetY, + left: newOffsetX, + width: newWidth, + height: newHeight, + scaleX: 1, + scaleY: 1 + }; - return Boolean( document[ fn.fullscreenElement ] ); + // Reset scalex/scaleY values; this helps for perfomance and does not break animation + $.fancybox.setTranslate(self.$content, reset); - }, - enabled : function() { + 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); - return Boolean( document[ fn.fullscreenEnabled ] ); + $.fancybox.animate(self.$content, newPos, 150); + } + }; - } - }; + Guestures.prototype.onTap = function (e) { + var self = this; + var $target = $(e.target); - $(document).on({ - 'onInit.fb' : function(e, instance) { - var $container; + var instance = self.instance; + var current = instance.current; - var $button = instance.$refs.toolbar.find('[data-fancybox-fullscreen]'); + var endPoints = (e && getPointerXY(e)) || self.startPoints; - if ( instance && !instance.FullScreen && instance.group[ instance.currIndex ].opts.fullScreen ) { - $container = instance.$refs.container; + 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; - $container.on('click.fb-fullscreen', '[data-fancybox-fullscreen]', function(e) { + var where; - e.stopPropagation(); - e.preventDefault(); + var process = function (prefix) { + var action = current.opts[prefix]; - FullScreen.toggle( $container[ 0 ] ); + if ($.isFunction(action)) { + action = action.apply(instance, [current, e]); + } - }); + if (!action) { + return; + } - if ( instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true ) { - FullScreen.request( $container[ 0 ] ); - } + switch (action) { + case "close": + instance.close(self.startEvent); - // Expose API - instance.FullScreen = FullScreen; + break; - } else { - $button.hide(); - } + case "toggleControls": + instance.toggleControls(); - }, + break; - 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { + case "next": + instance.next(); - // "P" or Spacebar - if ( instance && instance.FullScreen && keycode === 70 ) { - keypress.preventDefault(); + break; - instance.FullScreen.toggle( instance.$refs.container[ 0 ] ); - } + case "nextOrClose": + if (instance.group.length > 1) { + instance.next(); + } else { + instance.close(self.startEvent); + } - }, + break; - 'beforeClose.fb' : function( instance ) { - if ( instance && instance.FullScreen ) { - FullScreen.exit(); - } - } - }); + 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); + } + } - $(document).on(fn.fullscreenchange, function() { - var instance = $.fancybox.getInstance(); + break; + } + }; - // If image is zooming, then force to stop and reposition properly - if ( instance.current && instance.current.type === 'image' && instance.isAnimating ) { - instance.current.$content.css( 'transition', 'none' ); + // Ignore right click + if (e.originalEvent && e.originalEvent.button == 2) { + return; + } - instance.isAnimating = false; + // Skip if clicked on the scrollbar + if (!$target.is("img") && tapX > $target[0].clientWidth + $target.offset().left) { + return; + } - instance.update( true, true, 0 ); - } + // 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; -}(document, window.jQuery)); + // 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); // ========================================================================== // -// Thumbs -// Displays thumbnails in a grid +// SlideShow +// Enables slideshow functionality +// +// Example of usage: +// $.fancybox.getInstance().SlideShow.start() // // ========================================================================== -;(function (document, $) { - 'use strict'; +(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(); + } + }, - var FancyThumbs = function( instance ) { - this.instance = instance; - this.init(); - }; + clear: function () { + var self = this; - $.extend( FancyThumbs.prototype, { + clearTimeout(self.timer); - $button : null, - $grid : null, - $list : null, - isVisible : false, + self.timer = null; - init : function() { - var self = this; + if (self.$progress) { + self.$progress.removeAttr("style").hide(); + } + }, - var first = self.instance.group[0], - second = self.instance.group[1]; + start: function () { + var self = this, + current = self.instance.current; - self.$button = self.instance.$refs.toolbar.find( '[data-fancybox-thumbs]' ); + 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"); - if ( self.instance.group.length > 1 && self.instance.group[ self.instance.currIndex ].opts.thumbs && ( - ( first.type == 'image' || first.opts.thumb || first.opts.$thumb ) && - ( second.type == 'image' || second.opts.thumb || second.opts.$thumb ) - )) { + self.isActive = true; - self.$button.on('click', function() { - self.toggle(); - }); + if (current.isComplete) { + self.set(true); + } - self.isActive = true; + self.instance.trigger("onSlideShowChange", true); + } + }, - } else { - self.$button.hide(); + stop: function () { + var self = this, + current = self.instance.current; - self.isActive = false; - } + 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"); - create : function() { - var instance = this.instance, - list, - src; + self.isActive = false; - this.$grid = $('
').appendTo( instance.$refs.container ); + self.instance.trigger("onSlideShowChange", false); - list = '
    '; + if (self.$progress) { + self.$progress.removeAttr("style").hide(); + } + }, - $.each(instance.group, function( i, item ) { + toggle: function () { + var self = this; - src = item.opts.thumb || ( item.opts.$thumb ? item.opts.$thumb.attr('src') : null ); + if (self.isActive) { + self.stop(); + } else { + self.start(); + } + } + }); - if ( !src && item.type === 'image' ) { - src = item.src; - } + $(document).on({ + "onInit.fb": function (e, instance) { + if (instance && !instance.SlideShow) { + instance.SlideShow = new SlideShow(instance); + } + }, - if ( src && src.length ) { - list += '
  • '; - } + "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(); + } + }, - list += '
'; + "afterShow.fb": function (e, instance, current) { + var SlideShow = instance && instance.SlideShow; - this.$list = $( list ).appendTo( this.$grid ).on('click', 'li', function() { - instance.jumpTo( $(this).data('index') ); - }); + if (SlideShow && SlideShow.isActive) { + SlideShow.set(); + } + }, - this.$list.find('img').hide().one('load', function() { + "afterKeydown.fb": function (e, instance, current, keypress, keycode) { + var SlideShow = instance && instance.SlideShow; - var $parent = $(this).parent().removeClass('fancybox-thumbs-loading'), - thumbWidth = $parent.outerWidth(), - thumbHeight = $parent.outerHeight(), - width, - height, - widthRatio, - heightRatio; + // "P" or Spacebar + if (SlideShow && current.opts.slideShow && (keycode === 80 || keycode === 32) && !$(document.activeElement).is("button,a,input")) { + keypress.preventDefault(); - width = this.naturalWidth || this.width; - height = this.naturalHeight || this.height; + SlideShow.toggle(); + } + }, - //Calculate thumbnail width/height and center it + "beforeClose.fb onDeactivate.fb": function (e, instance) { + var SlideShow = instance && instance.SlideShow; - widthRatio = width / thumbWidth; - heightRatio = height / thumbHeight; + 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]; + } - if (widthRatio >= 1 && heightRatio >= 1) { - if (widthRatio > heightRatio) { - width = width / heightRatio; - height = thumbHeight; + return ret; + } + } - } else { - width = thumbWidth; - height = height / widthRatio; - } - } + return false; + })(); - $(this).css({ - width : Math.floor(width), - height : Math.floor(height), - 'margin-top' : Math.min( 0, Math.floor(thumbHeight * 0.3 - height * 0.3 ) ), - 'margin-left' : Math.min( 0, Math.floor(thumbWidth * 0.5 - width * 0.5 ) ) - }).show(); + if (fn) { + var FullScreen = { + request: function (elem) { + elem = elem || document.documentElement; - }) - .each(function() { - this.src = $( this ).data( 'src' ); - }); + 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]); + } + }; - focus : function() { + $.extend(true, $.fancybox.defaults, { + btnTpl: { + fullScreen: '" + }, + fullScreen: { + autoStart: false + } + }); - if ( this.instance.current ) { - this.$list - .children() - .removeClass('fancybox-thumbs-active') - .filter('[data-index="' + this.instance.current.index + '"]') - .addClass('fancybox-thumbs-active') - .focus(); - } + $(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; - close : function() { - this.$grid.hide(); - }, + instance.update(true, true, 0); - update : function() { + if (!instance.isComplete) { + instance.complete(); + } + } - this.instance.$refs.container.toggleClass( 'fancybox-show-thumbs', this.isVisible ); + instance.trigger("onFullscreenChange", isFullscreen); - if ( this.isVisible ) { + instance.$refs.container.toggleClass("fancybox-is-fullscreen", isFullscreen); - if ( !this.$grid ) { - this.create(); - } + instance.$refs.toolbar + .find("[data-fancybox-fullscreen]") + .toggleClass("fancybox-button--fsenter", !isFullscreen) + .toggleClass("fancybox-button--fsexit", isFullscreen); + } + }); + } - this.instance.trigger( 'onThumbsShow' ); + $(document).on({ + "onInit.fb": function (e, instance) { + var $container; - this.focus(); + if (!fn) { + instance.$refs.toolbar.find("[data-fancybox-fullscreen]").remove(); - } else if ( this.$grid ) { - this.instance.trigger( 'onThumbsHide' ); - } + return; + } - // Update content position - this.instance.update(); + 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(); - hide : function() { - this.isVisible = false; - this.update(); - }, + FullScreen.toggle(); + }); - show : function() { - this.isVisible = true; - this.update(); - }, + if (instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true) { + FullScreen.request(); + } - toggle : function() { - this.isVisible = !this.isVisible; - this.update(); - } + // 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; + } - $(document).on({ + 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(); + } - 'onInit.fb' : function(e, instance) { - if ( instance && !instance.Thumbs ) { - instance.Thumbs = new FancyThumbs( instance ); - } - }, + that.instance.trigger("onThumbsShow"); - 'beforeShow.fb' : function(e, instance, item, firstRun) { - var Thumbs = instance && instance.Thumbs; + that.focus(0); + } else if (that.$grid) { + that.instance.trigger("onThumbsHide"); + } - if ( !Thumbs || !Thumbs.isActive ) { - return; - } + // Update content position + that.instance.update(); + }, - if ( item.modal ) { - Thumbs.$button.hide(); + hide: function () { + this.isVisible = false; + this.update(); + }, - Thumbs.hide(); + show: function () { + this.isVisible = true; + this.update(); + }, - return; - } + toggle: function () { + this.isVisible = !this.isVisible; + this.update(); + } + }); - if ( firstRun && instance.opts.thumbs.autoStart === true ) { - Thumbs.show(); - } + $(document).on({ + "onInit.fb": function (e, instance) { + var Thumbs; - if ( Thumbs.isVisible ) { - Thumbs.focus(); - } - }, + if (instance && !instance.Thumbs) { + Thumbs = new FancyThumbs(instance); - 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { - var Thumbs = instance && instance.Thumbs; + if (Thumbs.isActive && Thumbs.opts.autoStart === true) { + Thumbs.show(); + } + } + }, - // "G" - if ( Thumbs && Thumbs.isActive && keycode === 71 ) { - keypress.preventDefault(); + "beforeShow.fb": function (e, instance, item, firstRun) { + var Thumbs = instance && instance.Thumbs; - Thumbs.toggle(); - } - }, + if (Thumbs && Thumbs.isVisible) { + Thumbs.focus(firstRun ? 0 : 250); + } + }, - 'beforeClose.fb' : function( e, instance ) { - var Thumbs = instance && instance.Thumbs; + "afterKeydown.fb": function (e, instance, current, keypress, keycode) { + var Thumbs = instance && instance.Thumbs; - if ( Thumbs && Thumbs.isVisible && instance.opts.thumbs.hideOnClose !== false ) { - Thumbs.close(); - } - } + // "G" + if (Thumbs && Thumbs.isActive && keycode === 71) { + keypress.preventDefault(); - }); + Thumbs.toggle(); + } + }, -}(document, window.jQuery)); + "beforeClose.fb": function (e, instance) { + var Thumbs = instance && instance.Thumbs; -// ========================================================================== + if (Thumbs && Thumbs.isVisible && Thumbs.opts.hideOnClose !== false) { + Thumbs.$grid.hide(); + } + } + }); +})(document, jQuery); +//// ========================================================================== // -// Hash -// Enables linking to each modal +// Share +// Displays simple form for sharing current url // // ========================================================================== -;(function (document, window, $) { - '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 ); - }; - } - - // Variable containing last hash value set by fancyBox - // It will be used to determine if fancyBox needs to close after hash change is detected - var currentHash = null; - - // Throtlling the history change - var timerID = null; - - // Get info about gallery name and current index from url - function parseUrl() { - var hash = window.location.hash.substr( 1 ); - var rez = hash.split( '-' ); - var index = rez.length > 1 && /^\+?\d+$/.test( rez[ rez.length - 1 ] ) ? parseInt( rez.pop( -1 ), 10 ) || 1 : 1; - var gallery = rez.join( '-' ); - - // Index is starting from 1 - if ( index < 1 ) { - index = 1; - } - - return { - hash : hash, - index : index, - gallery : gallery - }; +(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 = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "/": "/", + "`": "`", + "=": "=" + }; - // Trigger click evnt on links to open new fancyBox instance - function triggerFromUrl( url ) { - var $el; - - if ( url.gallery !== '' ) { - - // If we can find element matching 'data-fancybox' atribute, then trigger click event for that .. - $el = $( "[data-fancybox='" + $.escapeSelector( url.gallery ) + "']" ).eq( url.index - 1 ); - - if ( $el.length ) { - $el.trigger( 'click' ); + return String(string).replace(/[&<>"'`=\/]/g, function (s) { + return entityMap[s]; + }); + } - } else { + $(document).on("click", "[data-fancybox-share]", function () { + var instance = $.fancybox.getInstance(), + current = instance.current || null, + url, + tpl; - // .. if not, try finding element by ID - $( "#" + $.escapeSelector( url.gallery ) + "" ).trigger( 'click' ); + 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) + " "; } - } - - // Get gallery name from current instance - function getGallery( instance ) { - var opts; - - if ( !instance ) { - return false; - } - - opts = instance.current ? instance.current.opts : instance.opts; - - return opts.$orig ? opts.$orig.data( 'fancybox' ) : ( opts.hash || '' ); - } - - // Star when DOM becomes ready - $(function() { - - // Small delay is used to allow other scripts to process "dom ready" event - setTimeout(function() { - // Check if this module is not disabled - if ( $.fancybox.defaults.hash === false ) { - return; - } + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }; - // Update hash when opening/closing fancyBox - $(document).on({ - 'onInit.fb' : function( e, instance ) { - var url, gallery; + 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"); + } + } - if ( instance.group[ instance.currIndex ].opts.hash === false ) { - return; - } + // Get gallery name from current instance + function getGalleryID(instance) { + var opts, ret; - url = parseUrl(); - gallery = getGallery( instance ); + if (!instance) { + return false; + } - // Make sure gallery start index matches index from hash - if ( gallery && url.gallery && gallery == url.gallery ) { - instance.currIndex = url.index - 1; - } + 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; + } - 'beforeShow.fb' : function( e, instance, current, firstRun ) { - var gallery; + // Start when DOM becomes ready + $(function () { + // Check if user has disabled this module + if ($.fancybox.defaults.hash === false) { + return; + } - if ( current.opts.hash === false ) { - return; - } + // Update hash when opening/closing fancyBox + $(document).on({ + "onInit.fb": function (e, instance) { + var url, gallery; - gallery = getGallery( instance ); + if (instance.group[instance.currIndex].opts.hash === false) { + return; + } - // Update window hash - if ( gallery && gallery !== '' ) { + url = parseUrl(); + gallery = getGalleryID(instance); - if ( window.location.hash.indexOf( gallery ) < 0 ) { - instance.opts.origHash = window.location.hash; - } + // Make sure gallery start index matches index from hash + if (gallery && url.gallery && gallery == url.gallery) { + instance.currIndex = url.index - 1; + } + }, - currentHash = gallery + ( instance.group.length > 1 ? '-' + ( current.index + 1 ) : '' ); + "beforeShow.fb": function (e, instance, current, firstRun) { + var gallery; - if ( 'replaceState' in window.history ) { - if ( timerID ) { - clearTimeout( timerID ); - } + if (!current || current.opts.hash === false) { + return; + } - timerID = setTimeout(function() { - window.history[ firstRun ? 'pushState' : 'replaceState' ]( {} , document.title, window.location.pathname + window.location.search + '#' + currentHash ); + // Check if need to update window hash + gallery = getGalleryID(instance); - timerID = null; + if (!gallery) { + return; + } - }, 300); + // 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) : ""); - } else { - window.location.hash = currentHash; - } + // 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); + } - 'beforeClose.fb' : function( e, instance, current ) { - var gallery, origHash; + // 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 ( timerID ) { - clearTimeout( timerID ); - } + if (firstRun) { + instance.hasCreatedHistory = true; + } + } else { + window.location.hash = instance.currentHash; + } - if ( current.opts.hash === false ) { - return; - } + instance.hashTimer = null; + }, 300); + }, - gallery = getGallery( instance ); - origHash = instance && instance.opts.origHash ? instance.opts.origHash : ''; + "beforeClose.fb": function (e, instance, current) { + if (!current || current.opts.hash === false) { + return; + } - // Remove hash from location bar - if ( gallery && gallery !== '' ) { + 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; + } + } - if ( 'replaceState' in history ) { - window.history.replaceState( {} , document.title, window.location.pathname + window.location.search + origHash ); + instance.currentHash = null; + } + }); - } else { - window.location.hash = origHash; + // 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; + } + } + ); - // Keep original scroll position - $( window ).scrollTop( instance.scrollTop ).scrollLeft( instance.scrollLeft ); - } - } + if (fb) { + // Now, compare hash values + if (fb.currentHash !== url.gallery + "-" + url.index && !(url.index === 1 && fb.currentHash == url.gallery)) { + fb.currentHash = null; - currentHash = null; - } - }); + fb.close(); + } + } else if (url.gallery !== "") { + triggerFromUrl(url); + } + }); - // Check if need to close after url has changed - $(window).on('hashchange.fb', function() { - var url = parseUrl(); + // 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"; - if ( $.fancybox.getInstance() ) { - if ( currentHash && currentHash !== url.gallery + '-' + url.index && !( url.index === 1 && currentHash == url.gallery ) ) { - currentHash = null; + var prevTime = new Date().getTime(); - $.fancybox.close(); - } + $(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(); - } else if ( url.gallery !== '' ) { - triggerFromUrl( url ); - } - }); + if (instance.group.length < 2 || current.opts.wheel === false || (current.opts.wheel === "auto" && current.type !== "image")) { + return; + } - // If navigating away from current page - $(window).one('unload.fb popstate.fb', function() { - $.fancybox.getInstance( 'close', true, 0 ); - }); + e.preventDefault(); + e.stopPropagation(); - // Check current hash and trigger click event on matching element to start fancyBox, if needed - triggerFromUrl( parseUrl() ); + if (current.$slide.hasClass("fancybox-animated")) { + return; + } - }, 50); + e = e.originalEvent || e; - }); + if (currTime - prevTime < 250) { + return; + } + prevTime = currTime; -}(document, window, window.jQuery)); + instance[(-e.deltaY || -e.deltaX || e.wheelDelta || -e.detail) < 0 ? "next" : "previous"](); + }); + } + }); +})(document, jQuery); \ No newline at end of file diff --git a/dist/jquery.fancybox.min.css b/dist/jquery.fancybox.min.css index ab39c7a4..7cc60b29 100644 --- a/dist/jquery.fancybox.min.css +++ b/dist/jquery.fancybox.min.css @@ -1 +1 @@ -@charset "UTF-8";.fancybox-enabled{overflow:hidden}.fancybox-enabled body{overflow:visible;height:100%}.fancybox-is-hidden{position:absolute;top:-9999px;left:-9999px;visibility:hidden}.fancybox-container{position:fixed;top:0;left:0;width:100%;height:100%;z-index:99993;-webkit-tap-highlight-color:transparent;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0)}.fancybox-container~.fancybox-container{z-index:99992}.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-stage{position:absolute;top:0;right:0;bottom:0;left:0}.fancybox-outer{overflow-y:auto;-webkit-overflow-scrolling:touch}.fancybox-bg{background:#1e1e1e;opacity:0;transition-duration:inherit;transition-property:opacity;transition-timing-function:cubic-bezier(.47,0,.74,.71)}.fancybox-is-open .fancybox-bg{opacity:.87;transition-timing-function:cubic-bezier(.22,.61,.36,1)}.fancybox-caption-wrap,.fancybox-infobar,.fancybox-toolbar{position:absolute;direction:ltr;z-index:99997;opacity:0;visibility:hidden;transition:opacity .25s,visibility 0s linear .25s;box-sizing:border-box}.fancybox-show-caption .fancybox-caption-wrap,.fancybox-show-infobar .fancybox-infobar,.fancybox-show-toolbar .fancybox-toolbar{opacity:1;visibility:visible;transition:opacity .25s,visibility 0s}.fancybox-infobar{top:0;left:50%;margin-left:-79px}.fancybox-infobar__body{display:inline-block;width:70px;line-height:44px;font-size:13px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;text-align:center;color:#ddd;background-color:rgba(30,30,30,.7);pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-webkit-font-smoothing:subpixel-antialiased}.fancybox-toolbar{top:0;right:0}.fancybox-stage{overflow:hidden;direction:ltr;z-index:99994;-webkit-transform:translateZ(0)}.fancybox-slide{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;overflow:auto;outline:none;white-space:normal;box-sizing:border-box;text-align:center;z-index:99994;-webkit-overflow-scrolling:touch;display:none;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;-webkit-transform-style:preserve-3d;transform-style:preserve-3d}.fancybox-slide:before{content:"";display:inline-block;vertical-align:middle;height:100%;width:0}.fancybox-is-sliding .fancybox-slide,.fancybox-slide--current,.fancybox-slide--next,.fancybox-slide--previous{display:block}.fancybox-slide--image{overflow:visible}.fancybox-slide--image:before{display:none}.fancybox-slide--video .fancybox-content,.fancybox-slide--video iframe{background:#000}.fancybox-slide--map .fancybox-content,.fancybox-slide--map iframe{background:#e5e3df}.fancybox-slide--next{z-index:99995}.fancybox-slide>*{display:inline-block;position:relative;padding:24px;margin:44px 0;border-width:0;vertical-align:middle;text-align:left;background-color:#fff;overflow:auto;box-sizing:border-box}.fancybox-slide .fancybox-image-wrap{position:absolute;top:0;left:0;margin:0;padding:0;border:0;z-index:99995;background:transparent;cursor:default;overflow:visible;-webkit-transform-origin:top left;transform-origin:top left;background-size:100% 100%;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden}.fancybox-can-zoomOut .fancybox-image-wrap{cursor:zoom-out}.fancybox-can-zoomIn .fancybox-image-wrap{cursor:zoom-in}.fancybox-can-drag .fancybox-image-wrap{cursor:-webkit-grab;cursor:grab}.fancybox-is-dragging .fancybox-image-wrap{cursor:-webkit-grabbing;cursor:grabbing}.fancybox-image,.fancybox-spaceball{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;border:0;max-width:none;max-height:none}.fancybox-spaceball{z-index:1}.fancybox-slide--iframe .fancybox-content{padding:0;width:80%;height:80%;max-width:calc(100% - 100px);max-height:calc(100% - 88px);overflow:visible;background:#fff}.fancybox-iframe{display:block;padding:0;border:0;height:100%}.fancybox-error,.fancybox-iframe{margin:0;width:100%;background:#fff}.fancybox-error{padding:40px;max-width:380px;cursor:default}.fancybox-error p{margin:0;padding:0;color:#444;font:16px/20px Helvetica Neue,Helvetica,Arial,sans-serif}.fancybox-close-small{position:absolute;top:0;right:0;width:44px;height:44px;padding:0;margin:0;border:0;border-radius:0;outline:none;background:transparent;z-index:10;cursor:pointer}.fancybox-close-small:after{content:"×";position:absolute;top:5px;right:5px;width:30px;height:30px;font:20px/30px Arial,Helvetica Neue,Helvetica,sans-serif;color:#888;font-weight:300;text-align:center;border-radius:50%;border-width:0;background:#fff;transition:background .25s;box-sizing:border-box;z-index:2}.fancybox-close-small:focus:after{outline:1px dotted #888}.fancybox-close-small:hover:after{color:#555;background:#eee}.fancybox-slide--iframe .fancybox-close-small{top:0;right:-44px}.fancybox-slide--iframe .fancybox-close-small:after{background:transparent;font-size:35px;color:#aaa}.fancybox-slide--iframe .fancybox-close-small:hover:after{color:#fff}.fancybox-caption-wrap{bottom:0;left:0;right:0;padding:60px 30px 0;background:linear-gradient(180deg,transparent 0,rgba(0,0,0,.1) 20%,rgba(0,0,0,.2) 40%,rgba(0,0,0,.6) 80%,rgba(0,0,0,.8));pointer-events:none}.fancybox-caption{padding:30px 0;border-top:1px solid hsla(0,0%,100%,.4);font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;color:#fff;line-height:20px;-webkit-text-size-adjust:none}.fancybox-caption a,.fancybox-caption button,.fancybox-caption select{pointer-events:all}.fancybox-caption a{color:#fff;text-decoration:underline}.fancybox-button{display:inline-block;position:relative;margin:0;padding:0;border:0;width:44px;height:44px;line-height:44px;text-align:center;background:transparent;color:#ddd;border-radius:0;cursor:pointer;vertical-align:top;outline:none}.fancybox-button[disabled]{cursor:default;pointer-events:none}.fancybox-button,.fancybox-infobar__body{background:rgba(30,30,30,.6)}.fancybox-button:hover:not([disabled]){color:#fff;background:rgba(0,0,0,.8)}.fancybox-button:after,.fancybox-button:before{content:"";pointer-events:none;position:absolute;background-color:currentColor;color:currentColor;opacity:.9;box-sizing:border-box;display:inline-block}.fancybox-button[disabled]:after,.fancybox-button[disabled]:before{opacity:.3}.fancybox-button--left:after,.fancybox-button--right:after{top:18px;width:6px;height:6px;background:transparent;border-top:2px solid currentColor;border-right:2px solid currentColor}.fancybox-button--left:after{left:20px;-webkit-transform:rotate(-135deg);transform:rotate(-135deg)}.fancybox-button--right:after{right:20px;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.fancybox-button--left{border-bottom-left-radius:5px}.fancybox-button--right{border-bottom-right-radius:5px}.fancybox-button--close:after,.fancybox-button--close:before{content:"";display:inline-block;position:absolute;height:2px;width:16px;top:calc(50% - 1px);left:calc(50% - 8px)}.fancybox-button--close:before{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.fancybox-button--close:after{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.fancybox-arrow{position:absolute;top:50%;margin:-50px 0 0;height:100px;width:54px;padding:0;border:0;outline:none;background:none;cursor:pointer;z-index:99995;opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;transition:opacity .25s}.fancybox-arrow:after{content:"";position:absolute;top:28px;width:44px;height:44px;background-color:rgba(30,30,30,.8);background-image:url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjQ4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSI0OCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgNGwtMS40MSAxLjQxTDE2LjE3IDExSDR2MmgxMi4xN2wtNS41OCA1LjU5TDEyIDIwbDgtOHoiLz48L3N2Zz4=);background-repeat:no-repeat;background-position:50%;background-size:24px 24px}.fancybox-arrow--right{right:0}.fancybox-arrow--left{left:0;-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fancybox-arrow--left:after,.fancybox-arrow--right:after{left:0}.fancybox-show-nav .fancybox-arrow{opacity:.6}.fancybox-show-nav .fancybox-arrow[disabled]{opacity:.3}.fancybox-loading{border:6px solid hsla(0,0%,39%,.4);border-top:6px solid hsla(0,0%,100%,.6);border-radius:100%;height:50px;width:50px;-webkit-animation:a .8s infinite linear;animation:a .8s infinite linear;background:transparent;position:absolute;top:50%;left:50%;margin-top:-25px;margin-left:-25px;z-index:99999}@-webkit-keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fancybox-animated{transition-timing-function:cubic-bezier(0,0,.25,1)}.fancybox-fx-slide.fancybox-slide--previous{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);opacity:0}.fancybox-fx-slide.fancybox-slide--next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);opacity:0}.fancybox-fx-slide.fancybox-slide--current{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}.fancybox-fx-fade.fancybox-slide--next,.fancybox-fx-fade.fancybox-slide--previous{opacity:0;transition-timing-function:cubic-bezier(.19,1,.22,1)}.fancybox-fx-fade.fancybox-slide--current{opacity:1}.fancybox-fx-zoom-in-out.fancybox-slide--previous{-webkit-transform:scale3d(1.5,1.5,1.5);transform:scale3d(1.5,1.5,1.5);opacity:0}.fancybox-fx-zoom-in-out.fancybox-slide--next{-webkit-transform:scale3d(.5,.5,.5);transform:scale3d(.5,.5,.5);opacity:0}.fancybox-fx-zoom-in-out.fancybox-slide--current{-webkit-transform:scaleX(1);transform:scaleX(1);opacity:1}.fancybox-fx-rotate.fancybox-slide--previous{-webkit-transform:rotate(-1turn);transform:rotate(-1turn);opacity:0}.fancybox-fx-rotate.fancybox-slide--next{-webkit-transform:rotate(1turn);transform:rotate(1turn);opacity:0}.fancybox-fx-rotate.fancybox-slide--current{-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}.fancybox-fx-circular.fancybox-slide--previous{-webkit-transform:scale3d(0,0,0) translate3d(-100%,0,0);transform:scale3d(0,0,0) translate3d(-100%,0,0);opacity:0}.fancybox-fx-circular.fancybox-slide--next{-webkit-transform:scale3d(0,0,0) translate3d(100%,0,0);transform:scale3d(0,0,0) translate3d(100%,0,0);opacity:0}.fancybox-fx-circular.fancybox-slide--current{-webkit-transform:scaleX(1) translateZ(0);transform:scaleX(1) translateZ(0);opacity:1}.fancybox-fx-tube.fancybox-slide--previous{-webkit-transform:translate3d(-100%,0,0) scale(.1) skew(-10deg);transform:translate3d(-100%,0,0) scale(.1) skew(-10deg)}.fancybox-fx-tube.fancybox-slide--next{-webkit-transform:translate3d(100%,0,0) scale(.1) skew(10deg);transform:translate3d(100%,0,0) scale(.1) skew(10deg)}.fancybox-fx-tube.fancybox-slide--current{-webkit-transform:translateZ(0) scale(1);transform:translateZ(0) scale(1)}@media (max-width:800px){.fancybox-infobar{left:0;margin-left:0}.fancybox-button--left,.fancybox-button--right{display:none!important}.fancybox-caption{padding:20px 0;margin:0}}.fancybox-button--fullscreen:before{width:15px;height:11px;left:calc(50% - 7px);top:calc(50% - 6px);border:2px solid;background:none}.fancybox-button--pause:before,.fancybox-button--play: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}.fancybox-button--thumbs,.fancybox-thumbs{display:none}@media (min-width:800px){.fancybox-button--thumbs{display:inline-block}.fancybox-button--thumbs span{font-size:23px}.fancybox-button--thumbs:before{width:3px;height:3px;top:calc(50% - 2px);left:calc(50% - 2px);box-shadow:0 -4px 0,-4px -4px 0,4px -4px 0,inset 0 0 0 32px,-4px 0 0,4px 0 0,0 4px 0,-4px 4px 0,4px 4px 0}.fancybox-thumbs{position:absolute;top:0;right:0;bottom:0;left:auto;width:220px;margin:0;padding:5px 5px 0 0;background:#fff;word-break:normal;-webkit-tap-highlight-color:transparent;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;box-sizing:border-box;z-index:99995}.fancybox-show-thumbs .fancybox-thumbs{display:block}.fancybox-show-thumbs .fancybox-inner{right:220px}.fancybox-thumbs>ul{list-style:none;position:absolute;position:relative;width:100%;height:100%;margin:0;padding:0;overflow-x:hidden;overflow-y:auto;font-size:0}.fancybox-thumbs>ul>li{float:left;overflow:hidden;max-width:50%;padding:0;margin:0;width:105px;height:75px;position:relative;cursor:pointer;outline:none;border:5px solid transparent;border-top-width:0;border-right-width:0;-webkit-tap-highlight-color:transparent;-webkit-backface-visibility:hidden;backface-visibility:hidden;box-sizing:border-box}li.fancybox-thumbs-loading{background:rgba(0,0,0,.1)}.fancybox-thumbs>ul>li>img{position:absolute;top:0;left:0;min-width:100%;min-height:100%;max-width:none;max-height:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fancybox-thumbs>ul>li:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:2px;border:4px solid #4ea7f9;z-index:99991;opacity:0;transition:all .2s cubic-bezier(.25,.46,.45,.94)}.fancybox-thumbs>ul>li.fancybox-thumbs-active:before{opacity:1}} \ No newline at end of file +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-bg,.fancybox-inner,.fancybox-outer,.fancybox-stage{bottom:0;left:0;position:absolute;right:0;top:0}.fancybox-outer{-webkit-overflow-scrolling:touch;overflow-y:auto}.fancybox-bg{background:#1e1e1e;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-caption,.fancybox-infobar,.fancybox-navigation .fancybox-button,.fancybox-toolbar{direction:ltr;opacity:0;position:absolute;transition:opacity .25s ease,visibility 0s ease .25s;visibility:hidden;z-index:99997}.fancybox-show-caption .fancybox-caption,.fancybox-show-infobar .fancybox-infobar,.fancybox-show-nav .fancybox-navigation .fancybox-button,.fancybox-show-toolbar .fancybox-toolbar{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;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--current,.fancybox-slide--next,.fancybox-slide--previous{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;-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-pan .fancybox-content,.fancybox-can-swipe .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;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.fancybox-spaceball{z-index:1}.fancybox-slide--iframe .fancybox-content,.fancybox-slide--map .fancybox-content,.fancybox-slide--pdf .fancybox-content,.fancybox-slide--video .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-iframe,.fancybox-video{background:transparent;border:0;display:block;height:100%;margin:0;overflow:hidden;padding:0;width:100%}.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}.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:link,.fancybox-button:visited{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}.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--fsenter svg:nth-child(2),.fancybox-button--fsexit svg:first-child,.fancybox-button--pause svg:first-child,.fancybox-button--play svg:nth-child(2){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}.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}.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)}.fancybox-caption{background:linear-gradient(0deg,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%,transparent);bottom:0;color:#eee;font-size:14px;font-weight:400;left:0;line-height:1.5;padding:75px 44px 25px;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}.fancybox-loading{animation:a 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 a{to{transform:rotate(1turn)}}.fancybox-animated{transition-timing-function:cubic-bezier(0,0,.25,1)}.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:translateZ(0)}.fancybox-fx-fade.fancybox-slide--next,.fancybox-fx-fade.fancybox-slide--previous{opacity:0;transition-timing-function:cubic-bezier(.19,1,.22,1)}.fancybox-fx-fade.fancybox-slide--current{opacity:1}.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:scaleX(1)}.fancybox-fx-rotate.fancybox-slide--previous{opacity:0;transform:rotate(-1turn)}.fancybox-fx-rotate.fancybox-slide--next{opacity:0;transform:rotate(1turn)}.fancybox-fx-rotate.fancybox-slide--current{opacity:1;transform:rotate(0deg)}.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:scaleX(1) translateZ(0)}.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:translateZ(0) scale(1)}@media (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))}}}.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}.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;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:link,.fancybox-share__button:visited{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;outline:none;padding:10px 15px;width:100%}.fancybox-thumbs{background:#ddd;bottom:0;display:none;margin:0;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;padding:2px 2px 4px;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:50%;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}@media (max-width:576px){.fancybox-thumbs{width:110px}.fancybox-show-thumbs .fancybox-inner{right:110px}.fancybox-thumbs__list a{max-width:calc(100% - 10px)}} \ No newline at end of file diff --git a/dist/jquery.fancybox.min.js b/dist/jquery.fancybox.min.js index e5e20f0e..d5d10f6b 100644 --- a/dist/jquery.fancybox.min.js +++ b/dist/jquery.fancybox.min.js @@ -1,12 +1,13 @@ // ================================================== -// fancyBox v3.1.20 +// fancyBox v3.5.7 // // Licensed GPLv3 for open source use // or fancyBox Commercial License for commercial use // // http://fancyapps.com/fancybox/ -// Copyright 2017 fancyApps +// Copyright 2019 fancyApps // // ================================================== -!function(t,e,n,o){"use strict";function i(t){var e=t.currentTarget,o=t.data?t.data.options:{},i=t.data?t.data.items:[],a=n(e).attr("data-fancybox")||"",s=0;t.preventDefault(),t.stopPropagation(),a?(i=i.length?i.filter('[data-fancybox="'+a+'"]'):n('[data-fancybox="'+a+'"]'),s=i.index(e),s<0&&(s=0)):i=[e],n.fancybox.open(i,o,s)}if(n){if(n.fn.fancybox)return void n.error("fancyBox already initialized");var a={loop:!1,margin:[44,0],gutter:50,keyboard:!0,arrows:!0,infobar:!1,toolbar:!0,buttons:["slideShow","fullScreen","thumbs","close"],idleTime:4,smallBtn:"auto",protect:!1,modal:!1,image:{preload:"auto"},ajax:{settings:{data:{fancybox:!0}}},iframe:{tpl:'',preload:!0,css:{},attr:{scrolling:"auto"}},animationEffect:"zoom",animationDuration:366,zoomOpacity:"auto",transitionEffect:"fade",transitionDuration:366,slideClass:"",baseClass:"",baseTpl:'',spinnerTpl:'
',errorTpl:'

{{ERROR}}

',btnTpl:{slideShow:'',fullScreen:'',thumbs:'',close:'',smallBtn:''},parentEl:"body",autoFocus:!0,backFocus:!0,trapFocus:!0,fullScreen:{autoStart:!1},touch:{vertical:!0,momentum:!0},hash:null,media:{},slideShow:{autoStart:!1,speed:4e3},thumbs:{autoStart:!1,hideOnClose:!0},onInit:n.noop,beforeLoad:n.noop,afterLoad:n.noop,beforeShow:n.noop,afterShow:n.noop,beforeClose:n.noop,afterClose:n.noop,onActivate:n.noop,onDeactivate:n.noop,clickContent:function(t,e){return"image"===t.type&&"zoom"},clickSlide:"close",clickOutside:"close",dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1,mobile:{clickContent:function(t,e){return"image"===t.type&&"toggleControls"},clickSlide:function(t,e){return"image"===t.type?"toggleControls":"close"},dblclickContent:function(t,e){return"image"===t.type&&"zoom"},dblclickSlide:function(t,e){return"image"===t.type&&"zoom"}},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"},de:{CLOSE:"Schliessen",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"}}},s=n(t),r=n(e),c=0,l=function(t){return t&&t.hasOwnProperty&&t instanceof n},u=function(){return t.requestAnimationFrame||t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||function(e){return t.setTimeout(e,1e3/60)}}(),d=function(){var t,n=e.createElement("fakeelement"),i={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(t in i)if(n.style[t]!==o)return i[t]}(),f=function(t){return t&&t.length&&t[0].offsetHeight},h=function(t,o,i){var s=this;s.opts=n.extend(!0,{index:i},a,o||{}),o&&n.isArray(o.buttons)&&(s.opts.buttons=o.buttons),s.id=s.opts.id||++c,s.group=[],s.currIndex=parseInt(s.opts.index,10)||0,s.prevIndex=null,s.prevPos=null,s.currPos=0,s.firstRun=null,s.createGroup(t),s.group.length&&(s.$lastFocus=n(e.activeElement).blur(),s.slides={},s.init(t))};n.extend(h.prototype,{init:function(){var t,e,o,i=this,a=i.group[i.currIndex].opts;i.scrollTop=r.scrollTop(),i.scrollLeft=r.scrollLeft(),n.fancybox.getInstance()||n.fancybox.isMobile||"hidden"===n("body").css("overflow")||(t=n("body").width(),n("html").addClass("fancybox-enabled"),t=n("body").width()-t,t>1&&n("head").append('")),o="",n.each(a.buttons,function(t,e){o+=a.btnTpl[e]||""}),e=n(i.translate(i,a.baseTpl.replace("{{BUTTONS}}",o))).addClass("fancybox-is-hidden").attr("id","fancybox-container-"+i.id).addClass(a.baseClass).data("FancyBox",i).prependTo(a.parentEl),i.$refs={container:e},["bg","inner","infobar","toolbar","stage","caption"].forEach(function(t){i.$refs[t]=e.find(".fancybox-"+t)}),(!a.arrows||i.group.length<2)&&e.find(".fancybox-navigation").remove(),a.infobar||i.$refs.infobar.remove(),a.toolbar||i.$refs.toolbar.remove(),i.trigger("onInit"),i.activate(),i.jumpTo(i.currIndex)},translate:function(t,e){var n=t.opts.i18n[t.opts.lang];return e.replace(/\{\{(\w+)\}\}/g,function(t,e){var i=n[e];return i===o?t:i})},createGroup:function(t){var e=this,i=n.makeArray(t);n.each(i,function(t,i){var a,s,r,c,l={},u={},d=[];n.isPlainObject(i)?(l=i,u=i.opts||i):"object"===n.type(i)&&n(i).length?(a=n(i),d=a.data(),u="options"in d?d.options:{},u="object"===n.type(u)?u:{},l.src="src"in d?d.src:u.src||a.attr("href"),["width","height","thumb","type","filter"].forEach(function(t){t in d&&(u[t]=d[t])}),"srcset"in d&&(u.image={srcset:d.srcset}),u.$orig=a,l.type||l.src||(l.type="inline",l.src=i)):l={type:"html",src:i+""},l.opts=n.extend(!0,{},e.opts,u),n.fancybox.isMobile&&(l.opts=n.extend(!0,{},l.opts,l.opts.mobile)),s=l.type||l.opts.type,r=l.src||"",!s&&r&&(r.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)?s="image":r.match(/\.(pdf)((\?|#).*)?$/i)?s="pdf":"#"===r.charAt(0)&&(s="inline")),l.type=s,l.index=e.group.length,l.opts.$orig&&!l.opts.$orig.length&&delete l.opts.$orig,!l.opts.$thumb&&l.opts.$orig&&(l.opts.$thumb=l.opts.$orig.find("img:first")),l.opts.$thumb&&!l.opts.$thumb.length&&delete l.opts.$thumb,"function"===n.type(l.opts.caption)?l.opts.caption=l.opts.caption.apply(i,[e,l]):"caption"in d&&(l.opts.caption=d.caption),l.opts.caption=l.opts.caption===o?"":l.opts.caption+"","ajax"===s&&(c=r.split(/\s+/,2),c.length>1&&(l.src=c.shift(),l.opts.filter=c.shift())),"auto"==l.opts.smallBtn&&(n.inArray(s,["html","inline","ajax"])>-1?(l.opts.toolbar=!1,l.opts.smallBtn=!0):l.opts.smallBtn=!1),"pdf"===s&&(l.type="iframe",l.opts.iframe.preload=!1),l.opts.modal&&(l.opts=n.extend(!0,l.opts,{infobar:0,toolbar:0,smallBtn:0,keyboard:0,slideShow:0,fullScreen:0,thumbs:0,touch:0,clickContent:!1,clickSlide:!1,clickOutside:!1,dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1})),e.group.push(l)})},addEvents:function(){var o=this;o.removeEvents(),o.$refs.container.on("click.fb-close","[data-fancybox-close]",function(t){t.stopPropagation(),t.preventDefault(),o.close(t)}).on("click.fb-prev touchend.fb-prev","[data-fancybox-prev]",function(t){t.stopPropagation(),t.preventDefault(),o.previous()}).on("click.fb-next touchend.fb-next","[data-fancybox-next]",function(t){t.stopPropagation(),t.preventDefault(),o.next()}),s.on("orientationchange.fb resize.fb",function(t){t&&t.originalEvent&&"resize"===t.originalEvent.type?u(function(){o.update()}):(o.$refs.stage.hide(),setTimeout(function(){o.$refs.stage.show(),o.update()},500))}),r.on("focusin.fb",function(t){var i=n.fancybox?n.fancybox.getInstance():null;i.isClosing||!i.current||!i.current.opts.trapFocus||n(t.target).hasClass("fancybox-container")||n(t.target).is(e)||i&&"fixed"!==n(t.target).css("position")&&!i.$refs.container.has(t.target).length&&(t.stopPropagation(),i.focus(),s.scrollTop(o.scrollTop).scrollLeft(o.scrollLeft))}),r.on("keydown.fb",function(t){var e=o.current,i=t.keyCode||t.which;if(e&&e.opts.keyboard&&!n(t.target).is("input")&&!n(t.target).is("textarea"))return 8===i||27===i?(t.preventDefault(),void o.close(t)):37===i||38===i?(t.preventDefault(),void o.previous()):39===i||40===i?(t.preventDefault(),void o.next()):void o.trigger("afterKeydown",t,i)}),o.group[o.currIndex].opts.idleTime&&(o.idleSecondsCounter=0,r.on("mousemove.fb-idle mouseenter.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",function(){o.idleSecondsCounter=0,o.isIdle&&o.showControls(),o.isIdle=!1}),o.idleInterval=t.setInterval(function(){o.idleSecondsCounter++,o.idleSecondsCounter>=o.group[o.currIndex].opts.idleTime&&(o.isIdle=!0,o.idleSecondsCounter=0,o.hideControls())},1e3))},removeEvents:function(){var e=this;s.off("orientationchange.fb resize.fb"),r.off("focusin.fb keydown.fb .fb-idle"),this.$refs.container.off(".fb-close .fb-prev .fb-next"),e.idleInterval&&(t.clearInterval(e.idleInterval),e.idleInterval=null)},previous:function(t){return this.jumpTo(this.currPos-1,t)},next:function(t){return this.jumpTo(this.currPos+1,t)},jumpTo:function(t,e,i){var a,s,r,c,l,u,d,h=this,p=h.group.length;if(!(h.isSliding||h.isClosing||h.isAnimating&&h.firstRun)){if(t=parseInt(t,10),s=h.current?h.current.opts.loop:h.opts.loop,!s&&(t<0||t>=p))return!1;if(a=h.firstRun=null===h.firstRun,!(p<2&&!a&&h.isSliding)){if(c=h.current,h.prevIndex=h.currIndex,h.prevPos=h.currPos,r=h.createSlide(t),p>1&&((s||r.index>0)&&h.createSlide(t-1),(s||r.indexr.pos?"next":"previous"),c.$slide.removeClass("fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous"),c.isComplete=!1,e&&(r.isMoved||r.opts.transitionEffect)&&(r.isMoved?c.$slide.addClass(d):(d="fancybox-animated "+d+" fancybox-fx-"+r.opts.transitionEffect,n.fancybox.animate(c.$slide,d,e,function(){c.$slide.removeClass(d).removeAttr("style")}))))}}},createSlide:function(t){var e,o,i=this;return o=t%i.group.length,o=o<0?i.group.length+o:o,!i.slides[t]&&i.group[o]&&(e=n('
').appendTo(i.$refs.stage),i.slides[t]=n.extend(!0,{},i.group[o],{pos:t,$slide:e,isLoaded:!1}),i.updateSlide(i.slides[t])),i.slides[t]},scaleToActual:function(t,e,i){var a,s,r,c,l,u=this,d=u.current,f=d.$content,h=parseInt(d.$slide.width(),10),p=parseInt(d.$slide.height(),10),g=d.width,b=d.height;"image"!=d.type||d.hasError||!f||u.isAnimating||(n.fancybox.stop(f),u.isAnimating=!0,t=t===o?.5*h:t,e=e===o?.5*p:e,a=n.fancybox.getTranslate(f),c=g/a.width,l=b/a.height,s=.5*h-.5*g,r=.5*p-.5*b,g>h&&(s=a.left*c-(t*c-t),s>0&&(s=0),sp&&(r=a.top*l-(e*l-e),r>0&&(r=0),rt.width||o.height>t.height))},isScaledDown:function(){var t=this,e=t.current,o=e.$content,i=!1;return o&&(i=n.fancybox.getTranslate(o),i=i.width1||Math.abs(n.height()-o.height)>1),o},loadSlide:function(t){var e,o,i,a=this;if(!t.isLoading&&!t.isLoaded){switch(t.isLoading=!0,a.trigger("beforeLoad",t),e=t.type,o=t.$slide,o.off("refresh").trigger("onReset").addClass("fancybox-slide--"+(e||"unknown")).addClass(t.opts.slideClass),e){case"image":a.setImage(t);break;case"iframe":a.setIframe(t);break;case"html":a.setContent(t,t.src||t.content);break;case"inline":n(t.src).length?a.setContent(t,n(t.src)):a.setError(t);break;case"ajax":a.showLoading(t),i=n.ajax(n.extend({},t.opts.ajax.settings,{url:t.src,success:function(e,n){"success"===n&&a.setContent(t,e)},error:function(e,n){e&&"abort"!==n&&a.setError(t)}})),o.one("onReset",function(){i.abort()});break;default:a.setError(t)}return!0}},setImage:function(e){var o,i,a,s,r=this,c=e.opts.image.srcset;if(c){a=t.devicePixelRatio||1,s=t.innerWidth*a,i=c.split(",").map(function(t){var e={};return t.trim().split(/\s+/).forEach(function(t,n){var o=parseInt(t.substring(0,t.length-1),10);return 0===n?e.url=t:void(o&&(e.value=o,e.postfix=t[t.length-1]))}),e}),i.sort(function(t,e){return t.value-e.value});for(var l=0;l=s||"x"===u.postfix&&u.value>=a){o=u;break}}!o&&i.length&&(o=i[i.length-1]),o&&(e.src=o.url,e.width&&e.height&&"w"==o.postfix&&(e.height=e.width/e.height*o.value,e.width=o.value))}e.$content=n('
').addClass("fancybox-is-hidden").appendTo(e.$slide),e.opts.preload!==!1&&e.opts.width&&e.opts.height&&(e.opts.thumb||e.opts.$thumb)?(e.width=e.opts.width,e.height=e.opts.height,e.$ghost=n("").one("error",function(){n(this).remove(),e.$ghost=null,r.setBigImage(e)}).one("load",function(){r.afterLoad(e),r.setBigImage(e)}).addClass("fancybox-image").appendTo(e.$content).attr("src",e.opts.thumb||e.opts.$thumb.attr("src"))):r.setBigImage(e)},setBigImage:function(t){var e=this,o=n("");t.$image=o.one("error",function(){e.setError(t)}).one("load",function(){clearTimeout(t.timouts),t.timouts=null,e.isClosing||(t.width=this.naturalWidth,t.height=this.naturalHeight,t.opts.image.srcset&&o.attr("sizes","100vw").attr("srcset",t.opts.image.srcset),e.hideLoading(t),t.$ghost?t.timouts=setTimeout(function(){t.timouts=null,t.$ghost.hide()},Math.min(300,Math.max(1e3,t.height/1600))):e.afterLoad(t))}).addClass("fancybox-image").attr("src",t.src).appendTo(t.$content),o[0].complete?o.trigger("load"):o[0].error?o.trigger("error"):t.timouts=setTimeout(function(){o[0].complete||t.hasError||e.showLoading(t)},100)},setIframe:function(t){var e,i=this,a=t.opts.iframe,s=t.$slide;t.$content=n('
').css(a.css).appendTo(s),e=n(a.tpl.replace(/\{rnd\}/g,(new Date).getTime())).attr(a.attr).appendTo(t.$content),a.preload?(i.showLoading(t),e.on("load.fb error.fb",function(e){this.isReady=1,t.$slide.trigger("refresh"),i.afterLoad(t)}),s.on("refresh.fb",function(){var n,i,s,r,c,l=t.$content;if(1===e[0].isReady){try{n=e.contents(),i=n.find("body")}catch(t){}i&&i.length&&(a.css.width===o||a.css.height===o)&&(s=e[0].contentWindow.document.documentElement.scrollWidth,r=Math.ceil(i.outerWidth(!0)+(l.width()-s)),c=Math.ceil(i.outerHeight(!0)),l.css({width:a.css.width===o?r+(l.outerWidth()-l.innerWidth()):a.css.width,height:a.css.height===o?c+(l.outerHeight()-l.innerHeight()):a.css.height})),l.removeClass("fancybox-is-hidden")}})):this.afterLoad(t),e.attr("src",t.src),t.opts.smallBtn===!0&&t.$content.prepend(i.translate(t,t.opts.btnTpl.smallBtn)),s.one("onReset",function(){try{n(this).find("iframe").hide().attr("src","//about:blank")}catch(t){}n(this).empty(),t.isLoaded=!1})},setContent:function(t,e){var o=this;o.isClosing||(o.hideLoading(t),t.$slide.empty(),l(e)&&e.parent().length?(e.parent(".fancybox-slide--inline").trigger("onReset"),t.$placeholder=n("
").hide().insertAfter(e),e.css("display","inline-block")):t.hasError||("string"===n.type(e)&&(e=n("
").append(n.trim(e)).contents(),3===e[0].nodeType&&(e=n("
").html(e))),t.opts.filter&&(e=n("
").html(e).find(t.opts.filter))),t.$slide.one("onReset",function(){t.$placeholder&&(t.$placeholder.after(e.hide()).remove(),t.$placeholder=null),t.$smallBtn&&(t.$smallBtn.remove(),t.$smallBtn=null),t.hasError||(n(this).empty(),t.isLoaded=!1)}),t.$content=n(e).appendTo(t.$slide),t.opts.smallBtn&&!t.$smallBtn&&(t.$smallBtn=n(o.translate(t,t.opts.btnTpl.smallBtn)).appendTo(t.$content)),this.afterLoad(t))},setError:function(t){t.hasError=!0,t.$slide.removeClass("fancybox-slide--"+t.type),this.setContent(t,this.translate(t,t.opts.errorTpl))},showLoading:function(t){var e=this;t=t||e.current,t&&!t.$spinner&&(t.$spinner=n(e.opts.spinnerTpl).appendTo(t.$slide))},hideLoading:function(t){var e=this;t=t||e.current,t&&t.$spinner&&(t.$spinner.remove(),delete t.$spinner)},afterLoad:function(t){var e=this;e.isClosing||(t.isLoading=!1,t.isLoaded=!0,e.trigger("afterLoad",t),e.hideLoading(t),t.opts.protect&&t.$content&&!t.hasError&&(t.$content.on("contextmenu.fb",function(t){return 2==t.button&&t.preventDefault(),!0}),"image"===t.type&&n('
').appendTo(t.$content)),e.revealContent(t))},revealContent:function(t){var e,i,a,s,r,c=this,l=t.$slide,u=!1;return e=t.opts[c.firstRun?"animationEffect":"transitionEffect"],a=t.opts[c.firstRun?"animationDuration":"transitionDuration"],a=parseInt(t.forcedDuration===o?a:t.forcedDuration,10),!t.isMoved&&t.pos===c.currPos&&a||(e=!1),"zoom"!==e||t.pos===c.currPos&&a&&"image"===t.type&&!t.hasError&&(u=c.getThumbPos(t))||(e="fade"),"zoom"===e?(r=c.getFitPos(t),r.scaleX=Math.round(r.width/u.width*100)/100,r.scaleY=Math.round(r.height/u.height*100)/100,delete r.width,delete r.height,s=t.opts.zoomOpacity,"auto"==s&&(s=Math.abs(t.width/t.height-u.width/u.height)>.1),s&&(u.opacity=.1,r.opacity=1),n.fancybox.setTranslate(t.$content.removeClass("fancybox-is-hidden"),u),f(t.$content),void n.fancybox.animate(t.$content,r,a,function(){c.complete()})):(c.updateSlide(t),e?(n.fancybox.stop(l),i="fancybox-animated fancybox-slide--"+(t.pos>c.prevPos?"next":"previous")+" fancybox-fx-"+e,l.removeAttr("style").removeClass("fancybox-slide--current fancybox-slide--next fancybox-slide--previous").addClass(i),t.$content.removeClass("fancybox-is-hidden"),f(l),void n.fancybox.animate(l,"fancybox-slide--current",a,function(e){l.removeClass(i).removeAttr("style"),t.pos===c.currPos&&c.complete()},!0)):(f(l),t.$content.removeClass("fancybox-is-hidden"),void(t.pos===c.currPos&&c.complete())))},getThumbPos:function(o){var i,a=this,s=!1,r=function(e){for(var o,i=e[0],a=i.getBoundingClientRect(),s=[];null!==i.parentElement;)"hidden"!==n(i.parentElement).css("overflow")&&"auto"!==n(i.parentElement).css("overflow")||s.push(i.parentElement.getBoundingClientRect()),i=i.parentElement;return o=s.every(function(t){var e=Math.min(a.right,t.right)-Math.max(a.left,t.left),n=Math.min(a.bottom,t.bottom)-Math.max(a.top,t.top);return e>0&&n>0}),o&&a.bottom>0&&a.right>0&&a.left=t.currPos-1&&o.pos<=t.currPos+1?i[o.pos]=o:o&&(n.fancybox.stop(o.$slide),o.$slide.unbind().remove())}),t.slides=i,t.updateCursor(),t.trigger("afterShow"),(n(e.activeElement).is("[disabled]")||o.opts.autoFocus&&"image"!=o.type&&"iframe"!==o.type)&&t.focus())},preload:function(){var t,e,n=this;n.group.length<2||(t=n.slides[n.currPos+1],e=n.slides[n.currPos-1],t&&"image"===t.type&&n.loadSlide(t),e&&"image"===e.type&&n.loadSlide(e))},focus:function(){var t,e=this.current;this.isClosing||(t=e&&e.isComplete?e.$slide.find("button,:input,[tabindex],a").filter(":not([disabled]):visible:first"):null,t=t&&t.length?t:this.$refs.container,t.focus())},activate:function(){var t=this;n(".fancybox-container").each(function(){var e=n(this).data("FancyBox");e&&e.uid!==t.uid&&!e.isClosing&&e.trigger("onDeactivate")}),t.current&&(t.$refs.container.index()>0&&t.$refs.container.prependTo(e.body),t.updateControls()),t.trigger("onActivate"),t.addEvents()},close:function(t,e){var o,i,a,s,r,c,l=this,f=l.current,h=function(){l.cleanUp(t)};return!l.isClosing&&(l.isClosing=!0,l.trigger("beforeClose",t)===!1?(l.isClosing=!1,u(function(){l.update()}),!1):(l.removeEvents(),f.timouts&&clearTimeout(f.timouts),a=f.$content,o=f.opts.animationEffect,i=n.isNumeric(e)?e:o?f.opts.animationDuration:0,f.$slide.off(d).removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"),f.$slide.siblings().trigger("onReset").remove(),i&&l.$refs.container.removeClass("fancybox-is-open").addClass("fancybox-is-closing"),l.hideLoading(f),l.hideControls(),l.updateCursor(),"zoom"!==o||t!==!0&&a&&i&&"image"===f.type&&!f.hasError&&(c=l.getThumbPos(f))||(o="fade"),"zoom"===o?(n.fancybox.stop(a),r=n.fancybox.getTranslate(a),r.width=r.width*r.scaleX,r.height=r.height*r.scaleY,s=f.opts.zoomOpacity,"auto"==s&&(s=Math.abs(f.width/f.height-c.width/c.height)>.1),s&&(c.opacity=0),r.scaleX=r.width/c.width,r.scaleY=r.height/c.height,r.width=c.width,r.height=c.height,n.fancybox.setTranslate(f.$content,r),n.fancybox.animate(f.$content,c,i,h),!0):(o&&i?t===!0?setTimeout(h,i):n.fancybox.animate(f.$slide.removeClass("fancybox-slide--current"),"fancybox-animated fancybox-slide--previous fancybox-fx-"+o,i,h):h(),!0)))},cleanUp:function(t){var e,o=this;o.current.$slide.trigger("onReset"),o.$refs.container.empty().remove(),o.trigger("afterClose",t),o.$lastFocus&&!o.current.focusBack&&o.$lastFocus.focus(),o.current=null,e=n.fancybox.getInstance(),e?e.activate():(s.scrollTop(o.scrollTop).scrollLeft(o.scrollLeft),n("html").removeClass("fancybox-enabled"),n("#fancybox-style-noscroll").remove())},trigger:function(t,e){var o,i=Array.prototype.slice.call(arguments,1),a=this,s=e&&e.opts?e:a.current;return s?i.unshift(s):s=a,i.unshift(a),n.isFunction(s.opts[t])&&(o=s.opts[t].apply(s,i)),o===!1?o:void("afterClose"===t?r.trigger(t+".fb",i):a.$refs.container.trigger(t+".fb",i))},updateControls:function(t){var e=this,o=e.current,i=o.index,a=o.opts,s=a.caption,r=e.$refs.caption;o.$slide.trigger("refresh"),e.$caption=s&&s.length?r.html(s):null,e.isHiddenControls||e.showControls(),n("[data-fancybox-count]").html(e.group.length),n("[data-fancybox-index]").html(i+1),n("[data-fancybox-prev]").prop("disabled",!a.loop&&i<=0),n("[data-fancybox-next]").prop("disabled",!a.loop&&i>=e.group.length-1)},hideControls:function(){this.isHiddenControls=!0,this.$refs.container.removeClass("fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav")},showControls:function(){var t=this,e=t.current?t.current.opts:t.opts,n=t.$refs.container;t.isHiddenControls=!1,t.idleSecondsCounter=0,n.toggleClass("fancybox-show-toolbar",!(!e.toolbar||!e.buttons)).toggleClass("fancybox-show-infobar",!!(e.infobar&&t.group.length>1)).toggleClass("fancybox-show-nav",!!(e.arrows&&t.group.length>1)).toggleClass("fancybox-is-modal",!!e.modal),t.$caption?n.addClass("fancybox-show-caption "):n.removeClass("fancybox-show-caption")},toggleControls:function(){this.isHiddenControls?this.showControls():this.hideControls()}}),n.fancybox={version:"3.1.20",defaults:a,getInstance:function(t){var e=n('.fancybox-container:not(".fancybox-is-closing"):first').data("FancyBox"),o=Array.prototype.slice.call(arguments,1);return e instanceof h&&("string"===n.type(t)?e[t].apply(e,o):"function"===n.type(t)&&t.apply(e,o),e)},open:function(t,e,n){return new h(t,e,n)},close:function(t){var e=this.getInstance();e&&(e.close(),t===!0&&this.close())},destroy:function(){this.close(!0),r.off("click.fb-start")},isMobile:e.createTouch!==o&&/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent),use3d:function(){var n=e.createElement("div");return t.getComputedStyle&&t.getComputedStyle(n).getPropertyValue("transform")&&!(e.documentMode&&e.documentMode<11)}(),getTranslate:function(t){var e;if(!t||!t.length)return!1;if(e=t.eq(0).css("transform"),e&&e.indexOf("matrix")!==-1?(e=e.split("(")[1],e=e.split(")")[0],e=e.split(",")):e=[],e.length)e=e.length>10?[e[13],e[12],e[0],e[5]]:[e[5],e[4],e[0],e[3]],e=e.map(parseFloat);else{e=[0,0,1,1];var n=/\.*translate\((.*)px,(.*)px\)/i,o=n.exec(t.eq(0).attr("style"));o&&(e[0]=parseFloat(o[2]),e[1]=parseFloat(o[1]))}return{top:e[0],left:e[1],scaleX:e[2],scaleY:e[3],opacity:parseFloat(t.css("opacity")),width:t.width(),height:t.height()}},setTranslate:function(t,e){var n="",i={};if(t&&e)return e.left===o&&e.top===o||(n=(e.left===o?t.position().left:e.left)+"px, "+(e.top===o?t.position().top:e.top)+"px",n=this.use3d?"translate3d("+n+", 0px)":"translate("+n+")"),e.scaleX!==o&&e.scaleY!==o&&(n=(n.length?n+" ":"")+"scale("+e.scaleX+", "+e.scaleY+")"),n.length&&(i.transform=n),e.opacity!==o&&(i.opacity=e.opacity),e.width!==o&&(i.width=e.width),e.height!==o&&(i.height=e.height),t.css(i)},animate:function(t,e,i,a,s){var r=d||"transitionend";n.isFunction(i)&&(a=i,i=null),n.isPlainObject(e)||t.removeAttr("style"),t.on(r,function(i){(!i||!i.originalEvent||t.is(i.originalEvent.target)&&"z-index"!=i.originalEvent.propertyName)&&(t.off(r),n.isPlainObject(e)?e.scaleX!==o&&e.scaleY!==o&&(t.css("transition-duration","0ms"),e.width=t.width()*e.scaleX,e.height=t.height()*e.scaleY,e.scaleX=1,e.scaleY=1,n.fancybox.setTranslate(t,e)):s!==!0&&t.removeClass(e),n.isFunction(a)&&a(i))}),n.isNumeric(i)&&t.css("transition-duration",i+"ms"),n.isPlainObject(e)?n.fancybox.setTranslate(t,e):t.addClass(e),t.data("timer",setTimeout(function(){t.trigger("transitionend")},i+16))},stop:function(t){clearTimeout(t.data("timer")),t.off(d)}},n.fn.fancybox=function(t){var e;return t=t||{},e=t.selector||!1,e?n("body").off("click.fb-start",e).on("click.fb-start",e,{items:n(e),options:t},i):this.off("click.fb-start").on("click.fb-start",{items:this,options:t},i),this},r.on("click.fb-start","[data-fancybox]",i)}}(window,document,window.jQuery),function(t){"use strict";var e=function(e,n,o){if(e)return o=o||"","object"===t.type(o)&&(o=t.param(o,!0)),t.each(n,function(t,n){e=e.replace("$"+t,n||"")}),o.length&&(e+=(e.indexOf("?")>0?"&":"?")+o),e},n={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:"//www.youtube.com/embed/$4",thumb:"//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,api:1},paramPlace:3,type:"iframe",url:"//player.vimeo.com/video/$2"},metacafe:{matcher:/metacafe.com\/watch\/(\d+)\/(.*)?/,type:"iframe",url:"//www.metacafe.com/embed/$1/?ap=1"},dailymotion:{matcher:/dailymotion.com\/video\/(.*)\/?(.*)/,params:{additionalInfos:0,autoStart:1},type:"iframe",url:"//www.dailymotion.com/embed/video/$1"},vine:{matcher:/vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/,type:"iframe",url:"//vine.co/v/$1/embed/simple"},instagram:{matcher:/(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,type:"image",url:"//$1/p/$2/media/?size=l"},google_maps:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,type:"iframe",url:function(t){return"//maps.google."+t[2]+"/?ll="+(t[9]?t[9]+"&z="+Math.floor(t[10])+(t[12]?t[12].replace(/^\//,"&"):""):t[12])+"&output="+(t[12]&&t[12].indexOf("layer=c")>0?"svembed":"embed")}}};t(document).on("onInit.fb",function(o,i){t.each(i.group,function(o,i){var a,s,r,c,l,u,d,f=i.src||"",h=!1;i.type||(a=t.extend(!0,{},n,i.opts.media),t.each(a,function(n,o){if(r=f.match(o.matcher),u={},d=n,r){if(h=o.type,o.paramPlace&&r[o.paramPlace]){l=r[o.paramPlace],"?"==l[0]&&(l=l.substring(1)),l=l.split("&");for(var a=0;ae.clientHeight,a=("scroll"===o||"auto"===o)&&e.scrollWidth>e.clientWidth;return i||a},l=function(t){for(var e=!1;;){if(e=c(t.get(0)))break;if(t=t.parent(),!t.length||t.hasClass("fancybox-stage")||t.is("body"))break}return e},u=function(t){var e=this;e.instance=t,e.$bg=t.$refs.bg,e.$stage=t.$refs.stage,e.$container=t.$refs.container,e.destroy(),e.$container.on("touchstart.fb.touch mousedown.fb.touch",n.proxy(e,"ontouchstart"))};u.prototype.destroy=function(){this.$container.off(".fb.touch")},u.prototype.ontouchstart=function(o){var i=this,c=n(o.target),u=i.instance,d=u.current,f=d.$content,h="touchstart"==o.type;if(h&&i.$container.off("mousedown.fb.touch"),!d||i.instance.isAnimating||i.instance.isClosing)return o.stopPropagation(),void o.preventDefault();if((!o.originalEvent||2!=o.originalEvent.button)&&c.length&&!r(c)&&!r(c.parent())&&!(o.originalEvent.clientX>c[0].clientWidth+c.offset().left)&&(i.startPoints=a(o),i.startPoints&&!(i.startPoints.length>1&&u.isSliding))){if(i.$target=c,i.$content=f,i.canTap=!0,n(e).off(".fb.touch"),n(e).on(h?"touchend.fb.touch touchcancel.fb.touch":"mouseup.fb.touch mouseleave.fb.touch",n.proxy(i,"ontouchend")),n(e).on(h?"touchmove.fb.touch":"mousemove.fb.touch",n.proxy(i,"ontouchmove")),o.stopPropagation(),!u.current.opts.touch&&!u.canPan()||!c.is(i.$stage)&&!i.$stage.find(c).length)return void(c.is("img")&&o.preventDefault());n.fancybox.isMobile&&(l(i.$target)||l(i.$target.parent()))||o.preventDefault(),i.canvasWidth=Math.round(d.$slide[0].clientWidth),i.canvasHeight=Math.round(d.$slide[0].clientHeight),i.startTime=(new Date).getTime(),i.distanceX=i.distanceY=i.distance=0,i.isPanning=!1,i.isSwiping=!1,i.isZooming=!1,i.sliderStartPos=i.sliderLastPos||{top:0,left:0},i.contentStartPos=n.fancybox.getTranslate(i.$content),i.contentLastPos=null,1!==i.startPoints.length||i.isZooming||(i.canTap=!u.isSliding,"image"===d.type&&(i.contentStartPos.width>i.canvasWidth+1||i.contentStartPos.height>i.canvasHeight+1)?(n.fancybox.stop(i.$content),i.$content.css("transition-duration","0ms"),i.isPanning=!0):i.isSwiping=!0,i.$container.addClass("fancybox-controls--isGrabbing")),2!==i.startPoints.length||u.isAnimating||d.hasError||"image"!==d.type||!d.isLoaded&&!d.$ghost||(i.isZooming=!0,i.isSwiping=!1,i.isPanning=!1,n.fancybox.stop(i.$content),i.$content.css("transition-duration","0ms"),i.centerPointStartX=.5*(i.startPoints[0].x+i.startPoints[1].x)-n(t).scrollLeft(),i.centerPointStartY=.5*(i.startPoints[0].y+i.startPoints[1].y)-n(t).scrollTop(),i.percentageOfImageAtPinchPointX=(i.centerPointStartX-i.contentStartPos.left)/i.contentStartPos.width,i.percentageOfImageAtPinchPointY=(i.centerPointStartY-i.contentStartPos.top)/i.contentStartPos.height,i.startDistanceBetweenFingers=s(i.startPoints[0],i.startPoints[1]))}},u.prototype.ontouchmove=function(t){var e=this;if(e.newPoints=a(t),n.fancybox.isMobile&&(l(e.$target)||l(e.$target.parent())))return t.stopPropagation(),void(e.canTap=!1);if((e.instance.current.opts.touch||e.instance.canPan())&&e.newPoints&&e.newPoints.length&&(e.distanceX=s(e.newPoints[0],e.startPoints[0],"x"),e.distanceY=s(e.newPoints[0],e.startPoints[0],"y"),e.distance=s(e.newPoints[0],e.startPoints[0]),e.distance>0)){if(!e.$target.is(e.$stage)&&!e.$stage.find(e.$target).length)return;t.stopPropagation(),t.preventDefault(),e.isSwiping?e.onSwipe():e.isPanning?e.onPan():e.isZooming&&e.onZoom()}},u.prototype.onSwipe=function(){var e,a=this,s=a.isSwiping,r=a.sliderStartPos.left||0;s===!0?Math.abs(a.distance)>10&&(a.canTap=!1,a.instance.group.length<2&&a.instance.opts.touch.vertical?a.isSwiping="y":a.instance.isSliding||a.instance.opts.touch.vertical===!1||"auto"===a.instance.opts.touch.vertical&&n(t).width()>800?a.isSwiping="x":(e=Math.abs(180*Math.atan2(a.distanceY,a.distanceX)/Math.PI),a.isSwiping=e>45&&e<135?"y":"x"),a.instance.isSliding=a.isSwiping,a.startPoints=a.newPoints,n.each(a.instance.slides,function(t,e){n.fancybox.stop(e.$slide),e.$slide.css("transition-duration","0ms"),e.inTransition=!1,e.pos===a.instance.current.pos&&(a.sliderStartPos.left=n.fancybox.getTranslate(e.$slide).left)}),a.instance.SlideShow&&a.instance.SlideShow.isActive&&a.instance.SlideShow.stop()):("x"==s&&(a.distanceX>0&&(a.instance.group.length<2||0===a.instance.current.index&&!a.instance.current.opts.loop)?r+=Math.pow(a.distanceX,.8):a.distanceX<0&&(a.instance.group.length<2||a.instance.current.index===a.instance.group.length-1&&!a.instance.current.opts.loop)?r-=Math.pow(-a.distanceX,.8):r+=a.distanceX),a.sliderLastPos={top:"x"==s?0:a.sliderStartPos.top+a.distanceY,left:r},a.requestId&&(i(a.requestId),a.requestId=null),a.requestId=o(function(){a.sliderLastPos&&(n.each(a.instance.slides,function(t,e){var o=e.pos-a.instance.currPos;n.fancybox.setTranslate(e.$slide,{top:a.sliderLastPos.top,left:a.sliderLastPos.left+o*a.canvasWidth+o*e.opts.gutter})}),a.$container.addClass("fancybox-is-sliding"))}))},u.prototype.onPan=function(){var t,e,a,s=this;s.canTap=!1,t=s.contentStartPos.width>s.canvasWidth?s.contentStartPos.left+s.distanceX:s.contentStartPos.left,e=s.contentStartPos.top+s.distanceY,a=s.limitMovement(t,e,s.contentStartPos.width,s.contentStartPos.height),a.scaleX=s.contentStartPos.scaleX,a.scaleY=s.contentStartPos.scaleY,s.contentLastPos=a,s.requestId&&(i(s.requestId),s.requestId=null),s.requestId=o(function(){n.fancybox.setTranslate(s.$content,s.contentLastPos)})},u.prototype.limitMovement=function(t,e,n,o){var i,a,s,r,c=this,l=c.canvasWidth,u=c.canvasHeight,d=c.contentStartPos.left,f=c.contentStartPos.top,h=c.distanceX,p=c.distanceY;return i=Math.max(0,.5*l-.5*n),a=Math.max(0,.5*u-.5*o),s=Math.min(l-n,.5*l-.5*n),r=Math.min(u-o,.5*u-.5*o),n>l&&(h>0&&t>i&&(t=i-1+Math.pow(-i+d+h,.8)||0),h<0&&tu&&(p>0&&e>a&&(e=a-1+Math.pow(-a+f+p,.8)||0),p<0&&ea?(t=t>0?0:t,t=ts?(e=e>0?0:e,e=e50?(n.fancybox.animate(e.instance.current.$slide,{top:e.sliderStartPos.top+e.distanceY+150*e.velocityY,opacity:0},150),o=e.instance.close(!0,300)):"x"==t&&e.distanceX>50&&e.instance.group.length>1?o=e.instance.previous(e.speedX):"x"==t&&e.distanceX<-50&&e.instance.group.length>1&&(o=e.instance.next(e.speedX)),o!==!1||"x"!=t&&"y"!=t||e.instance.jumpTo(e.instance.current.index,150),e.$container.removeClass("fancybox-is-sliding")},u.prototype.endPanning=function(){var t,e,o,i=this;i.contentLastPos&&(i.instance.current.opts.touch.momentum===!1?(t=i.contentLastPos.left,e=i.contentLastPos.top):(t=i.contentLastPos.left+i.velocityX*i.speed,e=i.contentLastPos.top+i.velocityY*i.speed),o=i.limitPosition(t,e,i.contentStartPos.width,i.contentStartPos.height),o.width=i.contentStartPos.width,o.height=i.contentStartPos.height,n.fancybox.animate(i.$content,o,330))},u.prototype.endZooming=function(){var t,e,o,i,a=this,s=a.instance.current,r=a.newWidth,c=a.newHeight;a.contentLastPos&&(t=a.contentLastPos.left,e=a.contentLastPos.top,i={top:e,left:t,width:r,height:c,scaleX:1,scaleY:1},n.fancybox.setTranslate(a.$content,i),rs.width||c>s.height?a.instance.scaleToActual(a.centerPointStartX,a.centerPointStartY,150):(o=a.limitPosition(t,e,r,c),n.fancybox.setTranslate(a.content,n.fancybox.getTranslate(a.$content)),n.fancybox.animate(a.$content,o,150)))},u.prototype.onTap=function(t){var e,o=this,i=n(t.target),s=o.instance,r=s.current,c=t&&a(t)||o.startPoints,l=c[0]?c[0].x-o.$stage.offset().left:0,u=c[0]?c[0].y-o.$stage.offset().top:0,d=function(e){var i=r.opts[e];if(n.isFunction(i)&&(i=i.apply(s,[r,t])),i)switch(i){case"close":s.close(o.startEvent);break;case"toggleControls":s.toggleControls(!0);break;case"next":s.next();break;case"nextOrClose":s.group.length>1?s.next():s.close(o.startEvent);break;case"zoom":"image"==r.type&&(r.isLoaded||r.$ghost)&&(s.canPan()?s.scaleToFit():s.isScaledDown()?s.scaleToActual(l,u):s.group.length<2&&s.close(o.startEvent))}};if(!(t.originalEvent&&2==t.originalEvent.button||s.isSliding||l>i[0].clientWidth+i.offset().left)){if(i.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container"))e="Outside";else if(i.is(".fancybox-slide"))e="Slide";else{if(!s.current.$content||!s.current.$content.has(t.target).length)return;e="Content"}if(o.tapped){if(clearTimeout(o.tapped),o.tapped=null,Math.abs(l-o.tapX)>50||Math.abs(u-o.tapY)>50||s.isSliding)return this;d("dblclick"+e)}else o.tapX=l,o.tapY=u,r.opts["dblclick"+e]&&r.opts["dblclick"+e]!==r.opts["click"+e]?o.tapped=setTimeout(function(){o.tapped=null,d("click"+e)},300):d("click"+e);return this}},n(e).on("onActivate.fb",function(t,e){e&&!e.Guestures&&(e.Guestures=new u(e))}),n(e).on("beforeClose.fb",function(t,e){e&&e.Guestures&&e.Guestures.destroy()})}(window,document,window.jQuery),function(t,e){"use strict";var n=function(t){this.instance=t,this.init()};e.extend(n.prototype,{timer:null,isActive:!1,$button:null,speed:3e3,init:function(){var t=this;t.$button=t.instance.$refs.toolbar.find("[data-fancybox-play]").on("click",function(){t.toggle()}),(t.instance.group.length<2||!t.instance.group[t.instance.currIndex].opts.slideShow)&&t.$button.hide()},set:function(){var t=this;t.instance&&t.instance.current&&(t.instance.current.opts.loop||t.instance.currIndex1&&t.instance.group[t.instance.currIndex].opts.thumbs&&("image"==e.type||e.opts.thumb||e.opts.$thumb)&&("image"==n.type||n.opts.thumb||n.opts.$thumb)?(t.$button.on("click",function(){t.toggle()}),t.isActive=!0):(t.$button.hide(),t.isActive=!1)},create:function(){var t,n,o=this.instance;this.$grid=e('
').appendTo(o.$refs.container),t="
    ",e.each(o.group,function(e,o){n=o.opts.thumb||(o.opts.$thumb?o.opts.$thumb.attr("src"):null),n||"image"!==o.type||(n=o.src),n&&n.length&&(t+='
  • ')}),t+="
",this.$list=e(t).appendTo(this.$grid).on("click","li",function(){o.jumpTo(e(this).data("index"))}),this.$list.find("img").hide().one("load",function(){var t,n,o,i,a=e(this).parent().removeClass("fancybox-thumbs-loading"),s=a.outerWidth(),r=a.outerHeight();t=this.naturalWidth||this.width,n=this.naturalHeight||this.height,o=t/s,i=n/r,o>=1&&i>=1&&(o>i?(t/=i,n=r):(t=s,n/=o)),e(this).css({width:Math.floor(t),height:Math.floor(n),"margin-top":Math.min(0,Math.floor(.3*r-.3*n)),"margin-left":Math.min(0,Math.floor(.5*s-.5*t))}).show()}).each(function(){this.src=e(this).data("src")})},focus:function(){this.instance.current&&this.$list.children().removeClass("fancybox-thumbs-active").filter('[data-index="'+this.instance.current.index+'"]').addClass("fancybox-thumbs-active").focus()},close:function(){this.$grid.hide()},update:function(){this.instance.$refs.container.toggleClass("fancybox-show-thumbs",this.isVisible),this.isVisible?(this.$grid||this.create(),this.instance.trigger("onThumbsShow"),this.focus()):this.$grid&&this.instance.trigger("onThumbsHide"),this.instance.update()},hide:function(){this.isVisible=!1,this.update()},show:function(){this.isVisible=!0,this.update()},toggle:function(){this.isVisible=!this.isVisible,this.update()}}),e(t).on({"onInit.fb":function(t,e){e&&!e.Thumbs&&(e.Thumbs=new n(e))},"beforeShow.fb":function(t,e,n,o){var i=e&&e.Thumbs;if(i&&i.isActive){if(n.modal)return i.$button.hide(),void i.hide();o&&e.opts.thumbs.autoStart===!0&&i.show(),i.isVisible&&i.focus()}},"afterKeydown.fb":function(t,e,n,o,i){var a=e&&e.Thumbs;a&&a.isActive&&71===i&&(o.preventDefault(),a.toggle())},"beforeClose.fb":function(t,e){var n=e&&e.Thumbs;n&&n.isVisible&&e.opts.thumbs.hideOnClose!==!1&&n.close()}})}(document,window.jQuery),function(t,e,n){"use strict";function o(){var t=e.location.hash.substr(1),n=t.split("-"),o=n.length>1&&/^\+?\d+$/.test(n[n.length-1])?parseInt(n.pop(-1),10)||1:1,i=n.join("-");return o<1&&(o=1),{hash:t,index:o,gallery:i}}function i(t){var e;""!==t.gallery&&(e=n("[data-fancybox='"+n.escapeSelector(t.gallery)+"']").eq(t.index-1),e.length?e.trigger("click"):n("#"+n.escapeSelector(t.gallery)).trigger("click"))}function a(t){var e;return!!t&&(e=t.current?t.current.opts:t.opts,e.$orig?e.$orig.data("fancybox"):e.hash||"")}n.escapeSelector||(n.escapeSelector=function(t){var e=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,n=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t};return(t+"").replace(e,n)});var s=null,r=null;n(function(){setTimeout(function(){n.fancybox.defaults.hash!==!1&&(n(t).on({"onInit.fb":function(t,e){var n,i;e.group[e.currIndex].opts.hash!==!1&&(n=o(),i=a(e),i&&n.gallery&&i==n.gallery&&(e.currIndex=n.index-1))},"beforeShow.fb":function(n,o,i,c){var l;i.opts.hash!==!1&&(l=a(o),l&&""!==l&&(e.location.hash.indexOf(l)<0&&(o.opts.origHash=e.location.hash),s=l+(o.group.length>1?"-"+(i.index+1):""),"replaceState"in e.history?(r&&clearTimeout(r),r=setTimeout(function(){e.history[c?"pushState":"replaceState"]({},t.title,e.location.pathname+e.location.search+"#"+s),r=null},300)):e.location.hash=s))},"beforeClose.fb":function(o,i,c){var l,u;r&&clearTimeout(r),c.opts.hash!==!1&&(l=a(i),u=i&&i.opts.origHash?i.opts.origHash:"",l&&""!==l&&("replaceState"in history?e.history.replaceState({},t.title,e.location.pathname+e.location.search+u):(e.location.hash=u,n(e).scrollTop(i.scrollTop).scrollLeft(i.scrollLeft))),s=null)}}),n(e).on("hashchange.fb",function(){var t=o();n.fancybox.getInstance()?!s||s===t.gallery+"-"+t.index||1===t.index&&s==t.gallery||(s=null,n.fancybox.close()):""!==t.gallery&&i(t)}),n(e).one("unload.fb popstate.fb",function(){n.fancybox.getInstance("close",!0,0)}),i(o()))},50)})}(document,window,window.jQuery); \ No newline at end of file +!function(t,e,n,o){"use strict";function i(t,e){var o,i,a,s=[],r=0;t&&t.isDefaultPrevented()||(t.preventDefault(),e=e||{},t&&t.data&&(e=h(t.data.options,e)),o=e.$target||n(t.currentTarget).trigger("blur"),(a=n.fancybox.getInstance())&&a.$trigger&&a.$trigger.is(o)||(e.selector?s=n(e.selector):(i=o.attr("data-fancybox")||"",i?(s=t.data?t.data.items:[],s=s.length?s.filter('[data-fancybox="'+i+'"]'):n('[data-fancybox="'+i+'"]')):s=[o]),r=n(s).index(o),r<0&&(r=0),a=n.fancybox.open(s,e,r),a.$trigger=o))}if(t.console=t.console||{info:function(t){}},n){if(n.fn.fancybox)return void console.info("fancyBox already initialized");var a={closeExisting:!1,loop:!1,gutter:50,keyboard:!0,preventCaptionOverlap:!0,arrows:!0,infobar:!0,smallBtn:"auto",toolbar:"auto",buttons:["zoom","slideShow","thumbs","close"],idleTime:3,protect:!1,modal:!1,image:{preload:!1},ajax:{settings:{data:{fancybox:!0}}},iframe:{tpl:'',preload:!0,css:{},attr:{scrolling:"auto"}},video:{tpl:'',format:"",autoStart:!0},defaultType:"image",animationEffect:"zoom",animationDuration:366,zoomOpacity:"auto",transitionEffect:"fade",transitionDuration:366,slideClass:"",baseClass:"",baseTpl:'',spinnerTpl:'
',errorTpl:'

{{ERROR}}

',btnTpl:{download:'',zoom:'',close:'',arrowLeft:'',arrowRight:'',smallBtn:''},parentEl:"body",hideScrollbar:!0,autoFocus:!0,backFocus:!0,trapFocus:!0,fullScreen:{autoStart:!1},touch:{vertical:!0,momentum:!0},hash:null,media:{},slideShow:{autoStart:!1,speed:3e3},thumbs:{autoStart:!1,hideOnClose:!0,parentEl:".fancybox-container",axis:"y"},wheel:"auto",onInit:n.noop,beforeLoad:n.noop,afterLoad:n.noop,beforeShow:n.noop,afterShow:n.noop,beforeClose:n.noop,afterClose:n.noop,onActivate:n.noop,onDeactivate:n.noop,clickContent:function(t,e){return"image"===t.type&&"zoom"},clickSlide:"close",clickOutside:"close",dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1,mobile:{preventCaptionOverlap:!1,idleTime:!1,clickContent:function(t,e){return"image"===t.type&&"toggleControls"},clickSlide:function(t,e){return"image"===t.type?"toggleControls":"close"},dblclickContent:function(t,e){return"image"===t.type&&"zoom"},dblclickSlide:function(t,e){return"image"===t.type&&"zoom"}},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"}}},s=n(t),r=n(e),c=0,l=function(t){return t&&t.hasOwnProperty&&t instanceof n},d=function(){return t.requestAnimationFrame||t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||function(e){return t.setTimeout(e,1e3/60)}}(),u=function(){return t.cancelAnimationFrame||t.webkitCancelAnimationFrame||t.mozCancelAnimationFrame||t.oCancelAnimationFrame||function(e){t.clearTimeout(e)}}(),f=function(){var t,n=e.createElement("fakeelement"),o={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(t in o)if(void 0!==n.style[t])return o[t];return"transitionend"}(),p=function(t){return t&&t.length&&t[0].offsetHeight},h=function(t,e){var o=n.extend(!0,{},t,e);return n.each(e,function(t,e){n.isArray(e)&&(o[t]=e)}),o},g=function(t){var o,i;return!(!t||t.ownerDocument!==e)&&(n(".fancybox-container").css("pointer-events","none"),o={x:t.getBoundingClientRect().left+t.offsetWidth/2,y:t.getBoundingClientRect().top+t.offsetHeight/2},i=e.elementFromPoint(o.x,o.y)===t,n(".fancybox-container").css("pointer-events",""),i)},b=function(t,e,o){var i=this;i.opts=h({index:o},n.fancybox.defaults),n.isPlainObject(e)&&(i.opts=h(i.opts,e)),n.fancybox.isMobile&&(i.opts=h(i.opts,i.opts.mobile)),i.id=i.opts.id||++c,i.currIndex=parseInt(i.opts.index,10)||0,i.prevIndex=null,i.prevPos=null,i.currPos=0,i.firstRun=!0,i.group=[],i.slides={},i.addContent(t),i.group.length&&i.init()};n.extend(b.prototype,{init:function(){var o,i,a=this,s=a.group[a.currIndex],r=s.opts;r.closeExisting&&n.fancybox.close(!0),n("body").addClass("fancybox-active"),!n.fancybox.getInstance()&&!1!==r.hideScrollbar&&!n.fancybox.isMobile&&e.body.scrollHeight>t.innerHeight&&(n("head").append('"),n("body").addClass("compensate-for-scrollbar")),i="",n.each(r.buttons,function(t,e){i+=r.btnTpl[e]||""}),o=n(a.translate(a,r.baseTpl.replace("{{buttons}}",i).replace("{{arrows}}",r.btnTpl.arrowLeft+r.btnTpl.arrowRight))).attr("id","fancybox-container-"+a.id).addClass(r.baseClass).data("FancyBox",a).appendTo(r.parentEl),a.$refs={container:o},["bg","inner","infobar","toolbar","stage","caption","navigation"].forEach(function(t){a.$refs[t]=o.find(".fancybox-"+t)}),a.trigger("onInit"),a.activate(),a.jumpTo(a.currIndex)},translate:function(t,e){var n=t.opts.i18n[t.opts.lang]||t.opts.i18n.en;return e.replace(/\{\{(\w+)\}\}/g,function(t,e){return void 0===n[e]?t:n[e]})},addContent:function(t){var e,o=this,i=n.makeArray(t);n.each(i,function(t,e){var i,a,s,r,c,l={},d={};n.isPlainObject(e)?(l=e,d=e.opts||e):"object"===n.type(e)&&n(e).length?(i=n(e),d=i.data()||{},d=n.extend(!0,{},d,d.options),d.$orig=i,l.src=o.opts.src||d.src||i.attr("href"),l.type||l.src||(l.type="inline",l.src=e)):l={type:"html",src:e+""},l.opts=n.extend(!0,{},o.opts,d),n.isArray(d.buttons)&&(l.opts.buttons=d.buttons),n.fancybox.isMobile&&l.opts.mobile&&(l.opts=h(l.opts,l.opts.mobile)),a=l.type||l.opts.type,r=l.src||"",!a&&r&&((s=r.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))?(a="video",l.opts.video.format||(l.opts.video.format="video/"+("ogv"===s[1]?"ogg":s[1]))):r.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)?a="image":r.match(/\.(pdf)((\?|#).*)?$/i)?(a="iframe",l=n.extend(!0,l,{contentType:"pdf",opts:{iframe:{preload:!1}}})):"#"===r.charAt(0)&&(a="inline")),a?l.type=a:o.trigger("objectNeedsType",l),l.contentType||(l.contentType=n.inArray(l.type,["html","inline","ajax"])>-1?"html":l.type),l.index=o.group.length,"auto"==l.opts.smallBtn&&(l.opts.smallBtn=n.inArray(l.type,["html","inline","ajax"])>-1),"auto"===l.opts.toolbar&&(l.opts.toolbar=!l.opts.smallBtn),l.$thumb=l.opts.$thumb||null,l.opts.$trigger&&l.index===o.opts.index&&(l.$thumb=l.opts.$trigger.find("img:first"),l.$thumb.length&&(l.opts.$orig=l.opts.$trigger)),l.$thumb&&l.$thumb.length||!l.opts.$orig||(l.$thumb=l.opts.$orig.find("img:first")),l.$thumb&&!l.$thumb.length&&(l.$thumb=null),l.thumb=l.opts.thumb||(l.$thumb?l.$thumb[0].src:null),"function"===n.type(l.opts.caption)&&(l.opts.caption=l.opts.caption.apply(e,[o,l])),"function"===n.type(o.opts.caption)&&(l.opts.caption=o.opts.caption.apply(e,[o,l])),l.opts.caption instanceof n||(l.opts.caption=void 0===l.opts.caption?"":l.opts.caption+""),"ajax"===l.type&&(c=r.split(/\s+/,2),c.length>1&&(l.src=c.shift(),l.opts.filter=c.shift())),l.opts.modal&&(l.opts=n.extend(!0,l.opts,{trapFocus:!0,infobar:0,toolbar:0,smallBtn:0,keyboard:0,slideShow:0,fullScreen:0,thumbs:0,touch:0,clickContent:!1,clickSlide:!1,clickOutside:!1,dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1})),o.group.push(l)}),Object.keys(o.slides).length&&(o.updateControls(),(e=o.Thumbs)&&e.isActive&&(e.create(),e.focus()))},addEvents:function(){var e=this;e.removeEvents(),e.$refs.container.on("click.fb-close","[data-fancybox-close]",function(t){t.stopPropagation(),t.preventDefault(),e.close(t)}).on("touchstart.fb-prev click.fb-prev","[data-fancybox-prev]",function(t){t.stopPropagation(),t.preventDefault(),e.previous()}).on("touchstart.fb-next click.fb-next","[data-fancybox-next]",function(t){t.stopPropagation(),t.preventDefault(),e.next()}).on("click.fb","[data-fancybox-zoom]",function(t){e[e.isScaledDown()?"scaleToActual":"scaleToFit"]()}),s.on("orientationchange.fb resize.fb",function(t){t&&t.originalEvent&&"resize"===t.originalEvent.type?(e.requestId&&u(e.requestId),e.requestId=d(function(){e.update(t)})):(e.current&&"iframe"===e.current.type&&e.$refs.stage.hide(),setTimeout(function(){e.$refs.stage.show(),e.update(t)},n.fancybox.isMobile?600:250))}),r.on("keydown.fb",function(t){var o=n.fancybox?n.fancybox.getInstance():null,i=o.current,a=t.keyCode||t.which;if(9==a)return void(i.opts.trapFocus&&e.focus(t));if(!(!i.opts.keyboard||t.ctrlKey||t.altKey||t.shiftKey||n(t.target).is("input,textarea,video,audio,select")))return 8===a||27===a?(t.preventDefault(),void e.close(t)):37===a||38===a?(t.preventDefault(),void e.previous()):39===a||40===a?(t.preventDefault(),void e.next()):void e.trigger("afterKeydown",t,a)}),e.group[e.currIndex].opts.idleTime&&(e.idleSecondsCounter=0,r.on("mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",function(t){e.idleSecondsCounter=0,e.isIdle&&e.showControls(),e.isIdle=!1}),e.idleInterval=t.setInterval(function(){++e.idleSecondsCounter>=e.group[e.currIndex].opts.idleTime&&!e.isDragging&&(e.isIdle=!0,e.idleSecondsCounter=0,e.hideControls())},1e3))},removeEvents:function(){var e=this;s.off("orientationchange.fb resize.fb"),r.off("keydown.fb .fb-idle"),this.$refs.container.off(".fb-close .fb-prev .fb-next"),e.idleInterval&&(t.clearInterval(e.idleInterval),e.idleInterval=null)},previous:function(t){return this.jumpTo(this.currPos-1,t)},next:function(t){return this.jumpTo(this.currPos+1,t)},jumpTo:function(t,e){var o,i,a,s,r,c,l,d,u,f=this,h=f.group.length;if(!(f.isDragging||f.isClosing||f.isAnimating&&f.firstRun)){if(t=parseInt(t,10),!(a=f.current?f.current.opts.loop:f.opts.loop)&&(t<0||t>=h))return!1;if(o=f.firstRun=!Object.keys(f.slides).length,r=f.current,f.prevIndex=f.currIndex,f.prevPos=f.currPos,s=f.createSlide(t),h>1&&((a||s.index0)&&f.createSlide(t-1)),f.current=s,f.currIndex=s.index,f.currPos=s.pos,f.trigger("beforeShow",o),f.updateControls(),s.forcedDuration=void 0,n.isNumeric(e)?s.forcedDuration=e:e=s.opts[o?"animationDuration":"transitionDuration"],e=parseInt(e,10),i=f.isMoved(s),s.$slide.addClass("fancybox-slide--current"),o)return s.opts.animationEffect&&e&&f.$refs.container.css("transition-duration",e+"ms"),f.$refs.container.addClass("fancybox-is-open").trigger("focus"),f.loadSlide(s),void f.preload("image");c=n.fancybox.getTranslate(r.$slide),l=n.fancybox.getTranslate(f.$refs.stage),n.each(f.slides,function(t,e){n.fancybox.stop(e.$slide,!0)}),r.pos!==s.pos&&(r.isComplete=!1),r.$slide.removeClass("fancybox-slide--complete fancybox-slide--current"),i?(u=c.left-(r.pos*c.width+r.pos*r.opts.gutter),n.each(f.slides,function(t,o){o.$slide.removeClass("fancybox-animated").removeClass(function(t,e){return(e.match(/(^|\s)fancybox-fx-\S+/g)||[]).join(" ")});var i=o.pos*c.width+o.pos*o.opts.gutter;n.fancybox.setTranslate(o.$slide,{top:0,left:i-l.left+u}),o.pos!==s.pos&&o.$slide.addClass("fancybox-slide--"+(o.pos>s.pos?"next":"previous")),p(o.$slide),n.fancybox.animate(o.$slide,{top:0,left:(o.pos-s.pos)*c.width+(o.pos-s.pos)*o.opts.gutter},e,function(){o.$slide.css({transform:"",opacity:""}).removeClass("fancybox-slide--next fancybox-slide--previous"),o.pos===f.currPos&&f.complete()})})):e&&s.opts.transitionEffect&&(d="fancybox-animated fancybox-fx-"+s.opts.transitionEffect,r.$slide.addClass("fancybox-slide--"+(r.pos>s.pos?"next":"previous")),n.fancybox.animate(r.$slide,d,e,function(){r.$slide.removeClass(d).removeClass("fancybox-slide--next fancybox-slide--previous")},!1)),s.isLoaded?f.revealContent(s):f.loadSlide(s),f.preload("image")}},createSlide:function(t){var e,o,i=this;return o=t%i.group.length,o=o<0?i.group.length+o:o,!i.slides[t]&&i.group[o]&&(e=n('
').appendTo(i.$refs.stage),i.slides[t]=n.extend(!0,{},i.group[o],{pos:t,$slide:e,isLoaded:!1}),i.updateSlide(i.slides[t])),i.slides[t]},scaleToActual:function(t,e,o){var i,a,s,r,c,l=this,d=l.current,u=d.$content,f=n.fancybox.getTranslate(d.$slide).width,p=n.fancybox.getTranslate(d.$slide).height,h=d.width,g=d.height;l.isAnimating||l.isMoved()||!u||"image"!=d.type||!d.isLoaded||d.hasError||(l.isAnimating=!0,n.fancybox.stop(u),t=void 0===t?.5*f:t,e=void 0===e?.5*p:e,i=n.fancybox.getTranslate(u),i.top-=n.fancybox.getTranslate(d.$slide).top,i.left-=n.fancybox.getTranslate(d.$slide).left,r=h/i.width,c=g/i.height,a=.5*f-.5*h,s=.5*p-.5*g,h>f&&(a=i.left*r-(t*r-t),a>0&&(a=0),ap&&(s=i.top*c-(e*c-e),s>0&&(s=0),se-.5&&(l=e),d>o-.5&&(d=o),"image"===t.type?(u.top=Math.floor(.5*(o-d))+parseFloat(c.css("paddingTop")),u.left=Math.floor(.5*(e-l))+parseFloat(c.css("paddingLeft"))):"video"===t.contentType&&(a=t.opts.width&&t.opts.height?l/d:t.opts.ratio||16/9,d>l/a?d=l/a:l>d*a&&(l=d*a)),u.width=l,u.height=d,u)},update:function(t){var e=this;n.each(e.slides,function(n,o){e.updateSlide(o,t)})},updateSlide:function(t,e){var o=this,i=t&&t.$content,a=t.width||t.opts.width,s=t.height||t.opts.height,r=t.$slide;o.adjustCaption(t),i&&(a||s||"video"===t.contentType)&&!t.hasError&&(n.fancybox.stop(i),n.fancybox.setTranslate(i,o.getFitPos(t)),t.pos===o.currPos&&(o.isAnimating=!1,o.updateCursor())),o.adjustLayout(t),r.length&&(r.trigger("refresh"),t.pos===o.currPos&&o.$refs.toolbar.add(o.$refs.navigation.find(".fancybox-button--arrow_right")).toggleClass("compensate-for-scrollbar",r.get(0).scrollHeight>r.get(0).clientHeight)),o.trigger("onUpdate",t,e)},centerSlide:function(t){var e=this,o=e.current,i=o.$slide;!e.isClosing&&o&&(i.siblings().css({transform:"",opacity:""}),i.parent().children().removeClass("fancybox-slide--previous fancybox-slide--next"),n.fancybox.animate(i,{top:0,left:0,opacity:1},void 0===t?0:t,function(){i.css({transform:"",opacity:""}),o.isComplete||e.complete()},!1))},isMoved:function(t){var e,o,i=t||this.current;return!!i&&(o=n.fancybox.getTranslate(this.$refs.stage),e=n.fancybox.getTranslate(i.$slide),!i.$slide.hasClass("fancybox-animated")&&(Math.abs(e.top-o.top)>.5||Math.abs(e.left-o.left)>.5))},updateCursor:function(t,e){var o,i,a=this,s=a.current,r=a.$refs.container;s&&!a.isClosing&&a.Guestures&&(r.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan"),o=a.canPan(t,e),i=!!o||a.isZoomable(),r.toggleClass("fancybox-is-zoomable",i),n("[data-fancybox-zoom]").prop("disabled",!i),o?r.addClass("fancybox-can-pan"):i&&("zoom"===s.opts.clickContent||n.isFunction(s.opts.clickContent)&&"zoom"==s.opts.clickContent(s))?r.addClass("fancybox-can-zoomIn"):s.opts.touch&&(s.opts.touch.vertical||a.group.length>1)&&"video"!==s.contentType&&r.addClass("fancybox-can-swipe"))},isZoomable:function(){var t,e=this,n=e.current;if(n&&!e.isClosing&&"image"===n.type&&!n.hasError){if(!n.isLoaded)return!0;if((t=e.getFitPos(n))&&(n.width>t.width||n.height>t.height))return!0}return!1},isScaledDown:function(t,e){var o=this,i=!1,a=o.current,s=a.$content;return void 0!==t&&void 0!==e?i=t1.5||Math.abs(a.height-s.height)>1.5)),s},loadSlide:function(t){var e,o,i,a=this;if(!t.isLoading&&!t.isLoaded){if(t.isLoading=!0,!1===a.trigger("beforeLoad",t))return t.isLoading=!1,!1;switch(e=t.type,o=t.$slide,o.off("refresh").trigger("onReset").addClass(t.opts.slideClass),e){case"image":a.setImage(t);break;case"iframe":a.setIframe(t);break;case"html":a.setContent(t,t.src||t.content);break;case"video":a.setContent(t,t.opts.video.tpl.replace(/\{\{src\}\}/gi,t.src).replace("{{format}}",t.opts.videoFormat||t.opts.video.format||"").replace("{{poster}}",t.thumb||""));break;case"inline":n(t.src).length?a.setContent(t,n(t.src)):a.setError(t);break;case"ajax":a.showLoading(t),i=n.ajax(n.extend({},t.opts.ajax.settings,{url:t.src,success:function(e,n){"success"===n&&a.setContent(t,e)},error:function(e,n){e&&"abort"!==n&&a.setError(t)}})),o.one("onReset",function(){i.abort()});break;default:a.setError(t)}return!0}},setImage:function(t){var o,i=this;setTimeout(function(){var e=t.$image;i.isClosing||!t.isLoading||e&&e.length&&e[0].complete||t.hasError||i.showLoading(t)},50),i.checkSrcset(t),t.$content=n('
').addClass("fancybox-is-hidden").appendTo(t.$slide.addClass("fancybox-slide--image")),!1!==t.opts.preload&&t.opts.width&&t.opts.height&&t.thumb&&(t.width=t.opts.width,t.height=t.opts.height,o=e.createElement("img"),o.onerror=function(){n(this).remove(),t.$ghost=null},o.onload=function(){i.afterLoad(t)},t.$ghost=n(o).addClass("fancybox-image").appendTo(t.$content).attr("src",t.thumb)),i.setBigImage(t)},checkSrcset:function(e){var n,o,i,a,s=e.opts.srcset||e.opts.image.srcset;if(s){i=t.devicePixelRatio||1,a=t.innerWidth*i,o=s.split(",").map(function(t){var e={};return t.trim().split(/\s+/).forEach(function(t,n){var o=parseInt(t.substring(0,t.length-1),10);if(0===n)return e.url=t;o&&(e.value=o,e.postfix=t[t.length-1])}),e}),o.sort(function(t,e){return t.value-e.value});for(var r=0;r=a||"x"===c.postfix&&c.value>=i){n=c;break}}!n&&o.length&&(n=o[o.length-1]),n&&(e.src=n.url,e.width&&e.height&&"w"==n.postfix&&(e.height=e.width/e.height*n.value,e.width=n.value),e.opts.srcset=s)}},setBigImage:function(t){var o=this,i=e.createElement("img"),a=n(i);t.$image=a.one("error",function(){o.setError(t)}).one("load",function(){var e;t.$ghost||(o.resolveImageSlideSize(t,this.naturalWidth,this.naturalHeight),o.afterLoad(t)),o.isClosing||(t.opts.srcset&&(e=t.opts.sizes,e&&"auto"!==e||(e=(t.width/t.height>1&&s.width()/s.height()>1?"100":Math.round(t.width/t.height*100))+"vw"),a.attr("sizes",e).attr("srcset",t.opts.srcset)),t.$ghost&&setTimeout(function(){t.$ghost&&!o.isClosing&&t.$ghost.hide()},Math.min(300,Math.max(1e3,t.height/1600))),o.hideLoading(t))}).addClass("fancybox-image").attr("src",t.src).appendTo(t.$content),(i.complete||"complete"==i.readyState)&&a.naturalWidth&&a.naturalHeight?a.trigger("load"):i.error&&a.trigger("error")},resolveImageSlideSize:function(t,e,n){var o=parseInt(t.opts.width,10),i=parseInt(t.opts.height,10);t.width=e,t.height=n,o>0&&(t.width=o,t.height=Math.floor(o*n/e)),i>0&&(t.width=Math.floor(i*e/n),t.height=i)},setIframe:function(t){var e,o=this,i=t.opts.iframe,a=t.$slide;t.$content=n('
').css(i.css).appendTo(a),a.addClass("fancybox-slide--"+t.contentType),t.$iframe=e=n(i.tpl.replace(/\{rnd\}/g,(new Date).getTime())).attr(i.attr).appendTo(t.$content),i.preload?(o.showLoading(t),e.on("load.fb error.fb",function(e){this.isReady=1,t.$slide.trigger("refresh"),o.afterLoad(t)}),a.on("refresh.fb",function(){var n,o,s=t.$content,r=i.css.width,c=i.css.height;if(1===e[0].isReady){try{n=e.contents(),o=n.find("body")}catch(t){}o&&o.length&&o.children().length&&(a.css("overflow","visible"),s.css({width:"100%","max-width":"100%",height:"9999px"}),void 0===r&&(r=Math.ceil(Math.max(o[0].clientWidth,o.outerWidth(!0)))),s.css("width",r||"").css("max-width",""),void 0===c&&(c=Math.ceil(Math.max(o[0].clientHeight,o.outerHeight(!0)))),s.css("height",c||""),a.css("overflow","auto")),s.removeClass("fancybox-is-hidden")}})):o.afterLoad(t),e.attr("src",t.src),a.one("onReset",function(){try{n(this).find("iframe").hide().unbind().attr("src","//about:blank")}catch(t){}n(this).off("refresh.fb").empty(),t.isLoaded=!1,t.isRevealed=!1})},setContent:function(t,e){var o=this;o.isClosing||(o.hideLoading(t),t.$content&&n.fancybox.stop(t.$content),t.$slide.empty(),l(e)&&e.parent().length?((e.hasClass("fancybox-content")||e.parent().hasClass("fancybox-content"))&&e.parents(".fancybox-slide").trigger("onReset"),t.$placeholder=n("
").hide().insertAfter(e),e.css("display","inline-block")):t.hasError||("string"===n.type(e)&&(e=n("
").append(n.trim(e)).contents()),t.opts.filter&&(e=n("
").html(e).find(t.opts.filter))),t.$slide.one("onReset",function(){n(this).find("video,audio").trigger("pause"),t.$placeholder&&(t.$placeholder.after(e.removeClass("fancybox-content").hide()).remove(),t.$placeholder=null),t.$smallBtn&&(t.$smallBtn.remove(),t.$smallBtn=null),t.hasError||(n(this).empty(),t.isLoaded=!1,t.isRevealed=!1)}),n(e).appendTo(t.$slide),n(e).is("video,audio")&&(n(e).addClass("fancybox-video"),n(e).wrap("
"),t.contentType="video",t.opts.width=t.opts.width||n(e).attr("width"),t.opts.height=t.opts.height||n(e).attr("height")),t.$content=t.$slide.children().filter("div,form,main,video,audio,article,.fancybox-content").first(),t.$content.siblings().hide(),t.$content.length||(t.$content=t.$slide.wrapInner("
").children().first()),t.$content.addClass("fancybox-content"),t.$slide.addClass("fancybox-slide--"+t.contentType),o.afterLoad(t))},setError:function(t){t.hasError=!0,t.$slide.trigger("onReset").removeClass("fancybox-slide--"+t.contentType).addClass("fancybox-slide--error"),t.contentType="html",this.setContent(t,this.translate(t,t.opts.errorTpl)),t.pos===this.currPos&&(this.isAnimating=!1)},showLoading:function(t){var e=this;(t=t||e.current)&&!t.$spinner&&(t.$spinner=n(e.translate(e,e.opts.spinnerTpl)).appendTo(t.$slide).hide().fadeIn("fast"))},hideLoading:function(t){var e=this;(t=t||e.current)&&t.$spinner&&(t.$spinner.stop().remove(),delete t.$spinner)},afterLoad:function(t){var e=this;e.isClosing||(t.isLoading=!1,t.isLoaded=!0,e.trigger("afterLoad",t),e.hideLoading(t),!t.opts.smallBtn||t.$smallBtn&&t.$smallBtn.length||(t.$smallBtn=n(e.translate(t,t.opts.btnTpl.smallBtn)).appendTo(t.$content)),t.opts.protect&&t.$content&&!t.hasError&&(t.$content.on("contextmenu.fb",function(t){return 2==t.button&&t.preventDefault(),!0}),"image"===t.type&&n('
').appendTo(t.$content)),e.adjustCaption(t),e.adjustLayout(t),t.pos===e.currPos&&e.updateCursor(),e.revealContent(t))},adjustCaption:function(t){var e,n=this,o=t||n.current,i=o.opts.caption,a=o.opts.preventCaptionOverlap,s=n.$refs.caption,r=!1;s.toggleClass("fancybox-caption--separate",a),a&&i&&i.length&&(o.pos!==n.currPos?(e=s.clone().appendTo(s.parent()),e.children().eq(0).empty().html(i),r=e.outerHeight(!0),e.empty().remove()):n.$caption&&(r=n.$caption.outerHeight(!0)),o.$slide.css("padding-bottom",r||""))},adjustLayout:function(t){var e,n,o,i,a=this,s=t||a.current;s.isLoaded&&!0!==s.opts.disableLayoutFix&&(s.$content.css("margin-bottom",""),s.$content.outerHeight()>s.$slide.height()+.5&&(o=s.$slide[0].style["padding-bottom"],i=s.$slide.css("padding-bottom"),parseFloat(i)>0&&(e=s.$slide[0].scrollHeight,s.$slide.css("padding-bottom",0),Math.abs(e-s.$slide[0].scrollHeight)<1&&(n=i),s.$slide.css("padding-bottom",o))),s.$content.css("margin-bottom",n))},revealContent:function(t){var e,o,i,a,s=this,r=t.$slide,c=!1,l=!1,d=s.isMoved(t),u=t.isRevealed;return t.isRevealed=!0,e=t.opts[s.firstRun?"animationEffect":"transitionEffect"],i=t.opts[s.firstRun?"animationDuration":"transitionDuration"],i=parseInt(void 0===t.forcedDuration?i:t.forcedDuration,10),!d&&t.pos===s.currPos&&i||(e=!1),"zoom"===e&&(t.pos===s.currPos&&i&&"image"===t.type&&!t.hasError&&(l=s.getThumbPos(t))?c=s.getFitPos(t):e="fade"),"zoom"===e?(s.isAnimating=!0,c.scaleX=c.width/l.width,c.scaleY=c.height/l.height,a=t.opts.zoomOpacity,"auto"==a&&(a=Math.abs(t.width/t.height-l.width/l.height)>.1),a&&(l.opacity=.1,c.opacity=1),n.fancybox.setTranslate(t.$content.removeClass("fancybox-is-hidden"),l),p(t.$content),void n.fancybox.animate(t.$content,c,i,function(){s.isAnimating=!1,s.complete()})):(s.updateSlide(t),e?(n.fancybox.stop(r),o="fancybox-slide--"+(t.pos>=s.prevPos?"next":"previous")+" fancybox-animated fancybox-fx-"+e,r.addClass(o).removeClass("fancybox-slide--current"),t.$content.removeClass("fancybox-is-hidden"),p(r),"image"!==t.type&&t.$content.hide().show(0),void n.fancybox.animate(r,"fancybox-slide--current",i,function(){r.removeClass(o).css({transform:"",opacity:""}),t.pos===s.currPos&&s.complete()},!0)):(t.$content.removeClass("fancybox-is-hidden"),u||!d||"image"!==t.type||t.hasError||t.$content.hide().fadeIn("fast"),void(t.pos===s.currPos&&s.complete())))},getThumbPos:function(t){var e,o,i,a,s,r=!1,c=t.$thumb;return!(!c||!g(c[0]))&&(e=n.fancybox.getTranslate(c),o=parseFloat(c.css("border-top-width")||0),i=parseFloat(c.css("border-right-width")||0),a=parseFloat(c.css("border-bottom-width")||0),s=parseFloat(c.css("border-left-width")||0),r={top:e.top+o,left:e.left+s,width:e.width-i-s,height:e.height-o-a,scaleX:1,scaleY:1},e.width>0&&e.height>0&&r)},complete:function(){var t,e=this,o=e.current,i={};!e.isMoved()&&o.isLoaded&&(o.isComplete||(o.isComplete=!0,o.$slide.siblings().trigger("onReset"),e.preload("inline"),p(o.$slide),o.$slide.addClass("fancybox-slide--complete"),n.each(e.slides,function(t,o){o.pos>=e.currPos-1&&o.pos<=e.currPos+1?i[o.pos]=o:o&&(n.fancybox.stop(o.$slide),o.$slide.off().remove())}),e.slides=i),e.isAnimating=!1,e.updateCursor(),e.trigger("afterShow"),o.opts.video.autoStart&&o.$slide.find("video,audio").filter(":visible:first").trigger("play").one("ended",function(){Document.exitFullscreen?Document.exitFullscreen():this.webkitExitFullscreen&&this.webkitExitFullscreen(),e.next()}),o.opts.autoFocus&&"html"===o.contentType&&(t=o.$content.find("input[autofocus]:enabled:visible:first"),t.length?t.trigger("focus"):e.focus(null,!0)),o.$slide.scrollTop(0).scrollLeft(0))},preload:function(t){var e,n,o=this;o.group.length<2||(n=o.slides[o.currPos+1],e=o.slides[o.currPos-1],e&&e.type===t&&o.loadSlide(e),n&&n.type===t&&o.loadSlide(n))},focus:function(t,o){var i,a,s=this,r=["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(",");s.isClosing||(i=!t&&s.current&&s.current.isComplete?s.current.$slide.find("*:visible"+(o?":not(.fancybox-close-small)":"")):s.$refs.container.find("*:visible"),i=i.filter(r).filter(function(){return"hidden"!==n(this).css("visibility")&&!n(this).hasClass("disabled")}),i.length?(a=i.index(e.activeElement),t&&t.shiftKey?(a<0||0==a)&&(t.preventDefault(),i.eq(i.length-1).trigger("focus")):(a<0||a==i.length-1)&&(t&&t.preventDefault(),i.eq(0).trigger("focus"))):s.$refs.container.trigger("focus"))},activate:function(){var t=this;n(".fancybox-container").each(function(){var e=n(this).data("FancyBox");e&&e.id!==t.id&&!e.isClosing&&(e.trigger("onDeactivate"),e.removeEvents(),e.isVisible=!1)}),t.isVisible=!0,(t.current||t.isIdle)&&(t.update(),t.updateControls()),t.trigger("onActivate"),t.addEvents()},close:function(t,e){var o,i,a,s,r,c,l,u=this,f=u.current,h=function(){u.cleanUp(t)};return!u.isClosing&&(u.isClosing=!0,!1===u.trigger("beforeClose",t)?(u.isClosing=!1,d(function(){u.update()}),!1):(u.removeEvents(),a=f.$content,o=f.opts.animationEffect,i=n.isNumeric(e)?e:o?f.opts.animationDuration:0,f.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"),!0!==t?n.fancybox.stop(f.$slide):o=!1,f.$slide.siblings().trigger("onReset").remove(),i&&u.$refs.container.removeClass("fancybox-is-open").addClass("fancybox-is-closing").css("transition-duration",i+"ms"),u.hideLoading(f),u.hideControls(!0),u.updateCursor(),"zoom"!==o||a&&i&&"image"===f.type&&!u.isMoved()&&!f.hasError&&(l=u.getThumbPos(f))||(o="fade"),"zoom"===o?(n.fancybox.stop(a),s=n.fancybox.getTranslate(a),c={top:s.top,left:s.left,scaleX:s.width/l.width,scaleY:s.height/l.height,width:l.width,height:l.height},r=f.opts.zoomOpacity, +"auto"==r&&(r=Math.abs(f.width/f.height-l.width/l.height)>.1),r&&(l.opacity=0),n.fancybox.setTranslate(a,c),p(a),n.fancybox.animate(a,l,i,h),!0):(o&&i?n.fancybox.animate(f.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"),"fancybox-animated fancybox-fx-"+o,i,h):!0===t?setTimeout(h,i):h(),!0)))},cleanUp:function(e){var o,i,a,s=this,r=s.current.opts.$orig;s.current.$slide.trigger("onReset"),s.$refs.container.empty().remove(),s.trigger("afterClose",e),s.current.opts.backFocus&&(r&&r.length&&r.is(":visible")||(r=s.$trigger),r&&r.length&&(i=t.scrollX,a=t.scrollY,r.trigger("focus"),n("html, body").scrollTop(a).scrollLeft(i))),s.current=null,o=n.fancybox.getInstance(),o?o.activate():(n("body").removeClass("fancybox-active compensate-for-scrollbar"),n("#fancybox-style-noscroll").remove())},trigger:function(t,e){var o,i=Array.prototype.slice.call(arguments,1),a=this,s=e&&e.opts?e:a.current;if(s?i.unshift(s):s=a,i.unshift(a),n.isFunction(s.opts[t])&&(o=s.opts[t].apply(s,i)),!1===o)return o;"afterClose"!==t&&a.$refs?a.$refs.container.trigger(t+".fb",i):r.trigger(t+".fb",i)},updateControls:function(){var t=this,o=t.current,i=o.index,a=t.$refs.container,s=t.$refs.caption,r=o.opts.caption;o.$slide.trigger("refresh"),r&&r.length?(t.$caption=s,s.children().eq(0).html(r)):t.$caption=null,t.hasHiddenControls||t.isIdle||t.showControls(),a.find("[data-fancybox-count]").html(t.group.length),a.find("[data-fancybox-index]").html(i+1),a.find("[data-fancybox-prev]").prop("disabled",!o.opts.loop&&i<=0),a.find("[data-fancybox-next]").prop("disabled",!o.opts.loop&&i>=t.group.length-1),"image"===o.type?a.find("[data-fancybox-zoom]").show().end().find("[data-fancybox-download]").attr("href",o.opts.image.src||o.src).show():o.opts.toolbar&&a.find("[data-fancybox-download],[data-fancybox-zoom]").hide(),n(e.activeElement).is(":hidden,[disabled]")&&t.$refs.container.trigger("focus")},hideControls:function(t){var e=this,n=["infobar","toolbar","nav"];!t&&e.current.opts.preventCaptionOverlap||n.push("caption"),this.$refs.container.removeClass(n.map(function(t){return"fancybox-show-"+t}).join(" ")),this.hasHiddenControls=!0},showControls:function(){var t=this,e=t.current?t.current.opts:t.opts,n=t.$refs.container;t.hasHiddenControls=!1,t.idleSecondsCounter=0,n.toggleClass("fancybox-show-toolbar",!(!e.toolbar||!e.buttons)).toggleClass("fancybox-show-infobar",!!(e.infobar&&t.group.length>1)).toggleClass("fancybox-show-caption",!!t.$caption).toggleClass("fancybox-show-nav",!!(e.arrows&&t.group.length>1)).toggleClass("fancybox-is-modal",!!e.modal)},toggleControls:function(){this.hasHiddenControls?this.showControls():this.hideControls()}}),n.fancybox={version:"3.5.7",defaults:a,getInstance:function(t){var e=n('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"),o=Array.prototype.slice.call(arguments,1);return e instanceof b&&("string"===n.type(t)?e[t].apply(e,o):"function"===n.type(t)&&t.apply(e,o),e)},open:function(t,e,n){return new b(t,e,n)},close:function(t){var e=this.getInstance();e&&(e.close(),!0===t&&this.close(t))},destroy:function(){this.close(!0),r.add("body").off("click.fb-start","**")},isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),use3d:function(){var n=e.createElement("div");return t.getComputedStyle&&t.getComputedStyle(n)&&t.getComputedStyle(n).getPropertyValue("transform")&&!(e.documentMode&&e.documentMode<11)}(),getTranslate:function(t){var e;return!(!t||!t.length)&&(e=t[0].getBoundingClientRect(),{top:e.top||0,left:e.left||0,width:e.width,height:e.height,opacity:parseFloat(t.css("opacity"))})},setTranslate:function(t,e){var n="",o={};if(t&&e)return void 0===e.left&&void 0===e.top||(n=(void 0===e.left?t.position().left:e.left)+"px, "+(void 0===e.top?t.position().top:e.top)+"px",n=this.use3d?"translate3d("+n+", 0px)":"translate("+n+")"),void 0!==e.scaleX&&void 0!==e.scaleY?n+=" scale("+e.scaleX+", "+e.scaleY+")":void 0!==e.scaleX&&(n+=" scaleX("+e.scaleX+")"),n.length&&(o.transform=n),void 0!==e.opacity&&(o.opacity=e.opacity),void 0!==e.width&&(o.width=e.width),void 0!==e.height&&(o.height=e.height),t.css(o)},animate:function(t,e,o,i,a){var s,r=this;n.isFunction(o)&&(i=o,o=null),r.stop(t),s=r.getTranslate(t),t.on(f,function(c){(!c||!c.originalEvent||t.is(c.originalEvent.target)&&"z-index"!=c.originalEvent.propertyName)&&(r.stop(t),n.isNumeric(o)&&t.css("transition-duration",""),n.isPlainObject(e)?void 0!==e.scaleX&&void 0!==e.scaleY&&r.setTranslate(t,{top:e.top,left:e.left,width:s.width*e.scaleX,height:s.height*e.scaleY,scaleX:1,scaleY:1}):!0!==a&&t.removeClass(e),n.isFunction(i)&&i(c))}),n.isNumeric(o)&&t.css("transition-duration",o+"ms"),n.isPlainObject(e)?(void 0!==e.scaleX&&void 0!==e.scaleY&&(delete e.width,delete e.height,t.parent().hasClass("fancybox-slide--image")&&t.parent().addClass("fancybox-is-scaling")),n.fancybox.setTranslate(t,e)):t.addClass(e),t.data("timer",setTimeout(function(){t.trigger(f)},o+33))},stop:function(t,e){t&&t.length&&(clearTimeout(t.data("timer")),e&&t.trigger(f),t.off(f).css("transition-duration",""),t.parent().removeClass("fancybox-is-scaling"))}},n.fn.fancybox=function(t){var e;return t=t||{},e=t.selector||!1,e?n("body").off("click.fb-start",e).on("click.fb-start",e,{options:t},i):this.off("click.fb-start").on("click.fb-start",{items:this,options:t},i),this},r.on("click.fb-start","[data-fancybox]",i),r.on("click.fb-start","[data-fancybox-trigger]",function(t){n('[data-fancybox="'+n(this).attr("data-fancybox-trigger")+'"]').eq(n(this).attr("data-fancybox-index")||0).trigger("click.fb-start",{$trigger:n(this)})}),function(){var t=null;r.on("mousedown mouseup focus blur",".fancybox-button",function(e){switch(e.type){case"mousedown":t=n(this);break;case"mouseup":t=null;break;case"focusin":n(".fancybox-button").removeClass("fancybox-focus"),n(this).is(t)||n(this).is("[disabled]")||n(this).addClass("fancybox-focus");break;case"focusout":n(".fancybox-button").removeClass("fancybox-focus")}})}()}}(window,document,jQuery),function(t){"use strict";var e={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"},gmap_place:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,type:"iframe",url:function(t){return"//maps.google."+t[2]+"/?ll="+(t[9]?t[9]+"&z="+Math.floor(t[10])+(t[12]?t[12].replace(/^\//,"&"):""):t[12]+"").replace(/\?/,"&")+"&output="+(t[12]&&t[12].indexOf("layer=c")>0?"svembed":"embed")}},gmap_search:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,type:"iframe",url:function(t){return"//maps.google."+t[2]+"/maps?q="+t[5].replace("query=","q=").replace("api=1","")+"&output=embed"}}},n=function(e,n,o){if(e)return o=o||"","object"===t.type(o)&&(o=t.param(o,!0)),t.each(n,function(t,n){e=e.replace("$"+t,n||"")}),o.length&&(e+=(e.indexOf("?")>0?"&":"?")+o),e};t(document).on("objectNeedsType.fb",function(o,i,a){var s,r,c,l,d,u,f,p=a.src||"",h=!1;s=t.extend(!0,{},e,a.opts.media),t.each(s,function(e,o){if(c=p.match(o.matcher)){if(h=o.type,f=e,u={},o.paramPlace&&c[o.paramPlace]){d=c[o.paramPlace],"?"==d[0]&&(d=d.substring(1)),d=d.split("&");for(var i=0;i1&&("youtube"===n.contentSource||"vimeo"===n.contentSource)&&o.load(n.contentSource)}})}(jQuery),function(t,e,n){"use strict";var o=function(){return t.requestAnimationFrame||t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||function(e){return t.setTimeout(e,1e3/60)}}(),i=function(){return t.cancelAnimationFrame||t.webkitCancelAnimationFrame||t.mozCancelAnimationFrame||t.oCancelAnimationFrame||function(e){t.clearTimeout(e)}}(),a=function(e){var n=[];e=e.originalEvent||e||t.e,e=e.touches&&e.touches.length?e.touches:e.changedTouches&&e.changedTouches.length?e.changedTouches:[e];for(var o in e)e[o].pageX?n.push({x:e[o].pageX,y:e[o].pageY}):e[o].clientX&&n.push({x:e[o].clientX,y:e[o].clientY});return n},s=function(t,e,n){return e&&t?"x"===n?t.x-e.x:"y"===n?t.y-e.y:Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2)):0},r=function(t){if(t.is('a,area,button,[role="button"],input,label,select,summary,textarea,video,audio,iframe')||n.isFunction(t.get(0).onclick)||t.data("selectable"))return!0;for(var e=0,o=t[0].attributes,i=o.length;ee.clientHeight,a=("scroll"===o||"auto"===o)&&e.scrollWidth>e.clientWidth;return i||a},l=function(t){for(var e=!1;;){if(e=c(t.get(0)))break;if(t=t.parent(),!t.length||t.hasClass("fancybox-stage")||t.is("body"))break}return e},d=function(t){var e=this;e.instance=t,e.$bg=t.$refs.bg,e.$stage=t.$refs.stage,e.$container=t.$refs.container,e.destroy(),e.$container.on("touchstart.fb.touch mousedown.fb.touch",n.proxy(e,"ontouchstart"))};d.prototype.destroy=function(){var t=this;t.$container.off(".fb.touch"),n(e).off(".fb.touch"),t.requestId&&(i(t.requestId),t.requestId=null),t.tapped&&(clearTimeout(t.tapped),t.tapped=null)},d.prototype.ontouchstart=function(o){var i=this,c=n(o.target),d=i.instance,u=d.current,f=u.$slide,p=u.$content,h="touchstart"==o.type;if(h&&i.$container.off("mousedown.fb.touch"),(!o.originalEvent||2!=o.originalEvent.button)&&f.length&&c.length&&!r(c)&&!r(c.parent())&&(c.is("img")||!(o.originalEvent.clientX>c[0].clientWidth+c.offset().left))){if(!u||d.isAnimating||u.$slide.hasClass("fancybox-animated"))return o.stopPropagation(),void o.preventDefault();i.realPoints=i.startPoints=a(o),i.startPoints.length&&(u.touch&&o.stopPropagation(),i.startEvent=o,i.canTap=!0,i.$target=c,i.$content=p,i.opts=u.opts.touch,i.isPanning=!1,i.isSwiping=!1,i.isZooming=!1,i.isScrolling=!1,i.canPan=d.canPan(),i.startTime=(new Date).getTime(),i.distanceX=i.distanceY=i.distance=0,i.canvasWidth=Math.round(f[0].clientWidth),i.canvasHeight=Math.round(f[0].clientHeight),i.contentLastPos=null,i.contentStartPos=n.fancybox.getTranslate(i.$content)||{top:0,left:0},i.sliderStartPos=n.fancybox.getTranslate(f),i.stagePos=n.fancybox.getTranslate(d.$refs.stage),i.sliderStartPos.top-=i.stagePos.top,i.sliderStartPos.left-=i.stagePos.left,i.contentStartPos.top-=i.stagePos.top,i.contentStartPos.left-=i.stagePos.left,n(e).off(".fb.touch").on(h?"touchend.fb.touch touchcancel.fb.touch":"mouseup.fb.touch mouseleave.fb.touch",n.proxy(i,"ontouchend")).on(h?"touchmove.fb.touch":"mousemove.fb.touch",n.proxy(i,"ontouchmove")),n.fancybox.isMobile&&e.addEventListener("scroll",i.onscroll,!0),((i.opts||i.canPan)&&(c.is(i.$stage)||i.$stage.find(c).length)||(c.is(".fancybox-image")&&o.preventDefault(),n.fancybox.isMobile&&c.parents(".fancybox-caption").length))&&(i.isScrollable=l(c)||l(c.parent()),n.fancybox.isMobile&&i.isScrollable||o.preventDefault(),(1===i.startPoints.length||u.hasError)&&(i.canPan?(n.fancybox.stop(i.$content),i.isPanning=!0):i.isSwiping=!0,i.$container.addClass("fancybox-is-grabbing")),2===i.startPoints.length&&"image"===u.type&&(u.isLoaded||u.$ghost)&&(i.canTap=!1,i.isSwiping=!1,i.isPanning=!1,i.isZooming=!0,n.fancybox.stop(i.$content),i.centerPointStartX=.5*(i.startPoints[0].x+i.startPoints[1].x)-n(t).scrollLeft(),i.centerPointStartY=.5*(i.startPoints[0].y+i.startPoints[1].y)-n(t).scrollTop(),i.percentageOfImageAtPinchPointX=(i.centerPointStartX-i.contentStartPos.left)/i.contentStartPos.width,i.percentageOfImageAtPinchPointY=(i.centerPointStartY-i.contentStartPos.top)/i.contentStartPos.height,i.startDistanceBetweenFingers=s(i.startPoints[0],i.startPoints[1]))))}},d.prototype.onscroll=function(t){var n=this;n.isScrolling=!0,e.removeEventListener("scroll",n.onscroll,!0)},d.prototype.ontouchmove=function(t){var e=this;return void 0!==t.originalEvent.buttons&&0===t.originalEvent.buttons?void e.ontouchend(t):e.isScrolling?void(e.canTap=!1):(e.newPoints=a(t),void((e.opts||e.canPan)&&e.newPoints.length&&e.newPoints.length&&(e.isSwiping&&!0===e.isSwiping||t.preventDefault(),e.distanceX=s(e.newPoints[0],e.startPoints[0],"x"),e.distanceY=s(e.newPoints[0],e.startPoints[0],"y"),e.distance=s(e.newPoints[0],e.startPoints[0]),e.distance>0&&(e.isSwiping?e.onSwipe(t):e.isPanning?e.onPan():e.isZooming&&e.onZoom()))))},d.prototype.onSwipe=function(e){var a,s=this,r=s.instance,c=s.isSwiping,l=s.sliderStartPos.left||0;if(!0!==c)"x"==c&&(s.distanceX>0&&(s.instance.group.length<2||0===s.instance.current.index&&!s.instance.current.opts.loop)?l+=Math.pow(s.distanceX,.8):s.distanceX<0&&(s.instance.group.length<2||s.instance.current.index===s.instance.group.length-1&&!s.instance.current.opts.loop)?l-=Math.pow(-s.distanceX,.8):l+=s.distanceX),s.sliderLastPos={top:"x"==c?0:s.sliderStartPos.top+s.distanceY,left:l},s.requestId&&(i(s.requestId),s.requestId=null),s.requestId=o(function(){s.sliderLastPos&&(n.each(s.instance.slides,function(t,e){var o=e.pos-s.instance.currPos;n.fancybox.setTranslate(e.$slide,{top:s.sliderLastPos.top,left:s.sliderLastPos.left+o*s.canvasWidth+o*e.opts.gutter})}),s.$container.addClass("fancybox-is-sliding"))});else if(Math.abs(s.distance)>10){if(s.canTap=!1,r.group.length<2&&s.opts.vertical?s.isSwiping="y":r.isDragging||!1===s.opts.vertical||"auto"===s.opts.vertical&&n(t).width()>800?s.isSwiping="x":(a=Math.abs(180*Math.atan2(s.distanceY,s.distanceX)/Math.PI),s.isSwiping=a>45&&a<135?"y":"x"),"y"===s.isSwiping&&n.fancybox.isMobile&&s.isScrollable)return void(s.isScrolling=!0);r.isDragging=s.isSwiping,s.startPoints=s.newPoints,n.each(r.slides,function(t,e){var o,i;n.fancybox.stop(e.$slide),o=n.fancybox.getTranslate(e.$slide),i=n.fancybox.getTranslate(r.$refs.stage),e.$slide.css({transform:"",opacity:"","transition-duration":""}).removeClass("fancybox-animated").removeClass(function(t,e){return(e.match(/(^|\s)fancybox-fx-\S+/g)||[]).join(" ")}),e.pos===r.current.pos&&(s.sliderStartPos.top=o.top-i.top,s.sliderStartPos.left=o.left-i.left),n.fancybox.setTranslate(e.$slide,{top:o.top-i.top,left:o.left-i.left})}),r.SlideShow&&r.SlideShow.isActive&&r.SlideShow.stop()}},d.prototype.onPan=function(){var t=this;if(s(t.newPoints[0],t.realPoints[0])<(n.fancybox.isMobile?10:5))return void(t.startPoints=t.newPoints);t.canTap=!1,t.contentLastPos=t.limitMovement(),t.requestId&&i(t.requestId),t.requestId=o(function(){n.fancybox.setTranslate(t.$content,t.contentLastPos)})},d.prototype.limitMovement=function(){var t,e,n,o,i,a,s=this,r=s.canvasWidth,c=s.canvasHeight,l=s.distanceX,d=s.distanceY,u=s.contentStartPos,f=u.left,p=u.top,h=u.width,g=u.height;return i=h>r?f+l:f,a=p+d,t=Math.max(0,.5*r-.5*h),e=Math.max(0,.5*c-.5*g),n=Math.min(r-h,.5*r-.5*h),o=Math.min(c-g,.5*c-.5*g),l>0&&i>t&&(i=t-1+Math.pow(-t+f+l,.8)||0),l<0&&i0&&a>e&&(a=e-1+Math.pow(-e+p+d,.8)||0),d<0&&aa?(t=t>0?0:t,t=ts?(e=e>0?0:e,e=e1&&(o.dMs>130&&s>10||s>50);o.sliderLastPos=null,"y"==t&&!e&&Math.abs(o.distanceY)>50?(n.fancybox.animate(o.instance.current.$slide,{top:o.sliderStartPos.top+o.distanceY+150*o.velocityY,opacity:0},200),i=o.instance.close(!0,250)):r&&o.distanceX>0?i=o.instance.previous(300):r&&o.distanceX<0&&(i=o.instance.next(300)),!1!==i||"x"!=t&&"y"!=t||o.instance.centerSlide(200),o.$container.removeClass("fancybox-is-sliding")},d.prototype.endPanning=function(){var t,e,o,i=this;i.contentLastPos&&(!1===i.opts.momentum||i.dMs>350?(t=i.contentLastPos.left,e=i.contentLastPos.top):(t=i.contentLastPos.left+500*i.velocityX,e=i.contentLastPos.top+500*i.velocityY),o=i.limitPosition(t,e,i.contentStartPos.width,i.contentStartPos.height),o.width=i.contentStartPos.width,o.height=i.contentStartPos.height,n.fancybox.animate(i.$content,o,366))},d.prototype.endZooming=function(){var t,e,o,i,a=this,s=a.instance.current,r=a.newWidth,c=a.newHeight;a.contentLastPos&&(t=a.contentLastPos.left,e=a.contentLastPos.top,i={top:e,left:t,width:r,height:c,scaleX:1,scaleY:1},n.fancybox.setTranslate(a.$content,i),rs.width||c>s.height?a.instance.scaleToActual(a.centerPointStartX,a.centerPointStartY,150):(o=a.limitPosition(t,e,r,c),n.fancybox.animate(a.$content,o,150)))},d.prototype.onTap=function(e){var o,i=this,s=n(e.target),r=i.instance,c=r.current,l=e&&a(e)||i.startPoints,d=l[0]?l[0].x-n(t).scrollLeft()-i.stagePos.left:0,u=l[0]?l[0].y-n(t).scrollTop()-i.stagePos.top:0,f=function(t){var o=c.opts[t];if(n.isFunction(o)&&(o=o.apply(r,[c,e])),o)switch(o){case"close":r.close(i.startEvent);break;case"toggleControls":r.toggleControls();break;case"next":r.next();break;case"nextOrClose":r.group.length>1?r.next():r.close(i.startEvent);break;case"zoom":"image"==c.type&&(c.isLoaded||c.$ghost)&&(r.canPan()?r.scaleToFit():r.isScaledDown()?r.scaleToActual(d,u):r.group.length<2&&r.close(i.startEvent))}};if((!e.originalEvent||2!=e.originalEvent.button)&&(s.is("img")||!(d>s[0].clientWidth+s.offset().left))){if(s.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container"))o="Outside";else if(s.is(".fancybox-slide"))o="Slide";else{if(!r.current.$content||!r.current.$content.find(s).addBack().filter(s).length)return;o="Content"}if(i.tapped){if(clearTimeout(i.tapped),i.tapped=null,Math.abs(d-i.tapX)>50||Math.abs(u-i.tapY)>50)return this;f("dblclick"+o)}else i.tapX=d,i.tapY=u,c.opts["dblclick"+o]&&c.opts["dblclick"+o]!==c.opts["click"+o]?i.tapped=setTimeout(function(){i.tapped=null,r.isAnimating||f("click"+o)},500):f("click"+o);return this}},n(e).on("onActivate.fb",function(t,e){e&&!e.Guestures&&(e.Guestures=new d(e))}).on("beforeClose.fb",function(t,e){e&&e.Guestures&&e.Guestures.destroy()})}(window,document,jQuery),function(t,e){"use strict";e.extend(!0,e.fancybox.defaults,{btnTpl:{slideShow:''},slideShow:{autoStart:!1,speed:3e3,progress:!0}});var n=function(t){this.instance=t,this.init()};e.extend(n.prototype,{timer:null,isActive:!1,$button:null,init:function(){var t=this,n=t.instance,o=n.group[n.currIndex].opts.slideShow;t.$button=n.$refs.toolbar.find("[data-fancybox-play]").on("click",function(){t.toggle()}),n.group.length<2||!o?t.$button.hide():o.progress&&(t.$progress=e('
').appendTo(n.$refs.inner))},set:function(t){var n=this,o=n.instance,i=o.current;i&&(!0===t||i.opts.loop||o.currIndex'},fullScreen:{autoStart:!1}}),e(t).on(n.fullscreenchange,function(){var t=o.isFullscreen(),n=e.fancybox.getInstance();n&&(n.current&&"image"===n.current.type&&n.isAnimating&&(n.isAnimating=!1,n.update(!0,!0,0),n.isComplete||n.complete()),n.trigger("onFullscreenChange",t),n.$refs.container.toggleClass("fancybox-is-fullscreen",t),n.$refs.toolbar.find("[data-fancybox-fullscreen]").toggleClass("fancybox-button--fsenter",!t).toggleClass("fancybox-button--fsexit",t))})}e(t).on({"onInit.fb":function(t,e){var i;if(!n)return void e.$refs.toolbar.find("[data-fancybox-fullscreen]").remove();e&&e.group[e.currIndex].opts.fullScreen?(i=e.$refs.container,i.on("click.fb-fullscreen","[data-fancybox-fullscreen]",function(t){t.stopPropagation(),t.preventDefault(),o.toggle()}),e.opts.fullScreen&&!0===e.opts.fullScreen.autoStart&&o.request(),e.FullScreen=o):e&&e.$refs.toolbar.find("[data-fancybox-fullscreen]").hide()},"afterKeydown.fb":function(t,e,n,o,i){e&&e.FullScreen&&70===i&&(o.preventDefault(),e.FullScreen.toggle())},"beforeClose.fb":function(t,e){e&&e.FullScreen&&e.$refs.container.hasClass("fancybox-is-fullscreen")&&o.exit()}})}(document,jQuery),function(t,e){"use strict";var n="fancybox-thumbs";e.fancybox.defaults=e.extend(!0,{btnTpl:{thumbs:''},thumbs:{autoStart:!1,hideOnClose:!0,parentEl:".fancybox-container",axis:"y"}},e.fancybox.defaults);var o=function(t){this.init(t)};e.extend(o.prototype,{$button:null,$grid:null,$list:null,isVisible:!1,isActive:!1,init:function(t){var e=this,n=t.group,o=0;e.instance=t,e.opts=n[t.currIndex].opts.thumbs,t.Thumbs=e,e.$button=t.$refs.toolbar.find("[data-fancybox-thumbs]");for(var i=0,a=n.length;i1));i++);o>1&&e.opts?(e.$button.removeAttr("style").on("click",function(){e.toggle()}),e.isActive=!0):e.$button.hide()},create:function(){var t,o=this,i=o.instance,a=o.opts.parentEl,s=[];o.$grid||(o.$grid=e('
').appendTo(i.$refs.container.find(a).addBack().filter(a)),o.$grid.on("click","a",function(){i.jumpTo(e(this).attr("data-index"))})),o.$list||(o.$list=e('
').appendTo(o.$grid)),e.each(i.group,function(e,n){t=n.thumb,t||"image"!==n.type||(t=n.src),s.push('")}),o.$list[0].innerHTML=s.join(""),"x"===o.opts.axis&&o.$list.width(parseInt(o.$grid.css("padding-right"),10)+i.group.length*o.$list.children().eq(0).outerWidth(!0))},focus:function(t){var e,n,o=this,i=o.$list,a=o.$grid;o.instance.current&&(e=i.children().removeClass("fancybox-thumbs-active").filter('[data-index="'+o.instance.current.index+'"]').addClass("fancybox-thumbs-active"),n=e.position(),"y"===o.opts.axis&&(n.top<0||n.top>i.height()-e.outerHeight())?i.stop().animate({scrollTop:i.scrollTop()+n.top},t):"x"===o.opts.axis&&(n.lefta.scrollLeft()+(a.width()-e.outerWidth()))&&i.parent().stop().animate({scrollLeft:n.left},t))},update:function(){var t=this;t.instance.$refs.container.toggleClass("fancybox-show-thumbs",this.isVisible),t.isVisible?(t.$grid||t.create(),t.instance.trigger("onThumbsShow"),t.focus(0)):t.$grid&&t.instance.trigger("onThumbsHide"),t.instance.update()},hide:function(){this.isVisible=!1,this.update()},show:function(){this.isVisible=!0,this.update()},toggle:function(){this.isVisible=!this.isVisible,this.update()}}),e(t).on({"onInit.fb":function(t,e){var n;e&&!e.Thumbs&&(n=new o(e),n.isActive&&!0===n.opts.autoStart&&n.show())},"beforeShow.fb":function(t,e,n,o){var i=e&&e.Thumbs;i&&i.isVisible&&i.focus(o?0:250)},"afterKeydown.fb":function(t,e,n,o,i){var a=e&&e.Thumbs;a&&a.isActive&&71===i&&(o.preventDefault(),a.toggle())},"beforeClose.fb":function(t,e){var n=e&&e.Thumbs;n&&n.isVisible&&!1!==n.opts.hideOnClose&&n.$grid.hide()}})}(document,jQuery),function(t,e){"use strict";function n(t){var e={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};return String(t).replace(/[&<>"'`=\/]/g,function(t){return e[t]})}e.extend(!0,e.fancybox.defaults,{btnTpl:{share:''},share:{url:function(t,e){return!t.currentHash&&"inline"!==e.type&&"html"!==e.type&&(e.origSrc||e.src)||window.location}, +tpl:''}}),e(t).on("click","[data-fancybox-share]",function(){var t,o,i=e.fancybox.getInstance(),a=i.current||null;a&&("function"===e.type(a.opts.share.url)&&(t=a.opts.share.url.apply(a,[i,a])),o=a.opts.share.tpl.replace(/\{\{media\}\}/g,"image"===a.type?encodeURIComponent(a.src):"").replace(/\{\{url\}\}/g,encodeURIComponent(t)).replace(/\{\{url_raw\}\}/g,n(t)).replace(/\{\{descr\}\}/g,i.$caption?encodeURIComponent(i.$caption.text()):""),e.fancybox.open({src:i.translate(i,o),type:"html",opts:{touch:!1,animationEffect:!1,afterLoad:function(t,e){i.$refs.container.one("beforeClose.fb",function(){t.close(null,0)}),e.$content.find(".fancybox-share__button").click(function(){return window.open(this.href,"Share","width=550, height=450"),!1})},mobile:{autoFocus:!1}}}))})}(document,jQuery),function(t,e,n){"use strict";function o(){var e=t.location.hash.substr(1),n=e.split("-"),o=n.length>1&&/^\+?\d+$/.test(n[n.length-1])?parseInt(n.pop(-1),10)||1:1,i=n.join("-");return{hash:e,index:o<1?1:o,gallery:i}}function i(t){""!==t.gallery&&n("[data-fancybox='"+n.escapeSelector(t.gallery)+"']").eq(t.index-1).focus().trigger("click.fb-start")}function a(t){var e,n;return!!t&&(e=t.current?t.current.opts:t.opts,""!==(n=e.hash||(e.$orig?e.$orig.data("fancybox")||e.$orig.data("fancybox-trigger"):""))&&n)}n.escapeSelector||(n.escapeSelector=function(t){return(t+"").replace(/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t})}),n(function(){!1!==n.fancybox.defaults.hash&&(n(e).on({"onInit.fb":function(t,e){var n,i;!1!==e.group[e.currIndex].opts.hash&&(n=o(),(i=a(e))&&n.gallery&&i==n.gallery&&(e.currIndex=n.index-1))},"beforeShow.fb":function(n,o,i,s){var r;i&&!1!==i.opts.hash&&(r=a(o))&&(o.currentHash=r+(o.group.length>1?"-"+(i.index+1):""),t.location.hash!=="#"+o.currentHash&&(s&&!o.origHash&&(o.origHash=t.location.hash),o.hashTimer&&clearTimeout(o.hashTimer),o.hashTimer=setTimeout(function(){"replaceState"in t.history?(t.history[s?"pushState":"replaceState"]({},e.title,t.location.pathname+t.location.search+"#"+o.currentHash),s&&(o.hasCreatedHistory=!0)):t.location.hash=o.currentHash,o.hashTimer=null},300)))},"beforeClose.fb":function(n,o,i){i&&!1!==i.opts.hash&&(clearTimeout(o.hashTimer),o.currentHash&&o.hasCreatedHistory?t.history.back():o.currentHash&&("replaceState"in t.history?t.history.replaceState({},e.title,t.location.pathname+t.location.search+(o.origHash||"")):t.location.hash=o.origHash),o.currentHash=null)}}),n(t).on("hashchange.fb",function(){var t=o(),e=null;n.each(n(".fancybox-container").get().reverse(),function(t,o){var i=n(o).data("FancyBox");if(i&&i.currentHash)return e=i,!1}),e?e.currentHash===t.gallery+"-"+t.index||1===t.index&&e.currentHash==t.gallery||(e.currentHash=null,e.close()):""!==t.gallery&&i(t)}),setTimeout(function(){n.fancybox.getInstance()||i(o())},50))})}(window,document,jQuery),function(t,e){"use strict";var n=(new Date).getTime();e(t).on({"onInit.fb":function(t,e,o){e.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll",function(t){var o=e.current,i=(new Date).getTime();e.group.length<2||!1===o.opts.wheel||"auto"===o.opts.wheel&&"image"!==o.type||(t.preventDefault(),t.stopPropagation(),o.$slide.hasClass("fancybox-animated")||(t=t.originalEvent||t,i-n<250||(n=i,e[(-t.deltaY||-t.deltaX||t.wheelDelta||-t.detail)<0?"next":"previous"]())))})}})}(document,jQuery); \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 70a5a2f5..49a573db 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,373 +1,302 @@ - - - fancyBox - touch enabled, responsive and fully customizable lightbox script - - - - - - - - - + - -
-
-

fancyBox3 Documentation

- - -
-
- -
-
- - - -

Introduction

- -

- fancyBox is a JavaScript library used to present images, videos and any html content in an elegant way. - It has all features you would expect - touch enabled, responsive and fully customizable. -

- -

- -

- 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
  • -
  • Nexus 7 Chrome
  • -
- -

Setup

- -

- You can install fancyBox by linking .css and .js 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>
+
+  
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+ +
+

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">
@@ -381,887 +310,1012 @@ 

Setup

<!-- Your HTML content goes here --> <!-- JS --> - <script src="//code.jquery.com/jquery-3.2.1.min.js"></script> + <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="jquery.fancybox.min.js"></script> </body> </html>
+

Download fancybox

-

Important

- -
    -
  • Make sure you add the jQuery library first
  • -
  • 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 files on your computer, - the code must be running on a web server -
  • -
- -

Download fancyBox

+

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

-

- You can 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. +

-

Package Managers

- -

- fancyBox can also be installed via Bower or npm. -

+
# NPM
+npm install @fancyapps/fancybox --save
 
-
# Bower
+# Bower
 bower install fancybox --save
-
-# NPM
-npm install @fancyapps/fancybox --save
 
- - -

How to Use

- -

Initialize with data attributes

- -

- The most basic way to use fancyBox is by adding the data-fancybox attribute to a link. - A caption can be added using the data-caption attribute. Example: -

- -
<a href="image.jpg" data-fancybox data-caption="My caption">
+          

+ 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>
-

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

- -

Initialize with JavaScript

+

+ View demo on CodePen +

-

- Select elements with a jQuery selector and call the fancybox method: -

+

+ 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: +

-
<script type="text/javascript">
-	$("[data-fancybox]").fancybox({
-		// Options will go here
-	});
-</script>
- - -

- Using this method, click event handler is attached only to the currently selected elements. -
- To attach click event listener for elements that exist now or in the future, use selector option. Example: -

+
<a href="image_1.jpg" data-fancybox="gallery" data-caption="Caption #1">
+	<img src="thumbnail_1.jpg" alt="" />
+</a>
 
-			
$().fancybox({
-  selector : '[data-fancybox="images"]',
-  loop     : true
-});
+<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 +

-

- View demo on CodePen -

+

Initialize with JavaScript

-

Manual calling of fancyBox

+

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

-

- fancyBox can be activated at any point within Javascript and therefore does not necessarily need a trigger element. +

$('[data-fancybox="gallery"]').fancybox({
+	// Options will go here
+});
+
- Example of displaying a simple message: -

+

+ View demo on CodePen +

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

+ 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: +

-

- See API section for more information and examples. -

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

Grouping

+

+ 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: -

+

Use with Javascript

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

+ 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: +

-<a href="image_2.jpg" data-fancybox="group" data-caption="Caption #2"> - <img src="thumbnail_2.jpg" alt="" /> -</a> -
+
$.fancybox.open('<div class="message"><h2>Hello!</h2><p>You are awesome!</p></div>');
+

+ View demo on CodePen +

+

+ Display iframed page: +

-

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: - -

<a href="images.php?id=123" data-type="image" data-caption="Caption">
+          
$.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

-

Images

+

+ The standard way of using fancybox is with a number of thumbnail images that link to larger 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">
+          
<a href="image.jpg" data-fancybox="images" data-caption="My caption">
 	<img src="thumbnail.jpg" alt="" />
 </a>
-

- View demo on CodePen -

+

+ 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: -

+

+ 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
  • -
+
    +
  • + 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">
+          
<a href="image.jpg" data-fancybox="images" data-width="2048" data-height="1365">
     <img src="thumbnail.jpg" />
 </a>
-

- View demo on CodePen -

+

+ 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 "scrset" so I 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: -

+

+ 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">
+          
<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 -

+

+ 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. -

+

+ 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({
+          
$('[data-fancybox]').fancybox({
 	protect: true
 });
-

- View demo on CodePen -

+

+ View demo on CodePen +

-

Inline HTML

+

Video

-

- For inline content, create a hidden element with unique id: -

+

+ 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. +

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

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

-

- And 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 href="https://www.youtube.com/watch?v=_sI_Ps7JSEk">
+    YouTube video
+</a>
 
-			
<a data-fancybox data-src="#hidden-content" href="javascript:;">
-	Hidden div
-</a>
-

- View demo on CodePen -

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

- 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. -

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

Ajax

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

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

+<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 data-type="ajax" data-src="my_page.com/path/to/ajax/" href="javascript:;">
-	AJAX content
+<a data-fancybox href="https://vimeo.com/191947042?color=f00">
+    Vimeo video - custom color
 </a>
-

- View demo on CodePen -

+

+ View demo on CodePen +

+ +

+ Via JavaScript: +

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

+ 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 -

-

Iframe

+

Iframe

-

- If the content can be shown on a page, and placement in an iframe is not blocked by script or security configuration of that page, - it can be presented in a fancyBox: -

+

+ 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:;">
+          
<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
+    Sample PDF file 
 </a>
 
-

- View demo on CodePen -

- - -

- To access and control fancyBox in parent window from inside an iframe: -

- -
// Adjust iframe height according to the contents
-parent.jQuery.fancybox.getInstance().update();
-
-// Close current fancyBox instance
-parent.jQuery.fancybox.getInstance().close();
- - -

- 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({
+          

+ 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 : {
-		css : {
-			width : '600px'
-		}
+		preload : false
 	}
-});
- - -

- If you have not disabled iframe preloading (using an "preload" option), then 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. -

- - - -

Embedding

- -

- Supported sites can be used with fancyBox by just providing the page URL: -

- -
<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 href="https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572">
-	Google Map
-</a>
-
-<a data-fancybox href="https://www.instagram.com/p/BNXYW8-goPI/?taken-by=jamesrelfdyer" data-caption="<span title=&quot;Edited&quot;>balloon rides at dawn ✨🎈<br>was such a magical experience floating over napa valley as the golden light hit the hills.<br><a href=&quot;https://www.instagram.com/jamesrelfdyer/&quot;>@jamesrelfdyer</a></span>">
-	Instagram photo
-</a>
-

- View demo on CodePen -

+}) +
+

+ View demo on CodePen +

-

Video dimensions

-

- Resize video display with the following CSS: -

+

+ Iframe dimensions can be controlled by CSS: +

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

- View demo on CodePen -

- -

- Obviously, you can choose any size you like, any combination with min/max values. -
- Aspect ratio lock for videos is not implemented yet, but if you wish, you can use this snippet. -

- - -

Video parameters

+

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

-

- Controlling a 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'
-	}
+          
$("[data-fancybox]").fancybox({
+    iframe : {
+        css : {
+            width : '600px'
+        }
+    }
 });
-

- View demo on CodePen -

- - -

Options

- -

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

- -
defaults = {
-
-	// Enable infinite gallery navigation
-	loop : false,
-
-	// Space around image, ignored if zoomed-in or viewport smaller than 800px
-	margin : [44, 0],
-
-	// Horizontal space between slides
-	gutter : 50,
-
-	// Enable keyboard navigation
-	keyboard : true,
-
-	// Should display navigation arrows at the screen edges
-	arrows : true,
-
-	// Should display infobar (counter and arrows at the top)
-	infobar : false,
 
-	// Should display toolbar (buttons at the top)
-	toolbar : true,
+          

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

- // 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 : [ - 'slideShow', - 'fullScreen', - 'thumbs', - 'close' - ], +
// Close current fancybox instance
+parent.jQuery.fancybox.getInstance().close();
 
-	// Detect "idle" time in seconds
-	idleTime : 4,
-
-	// Should display buttons at top right corner of the content
-	// If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax'
-	// Use template from `btnTpl.smallBtn` for customization
-	smallBtn : 'auto',
-
-	// 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
-		// Requires predefined image dimensions
-		// If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found
-		preload : "auto",
-
-	},
-
-	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" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen allowtransparency="true" 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'
-		}
-
-	},
-
-	// 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">' +
-					'<button data-fancybox-prev title="{{PREV}}" class="fancybox-button fancybox-button--left"></button>' +
-					'<div class="fancybox-infobar__body">' +
-						'<span data-fancybox-index></span>&nbsp;/&nbsp;<span data-fancybox-count></span>' +
-					'</div>' +
-					'<button data-fancybox-next title="{{NEXT}}" class="fancybox-button fancybox-button--right"></button>' +
-				'</div>' +
-				'<div class="fancybox-toolbar">' +
-					'{{BUTTONS}}' +
-				'</div>' +
-				'<div class="fancybox-navigation">' +
-					'<button data-fancybox-prev title="{{PREV}}" class="fancybox-arrow fancybox-arrow--left" />' +
-					'<button data-fancybox-next title="{{NEXT}}" class="fancybox-arrow fancybox-arrow--right" />' +
-				'</div>' +
-				'<div class="fancybox-stage"></div>' +
-				'<div class="fancybox-caption-wrap">' +
-					'<div class="fancybox-caption"></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 : {
-		slideShow  : '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}"></button>',
-		fullScreen : '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fullscreen" title="{{FULL_SCREEN}}"></button>',
-		thumbs     : '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}"></button>',
-		close      : '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}"></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 data-fancybox-close class="fancybox-close-small" title="{{CLOSE}}"></button>'
-	},
-
-	// Container is injected into this element
-	parentEl : 'body',
-
-
-	// 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,
-	},
-
-	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     : 4000
-	},
-
-	thumbs : {
-		autoStart   : false,   // Display thumbnails on opening
-		hideOnClose : true     // Hide thumbnail grid when closing animation starts
-	},
-
-	// Callbacks
-	//==========
+// Adjust iframe height according to the contents
+parent.jQuery.fancybox.getInstance().update();
+
- // See Documentation/API/Events for more information - // Example: - /* - afterShow: function( instance, current ) { - console.info( 'Clicked element:' ); - console.info( current.opts.$orig ); - } - */ +

Inline

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

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

- beforeLoad : $.noop, // Before the content of a slide is being loaded - afterLoad : $.noop, // When the content of a slide is done loading +
<div style="display: none;" id="hidden-content">
+	<h2>Hello</h2>
+	<p>You are awesome.</p>
+</div>
- beforeShow : $.noop, // Before open animation starts - afterShow : $.noop, // When content is done loading and animating +

+ 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): +

- beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close. - afterClose : $.noop, // After instance has been closed +
<a data-fancybox data-src="#hidden-content" href="javascript:;">
+	Trigger the fancybox
+</a>
+

+ View demo on CodePen +

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

+ 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 - - // Interaction - // =========== + view demo on CodePen. +

- // 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; - }, +

Ajax

- // Clicked on the slide - clickSlide : 'close', - - // Clicked on the background (backdrop) element - clickOutside : 'close', - - // Same as previous two, but for double click - dblclickContent : false, - dblclickSlide : false, - dblclickOutside : false, - - - // Custom options when mobile device is detected - // ============================================= - - mobile : { - 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; - } - }, +

+ 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 +

- // 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' - }, - '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' - } - } +

+ 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: -

+

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

-
$("[data-fancybox]").fancybox({
+          
$('[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";
+

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

-

- Custom options for each element individually can be set by adding a data-options - attribute to the element. +

$.fancybox.defaults.animationEffect = "fade";
- This attribute should contain the properly formatted JSON object: -

+

+ 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 : "iframe.html"}' href="javascript:;">
-	Open external page using iframe
-</a>
- - - - -

API

+<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 +

-

- 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. -

+

API

-

Core methods

+

+ 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 are methods which affect/handle instances: -

+

Core methods

+

+ Core methods are methods which affect/handle instances: +

-
// Close only the currently active or all fancyBox instances
-$.fancybox.close( all );
 
-// Open the fancyBox right away
+          
// Start new fancybox instance
 $.fancybox.open( items, opts, index );
-
- - -

- Gallery items can be collection of jQuery objects or array containing plain objects. This can be used, for example, to create content filter. -

-
var $links = $('.fancybox');
+// Get refrence to currently active fancybox instance
+$.fancybox.getInstance();
 
-$links.on('click', function() {
+// Close currently active fancybox instance (pass `true` to close all instances) 
+$.fancybox.close();
 
-	$.fancybox.open( $links, {
-		// Custom options
-	}, $links.index( this ) );
-
-	return false;
-});
-

- View demo on CodePen -

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

Starting fancybox

-

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

+

+ 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: +

-

- Example of opening image gallery: -

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

- View demo on CodePen -

+

+ View demo on CodePen +

-

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

+

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

-
$.fancybox.open({
+          
$.fancybox.open({
 	src  : '#hidden-content',
 	type : 'inline',
 	opts : {
@@ -1271,53 +1325,69 @@ 

Core methods

} });
-

- View demo on CodePen -

- -

- If you wish to display some html content (for example, a message), then you can use a simpler syntax. - It is advised to use a wrapper around your content. -

+

+ 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 +

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

- 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. +

-

Instance methods

+

+ 1) Using API method to get currently active instance: +

-

- In order to use these methods, you need an instance of the plugin's object. -

+
var instance = $.fancybox.getInstance();
-
var instance = $.fancybox.open(
+          

+ 2) While starting fancybox programmatically: +

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

- Get reference to currently active instance: -

- -
var instance = $.fancybox.getInstance();
- -

- The first argument of the callback is reference to instance: -

+

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

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

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

+

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

- -
// Go to next gallery item
+          
// Go to next gallery item
 instance.next( duration );
 
 // Go to previous gallery item
@@ -1366,29 +1436,25 @@ 

Instance methods

instance.close();
+

+ You can also do something like this: +

-

- You can also do something like this: -

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

- or simply: -

+

+ or simply: +

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

Events

+

+ fancybox fires several events: +

- -

Events

- -

- fancyBox fires several events: -

- -
beforeLoad   : Before the content of a slide is being loaded
+          
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
@@ -1401,12 +1467,12 @@ 

Events

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: +

-

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

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

Events

}); </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 ) {
+          

+ 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: -

+

+ 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 ) {
+          
beforeClose : function( instance, current, e ) {
 	if ( $('#my-field').val() == '' ) {
 		return false;
 	}
 }
 
-

Modules

+
+
+

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. -

+

+ 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: -

+

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

-
fullScreen : {
-	autoStart : false,
+          
fullScreen: {
+  autoStart: false
 },
 
 touch : {
-	vertical : true,  // Allow to drag content vertically
-	momentum : true   // Continuous movement when panning
+  vertical : true,  // Allow to drag content vertically
+  momentum : true   // Continuous movement when panning
 },
 
 // Hash value when initializing manually,
@@ -1489,63 +1558,115 @@ 

Modules

// Example: /* media : { - youtube : { - params : { - autoplay : 0 - } - } + youtube : { + params : { + autoplay : 0 + } + } } */ media : {}, slideShow : { - autoStart : false, - speed : 4000 + autoStart : false, + speed : 4000 }, thumbs : { - autoStart : false, // Display thumbnails on opening - hideOnClose : true // Hide thumbnail grid when closing animation starts + 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>" }
-

- Example (show thumbnails on start): -

+
Couple of examples
+ +

+ Show thumbnails on start: +

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

- View demo on CodePen -

+});
+

+ 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 $. -

+

+ 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: -

+

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

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

- This example shows how to call method that toggles thumbnails: -

+

+ This example shows how to call method that toggles thumbnails: +

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

- List of available methods: -

+

+ List of available methods: +

-
Thumbs.focus()
-Thumbs.close();
+          
Thumbs.focus()
 Thumbs.update();
 Thumbs.hide();
 Thumbs.show();
@@ -1562,230 +1683,318 @@ 

Modules

SlideShow.toggle();
-

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

+

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

-
$.fancybox.defaults.hash = false;
+
$.fancybox.defaults.hash = false;
- -

FAQ

+

FAQ

-

Opening/closing causes fixed element to jump

+

+ #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: -

+

+ 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">
+          
<nav class="navbar navbar-inverse navbar-fixed-top compensate-for-scrollbar">
 	<div class="container">
 		..
 	</div>
 </nav>
-

- 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: -

+

+ 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 {
+          
.navbar {
 	position: fixed;
 	top: 0;
 	left: 0;
 	right: 0;
 }
-

How to customize caption

+

+ #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: -

+

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

-
$( '[data-fancybox]' ).fancybox({
-	caption : function( instance, item ) {
-		var caption, link;
+          
$( '[data-fancybox="images"]' ).fancybox({
+    caption : function( instance, item ) {
+        var caption = $(this).data('caption') || '';
 
-		if ( item.type === 'image' ) {
-			caption = $(this).data('caption');
-			link    = '<a href="' + item.src + '">Download image</a>';
+        if ( item.type === 'image' ) {
+            caption = (caption.length ? caption + '<br />' : '') + '<a href="' + item.src + '">Download image</a>' ;
+        }
 
-			return (caption ? caption + '<br />' : '') + link;
-		}
-	}
+        return caption;
+    }
 });
-

- View demo on CodePen -

+

+ View demo on CodePen +

+

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

-

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

+
$( '[data-fancybox="images"]' ).fancybox({
+    infobar : false,
+    caption : function( instance, item ) {
+        var caption = $(this).data('caption') || '';
 
-				
$( '[data-fancybox]' ).fancybox({
-	caption : function( instance, item ) {
-		return $(this).find('figcaption').html();
-	}
+        return ( caption.length ? caption + '<br />' : '' ) + 'Image <span data-fancybox-index></span> of <span data-fancybox-count></span>';
+    }
 });
-

- View demo on CodePen -

- +

+ View demo on CodePen +

-

How to create custom button in the toolbar

+

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

-

- Example of creating reusable button and changing href property dynamically -

- -
// Create template for download button
-$.fancybox.defaults.btnTpl.download = '<a download class="fancybox-button fancybox-download"></a>';
-
-// Choose what buttons to display by default
-$.fancybox.defaults.buttons = [
-  'slideShow',
-  'fullScreen',
-  'thumbs',
-  'download',
-  'close'
-];
-
-// Dynamically update download url
-$( '[data-fancybox]' ).fancybox({
-	beforeShow : function( instance, current ) {
-		$('.fancybox-download').attr('href', current.src);
+          
$( '[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');
 });
-
- -

- View demo on CodePen -

- -

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 -

- -
+// 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
+});
 
-		
-
- - - + sticky.init(); + }); + + diff --git a/gulpfile.js b/gulpfile.js index 137dcfa7..efdde6ec 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,66 +1,68 @@ -var gulp = require('gulp'), - livereload = require('gulp-livereload'), - concat = require('gulp-concat'), - uglify = require('gulp-uglify'), - rename = require('gulp-rename'), - cssnano = require('gulp-cssnano'), - sass = require('gulp-sass'), - autoprefixer = require('gulp-autoprefixer'), - header = require('gulp-header'), - replace = require('gulp-replace'), - gutil = require('gulp-util'); +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'); +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', - ]) - .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')); - }); - +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(sass()) - .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')); +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']); +gulp.task("default", ["scripts", "css"]); diff --git a/package.json b/package.json index 5adc1742..132b29cd 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "@fancyapps/fancybox", "description": "Touch enabled, responsive and fully customizable jQuery lightbox script", - "version": "3.1.20", - "homepage": "http://fancyapps.com/fancybox/", - "main": "./dist/jquery.fancybox.min.js", + "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": { @@ -25,7 +26,6 @@ "gulp-notify": "^2.2.0", "gulp-rename": "^1.2.2", "gulp-replace": "^0.5.4", - "gulp-sass": "^3.0.0", "gulp-uglify": "^2.0.0", "gulp-util": "^3.0.8", "jshint": "^2.9.4" @@ -48,4 +48,4 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } -} +} \ No newline at end of file diff --git a/src/css/core.css b/src/css/core.css index 1060d138..5b6f7282 100644 --- a/src/css/core.css +++ b/src/css/core.css @@ -1,664 +1,658 @@ -.fancybox-enabled { - overflow: hidden; +body.compensate-for-scrollbar { + overflow: hidden; } -.fancybox-enabled body { - overflow: visible; - height: 100%; +.fancybox-active { + height: auto; } .fancybox-is-hidden { - position: absolute; - top: -9999px; - left: -9999px; - visibility: hidden; + left: -9999px; + margin: 0; + position: absolute !important; + top: -9999px; + visibility: hidden; } .fancybox-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 99993; - -webkit-tap-highlight-color: rgba(0,0,0,0); - backface-visibility: hidden; - transform: translateZ(0); + -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; } -/* Make sure that the first one is on the top */ -.fancybox-container ~ .fancybox-container { - z-index: 99992; +.fancybox-container * { + box-sizing: border-box; } .fancybox-outer, .fancybox-inner, .fancybox-bg, .fancybox-stage { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; } .fancybox-outer { - overflow-y: auto; - -webkit-overflow-scrolling: touch; + -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); + 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: 0.87; - transition-timing-function: cubic-bezier(.22,.61,.36,1); + opacity: .9; + transition-timing-function: cubic-bezier(.22, .61, .36, 1); } .fancybox-infobar, .fancybox-toolbar, -.fancybox-caption-wrap { - position: absolute; - direction: ltr; - z-index: 99997; - opacity: 0; - visibility: hidden; - transition: opacity .25s, visibility 0s linear .25s; - box-sizing: border-box; +.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-wrap { - opacity: 1; - visibility: visible; - transition: opacity .25s, visibility 0s; +.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 { - top: 0; - left : 50%; - margin-left: -79px; -} - -.fancybox-infobar__body { - display: inline-block; - width: 70px; - line-height: 44px; - font-size: 13px; - font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; - text-align: center; - color: #ddd; - background-color: rgba(30,30,30,0.7); - pointer-events: none; - user-select: none; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: rgba(0,0,0,0); - -webkit-font-smoothing: subpixel-antialiased; + 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 { - top: 0; - right: 0; + right: 0; + top: 0; } .fancybox-stage { - overflow: hidden; - direction: ltr; - z-index: 99994; - -webkit-transform: translate3d(0, 0, 0); + direction: ltr; + overflow: visible; + transform: translateZ(0); + z-index: 99994; +} + +.fancybox-is-open .fancybox-stage { + overflow: hidden; } .fancybox-slide { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - overflow: auto; - outline: none; - white-space: normal; - box-sizing: border-box; - text-align: center; - z-index: 99994; - -webkit-overflow-scrolling: touch; - display: none; - backface-visibility: hidden; - transition-property: transform, opacity; - transform-style: preserve-3d; + -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; - vertical-align: middle; - height: 100%; - width: 0; + 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; + display: block; } .fancybox-slide--image { - overflow: visible; + overflow: hidden; + padding: 44px 0; } .fancybox-slide--image::before { - display: none; -} - -.fancybox-slide--video .fancybox-content, -.fancybox-slide--video iframe { - background: #000; -} - -.fancybox-slide--map .fancybox-content, -.fancybox-slide--map iframe { - background: #E5E3DF; -} - -.fancybox-slide--next { - z-index: 99995; -} - -.fancybox-slide > * { - display: inline-block; - position: relative; - padding: 24px; - margin: 44px 0 44px; - border-width: 0; - vertical-align: middle; - text-align: left; - background-color: #fff; - overflow: auto; - box-sizing: border-box; + 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-slide .fancybox-image-wrap { - position: absolute; - top: 0; - left: 0; - margin: 0; - padding: 0; - border: 0; - z-index: 99995; - background: transparent; - cursor: default; - overflow: visible; - transform-origin: top left; - background-size: 100% 100%; - background-repeat: no-repeat; - backface-visibility: hidden; +.fancybox-can-zoomOut .fancybox-content { + cursor: zoom-out; } -.fancybox-can-zoomOut .fancybox-image-wrap { - cursor: zoom-out; +.fancybox-can-zoomIn .fancybox-content { + cursor: zoom-in; } -.fancybox-can-zoomIn .fancybox-image-wrap { - cursor: zoom-in; +.fancybox-can-swipe .fancybox-content, +.fancybox-can-pan .fancybox-content { + cursor: grab; } -.fancybox-can-drag .fancybox-image-wrap { - cursor: grab; +.fancybox-is-grabbing .fancybox-content { + cursor: grabbing; } -.fancybox-is-dragging .fancybox-image-wrap { - cursor: grabbing; +.fancybox-container [data-selectable='true'] { + cursor: text; } .fancybox-image, .fancybox-spaceball { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - border: 0; - max-width: none; - max-height: none; + 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-spaceball { + z-index: 1; } +.fancybox-slide--video .fancybox-content, +.fancybox-slide--map .fancybox-content, +.fancybox-slide--pdf .fancybox-content, .fancybox-slide--iframe .fancybox-content { - padding: 0; - width: 80%; - height: 80%; - max-width: calc(100% - 100px); - max-height: calc(100% - 88px); - overflow: visible; - background: #fff; -} - -.fancybox-iframe { - display: block; - margin: 0; - padding: 0; - border: 0; - width: 100%; - height: 100%; - background: #fff; -} - -.fancybox-error { - margin: 0; - padding: 40px; - width: 100%; - max-width: 380px; - background: #fff; - cursor: default; -} - -.fancybox-error p { - margin: 0; - padding: 0; - color: #444; - font: 16px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; + height: 100%; + overflow: visible; + padding: 0; + width: 100%; } -.fancybox-close-small { - position: absolute; - top: 0; - right: 0; - width: 44px; - height: 44px; - padding: 0; - margin: 0; - border: 0; - border-radius: 0; - outline: none; - background: transparent; - z-index: 10; - cursor: pointer; +.fancybox-slide--video .fancybox-content { + background: #000; } -.fancybox-close-small:after { - content: '×'; - position: absolute; - top : 5px; - right: 5px; - width: 30px; - height: 30px; - font: 20px/30px Arial,"Helvetica Neue",Helvetica,sans-serif; - color: #888; - font-weight: 300; - text-align: center; - border-radius: 50%; - border-width: 0; - background: #fff; - transition: background .25s; - box-sizing: border-box; - z-index: 2; +.fancybox-slide--map .fancybox-content { + background: #e5e3df; } -.fancybox-close-small:focus:after { - outline: 1px dotted #888; +.fancybox-slide--iframe .fancybox-content { + background: #fff; } -.fancybox-close-small:hover:after { - color: #555; - background: #eee; +.fancybox-video, +.fancybox-iframe { + background: transparent; + border: 0; + display: block; + height: 100%; + margin: 0; + overflow: hidden; + padding: 0; + width: 100%; } -.fancybox-slide--iframe .fancybox-close-small { - top : 0; - right : -44px; +/* Fix iOS */ +.fancybox-iframe { + left: 0; + position: absolute; + top: 0; } -.fancybox-slide--iframe .fancybox-close-small:after { - background: transparent; - font-size: 35px; - color: #aaa; +.fancybox-error { + background: #fff; + cursor: default; + max-width: 400px; + padding: 40px; + width: 100%; } -.fancybox-slide--iframe .fancybox-close-small:hover:after { - color: #fff; +.fancybox-error p { + color: #444; + font-size: 16px; + line-height: 20px; + margin: 0; + padding: 0; } +/* Buttons */ -/* Caption */ - -.fancybox-caption-wrap { - bottom: 0; - left: 0; - right: 0; - padding: 60px 30px 0 30px; - background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.1) 20%, rgba(0,0,0,0.2) 40%,rgba(0,0,0,0.6) 80%, rgba(0,0,0,0.8) 100%); - pointer-events: none; +.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-caption { - padding: 30px 0; - border-top: 1px solid rgba(255,255,255,0.4); - font-size: 14px; - font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; - color: #fff; - line-height: 20px; - -webkit-text-size-adjust: none; +.fancybox-button, +.fancybox-button:visited, +.fancybox-button:link { + color: #ccc; } -.fancybox-caption a, -.fancybox-caption button, -.fancybox-caption select { - pointer-events: all; +.fancybox-button:hover { + color: #fff; } -.fancybox-caption a { - color: #fff; - text-decoration: underline; +.fancybox-button:focus { + outline: none; } - -/* Buttons */ - -.fancybox-button { - display: inline-block; - position: relative; - margin: 0; - padding: 0; - border: 0; - width: 44px; - height: 44px; - line-height: 44px; - text-align: center; - background: transparent; - color: #ddd; - border-radius: 0; - cursor: pointer; - vertical-align: top; - outline: none; +.fancybox-button.fancybox-focus { + outline: 1px dotted; } -.fancybox-button[disabled] { - cursor: default; - pointer-events: none; +.fancybox-button[disabled], +.fancybox-button[disabled]:hover { + color: #888; + cursor: default; + outline: none; } -.fancybox-infobar__body, .fancybox-button { - background: rgba(30,30,30,0.6); +/* Fix IE11 */ +.fancybox-button div { + height: 100%; } -.fancybox-button:hover:not([disabled]) { - color: #fff; - background: rgba(0,0,0,0.8); +.fancybox-button svg { + display: block; + height: 100%; + overflow: visible; + position: relative; + width: 100%; } -.fancybox-button::before, -.fancybox-button::after { - content: ''; - pointer-events: none; - position: absolute; - background-color: currentColor; - color: currentColor; - opacity: 0.9; - box-sizing: border-box; - display: inline-block; +.fancybox-button svg path { + fill: currentColor; + stroke-width: 0; } -.fancybox-button[disabled]::before, -.fancybox-button[disabled]::after { - opacity: 0.3; +.fancybox-button--play svg:nth-child(2), +.fancybox-button--fsenter svg:nth-child(2) { + display: none; } -.fancybox-button--left::after, -.fancybox-button--right::after { - top: 18px; - width: 6px; - height: 6px; - background: transparent; - border-top: solid 2px currentColor; - border-right: solid 2px currentColor; +.fancybox-button--pause svg:nth-child(1), +.fancybox-button--fsexit svg:nth-child(1) { + display: none; } -.fancybox-button--left::after { - left: 20px; - transform: rotate(-135deg); +.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; } -.fancybox-button--right::after { - right: 20px; - transform: rotate(45deg); -} +/* Close button on the top right corner of html content */ -.fancybox-button--left { - border-bottom-left-radius: 5px; +.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-button--right { - border-bottom-right-radius: 5px; +.fancybox-close-small:hover { + color: #fff; + opacity: 1; } -.fancybox-button--close::before, .fancybox-button--close::after { - content: ''; - display: inline-block; - position: absolute; - height: 2px; - width: 16px; - top: calc(50% - 1px); - left: calc(50% - 8px); +.fancybox-slide--html .fancybox-close-small { + color: currentColor; + padding: 10px; + right: 0; + top: 0; } -.fancybox-button--close::before { - transform: rotate(45deg); +.fancybox-slide--image.fancybox-is-scaling .fancybox-content { + overflow: hidden; } -.fancybox-button--close::after { - transform: rotate(-45deg); +.fancybox-is-scaling .fancybox-close-small, +.fancybox-is-zoomable.fancybox-can-pan .fancybox-close-small { + display: none; } - /* Navigation arrows */ -.fancybox-arrow { - position: absolute; - top: 50%; - margin: -50px 0 0 0; - height: 100px; - width: 54px; - padding: 0; - border: 0; - outline: none; - background: none; - cursor: pointer; - z-index: 99995; - opacity: 0; - user-select: none; - transition: opacity .25s; +.fancybox-navigation .fancybox-button { + background-clip: content-box; + height: 100px; + opacity: 0; + position: absolute; + top: calc(50% - 50px); + width: 70px; } -.fancybox-arrow::after { - content : ''; - position: absolute; - top: 28px; - width: 44px; - height: 44px; - background-color: rgba(30,30,30,0.8); - background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjQ4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSI0OCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgNGwtMS40MSAxLjQxTDE2LjE3IDExSDR2MmgxMi4xN2wtNS41OCA1LjU5TDEyIDIwbDgtOHoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-position: center center; - background-size: 24px 24px; +.fancybox-navigation .fancybox-button div { + padding: 7px; } -.fancybox-arrow--right { - right: 0; +.fancybox-navigation .fancybox-button--arrow_left { + left: 0; + left: env(safe-area-inset-left); + padding: 31px 26px 31px 6px; } -.fancybox-arrow--left { - left: 0; - transform: scaleX(-1); +.fancybox-navigation .fancybox-button--arrow_right { + padding: 31px 6px 31px 26px; + right: 0; + right: env(safe-area-inset-right); } -.fancybox-arrow--right::after, -.fancybox-arrow--left::after { - left: 0; -} +/* Caption */ -.fancybox-show-nav .fancybox-arrow { - opacity: 0.6; +.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-show-nav .fancybox-arrow[disabled] { - opacity: 0.3; +.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 { - border: 6px solid rgba(100, 100, 100, .4); - border-top: 6px solid rgba(255, 255, 255, .6); - border-radius: 100%; - height: 50px; - width: 50px; - animation: fancybox-rotate .8s infinite linear; - background: transparent; - position: absolute; - top: 50%; - left: 50%; - margin-top: -25px; - margin-left: -25px; - z-index: 99999; + 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 { - from { transform: rotate( 0deg ); } - to { transform: rotate( 359deg ); } + 100% { + transform: rotate(360deg); + } } - /* Transition effects */ .fancybox-animated { - transition-timing-function: cubic-bezier(0, 0, 0.25, 1); + transition-timing-function: cubic-bezier(0, 0, .25, 1); } /* transitionEffect: slide */ .fancybox-fx-slide.fancybox-slide--previous { - transform: translate3d(-100%, 0, 0); - opacity: 0; + opacity: 0; + transform: translate3d(-100%, 0, 0); } .fancybox-fx-slide.fancybox-slide--next { - transform: translate3d(100%, 0, 0); - opacity: 0; + opacity: 0; + transform: translate3d(100%, 0, 0); } .fancybox-fx-slide.fancybox-slide--current { - transform: translate3d(0, 0, 0); - opacity: 1; + 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); + opacity: 0; + transition-timing-function: cubic-bezier(.19, 1, .22, 1); } .fancybox-fx-fade.fancybox-slide--current { - opacity: 1; + opacity: 1; } /* transitionEffect: zoom-in-out */ .fancybox-fx-zoom-in-out.fancybox-slide--previous { - transform: scale3d(1.5, 1.5, 1.5); - opacity: 0; + opacity: 0; + transform: scale3d(1.5, 1.5, 1.5); } .fancybox-fx-zoom-in-out.fancybox-slide--next { - transform: scale3d(0.5, 0.5, 0.5); - opacity: 0; + opacity: 0; + transform: scale3d(.5, .5, .5); } .fancybox-fx-zoom-in-out.fancybox-slide--current { - transform: scale3d(1, 1, 1); - opacity: 1; + opacity: 1; + transform: scale3d(1, 1, 1); } /* transitionEffect: rotate */ .fancybox-fx-rotate.fancybox-slide--previous { - transform: rotate(-360deg); - opacity: 0; + opacity: 0; + transform: rotate(-360deg); } .fancybox-fx-rotate.fancybox-slide--next { - transform: rotate(360deg); - opacity: 0; + opacity: 0; + transform: rotate(360deg); } .fancybox-fx-rotate.fancybox-slide--current { - transform: rotate(0deg); - opacity: 1; + opacity: 1; + transform: rotate(0deg); } /* transitionEffect: circular */ .fancybox-fx-circular.fancybox-slide--previous { - transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0); - opacity: 0; + opacity: 0; + transform: scale3d(0, 0, 0) translate3d(-100%, 0, 0); } .fancybox-fx-circular.fancybox-slide--next { - transform: scale3d(0, 0, 0) translate3d(100%, 0, 0); - opacity: 0; + opacity: 0; + transform: scale3d(0, 0, 0) translate3d(100%, 0, 0); } .fancybox-fx-circular.fancybox-slide--current { - transform: scale3d(1, 1, 1) translate3d(0, 0, 0); - opacity: 1; + 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(0.1) skew(-10deg); + transform: translate3d(-100%, 0, 0) scale(.1) skew(-10deg); } .fancybox-fx-tube.fancybox-slide--next { - transform: translate3d(100%, 0, 0) scale(0.1) skew(10deg); + transform: translate3d(100%, 0, 0) scale(.1) skew(10deg); } .fancybox-fx-tube.fancybox-slide--current { - transform: translate3d(0, 0, 0) scale(1); + transform: translate3d(0, 0, 0) scale(1); } - /* Styling for Small-Screen Devices */ - -@media all and (max-width: 800px) { - - .fancybox-infobar { - left: 0; - margin-left: 0; - } - - .fancybox-button--left, - .fancybox-button--right { - display: none !important; - } - - .fancybox-caption { - padding: 20px 0; - margin: 0; - } -} +@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)); + } + } +} \ No newline at end of file diff --git a/src/css/share.css b/src/css/share.css new file mode 100644 index 00000000..32b00c03 --- /dev/null +++ b/src/css/share.css @@ -0,0 +1,96 @@ +/* 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%; +} \ No newline at end of file diff --git a/src/css/thumbs.css b/src/css/thumbs.css index 215e5f7f..e75d60e9 100644 --- a/src/css/thumbs.css +++ b/src/css/thumbs.css @@ -1,122 +1,119 @@ - /* 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-button--thumbs { - display: none; +.fancybox-thumbs-x .fancybox-thumbs__list { + overflow: hidden; } -@media all and (min-width: 800px) { - - .fancybox-button--thumbs { - display: inline-block; - } - - .fancybox-button--thumbs span { - font-size: 23px; - } - - .fancybox-button--thumbs::before { - width: 3px; - height: 3px; - top: calc(50% - 2px); - left: calc(50% - 2px); - box-shadow: 0 -4px 0, -4px -4px 0, 4px -4px 0, 0 0 0 32px inset, -4px 0 0, 4px 0 0, 0 4px 0, -4px 4px 0, 4px 4px 0; - } - - .fancybox-thumbs { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: auto; - width: 220px; - margin: 0; - padding: 5px 5px 0 0; - background: #fff; - word-break: normal; - -webkit-tap-highlight-color: rgba(0,0,0,0); - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - box-sizing: border-box; - z-index: 99995; - } - - .fancybox-show-thumbs .fancybox-thumbs { - display: block; - } - - .fancybox-show-thumbs .fancybox-inner { - right: 220px; - } - - .fancybox-thumbs > ul { - list-style: none; - position: absolute; - position: relative; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - overflow-x: hidden; - overflow-y: auto; - font-size: 0; - } - - .fancybox-thumbs > ul > li { - float: left; - overflow: hidden; - max-width: 50%; - padding: 0; - margin: 0; - width: 105px; - height: 75px; - position: relative; - cursor: pointer; - outline: none; - border: 5px solid transparent; - border-top-width: 0; - border-right-width: 0; - -webkit-tap-highlight-color: transparent; - backface-visibility: hidden; - box-sizing: border-box; - } - - li.fancybox-thumbs-loading { - background: rgba(0,0,0,0.1); - } - - .fancybox-thumbs > ul > li > img { - position: absolute; - top: 0; - left: 0; - min-width: 100%; - min-height: 100%; - max-width: none; - max-height: none; - -webkit-touch-callout: none; - user-select: none; - } - - .fancybox-thumbs > ul > li:before { - content:''; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - border-radius: 2px; - border: 4px solid #4ea7f9; - z-index: 99991; - opacity: 0; - transition: all 0.2s cubic-bezier(.25,.46,.45,.94); - } - - .fancybox-thumbs > ul > li.fancybox-thumbs-active:before { - opacity: 1; - } +.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); + } +} \ No newline at end of file diff --git a/src/js/core.js b/src/js/core.js index 49fa5dc3..998995ed 100644 --- a/src/js/core.js +++ b/src/js/core.js @@ -1,2959 +1,3425 @@ -;(function (window, document, $, undefined) { - 'use strict'; - - // If there's no jQuery, fancyBox can't work - // ========================================= - - if ( !$ ) { - return; +(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" + }; - // Check if fancyBox is already initialized - // ======================================== - - if ( $.fn.fancybox ) { - - $.error('fancyBox already initialized'); - - return; + for (t in transitions) { + if (el.style[t] !== undefined) { + return transitions[t]; + } } - // Private default settings - // ======================== - - var defaults = { - - // Enable infinite gallery navigation - loop : false, - - // Space around image, ignored if zoomed-in or viewport smaller than 800px - margin : [44, 0], - - // Horizontal space between slides - gutter : 50, - - // Enable keyboard navigation - keyboard : true, - - // Should display navigation arrows at the screen edges - arrows : true, - - // Should display infobar (counter and arrows at the top) - infobar : false, - - // Should display toolbar (buttons at the top) - toolbar : true, - - // 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 : [ - 'slideShow', - 'fullScreen', - 'thumbs', - 'close' - ], - - // Detect "idle" time in seconds - idleTime : 4, - - // Should display buttons at top right corner of the content - // If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax' - // Use template from `btnTpl.smallBtn` for customization - smallBtn : 'auto', - - // 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 - // Requires predefined image dimensions - // If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found - preload : "auto", - - }, - - 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' - } - - }, - - // 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 : { - slideShow : '', - fullScreen : '', - thumbs : '', - close : '', - - // 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', - - - // 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, - }, - - 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 : 4000 - }, - - thumbs : { - autoStart : false, // Display thumbnails on opening - hideOnClose : true // Hide thumbnail grid when closing animation starts - }, - - // 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 - clickOutside : 'close', - - // Same as previous two, but for double click - dblclickContent : false, - dblclickSlide : false, - dblclickOutside : false, + 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; + }; - // Custom options when mobile device is detected - // ============================================= + // How much of an element is visible in viewport + // ============================================= - mobile : { - 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; - } - }, + var inViewport = function (elem) { + var elemCenter, rez; + if (!elem || elem.ownerDocument !== document) { + return 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' - }, - 'de' : { - CLOSE : 'Schliessen', - 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' - } - } + $(".fancybox-container").css("pointer-events", "none"); + elemCenter = { + x: elem.getBoundingClientRect().left + elem.offsetWidth / 2, + y: elem.getBoundingClientRect().top + elem.offsetHeight / 2 }; - // Few useful variables and methods - // ================================ - - var $W = $(window); - var $D = $(document); + rez = document.elementFromPoint(elemCenter.x, elemCenter.y) === elem; - var called = 0; + $(".fancybox-container").css("pointer-events", ""); + return rez; + }; - // Check if an object is a jQuery object and not a native JavaScript object - // ======================================================================== + // Class definition + // ================ - 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 FancyBox = function (content, opts, index) { + var self = this; + self.opts = mergeOpts({ + index: index + }, $.fancybox.defaults); - // Detect the supported transition-end event property name - // ======================================================= - - var transitionEnd = (function () { - var t, el = document.createElement("fakeelement"); + if ($.isPlainObject(opts)) { + self.opts = mergeOpts(self.opts, opts); + } - var transitions = { - "transition" : "transitionend", - "OTransition" : "oTransitionEnd", - "MozTransition" : "transitionend", - "WebkitTransition": "webkitTransitionEnd" - }; + if ($.fancybox.isMobile) { + self.opts = mergeOpts(self.opts, self.opts.mobile); + } - for (t in transitions) { - if (el.style[t] !== undefined){ - return transitions[t]; - } - } - })(); + self.id = self.opts.id || ++called; + self.currIndex = parseInt(self.opts.index, 10) || 0; + self.prevIndex = null; - // Force redraw on an element. - // This helps in cases where the browser doesn't redraw an updated element properly. - // ================================================================================= + self.prevPos = null; + self.currPos = 0; - var forceRedraw = function( $el ) { - return ( $el && $el.length && $el[0].offsetHeight ); - }; + self.firstRun = true; + // All group items + self.group = []; - // Class definition - // ================ + // Existing slides (for current, next and previous gallery items) + self.slides = {}; - var FancyBox = function( content, opts, index ) { - var self = this; + // Create group elements + self.addContent(content); - self.opts = $.extend( true, { index : index }, defaults, opts || {} ); + if (!self.group.length) { + return; + } - // Exclude buttons option from deep merging - if ( opts && $.isArray( opts.buttons ) ) { - self.opts.buttons = opts.buttons; + 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 + "" + }; } - self.id = self.opts.id || ++called; - self.group = []; - - self.currIndex = parseInt( self.opts.index, 10 ) || 0; - self.prevIndex = null; + // Each gallery object has full collection of options + obj.opts = $.extend(true, {}, self.opts, opts); - self.prevPos = null; - self.currPos = 0; - - self.firstRun = null; - - // Create group elements from original item collection - self.createGroup( content ); - - if ( !self.group.length ) { - return; + // Do not merge buttons array + if ($.isArray(opts.buttons)) { + obj.opts.buttons = opts.buttons; } - // Save last active element and current scroll position - self.$lastFocus = $(document.activeElement).blur(); - - // Collection of gallery objects - self.slides = {}; - - self.init( content ); - - }; - - $.extend(FancyBox.prototype, { - - // Create DOM structure - // ==================== - - init : function() { - var self = this; - - var testWidth, $container, buttonStr; - - var firstItemOpts = self.group[ self.currIndex ].opts; - - self.scrollTop = $D.scrollTop(); - self.scrollLeft = $D.scrollLeft(); - - - // Hide scrollbars - // =============== - - if ( !$.fancybox.getInstance() && !$.fancybox.isMobile && $( 'body' ).css('overflow') !== 'hidden' ) { - testWidth = $( 'body' ).width(); - - $( 'html' ).addClass( 'fancybox-enabled' ); - - // Compare body width after applying "overflow: hidden" - testWidth = $( 'body' ).width() - testWidth; - - // If width has changed - compensate missing scrollbars by adding right margin - if ( testWidth > 1 ) { - $( 'head' ).append( '' ); - } - } - - - // 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 ] || '' ); - }); + if ($.fancybox.isMobile && obj.opts.mobile) { + obj.opts = mergeOpts(obj.opts, obj.opts.mobile); + } - // 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 ) ) ) - .addClass( 'fancybox-is-hidden' ) - .attr('id', 'fancybox-container-' + self.id) - .addClass( firstItemOpts.baseClass ) - .data( 'FancyBox', self ) - .prependTo( firstItemOpts.parentEl ); - - // Create object holding references to jQuery wrapped nodes - self.$refs = { - container : $container - }; - - [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption' ].forEach(function(item) { - self.$refs[ item ] = $container.find( '.fancybox-' + item ); - }); + // Step 2 - Make sure we have content type, if not - try to guess + // ============================================================== - // Check for redundant elements - if ( !firstItemOpts.arrows || self.group.length < 2 ) { - $container.find('.fancybox-navigation').remove(); - } + type = obj.type || obj.opts.type; + src = obj.src || ""; - if ( !firstItemOpts.infobar ) { - self.$refs.infobar.remove(); - } + if (!type && src) { + if ((found = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) { + type = "video"; - if ( !firstItemOpts.toolbar ) { - self.$refs.toolbar.remove(); + if (!obj.opts.video.format) { + obj.opts.video.format = "video/" + (found[1] === "ogv" ? "ogg" : found[1]); } - - self.trigger( 'onInit' ); - - // Bring to front and enable events - 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 ]; - - return str.replace(/\{\{(\w+)\}\}/g, function(match, n) { - var value = arr[n]; - - if ( value === undefined ) { - return match; - } - - return value; - }); - }, - - // Create array of gally item objects - // Check if each object has valid type and content - // =============================================== - - createGroup : function ( content ) { - var self = this; - var items = $.makeArray( content ); - - $.each(items, function( i, item ) { - var obj = {}, - opts = {}, - data = [], - $item, - type, - 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 propbably have jQuery collection returned by some selector - - $item = $( item ); - data = $item.data(); - - opts = 'options' in data ? data.options : {}; - opts = $.type( opts ) === 'object' ? opts : {}; - - obj.src = 'src' in data ? data.src : ( opts.src || $item.attr( 'href' ) ); - - [ 'width', 'height', 'thumb', 'type', 'filter' ].forEach(function(item) { - if ( item in data ) { - opts[ item ] = data[ item ]; - } - }); - - if ( 'srcset' in data ) { - opts.image = { srcset : data.srcset }; - } - - opts.$orig = $item; - - 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 ); - - if ( $.fancybox.isMobile ) { - obj.opts = $.extend( true, {}, 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 ( 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 = 'pdf'; - - } else if ( src.charAt(0) === '#' ) { - type = 'inline'; - } - } - - obj.type = type; - - - // Step 3 - Some adjustments - // ========================= - - obj.index = self.group.length; - - // Check if $orig and $thumb objects exist - if ( obj.opts.$orig && !obj.opts.$orig.length ) { - delete obj.opts.$orig; + } 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 } - - if ( !obj.opts.$thumb && obj.opts.$orig ) { - obj.opts.$thumb = obj.opts.$orig.find( 'img:first' ); - } - - if ( obj.opts.$thumb && !obj.opts.$thumb.length ) { - delete obj.opts.$thumb; - } - - // Caption is a "special" option, it can be passed as a method - if ( $.type( obj.opts.caption ) === 'function' ) { - obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] ); - - } else if ( 'caption' in data ) { - obj.opts.caption = data.caption; - } - - // Make sure we have caption as a string - 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 ( type === 'ajax' ) { - srcParts = src.split(/\s+/, 2); - - if ( srcParts.length > 1 ) { - obj.src = srcParts.shift(); - - obj.opts.filter = srcParts.shift(); - } - } - - if ( obj.opts.smallBtn == 'auto' ) { - - if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) { - obj.opts.toolbar = false; - obj.opts.smallBtn = true; - - } else { - obj.opts.smallBtn = false; - } - - } - - // If the type is "pdf", then simply load file into iframe - if ( type === 'pdf' ) { - obj.type = 'iframe'; - - obj.opts.iframe.preload = false; - } - - // Hide all buttons and disable interactivity for modal items - if ( obj.opts.modal ) { - - obj.opts = $.extend(true, obj.opts, { - // 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 ); - - }); - - }, - - - // Attach an event handler functions for: - // - navigation buttons - // - browser scrolling, resizing; - // - focusing - // - keyboard - // - detect idle - // ====================================== - - 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( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) { - e.stopPropagation(); - e.preventDefault(); - - self.previous(); - - }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) { - e.stopPropagation(); - e.preventDefault(); - - self.next(); - + } }); + } else if (src.charAt(0) === "#") { + type = "inline"; + } + } + if (type) { + obj.type = type; + } else { + self.trigger("objectNeedsType", obj); + } - // Handle page scrolling and browser resizing - $W.on('orientationchange.fb resize.fb', function(e) { - - if ( e && e.originalEvent && e.originalEvent.type === "resize" ) { - - requestAFrame(function() { - self.update(); - }); - - } else { - - self.$refs.stage.hide(); - - setTimeout(function() { - self.$refs.stage.show(); - - self.update(); - }, 500); + if (!obj.contentType) { + obj.contentType = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1 ? "html" : obj.type; + } - } + // Step 3 - Some adjustments + // ========================= - }); + obj.index = self.group.length; - // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal - // (a.k.a. "escaping the modal") - $D.on('focusin.fb', function(e) { - var instance = $.fancybox ? $.fancybox.getInstance() : null; + if (obj.opts.smallBtn == "auto") { + obj.opts.smallBtn = $.inArray(obj.type, ["html", "inline", "ajax"]) > -1; + } - if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) { - return; - } + if (obj.opts.toolbar === "auto") { + obj.opts.toolbar = !obj.opts.smallBtn; + } - if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) { - e.stopPropagation(); + // Find thumbnail image, check if exists and if is in the viewport + obj.$thumb = obj.opts.$thumb || null; - instance.focus(); + if (obj.opts.$trigger && obj.index === self.opts.index) { + obj.$thumb = obj.opts.$trigger.find("img:first"); - // Sometimes page gets scrolled, set it back - $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); - } - }); + 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"); + } - // Enable keyboard navigation - $D.on('keydown.fb', function (e) { - var current = self.current, - keycode = e.keyCode || e.which; - - if ( !current || !current.opts.keyboard ) { - return; - } - - if ( $(e.target).is('input') || $(e.target).is('textarea') ) { - 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 mouseenter.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function() { - 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.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( 'focusin.fb 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, slide ) { - var self = this, - firstRun, - loop, - current, - previous, - canvasWidth, - currentPos, - transitionProps; - - var groupLen = self.group.length; - - if ( self.isSliding || self.isClosing || ( self.isAnimating && self.firstRun ) ) { - return; - } - - pos = parseInt( pos, 10 ); - loop = self.current ? self.current.opts.loop : self.opts.loop; - - if ( !loop && ( pos < 0 || pos >= groupLen ) ) { - return false; - } - - firstRun = self.firstRun = ( self.firstRun === null ); - - if ( groupLen < 2 && !firstRun && !!self.isSliding ) { - return; - } - - previous = self.current; - - self.prevIndex = self.currIndex; - self.prevPos = self.currPos; - - // Create slides - current = self.createSlide( pos ); - - if ( groupLen > 1 ) { - if ( loop || current.index > 0 ) { - self.createSlide( pos - 1 ); - } - - if ( loop || current.index < groupLen - 1 ) { - self.createSlide( pos + 1 ); - } - } - - self.current = current; - self.currIndex = current.index; - self.currPos = current.pos; - - self.trigger( 'beforeShow', firstRun ); - - self.updateControls(); - - currentPos = $.fancybox.getTranslate( current.$slide ); - - current.isMoved = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' ); - current.forcedDuration = undefined; - - if ( $.isNumeric( duration ) ) { - current.forcedDuration = duration; - } else { - duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ]; - } - - duration = parseInt( duration, 10 ); - - // 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.removeClass( 'fancybox-is-hidden' ); - - forceRedraw( self.$refs.container ); - - self.$refs.container.addClass( 'fancybox-is-open' ); - - // Make first slide visible (to display loading icon, if needed) - current.$slide.addClass( 'fancybox-slide--current' ); - - self.loadSlide( current ); - - self.preload(); - - return; - } - - // Clean up - $.each(self.slides, function( index, slide ) { - $.fancybox.stop( slide.$slide ); - }); - - // Make current that slide is visible even if content is still loading - current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' ); - - // If slides have been dragged, animate them to correct position - if ( current.isMoved ) { - canvasWidth = Math.round( current.$slide.width() ); - - $.each(self.slides, function( index, slide ) { - var pos = slide.pos - current.pos; - - $.fancybox.animate( slide.$slide, { - top : 0, - left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ) - }, duration, function() { - - slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' ); - - if ( slide.pos === self.currPos ) { - current.isMoved = false; - - self.complete(); - } - }); - }); - - } else { - self.$refs.stage.children().removeAttr( 'style' ); - } - - // Start transition that reveals current content - // or wait when it will be loaded - - if ( current.isLoaded ) { - self.revealContent( current ); - - } else { - self.loadSlide( current ); - } - - self.preload(); - - if ( previous.pos === current.pos ) { - return; - } - - // Handle previous slide - // ===================== - - transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' ); - - previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ); - - previous.isComplete = false; - - if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) { - return; - } - - if ( current.isMoved ) { - previous.$slide.addClass( transitionProps ); - - } else { - - transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect; - - $.fancybox.animate( previous.$slide, transitionProps, duration, function() { - previous.$slide.removeClass( transitionProps ).removeAttr( 'style' ); - }); - - } - - }, - - - // Create new "slide" element - // These are gallery items that are actually added to DOM - // ======================================================= - - createSlide : function( pos ) { - - var self = this; - var $slide; - var 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 - // =========================================== - - scaleToActual : function( x, y, duration ) { - - var self = this; - - var current = self.current; - var $what = current.$content; - - var imgPos, posX, posY, scaleX, scaleY; - - var canvasWidth = parseInt( current.$slide.width(), 10 ); - var canvasHeight = parseInt( current.$slide.height(), 10 ); - - var newImgWidth = current.width; - var newImgHeight = current.height; - - if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating) { - return; - } - - $.fancybox.stop( $what ); - - self.isAnimating = true; - - x = x === undefined ? canvasWidth * 0.5 : x; - y = y === undefined ? canvasHeight * 0.5 : y; - - imgPos = $.fancybox.getTranslate( $what ); - - 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( $what, { - top : posY, - left : posX, - scaleX : scaleX, - scaleY : scaleY - }, duration || 330, 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; - - var current = self.current; - var $what = current.$content; - var end; - - if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) { - return; - } - - $.fancybox.stop( $what ); - - self.isAnimating = true; - - end = self.getFitPos( current ); - - self.updateCursor( end.width, end.height ); - - $.fancybox.animate( $what, { - top : end.top, - left : end.left, - scaleX : end.width / $what.width(), - scaleY : end.height / $what.height() - }, duration || 330, function() { - self.isAnimating = false; - }); - - }, - - // Calculate image size to fit inside viewport - // =========================================== - - getFitPos : function( slide ) { - var self = this; - var $what = slide.$content; - - var imgWidth = slide.width; - var imgHeight = slide.height; - - var margin = slide.opts.margin; - - var canvasWidth, canvasHeight, minRatio, width, height; - - if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) { - return false; - } - - // Convert "margin to CSS style: [ top, right, bottom, left ] - if ( $.type( margin ) === "number" ) { - margin = [ margin, margin ]; - } - - if ( margin.length == 2 ) { - margin = [ margin[0], margin[1], margin[0], margin[1] ]; - } - - if ( $W.width() < 800 ) { - margin = [ 0, 0, 0, 0 ]; - } - - // We can not use $slide width here, because it can have different diemensions while in transiton - canvasWidth = parseInt( self.$refs.stage.width(), 10 ) - ( margin[ 1 ] + margin[ 3 ] ); - canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] ); - - minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight ); - - width = Math.floor( minRatio * imgWidth ); - height = Math.floor( minRatio * imgHeight ); - - // Use floor rounding to make sure it really fits - return { - top : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ], - left : Math.floor( ( canvasWidth - width ) * 0.5 ) + margin[ 3 ], - width : width, - height : height - }; - - }, - - - // Update position and content of all slides - // ========================================= - - update : function() { - - var self = this; - - $.each( self.slides, function( key, slide ) { - self.updateSlide( slide ); - }); - - }, - - - // Update slide position and scale content to fit - // ============================================== - - updateSlide : function( slide ) { - - var self = this; - var $what = slide.$content; - - if ( $what && ( slide.width || slide.height ) ) { - $.fancybox.stop( $what ); - - $.fancybox.setTranslate( $what, self.getFitPos( slide ) ); - - if ( slide.pos === self.currPos ) { - self.updateCursor(); - } - } - - slide.$slide.trigger( 'refresh' ); - - self.trigger( 'onUpdate', slide ); - - }, - - // Update cursor style depending if content can be zoomed - // ====================================================== - - updateCursor : function( nextWidth, nextHeight ) { - - var self = this; - var isScaledDown; - - var $container = self.$refs.container.removeClass('fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut'); - - if ( !self.current || self.isClosing ) { - return; - } - - if ( self.isZoomable() ) { - - $container.addClass( 'fancybox-is-zoomable' ); - - if ( nextWidth !== undefined && nextHeight !== undefined ) { - isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height; - - } else { - isScaledDown = self.isScaledDown(); - } - - if ( isScaledDown ) { - - // If image is scaled down, then, obviously, it can be zoomed to full size - $container.addClass('fancybox-can-zoomIn'); - - } else { - - if ( self.current.opts.touch ) { - - // If image size ir largen than available available and touch module is not disable, - // then user can do panning - $container.addClass('fancybox-can-drag'); - - } else { - $container.addClass('fancybox-can-zoomOut'); - } - - } - - } else if ( self.current.opts.touch ) { - $container.addClass('fancybox-can-drag'); - } - - }, - - - // Check if current slide is zoomable - // ================================== - - isZoomable : function() { - - var self = this; - - var current = self.current; - var fitPos; - - if ( !current || self.isClosing ) { - return; - } - - // Assume that slide is zoomable if - // - image is loaded successfuly - // - click action is "zoom" - // - actual size of the image is smaller than available area - if ( current.type === 'image' && current.isLoaded && !current.hasError && - ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) === "zoom" ) ) - ) { - - fitPos = self.getFitPos( current ); - - if ( current.width > fitPos.width || current.height > fitPos.height ) { - return true; - } - - } - - return false; - - }, - - - // Check if current image dimensions are smaller than actual - // ========================================================= - - isScaledDown : function() { - - var self = this; - - var current = self.current; - var $what = current.$content; - - var rez = false; - - if ( $what ) { - rez = $.fancybox.getTranslate( $what ); - rez = rez.width < current.width || rez.height < current.height; - } - - return rez; - - }, - - - // Check if image dimensions exceed parent element - // =============================================== - - canPan : function() { - - var self = this; - - var current = self.current; - var $what = current.$content; - - var rez = false; - - if ( $what ) { - rez = self.getFitPos( current ); - rez = Math.abs( $what.width() - rez.width ) > 1 || Math.abs( $what.height() - rez.height ) > 1; - - } - - return rez; - - }, - - - // Load content into the slide - // =========================== - - loadSlide : function( slide ) { - - var self = this, type, $slide; - var ajaxLoad; - - if ( slide.isLoading ) { - return; - } - - if ( slide.isLoaded ) { - return; - } - - slide.isLoading = true; - - self.trigger( 'beforeLoad', slide ); - - type = slide.type; - $slide = slide.$slide; - - $slide - .off( 'refresh' ) - .trigger( 'onReset' ) - .addClass( 'fancybox-slide--' + ( type || 'unknown' ) ) - .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 '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; - var srcset = slide.opts.image.srcset; - - var found, temp, pxRatio, windowWidth; - - // If we have "srcset", then we need to find 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 (obj.$thumb && !obj.$thumb.length) { + obj.$thumb = null; + } - // 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; - } - } - } + obj.thumb = obj.opts.thumb || (obj.$thumb ? obj.$thumb[0].src : null); - // This will be wrapper containing both ghost and actual image - slide.$content = $('
') - .addClass( 'fancybox-is-hidden' ) - .appendTo( slide.$slide ); + // "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]); + } - // 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.opts.thumb || slide.opts.$thumb ) ) { + // 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 + ""; + } - slide.width = slide.opts.width; - slide.height = slide.opts.height; + // Check if url contains "filter" used to filter the content + // Example: "ajax.html #something" + if (obj.type === "ajax") { + srcParts = src.split(/\s+/, 2); - slide.$ghost = $('') - .one('error', function() { + if (srcParts.length > 1) { + obj.src = srcParts.shift(); - $(this).remove(); + obj.opts.filter = srcParts.shift(); + } + } - slide.$ghost = null; + // 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 + }); + } - self.setBigImage( slide ); + // Step 4 - Add processed object to group + // ====================================== - }) - .one('load', function() { + self.group.push(obj); + }); - self.afterLoad( slide ); + // Update controls if gallery is already opened + if (Object.keys(self.slides).length) { + self.updateControls(); - self.setBigImage( slide ); + // Update thumbnails, if needed + thumbs = self.Thumbs; - }) - .addClass( 'fancybox-image' ) - .appendTo( slide.$content ) - .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) ); + if (thumbs && thumbs.isActive) { + thumbs.create(); - } else { + 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(); + } - self.setBigImage( slide ); + 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 + // ======================================= - // Create full-size image - // ====================== + if (keycode == 9) { + if (current.opts.trapFocus) { + self.focus(e); + } - setBigImage : function ( slide ) { - var self = this; - var $img = $(''); + return; + } - slide.$image = $img - .one('error', function() { + // Enable keyboard navigation + // ========================== - self.setError( slide ); + if (!current.opts.keyboard || e.ctrlKey || e.altKey || e.shiftKey || $(e.target).is("input,textarea,video,audio,select")) { + return; + } - }) - .one('load', function() { + // Backspace and Esc keys + if (keycode === 8 || keycode === 27) { + e.preventDefault(); - // Clear timeout that checks if loading icon needs to be displayed - clearTimeout( slide.timouts ); + self.close(e); - slide.timouts = null; + return; + } - if ( self.isClosing ) { - return; - } + // Left arrow and Up arrow + if (keycode === 37 || keycode === 38) { + e.preventDefault(); - slide.width = this.naturalWidth; - slide.height = this.naturalHeight; + self.previous(); - if ( slide.opts.image.srcset ) { - $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset ); - } + return; + } - self.hideLoading( slide ); + // Righ arrow and Down arrow + if (keycode === 39 || keycode === 40) { + e.preventDefault(); - if ( slide.$ghost ) { + self.next(); - slide.timouts = setTimeout(function() { - slide.timouts = null; + return; + } - slide.$ghost.hide(); + self.trigger("afterKeydown", e, keycode); + }); - }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) ); + // Hide controls after some inactivity period + if (self.group[self.currIndex].opts.idleTime) { + self.idleSecondsCounter = 0; - } else { - self.afterLoad( slide ); - } + $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; - }) - .addClass( 'fancybox-image' ) - .attr('src', slide.src) - .appendTo( slide.$content ); + if (self.isIdle) { + self.showControls(); + } - if ( $img[0].complete ) { - $img.trigger( 'load' ); + self.isIdle = false; + } + ); - } else if( $img[0].error ) { - $img.trigger( 'error' ); + self.idleInterval = window.setInterval(function () { + self.idleSecondsCounter++; - } else { + if (self.idleSecondsCounter >= self.group[self.currIndex].opts.idleTime && !self.isDragging) { + self.isIdle = true; + self.idleSecondsCounter = 0; - slide.timouts = setTimeout(function() { - if ( !$img[0].complete && !slide.hasError ) { - self.showLoading( slide ); - } + self.hideControls(); + } + }, 1000); + } + }, - }, 100); + // 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"); - // Create iframe wrapper, iframe and bindings - // ========================================== + if (self.idleInterval) { + window.clearInterval(self.idleInterval); - setIframe : function( slide ) { - var self = this, - opts = slide.opts.iframe, - $slide = slide.$slide, - $iframe; + self.idleInterval = null; + } + }, - slide.$content = $('
') - .css( opts.css ) - .appendTo( $slide ); + // Change to previous gallery item + // =============================== - $iframe = $( opts.tpl.replace(/\{rnd\}/g, new Date().getTime()) ) - .attr( opts.attr ) - .appendTo( slide.$content ); + previous: function (duration) { + return this.jumpTo(this.currPos - 1, duration); + }, - if ( opts.preload ) { + // Change to next gallery item + // =========================== - self.showLoading( slide ); + next: function (duration) { + return this.jumpTo(this.currPos + 1, duration); + }, - // Unfortunately, it is not always possible to determine if iframe is successfully loaded - // (due to browser security policy) + // Switch to selected gallery item + // =============================== - $iframe.on('load.fb error.fb', function(e) { - this.isReady = 1; + jumpTo: function (pos, duration) { + var self = this, + groupLen = self.group.length, + firstRun, + isMoved, + loop, + current, + previous, + slidePos, + stagePos, + prop, + diff; - slide.$slide.trigger( 'refresh' ); + if (self.isDragging || self.isClosing || (self.isAnimating && self.firstRun)) { + return; + } - self.afterLoad( slide ); - }); + // Should loop? + pos = parseInt(pos, 10); + loop = self.current ? self.current.opts.loop : self.opts.loop; - // Recalculate iframe content size - // =============================== + if (!loop && (pos < 0 || pos >= groupLen)) { + return false; + } - $slide.on('refresh.fb', function() { - var $wrap = slide.$content, - $contents, - $body, - scrollWidth, - frameWidth, - frameHeight; + // Check if opening for the first time; this helps to speed things up + firstRun = self.firstRun = !Object.keys(self.slides).length; - if ( $iframe[0].isReady !== 1 ) { - return; - } + // Create slides + previous = self.current; - // Check if content is accessible, - // it will fail if frame is not with the same origin + self.prevIndex = self.currIndex; + self.prevPos = self.currPos; - try { - $contents = $iframe.contents(); - $body = $contents.find('body'); + current = self.createSlide(pos); - } catch (ignore) {} + if (groupLen > 1) { + if (loop || current.index < groupLen - 1) { + self.createSlide(pos + 1); + } - // Calculate dimensions for the wrapper - if ( $body && $body.length && !( opts.css.width !== undefined && opts.css.height !== undefined ) ) { + if (loop || current.index > 0) { + self.createSlide(pos - 1); + } + } - scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth; + self.current = current; + self.currIndex = current.index; + self.currPos = current.pos; - frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) ); - frameHeight = Math.ceil( $body.outerHeight(true) ); + self.trigger("beforeShow", firstRun); - // Resize wrapper to fit iframe content - $wrap.css({ - 'width' : opts.css.width === undefined ? frameWidth + ( $wrap.outerWidth() - $wrap.innerWidth() ) : opts.css.width, - 'height' : opts.css.height === undefined ? frameHeight + ( $wrap.outerHeight() - $wrap.innerHeight() ) : opts.css.height - }); + self.updateControls(); - } + // Validate duration length + current.forcedDuration = undefined; - $wrap.removeClass( 'fancybox-is-hidden' ); + if ($.isNumeric(duration)) { + current.forcedDuration = duration; + } else { + duration = current.opts[firstRun ? "animationDuration" : "transitionDuration"]; + } - }); + duration = parseInt(duration, 10); - } else { + // Check if user has swiped the slides or if still animating + isMoved = self.isMoved(current); - this.afterLoad( slide ); + // 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"); + } - $iframe.attr( 'src', slide.src ); + self.$refs.container.addClass("fancybox-is-open").trigger("focus"); - if ( slide.opts.smallBtn === true ) { - slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) ); - } + // Attempt to load content into slide + // This will later call `afterLoad` -> `revealContent` + self.loadSlide(current); - // Remove iframe if closing or changing gallery item - $slide.one( 'onReset', function () { + self.preload("image"); - // This helps IE not to throw errors when closing - try { + 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 + // ======================================================= - $( this ).find( 'iframe' ).hide().attr( 'src', '//about:blank' ); + 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; + } - } catch ( ignore ) {} + self.isAnimating = true; - $( this ).empty(); + $.fancybox.stop($content); - slide.isLoaded = false; + 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; - // Wrap and append content to the slide - // ====================================== + // Get center position for original image + posX = canvasWidth * 0.5 - newImgWidth * 0.5; + posY = canvasHeight * 0.5 - newImgHeight * 0.5; - setContent : function ( slide, content ) { + // Make sure image does not move away from edges + if (newImgWidth > canvasWidth) { + posX = imgPos.left * scaleX - (x * scaleX - x); - var self = this; + if (posX > 0) { + posX = 0; + } - if ( self.isClosing ) { - return; - } + if (posX < canvasWidth - newImgWidth) { + posX = canvasWidth - newImgWidth; + } + } - self.hideLoading( slide ); + if (newImgHeight > canvasHeight) { + posY = imgPos.top * scaleY - (y * scaleY - y); - slide.$slide.empty(); + if (posY > 0) { + posY = 0; + } - if ( isQuery( content ) && content.parent().length ) { + if (posY < canvasHeight - newImgHeight) { + posY = canvasHeight - newImgHeight; + } + } - // 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 user is navigating gallery fast, then the content might be already inside fancyBox - // ===================================================================================== + self.updateCursor(newImgWidth, newImgHeight); - // Make sure content is not already moved to fancyBox - content.parent( '.fancybox-slide--inline' ).trigger( 'onReset' ); + $.fancybox.animate( + $content, { + top: posY, + left: posX, + scaleX: scaleX, + scaleY: scaleY + }, + duration || 366, + function () { + self.isAnimating = false; + } + ); - // Create temporary element marking original place of the content - slide.$placeholder = $( '
' ).hide().insertAfter( content ); + // Stop slideshow + if (self.SlideShow && self.SlideShow.isActive) { + self.SlideShow.stop(); + } + }, - // Make sure content is visible - content.css('display', 'inline-block'); + // Scale image to fit inside parent element + // ======================================== - } else if ( !slide.hasError ) { + scaleToFit: function (duration) { + var self = this, + current = self.current, + $content = current.$content, + end; - // If content is just a plain text, try to convert it to html - if ( $.type( content ) === 'string' ) { - content = $('
').append( $.trim( content ) ).contents(); + if (self.isAnimating || self.isMoved() || !$content || !(current.type == "image" && current.isLoaded && !current.hasError)) { + return; + } - // If we have text node, then add wrapping element to make vertical alignment work - if ( content[0].nodeType === 3 ) { - content = $('
').html( content ); - } - } + self.isAnimating = true; - // If "filter" option is provided, then filter content - if ( slide.opts.filter ) { - content = $('
').html( content ).find( slide.opts.filter ); - } + $.fancybox.stop($content); - } + end = self.getFitPos(current); - slide.$slide.one('onReset', function () { + self.updateCursor(end.width, end.height); - // Put content back - if ( slide.$placeholder ) { - slide.$placeholder.after( content.hide() ).remove(); + $.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; + } + } - slide.$placeholder = null; - } + rez.width = width; + rez.height = height; - // Remove custom close button - if ( slide.$smallBtn ) { - slide.$smallBtn.remove(); + return rez; + }, - slide.$smallBtn = null; - } + // Update content size and position for all slides + // ============================================== - // Remove content and mark slide as not loaded - if ( !slide.hasError ) { - $(this).empty(); + update: function (e) { + var self = this; - slide.isLoaded = false; - } + $.each(self.slides, function (key, slide) { + self.updateSlide(slide, e); + }); + }, - }); + // Update slide content position and size + // ====================================== - slide.$content = $( content ).appendTo( slide.$slide ); + 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; - if ( slide.opts.smallBtn && !slide.$smallBtn ) { - slide.$smallBtn = $( self.translate( slide, slide.opts.btnTpl.smallBtn ) ).appendTo( slide.$content ); - } + // First, prevent caption overlap, if needed + self.adjustCaption(slide); - this.afterLoad( slide ); - }, + // Then resize content to fit inside the slide + if ($content && (width || height || slide.contentType === "video") && !slide.hasError) { + $.fancybox.stop($content); - // Display error message - // ===================== + $.fancybox.setTranslate($content, self.getFitPos(slide)); - setError : function ( slide ) { + if (slide.pos === self.currPos) { + self.isAnimating = false; - slide.hasError = true; + self.updateCursor(); + } + } - slide.$slide.removeClass( 'fancybox-slide--' + slide.type ); + // Then some adjustments + self.adjustLayout(slide); - this.setContent( slide, this.translate( slide, slide.opts.errorTpl ) ); + 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); + }, - // Show loading icon inside the slide - // ================================== + // Horizontally center slide + // ========================= - showLoading : function( slide ) { + centerSlide: function (duration) { + var self = this, + current = self.current, + $slide = current.$slide; - var self = this; + 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 + ); + }, - slide = slide || self.current; + // Check if current slide is moved (swiped) + // ======================================== - if ( slide && !slide.$spinner ) { - slide.$spinner = $( self.opts.spinnerTpl ).appendTo( slide.$slide ); - } + isMoved: function (slide) { + var current = slide || this.current, + slidePos, + stagePos; - }, + if (!current) { + return false; + } - // Remove loading icon from the slide - // ================================== + stagePos = $.fancybox.getTranslate(this.$refs.stage); + slidePos = $.fancybox.getTranslate(current.$slide); - hideLoading : function( slide ) { + return ( + !current.$slide.hasClass("fancybox-animated") && + (Math.abs(slidePos.top - stagePos.top) > 0.5 || Math.abs(slidePos.left - stagePos.left) > 0.5) + ); + }, - var self = this; + // Update cursor style depending if content can be zoomed + // ====================================================== - slide = slide || self.current; + updateCursor: function (nextWidth, nextHeight) { + var self = this, + current = self.current, + $container = self.$refs.container, + canPan, + isZoomable; - if ( slide && slide.$spinner ) { - slide.$spinner.remove(); + if (!current || self.isClosing || !self.Guestures) { + return; + } - delete slide.$spinner; - } + $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(); - // Adjustments after slide content has been loaded - // =============================================== + $container.toggleClass("fancybox-is-zoomable", isZoomable); - afterLoad : function( slide ) { + $("[data-fancybox-zoom]").prop("disabled", !isZoomable); - var self = this; + 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"); + } + }, - if ( self.isClosing ) { - return; - } + // Check if current slide is zoomable + // ================================== - slide.isLoading = false; - slide.isLoaded = true; + isZoomable: function () { + var self = this, + current = self.current, + fitPos; - self.trigger( 'afterLoad', slide ); + // 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; + } - self.hideLoading( slide ); + fitPos = self.getFitPos(current); - if ( slide.opts.protect && slide.$content && !slide.hasError ) { + 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); + } - // Disable right click - slide.$content.on( 'contextmenu.fb', function( e ) { - if ( e.button == 2 ) { - e.preventDefault(); - } + if (pos && rez) { + rez = Math.abs(pos.width - rez.width) > 1.5 || Math.abs(pos.height - rez.height) > 1.5; + } + } - return true; - }); + return rez; + }, - // 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 ); - } + // Load content into the slide + // =========================== - } + loadSlide: function (slide) { + var self = this, + type, + $slide, + ajaxLoad; - self.revealContent( slide ); + if (slide.isLoading || slide.isLoaded) { + return; + } - }, + slide.isLoading = true; + if (self.trigger("beforeLoad", slide) === false) { + slide.isLoading = false; - // Make content visible - // This method is called right after content has been loaded or - // user navigates gallery and transition should start - // ============================================================ + return false; + } - revealContent : function( slide ) { + type = slide.type; + $slide = slide.$slide; - var self = this; - var $slide = slide.$slide; + $slide + .off("refresh") + .trigger("onReset") + .addClass(slide.opts.slideClass); - var effect, effectClassName, duration, opacity, end, start = false; + // Create content depending on the type + switch (type) { + case "image": + self.setImage(slide); - effect = slide.opts[ self.firstRun ? 'animationEffect' : 'transitionEffect' ]; - duration = slide.opts[ self.firstRun ? 'animationDuration' : 'transitionDuration' ]; + break; - duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10 ); + case "iframe": + self.setIframe(slide); - if ( slide.isMoved || slide.pos !== self.currPos || !duration ) { - effect = false; - } + break; - // Check if can zoom - if ( effect === 'zoom' && !( slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && ( start = self.getThumbPos( slide ) ) ) ) { - effect = 'fade'; - } + case "html": + self.setContent(slide, slide.src || slide.content); + break; - // Zoom animation - // ============== + 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 || "") + ); - if ( effect === 'zoom' ) { - end = self.getFitPos( slide ); + break; - end.scaleX = Math.round( (end.width / start.width) * 100 ) / 100; - end.scaleY = Math.round( (end.height / start.height) * 100 ) / 100; + case "inline": + if ($(slide.src).length) { + self.setContent(slide, $(slide.src)); + } else { + self.setError(slide); + } - delete end.width; - delete end.height; + break; - // Check if we need to animate opacity - opacity = slide.opts.zoomOpacity; + case "ajax": + self.showLoading(slide); - if ( opacity == 'auto' ) { - opacity = Math.abs( slide.width / slide.height - start.width / start.height ) > 0.1; + ajaxLoad = $.ajax( + $.extend({}, slide.opts.ajax.settings, { + url: slide.src, + success: function (data, textStatus) { + if (textStatus === "success") { + self.setContent(slide, data); } - - if ( opacity ) { - start.opacity = 0.1; - end.opacity = 1; + }, + error: function (jqXHR, textStatus) { + if (jqXHR && textStatus !== "abort") { + self.setError(slide); } + } + }) + ); - // Draw image at start position - $.fancybox.setTranslate( slide.$content.removeClass( 'fancybox-is-hidden' ), start ); + $slide.one("onReset", function () { + ajaxLoad.abort(); + }); - forceRedraw( slide.$content ); + break; - // Start animation - $.fancybox.animate( slide.$content, end, duration, function() { - self.complete(); - }); + default: + self.setError(slide); - return; - } + break; + } + return true; + }, - self.updateSlide( slide ); + // Use thumbnail image, if possible + // ================================ + setImage: function (slide) { + var self = this, + ghost; - // Simply show content - // =================== + // Check if need to show loading icon + setTimeout(function () { + var $img = slide.$image; - if ( !effect ) { - forceRedraw( $slide ); + if (!self.isClosing && slide.isLoading && (!$img || !$img.length || !$img[0].complete) && !slide.hasError) { + self.showLoading(slide); + } + }, 50); - slide.$content.removeClass( 'fancybox-is-hidden' ); + //Check if image has srcset + self.checkSrcset(slide); - if ( slide.pos === self.currPos ) { - self.complete(); - } + // This will be wrapper containing both ghost and actual image + slide.$content = $('
') + .addClass("fancybox-is-hidden") + .appendTo(slide.$slide.addClass("fancybox-slide--image")); - return; - } + // 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; - $.fancybox.stop( $slide ); + ghost = document.createElement("img"); - effectClassName = 'fancybox-animated fancybox-slide--' + ( slide.pos > self.prevPos ? 'next' : 'previous' ) + ' fancybox-fx-' + effect; + ghost.onerror = function () { + $(this).remove(); - $slide.removeAttr( 'style' ).removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ).addClass( effectClassName ); + slide.$ghost = null; + }; - slide.$content.removeClass( 'fancybox-is-hidden' ); + ghost.onload = function () { + self.afterLoad(slide); + }; - //Force reflow for CSS3 transitions - forceRedraw( $slide ); + slide.$ghost = $(ghost) + .addClass("fancybox-image") + .appendTo(slide.$content) + .attr("src", slide.thumb); + } - $.fancybox.animate( $slide, 'fancybox-slide--current', duration, function(e) { - $slide.removeClass( effectClassName ).removeAttr( 'style' ); + // Start loading actual image + self.setBigImage(slide); + }, - if ( slide.pos === self.currPos ) { - self.complete(); - } + // 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]; + } + }); - }, true); + 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]; - // Check if we can and have to zoom from thumbnail - //================================================ + if ((el.postfix === "w" && el.value >= windowWidth) || (el.postfix === "x" && el.value >= pxRatio)) { + found = el; + break; + } + } - getThumbPos : function( slide ) { + // If not found, take the last one + if (!found && temp.length) { + found = temp[temp.length - 1]; + } - var self = this; - var rez = false; + if (found) { + slide.src = found.url; - // Check if element is inside the viewport by at least 1 pixel - var isElementVisible = function( $el ) { - var element = $el[0]; + // 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; + } - var elementRect = element.getBoundingClientRect(); - var parentRects = []; + slide.opts.srcset = srcset; + } + } + }, - var visibleInAllParents; + // Create full-size image + // ====================== - while ( element.parentElement !== null ) { - if ( $(element.parentElement).css('overflow') === 'hidden' || $(element.parentElement).css('overflow') === 'auto' ) { - parentRects.push(element.parentElement.getBoundingClientRect()); - } + setBigImage: function (slide) { + var self = this, + img = document.createElement("img"), + $img = $(img); - element = element.parentElement; - } + slide.$image = $img + .one("error", function () { + self.setError(slide); + }) + .one("load", function () { + var sizes; - visibleInAllParents = parentRects.every(function(parentRect){ - var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left); - var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top); - - return visiblePixelX > 0 && visiblePixelY > 0; - }); - - return visibleInAllParents && - elementRect.bottom > 0 && elementRect.right > 0 && - elementRect.left < $(window).width() && elementRect.top < $(window).height(); - }; - - var $thumb = slide.opts.$thumb; - var thumbPos = $thumb ? $thumb.offset() : 0; - var slidePos; - - if ( thumbPos && $thumb[0].ownerDocument === document && isElementVisible( $thumb ) ) { - slidePos = self.$refs.stage.offset(); - - rez = { - top : thumbPos.top - slidePos.top + parseFloat( $thumb.css( "border-top-width" ) || 0 ), - left : thumbPos.left - slidePos.left + parseFloat( $thumb.css( "border-left-width" ) || 0 ), - width : $thumb.width(), - height : $thumb.height(), - scaleX : 1, - scaleY : 1 - }; - } + if (!slide.$ghost) { + self.resolveImageSlideSize(slide, this.naturalWidth, this.naturalHeight); - return rez; - }, + self.afterLoad(slide); + } + if (self.isClosing) { + return; + } - // Final adjustments after current gallery item is moved to position - // and it`s content is loaded - // ================================================================== + if (slide.opts.srcset) { + sizes = slide.opts.sizes; - complete : function() { + if (!sizes || sizes === "auto") { + sizes = + (slide.width / slide.height > 1 && $W.width() / $W.height() > 1 ? "100" : Math.round((slide.width / slide.height) * 100)) + + "vw"; + } - var self = this; + $img.attr("sizes", sizes).attr("srcset", slide.opts.srcset); + } - var current = self.current; - var slides = {}; + // 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))); + } - if ( current.isMoved || !current.isLoaded || current.isComplete ) { - return; - } + self.hideLoading(slide); + }) + .addClass("fancybox-image") + .attr("src", slide.src) + .appendTo(slide.$content); - current.isComplete = true; + if ((img.complete || img.readyState == "complete") && $img.naturalWidth && $img.naturalHeight) { + $img.trigger("load"); + } else if (img.error) { + $img.trigger("error"); + } + }, - current.$slide.siblings().trigger( 'onReset' ); + // Computes the slide size from image size and maxWidth/maxHeight + // ============================================================== - // Trigger any CSS3 transiton inside the slide - forceRedraw( current.$slide ); + resolveImageSlideSize: function (slide, imgWidth, imgHeight) { + var maxWidth = parseInt(slide.opts.width, 10), + maxHeight = parseInt(slide.opts.height, 10); - current.$slide.addClass( 'fancybox-slide--complete' ); + // Sets the default values from the image + slide.width = imgWidth; + slide.height = imgHeight; - // Remove unnecessary slides - $.each( self.slides, function( key, slide ) { - if ( slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1 ) { - slides[ slide.pos ] = slide; + if (maxWidth > 0) { + slide.width = maxWidth; + slide.height = Math.floor((maxWidth * imgHeight) / imgWidth); + } - } else if ( slide ) { + if (maxHeight > 0) { + slide.width = Math.floor((maxHeight * imgWidth) / imgHeight); + slide.height = maxHeight; + } + }, - $.fancybox.stop( slide.$slide ); + // Create iframe wrapper, iframe and bindings + // ========================================== - slide.$slide.unbind().remove(); - } - }); + setIframe: function (slide) { + var self = this, + opts = slide.opts.iframe, + $slide = slide.$slide, + $iframe; - self.slides = slides; + slide.$content = $('
') + .css(opts.css) + .appendTo($slide); - self.updateCursor(); + $slide.addClass("fancybox-slide--" + slide.contentType); - self.trigger( 'afterShow' ); + slide.$iframe = $iframe = $(opts.tpl.replace(/\{rnd\}/g, new Date().getTime())) + .attr(opts.attr) + .appendTo(slide.$content); - // Try to focus on the first focusable element - if ( $( document.activeElement ).is( '[disabled]' ) || ( current.opts.autoFocus && !( current.type == 'image' || current.type === 'iframe' ) ) ) { - self.focus(); - } + 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; - // Preload next and previous slides - // ================================ + slide.$slide.trigger("refresh"); - preload : function() { - var self = this; - var next, prev; + self.afterLoad(slide); + }); - if ( self.group.length < 2 ) { - return; - } + // Recalculate iframe content size + // =============================== - next = self.slides[ self.currPos + 1 ]; - prev = self.slides[ self.currPos - 1 ]; + $slide.on("refresh.fb", function () { + var $content = slide.$content, + frameWidth = opts.css.width, + frameHeight = opts.css.height, + $contents, + $body; - if ( next && next.type === 'image' ) { - self.loadSlide( next ); - } + 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 ( prev && prev.type === 'image' ) { - self.loadSlide( prev ); + 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))); + } - // Try to find and focus on the first focusable element - // ==================================================== + $content.css("height", frameHeight ? frameHeight : ""); - focus : function() { - var current = this.current; - var $el; + $slide.css("overflow", "auto"); + } - if ( this.isClosing ) { - return; - } + $content.removeClass("fancybox-is-hidden"); + }); + } else { + self.afterLoad(slide); + } - // Skip for images and iframes - $el = current && current.isComplete ? current.$slide.find('button,:input,[tabindex],a').filter(':not([disabled]):visible:first') : null; - $el = $el && $el.length ? $el : this.$refs.container; + $iframe.attr("src", slide.src); - $el.focus(); - }, + // 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(); - // Activates current instance - brings container to the front and enables keyboard, - // notifies other instances about deactivating - // ================================================================================= + slide.isLoaded = false; + slide.isRevealed = false; + }); + }, - activate : function () { - var self = this; + // Wrap and append content to the slide + // ====================================== - // Deactivate all instances - $( '.fancybox-container' ).each(function () { - var instance = $(this).data( 'FancyBox' ); + setContent: function (slide, content) { + var self = this; - // Skip self and closing instances - if (instance && instance.uid !== self.uid && !instance.isClosing) { - instance.trigger( 'onDeactivate' ); - } + if (self.isClosing) { + return; + } - }); + self.hideLoading(slide); - if ( self.current ) { - if ( self.$refs.container.index() > 0 ) { - self.$refs.container.prependTo( document.body ); - } + if (slide.$content) { + $.fancybox.stop(slide.$content); + } - self.updateControls(); - } + slide.$slide.empty(); - self.trigger( 'onActivate' ); + // 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"); + } - self.addEvents(); + // 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"); - // Start closing procedure - // This will start "zoom-out" animation if needed and clean everything up afterwards - // ================================================================================= + // Put content back + if (slide.$placeholder) { + slide.$placeholder.after(content.removeClass("fancybox-content").hide()).remove(); - close : function( e, d ) { + slide.$placeholder = null; + } - var self = this; - var current = self.current; + // Remove custom close button + if (slide.$smallBtn) { + slide.$smallBtn.remove(); - var effect, duration; - var $what, opacity, start, end; + slide.$smallBtn = null; + } - var done = function() { - self.cleanUp( e ); - }; + // Remove content and mark slide as not loaded + if (!slide.hasError) { + $(this).empty(); - if ( self.isClosing ) { - return false; - } + slide.isLoaded = false; + slide.isRevealed = false; + } + }); - self.isClosing = true; + $(content).appendTo(slide.$slide); - // If beforeClose callback prevents closing, make sure content is centered - if ( self.trigger( 'beforeClose', e ) === false ) { - self.isClosing = false; + if ($(content).is("video,audio")) { + $(content).addClass("fancybox-video"); - requestAFrame(function() { - self.update(); - }); + $(content).wrap("
"); - return false; - } + slide.contentType = "video"; - // Remove all events - // If there are multiple instances, they will be set again by "activate" method - self.removeEvents(); + slide.opts.width = slide.opts.width || $(content).attr("width"); + slide.opts.height = slide.opts.height || $(content).attr("height"); + } - if ( current.timouts ) { - clearTimeout( current.timouts ); - } + slide.$content = slide.$slide + .children() + .filter("div,form,main,video,audio,article,.fancybox-content") + .first(); - $what = current.$content; - effect = current.opts.animationEffect; - duration = $.isNumeric( d ) ? d : ( effect ? current.opts.animationDuration : 0 ); + slide.$content.siblings().hide(); - // Remove other slides - current.$slide.off( transitionEnd ).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated' ); + // 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(); + } - current.$slide.siblings().trigger( 'onReset' ).remove(); + slide.$content.addClass("fancybox-content"); - // Trigger animations - if ( duration ) { - self.$refs.container.removeClass( 'fancybox-is-open' ).addClass( 'fancybox-is-closing' ); - } + slide.$slide.addClass("fancybox-slide--" + slide.contentType); - // Clean up - self.hideLoading( current ); + self.afterLoad(slide); + }, - self.hideControls(); + // Display error message + // ===================== - self.updateCursor(); + setError: function (slide) { + slide.hasError = true; - // Check if possible to zoom-out - if ( effect === 'zoom' && !( e !== true && $what && duration && current.type === 'image' && !current.hasError && ( end = self.getThumbPos( current ) ) ) ) { - effect = 'fade'; - } + slide.$slide + .trigger("onReset") + .removeClass("fancybox-slide--" + slide.contentType) + .addClass("fancybox-slide--error"); - if ( effect === 'zoom' ) { - $.fancybox.stop( $what ); + slide.contentType = "html"; - start = $.fancybox.getTranslate( $what ); + this.setContent(slide, this.translate(slide, slide.opts.errorTpl)); - start.width = start.width * start.scaleX; - start.height = start.height * start.scaleY; + if (slide.pos === this.currPos) { + this.isAnimating = false; + } + }, - // Check if we need to animate opacity - opacity = current.opts.zoomOpacity; + // Show loading icon inside the slide + // ================================== - if ( opacity == 'auto' ) { - opacity = Math.abs( current.width / current.height - end.width / end.height ) > 0.1; - } + showLoading: function (slide) { + var self = this; - if ( opacity ) { - end.opacity = 0; - } + slide = slide || self.current; - start.scaleX = start.width / end.width; - start.scaleY = start.height / end.height; + if (slide && !slide.$spinner) { + slide.$spinner = $(self.translate(self, self.opts.spinnerTpl)) + .appendTo(slide.$slide) + .hide() + .fadeIn("fast"); + } + }, - start.width = end.width; - start.height = end.height; + // Remove loading icon from the slide + // ================================== - $.fancybox.setTranslate( current.$content, start ); + hideLoading: function (slide) { + var self = this; - $.fancybox.animate( current.$content, end, duration, done ); + slide = slide || self.current; - return true; - } + if (slide && slide.$spinner) { + slide.$spinner.stop().remove(); - if ( effect && duration ) { + delete slide.$spinner; + } + }, - // If skip animation - if ( e === true ) { - setTimeout( done, duration ); + // Adjustments after slide content has been loaded + // =============================================== - } else { - $.fancybox.animate( current.$slide.removeClass( 'fancybox-slide--current' ), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done ); - } + afterLoad: function (slide) { + var self = this; - } else { - done(); - } + if (self.isClosing) { + return; + } - return true; - }, + slide.isLoading = false; + slide.isLoaded = true; + self.trigger("afterLoad", slide); - // Final adjustments after removing the instance - // ============================================= + self.hideLoading(slide); - cleanUp : function( e ) { - var self = this, - instance; + // 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); + } - self.current.$slide.trigger( 'onReset' ); + // Disable right click + if (slide.opts.protect && slide.$content && !slide.hasError) { + slide.$content.on("contextmenu.fb", function (e) { + if (e.button == 2) { + e.preventDefault(); + } - self.$refs.container.empty().remove(); + return true; + }); - self.trigger( 'afterClose', e ); + // 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); + } + } - // Place back focus - if ( self.$lastFocus && !!!self.current.focusBack ) { - self.$lastFocus.focus(); - } + self.adjustCaption(slide); - self.current = null; + self.adjustLayout(slide); - // Check if there are other instances - instance = $.fancybox.getInstance(); + if (slide.pos === self.currPos) { + self.updateCursor(); + } - if ( instance ) { - instance.activate(); + self.revealContent(slide); + }, - } else { + // Prevent caption overlap, + // fix css inconsistency across browsers + // ===================================== - $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); + 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; - $( 'html' ).removeClass( 'fancybox-enabled' ); + $caption.toggleClass("fancybox-caption--separate", preventOverlap); - $( '#fancybox-style-noscroll' ).remove(); - } + 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); - // Call callback and trigger an event - // ================================== + $clone.empty().remove(); + } else if (self.$caption) { + captionH = self.$caption.outerHeight(true); + } - trigger : function( name, slide ) { - var args = Array.prototype.slice.call(arguments, 1), - self = this, - obj = slide && slide.opts ? slide : self.current, - rez; + current.$slide.css("padding-bottom", captionH || ""); + } + }, - if ( obj ) { - args.unshift( obj ); + // Simple hack to fix inconsistency across browsers, described here (affects Edge, too): + // https://bugzilla.mozilla.org/show_bug.cgi?id=748518 + // ==================================================================================== - } else { - obj = self; - } + adjustLayout: function (slide) { + var self = this, + current = slide || self.current, + scrollHeight, + marginBottom, + inlinePadding, + actualPadding; - args.unshift( self ); + if (current.isLoaded && current.opts.disableLayoutFix !== true) { + current.$content.css("margin-bottom", ""); - if ( $.isFunction( obj.opts[ name ] ) ) { - rez = obj.opts[ name ].apply( obj, args ); - } + // 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 ( rez === false ) { - return rez; - } + if (parseFloat(actualPadding) > 0) { + scrollHeight = current.$slide[0].scrollHeight; - if ( name === 'afterClose' ) { - $D.trigger( name + '.fb', args ); + current.$slide.css("padding-bottom", 0); - } else { - self.$refs.container.trigger( name + '.fb', args ); + 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; - // Update infobar values, navigation button states and reveal caption - // ================================================================== + end.scaleX = end.width / start.width; + end.scaleY = end.height / start.height; - updateControls : function ( force ) { + // Check if we need to animate opacity + opacity = slide.opts.zoomOpacity; - var self = this; + if (opacity == "auto") { + opacity = Math.abs(slide.width / slide.height - start.width / start.height) > 0.1; + } - var current = self.current; - var index = current.index; - var opts = current.opts; - var caption = opts.caption; - var $caption = self.$refs.caption; + if (opacity) { + start.opacity = 0.1; + end.opacity = 1; + } - // Recalculate content dimensions - current.$slide.trigger( 'refresh' ); + // Draw image at start position + $.fancybox.setTranslate(slide.$content.removeClass("fancybox-is-hidden"), start); - self.$caption = caption && caption.length ? $caption.html( caption ) : null; + forceRedraw(slide.$content); - if ( !self.isHiddenControls ) { - self.showControls(); - } + // Start animation + $.fancybox.animate(slide.$content, end, duration, function () { + self.isAnimating = false; - // Update info and navigation elements - $('[data-fancybox-count]').html( self.group.length ); - $('[data-fancybox-index]').html( index + 1 ); + self.complete(); + }); - $('[data-fancybox-prev]').prop('disabled', ( !opts.loop && index <= 0 ) ); - $('[data-fancybox-next]').prop('disabled', ( !opts.loop && index >= self.group.length - 1 ) ); + return; + } - }, + self.updateSlide(slide); - // Hide toolbar and caption - // ======================== + // Simply show content if no effect + // ================================ + if (!effect) { + slide.$content.removeClass("fancybox-is-hidden"); - hideControls : function () { + if (!isRevealed && isMoved && slide.type === "image" && !slide.hasError) { + slide.$content.hide().fadeIn("fast"); + } - this.isHiddenControls = true; + if (slide.pos === self.currPos) { + self.complete(); + } - this.$refs.container.removeClass('fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav'); + return; + } - }, + // Prepare for CSS transiton + // ========================= + $.fancybox.stop($slide); - showControls : function() { + //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; - var self = this; - var opts = self.current ? self.current.opts : self.opts; - var $container = self.$refs.container; + $slide.addClass(effectClassName).removeClass("fancybox-slide--current"); //.addClass(effectClassName); - self.isHiddenControls = false; - self.idleSecondsCounter = 0; + slide.$content.removeClass("fancybox-is-hidden"); - $container - .toggleClass('fancybox-show-toolbar', !!( opts.toolbar && opts.buttons ) ) - .toggleClass('fancybox-show-infobar', !!( opts.infobar && self.group.length > 1 ) ) - .toggleClass('fancybox-show-nav', !!( opts.arrows && self.group.length > 1 ) ) - .toggleClass('fancybox-is-modal', !!opts.modal ); + // Force reflow + forceRedraw($slide); - if ( self.$caption ) { - $container.addClass( 'fancybox-show-caption '); + if (slide.type !== "image") { + slide.$content.hide().show(0); + } - } else { - $container.removeClass( 'fancybox-show-caption' ); - } + $.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; - // Toggle toolbar and caption - // ========================== + current.$slide.siblings().trigger("onReset"); - toggleControls : function() { + self.preload("inline"); - if ( this.isHiddenControls ) { - this.showControls(); + // Trigger any CSS transiton inside the slide + forceRedraw(current.$slide); - } else { - this.hideControls(); - } + 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; - $.fancybox = { + self.updateCursor(); - version : "{fancybox-version}", - defaults : defaults, + 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(); + } - // 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 ); - // }); - // ====================================================== + self.next(); + }); + } - getInstance : function ( command ) { - var instance = $('.fancybox-container:not(".fancybox-is-closing"):first').data( 'FancyBox' ); - var args = Array.prototype.slice.call(arguments, 1); + // 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 ( instance instanceof FancyBox ) { + if ($el.length) { + $el.trigger("focus"); + } else { + self.focus(null, true); + } + } - if ( $.type( command ) === 'string' ) { - instance[ command ].apply( instance, args ); + // Avoid jumping + current.$slide.scrollTop(0).scrollLeft(0); + }, - } else if ( $.type( command ) === 'function' ) { - command.apply( instance, args ); + // Preload next and previous slides + // ================================ - } + preload: function (type) { + var self = this, + prev, + next; - return instance; + 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(); } - return false; - - }, + 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 + // ================================================================================= - // Create new instance - // =================== + activate: function () { + var self = this; - open : function ( items, opts, index ) { - return new FancyBox( items, opts, index ); - }, + // 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"); - // Close current or all instances - // ============================== + instance.removeEvents(); - close : function ( all ) { - var instance = this.getInstance(); + instance.isVisible = false; + } + }); - if ( instance ) { - instance.close(); + self.isVisible = true; - // Try to find and close next instance + if (self.current || self.isIdle) { + self.update(); - if ( all === true ) { - this.close(); - } - } + self.updateControls(); + } - }, + self.trigger("onActivate"); - // Close instances and unbind all events - // ============================== + self.addEvents(); + }, - destroy : function() { + // Start closing procedure + // This will start "zoom-out" animation if needed and clean everything up afterwards + // ================================================================================= - this.close( true ); + 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 + }; - $D.off( 'click.fb-start' ); + // 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; + } - // Try to detect mobile devices - // ============================ + $.fancybox.setTranslate($content, start); - isMobile : document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent), + forceRedraw($content); + $.fancybox.animate($content, end, duration, done); - // Detect if 'translate3d' support is available - // ============================================ + return true; + } - use3d : (function() { - var div = document.createElement('div'); + 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 window.getComputedStyle && window.getComputedStyle( div ).getPropertyValue('transform') && !(document.documentMode && document.documentMode < 11); - }()), + return true; + }, + // Final adjustments after removing the instance + // ============================================= - // Helper function to get current visual state of an element - // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] - // ===================================================================== + cleanUp: function (e) { + var self = this, + instance, + $focus = self.current.opts.$orig, + x, + y; - getTranslate : function( $el ) { - var matrix; + self.current.$slide.trigger("onReset"); - if ( !$el || !$el.length ) { - return false; - } + self.$refs.container.empty().remove(); - matrix = $el.eq( 0 ).css('transform'); + self.trigger("afterClose", e); - if ( matrix && matrix.indexOf( 'matrix' ) !== -1 ) { - matrix = matrix.split('(')[1]; - matrix = matrix.split(')')[0]; - matrix = matrix.split(','); - } else { - matrix = []; - } + // Place back focus + if (!!self.current.opts.backFocus) { + if (!$focus || !$focus.length || !$focus.is(":visible")) { + $focus = self.$trigger; + } - if ( matrix.length ) { + if ($focus && $focus.length) { + x = window.scrollX; + y = window.scrollY; - // If IE - if ( matrix.length > 10 ) { - matrix = [ matrix[13], matrix[12], matrix[0], matrix[5] ]; + $focus.trigger("focus"); - } else { - matrix = [ matrix[5], matrix[4], matrix[0], matrix[3]]; - } + $("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 + // ======================== - matrix = matrix.map(parseFloat); + 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); + } - } else { - matrix = [ 0, 0, 1, 1 ]; + return instance; + } - var transRegex = /\.*translate\((.*)px,(.*)px\)/i; - var transRez = transRegex.exec( $el.eq( 0 ).attr('style') ); + return false; + }, - if ( transRez ) { - matrix[ 0 ] = parseFloat( transRez[2] ); - matrix[ 1 ] = parseFloat( transRez[1] ); - } - } + // Create new instance + // =================== - return { - top : matrix[ 0 ], - left : matrix[ 1 ], - scaleX : matrix[ 2 ], - scaleY : matrix[ 3 ], - opacity : parseFloat( $el.css('opacity') ), - width : $el.width(), - height : $el.height() - }; + open: function (items, opts, index) { + return new FancyBox(items, opts, index); + }, - }, + // Close current or all instances + // ============================== + close: function (all) { + var instance = this.getInstance(); - // Shortcut for setting "translate3d" properties for element - // Can set be used to set opacity, too - // ======================================================== + if (instance) { + instance.close(); - setTranslate : function( $el, props ) { - var str = ''; - var css = {}; + // Try to find and close next instance + if (all === true) { + this.close(all); + } + } + }, - if ( !$el || !props ) { - return; - } + // Close all instances and unbind all events + // ========================================= - 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'; + destroy: function () { + this.close(true); - if ( this.use3d ) { - str = 'translate3d(' + str + ', 0px)'; + $D.add("body").off("click.fb-start", "**"); + }, - } else { - str = 'translate(' + str + ')'; - } - } + // Try to detect mobile devices + // ============================ - if ( props.scaleX !== undefined && props.scaleY !== undefined ) { - str = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')'; - } + isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), - if ( str.length ) { - css.transform = str; - } + // Detect if 'translate3d' support is available + // ============================================ - if ( props.opacity !== undefined ) { - css.opacity = props.opacity; - } + use3d: (function () { + var div = document.createElement("div"); - if ( props.width !== undefined ) { - css.width = props.width; - } + return ( + window.getComputedStyle && + window.getComputedStyle(div) && + window.getComputedStyle(div).getPropertyValue("transform") && + !(document.documentMode && document.documentMode < 11) + ); + })(), - if ( props.height !== undefined ) { - css.height = props.height; - } + // Helper function to get current visual state of an element + // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] + // ===================================================================== - return $el.css( css ); - }, + getTranslate: function ($el) { + var domRect; + if (!$el || !$el.length) { + return false; + } - // Simple CSS transition handler - // ============================= + domRect = $el[0].getBoundingClientRect(); - animate : function ( $el, to, duration, callback, leaveAnimationName ) { - var event = transitionEnd || 'transitionend'; + return { + top: domRect.top || 0, + left: domRect.left || 0, + width: domRect.width, + height: domRect.height, + opacity: parseFloat($el.css("opacity")) + }; + }, - if ( $.isFunction( duration ) ) { - callback = duration; - duration = null; - } + // Shortcut for setting "translate3d" properties for element + // Can set be used to set opacity, too + // ======================================================== - if ( !$.isPlainObject( to ) ) { - $el.removeAttr('style'); - } + setTranslate: function ($el, props) { + var str = "", + css = {}; - $el.on( event, function(e) { + if (!$el || !props) { + return; + } - // Skip events from child elements and z-index change - if ( e && e.originalEvent && ( !$el.is( e.originalEvent.target ) || e.originalEvent.propertyName == 'z-index' ) ) { - 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"; - $el.off( event ); + if (this.use3d) { + str = "translate3d(" + str + ", 0px)"; + } else { + str = "translate(" + str + ")"; + } + } - if ( $.isPlainObject( to ) ) { + if (props.scaleX !== undefined && props.scaleY !== undefined) { + str += " scale(" + props.scaleX + ", " + props.scaleY + ")"; + } else if (props.scaleX !== undefined) { + str += " scaleX(" + props.scaleX + ")"; + } - if ( to.scaleX !== undefined && to.scaleY !== undefined ) { - $el.css( 'transition-duration', '0ms' ); + if (str.length) { + css.transform = str; + } - to.width = $el.width() * to.scaleX; - to.height = $el.height() * to.scaleY; + if (props.opacity !== undefined) { + css.opacity = props.opacity; + } - to.scaleX = 1; - to.scaleY = 1; + if (props.width !== undefined) { + css.width = props.width; + } - $.fancybox.setTranslate( $el, to ); - } + if (props.height !== undefined) { + css.height = props.height; + } - } else if ( leaveAnimationName !== true ) { - $el.removeClass( to ); - } + return $el.css(css); + }, - if ( $.isFunction( callback ) ) { - callback( e ); - } + // Simple CSS transition handler + // ============================= - }); + animate: function ($el, to, duration, callback, leaveAnimationName) { + var self = this, + from; - if ( $.isNumeric( duration ) ) { - $el.css( 'transition-duration', duration + 'ms' ); - } + if ($.isFunction(duration)) { + callback = duration; + duration = null; + } - if ( $.isPlainObject( to ) ) { - $.fancybox.setTranslate( $el, to ); + self.stop($el); - } else { - $el.addClass( to ); - } + from = self.getTranslate($el); - $el.data("timer", setTimeout(function() { - $el.trigger( 'transitionend' ); - }, duration + 16)); + $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); - stop : function( $el ) { - clearTimeout( $el.data("timer") ); + if ($.isNumeric(duration)) { + $el.css("transition-duration", ""); + } - $el.off( transitionEnd ); + 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"); + } - // Default click handler for "fancyboxed" links - // ============================================ + // 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; - function _run( e ) { - var target = e.currentTarget, - opts = e.data ? e.data.options : {}, - items = e.data ? e.data.items : [], - value = $(target).attr( 'data-fancybox' ) || '', - index = 0; - - e.preventDefault(); - e.stopPropagation(); - - // Get all related items and find index for clicked one - if ( value ) { - items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' ); - index = items.index( target ); - - // Sometimes current item can not be found - // (for example, when slider clones items) - if ( index < 0 ) { - index = 0; - } + if ($el.parent().hasClass("fancybox-slide--image")) { + $el.parent().addClass("fancybox-is-scaling"); + } + } - } else { - items = [ target ]; + $.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); } - $.fancybox.open( items, opts, index ); - } + $el.off(transitionEnd).css("transition-duration", ""); + $el.parent().removeClass("fancybox-is-scaling"); + } + } + }; - // Create a jQuery plugin - // ====================== + // Default click handler for "fancyboxed" links + // ============================================ - $.fn.fancybox = function (options) { - var selector; + function _run(e, opts) { + var items = [], + index = 0, + $target, + value, + instance; - options = options || {}; - selector = options.selector || false; + // Avoid opening multiple times + if (e && e.isDefaultPrevented()) { + return; + } - if ( selector ) { + e.preventDefault(); - $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, { - items : $( selector ), - options : options - }, _run ); + opts = opts || {}; - } else { + if (e && e.data) { + opts = mergeOpts(e.data.options, opts); + } - this.off( 'click.fb-start' ).on( 'click.fb-start', { - items : this, - options : options - }, _run); + $target = opts.$target || $(e.currentTarget).trigger("blur"); + instance = $.fancybox.getInstance(); - } + if (instance && instance.$trigger && instance.$trigger.is($target)) { + return; + } - return this; - }; + 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); - // Self initializing plugin - // ======================== + // Sometimes current item can not be found + if (index < 0) { + index = 0; + } - $D.on( 'click.fb-start', '[data-fancybox]', _run ); + 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 + ); + } -}( window, document, window.jQuery )); + 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); \ No newline at end of file diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js index 360595c9..15081ff1 100644 --- a/src/js/fullscreen.js +++ b/src/js/fullscreen.js @@ -4,183 +4,170 @@ // 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 val; - var ret = {}; - var i, j; - - for ( i = 0; i < fnMap.length; i++ ) { - val = fnMap[ i ]; - - if ( val && val[ 1 ] in document ) { - for ( j = 0; j < val.length; j++ ) { - ret[ fnMap[ 0 ][ j ] ] = val[ j ]; - } - - return ret; - } - } - - return false; - })(); - - // If browser does not have Full Screen API, then simply unset default button template and stop - if ( !fn ) { - $.fancybox.defaults.btnTpl.fullScreen = false; - - return; - } - - 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 ] ); - - } - }; - - $(document).on({ - 'onInit.fb' : function(e, instance) { - var $container; - - var $button = instance.$refs.toolbar.find('[data-fancybox-fullscreen]'); - - if ( instance && !instance.FullScreen && 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( $container[ 0 ] ); - - }); - - if ( instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true ) { - FullScreen.request( $container[ 0 ] ); - } - - // Expose API - instance.FullScreen = FullScreen; - - } else { - $button.hide(); - } - - }, - - 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { - - // "P" or Spacebar - if ( instance && instance.FullScreen && keycode === 70 ) { - keypress.preventDefault(); - - instance.FullScreen.toggle( instance.$refs.container[ 0 ] ); - } - - }, - - 'beforeClose.fb' : function( instance ) { - if ( instance && instance.FullScreen ) { - FullScreen.exit(); - } - } - }); - - $(document).on(fn.fullscreenchange, function() { - var instance = $.fancybox.getInstance(); - - // If image is zooming, then force to stop and reposition properly - if ( instance.current && instance.current.type === 'image' && instance.isAnimating ) { - instance.current.$content.css( 'transition', 'none' ); - - instance.isAnimating = false; - - instance.update( true, true, 0 ); - } - - }); - -}(document, window.jQuery)); +(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); \ No newline at end of file diff --git a/src/js/guestures.js b/src/js/guestures.js index d31ca167..3e77a5fa 100644 --- a/src/js/guestures.js +++ b/src/js/guestures.js @@ -4,917 +4,920 @@ // 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 pointers = 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,button,input,select,textarea') || $.isFunction( $el.get(0).onclick ) ) { - 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']; - var overflowX = window.getComputedStyle( el )['overflow-x']; - - var vertical = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight; - var 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() { - this.$container.off( '.fb.touch' ); - }; - - Guestures.prototype.ontouchstart = function( e ) { - var self = this; - - var $target = $( e.target ); - var instance = self.instance; - var current = instance.current; - var $content = current.$content; - - var isTouchDevice = ( e.type == 'touchstart' ); - - // Do not respond to both events - if ( isTouchDevice ) { - self.$container.off( 'mousedown.fb.touch' ); - } - - // Ignore clicks while zooming or closing - if ( !current || self.instance.isAnimating || self.instance.isClosing ) { - e.stopPropagation(); - e.preventDefault(); - - return; - } - - // Ignore right click - if ( e.originalEvent && e.originalEvent.button == 2 ) { - return; - } - - // Ignore taping on links, buttons, input elements - if ( !$target.length || isClickable( $target ) || isClickable( $target.parent() ) ) { - return; - } - - // Ignore clicks on the scrollbar - if ( e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left ) { - return; - } - - self.startPoints = pointers( e ); - - // Prevent zooming if already swiping - if ( !self.startPoints || ( self.startPoints.length > 1 && instance.isSliding ) ) { - return; - } - - self.$target = $target; - self.$content = $content; - self.canTap = true; - - $(document).off( '.fb.touch' ); - - $(document).on( isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch', $.proxy(self, "ontouchend")); - $(document).on( isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch', $.proxy(self, "ontouchmove")); - - e.stopPropagation(); - - if ( !(instance.current.opts.touch || instance.canPan() ) || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) { - - // Prevent ghosting - if ( $target.is('img') ) { - e.preventDefault(); - } - - return; - } - - if ( !( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) ) { - e.preventDefault(); - } - - self.canvasWidth = Math.round( current.$slide[0].clientWidth ); - self.canvasHeight = Math.round( current.$slide[0].clientHeight ); - - self.startTime = new Date().getTime(); - self.distanceX = self.distanceY = self.distance = 0; - - self.isPanning = false; - self.isSwiping = false; - self.isZooming = false; - - self.sliderStartPos = self.sliderLastPos || { top: 0, left: 0 }; - self.contentStartPos = $.fancybox.getTranslate( self.$content ); - self.contentLastPos = null; - - if ( self.startPoints.length === 1 && !self.isZooming ) { - self.canTap = !instance.isSliding; - - if ( current.type === 'image' && ( self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1 ) ) { - - $.fancybox.stop( self.$content ); - - self.$content.css( 'transition-duration', '0ms' ); - - self.isPanning = true; - - } else { - - self.isSwiping = true; - } - - self.$container.addClass('fancybox-controls--isGrabbing'); - } - - if ( self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && ( current.isLoaded || current.$ghost ) ) { - self.isZooming = true; - - self.isSwiping = false; - self.isPanning = false; - - $.fancybox.stop( self.$content ); - - self.$content.css( 'transition-duration', '0ms' ); - - 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.ontouchmove = function( e ) { - - var self = this; - - self.newPoints = pointers( e ); - - if ( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) { - e.stopPropagation(); - - self.canTap = false; - - return; - } - - if ( !( self.instance.current.opts.touch || self.instance.canPan() ) || !self.newPoints || !self.newPoints.length ) { - return; - } - - 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.$target.is( self.$stage ) || self.$stage.find( self.$target ).length ) ) { - return; - } - - e.stopPropagation(); - e.preventDefault(); - - if ( self.isSwiping ) { - self.onSwipe(); - - } else if ( self.isPanning ) { - self.onPan(); - - } else if ( self.isZooming ) { - self.onZoom(); - } - - } - - }; - - Guestures.prototype.onSwipe = function() { - - var self = this; - - var swiping = self.isSwiping; - var left = self.sliderStartPos.left || 0; - var angle; - - if ( swiping === true ) { - - if ( Math.abs( self.distance ) > 10 ) { - - self.canTap = false; - - if ( self.instance.group.length < 2 && self.instance.opts.touch.vertical ) { - self.isSwiping = 'y'; - - } else if ( self.instance.isSliding || self.instance.opts.touch.vertical === false || ( self.instance.opts.touch.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'; - } - - self.instance.isSliding = self.isSwiping; - - // Reset points to avoid jumping, because we dropped first swipes to calculate the angle - self.startPoints = self.newPoints; - - $.each(self.instance.slides, function( index, slide ) { - $.fancybox.stop( slide.$slide ); - - slide.$slide.css( 'transition-duration', '0ms' ); - - slide.inTransition = false; - - if ( slide.pos === self.instance.current.pos ) { - self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide ).left; - } - }); - - //self.instance.current.isMoved = true; - - // Stop slideshow - if ( self.instance.SlideShow && self.instance.SlideShow.isActive ) { - self.instance.SlideShow.stop(); - } - } - - } else { - - if ( swiping == 'x' ) { - - // Sticky edges - 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; - - var newOffsetX, newOffsetY, newPos; +(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(); - self.canTap = false; + return; + } - if ( self.contentStartPos.width > self.canvasWidth ) { - newOffsetX = self.contentStartPos.left + self.distanceX; + self.realPoints = self.startPoints = getPointerXY(e); - } else { - newOffsetX = self.contentStartPos.left; - } + if (!self.startPoints.length) { + return; + } - newOffsetY = self.contentStartPos.top + self.distanceY; + // Allow other scripts to catch touch event if "touch" is set to false + if (current.touch) { + e.stopPropagation(); + } - newPos = self.limitMovement( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); + self.startEvent = e; - newPos.scaleX = self.contentStartPos.scaleX; - newPos.scaleY = self.contentStartPos.scaleY; + self.canTap = true; + self.$target = $target; + self.$content = $content; + self.opts = current.opts.touch; - self.contentLastPos = newPos; + self.isPanning = false; + self.isSwiping = false; + self.isZooming = false; + self.isScrolling = false; + self.canPan = instance.canPan(); - if ( self.requestId ) { - cancelAFrame( self.requestId ); + self.startTime = new Date().getTime(); + self.distanceX = self.distanceY = self.distance = 0; - self.requestId = null; - } + self.canvasWidth = Math.round($slide[0].clientWidth); + self.canvasHeight = Math.round($slide[0].clientHeight); - self.requestId = requestAFrame(function() { - $.fancybox.setTranslate( self.$content, self.contentLastPos ); - }); - }; + self.contentLastPos = null; + self.contentStartPos = $.fancybox.getTranslate(self.$content) || { + top: 0, + left: 0 + }; + self.sliderStartPos = $.fancybox.getTranslate($slide); - // Make panning sticky to the edges - Guestures.prototype.limitMovement = function( newOffsetX, newOffsetY, newWidth, newHeight ) { + // Since position will be absolute, but we need to make it relative to the stage + self.stagePos = $.fancybox.getTranslate(instance.$refs.stage); - var self = this; + self.sliderStartPos.top -= self.stagePos.top; + self.sliderStartPos.left -= self.stagePos.left; - var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY; + self.contentStartPos.top -= self.stagePos.top; + self.contentStartPos.left -= self.stagePos.left; - var canvasWidth = self.canvasWidth; - var canvasHeight = self.canvasHeight; - - var currentOffsetX = self.contentStartPos.left; - var currentOffsetY = self.contentStartPos.top; - - var distanceX = self.distanceX; - var distanceY = self.distanceY; - - // Slow down proportionally to traveled distance - - minTranslateX = Math.max(0, canvasWidth * 0.5 - newWidth * 0.5 ); - minTranslateY = Math.max(0, canvasHeight * 0.5 - newHeight * 0.5 ); - - maxTranslateX = Math.min( canvasWidth - newWidth, canvasWidth * 0.5 - newWidth * 0.5 ); - maxTranslateY = Math.min( canvasHeight - newHeight, canvasHeight * 0.5 - newHeight * 0.5 ); - - if ( newWidth > canvasWidth ) { - - // -> - 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 ( newHeight > canvasHeight ) { - - // \/ - 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 currentWidth = self.contentStartPos.width; - var currentHeight = self.contentStartPos.height; - - var currentOffsetX = self.contentStartPos.left; - var currentOffsetY = self.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 : self.contentStartPos.scaleX * pinchRatio, - scaleY : self.contentStartPos.scaleY * pinchRatio - }; - - self.canTap = false; - - self.newWidth = newWidth; - self.newHeight = newHeight; - - self.contentLastPos = newPos; - - if ( self.requestId ) { - cancelAFrame( self.requestId ); - - self.requestId = null; - } - - self.requestId = requestAFrame(function() { - $.fancybox.setTranslate( self.$content, self.contentLastPos ); - }); - - }; - - Guestures.prototype.ontouchend = function( e ) { - - var self = this; - var dMs = Math.max( (new Date().getTime() ) - self.startTime, 1); - - var swiping = self.isSwiping; - var panning = self.isPanning; - var zooming = self.isZooming; - - self.endPoints = pointers( e ); - - self.$container.removeClass( 'fancybox-controls--isGrabbing' ); - - $(document).off( '.fb.touch' ); - - if ( self.requestId ) { - cancelAFrame( self.requestId ); - - self.requestId = null; - } - - self.isSwiping = false; - self.isPanning = false; - self.isZooming = false; - - if ( self.canTap ) { - return self.onTap( e ); - } - - self.speed = 366; - - // Speed in px/ms - self.velocityX = self.distanceX / dMs * 0.5; - self.velocityY = self.distanceY / dMs * 0.5; - - self.speedX = Math.max( self.speed * 0.5, Math.min( self.speed * 1.5, ( 1 / Math.abs( self.velocityX ) ) * self.speed ) ); - - if ( panning ) { - self.endPanning(); - - } else if ( zooming ) { - self.endZooming(); - - } else { - self.endSwiping( swiping ); - } - - return; - }; - - Guestures.prototype.endSwiping = function( swiping ) { - - var self = this; - var ret = false; - - self.instance.isSliding = false; - self.sliderLastPos = null; - - // Close if swiped vertically / navigate if horizontally - if ( swiping == 'y' && 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 - }, 150 ); - - ret = self.instance.close( true, 300 ); - - } else if ( swiping == 'x' && self.distanceX > 50 && self.instance.group.length > 1 ) { - ret = self.instance.previous( self.speedX ); - - } else if ( swiping == 'x' && self.distanceX < -50 && self.instance.group.length > 1 ) { - ret = self.instance.next( self.speedX ); - } - - if ( ret === false && ( swiping == 'x' || swiping == 'y' ) ) { - self.instance.jumpTo( self.instance.current.index, 150 ); - } - - self.$container.removeClass( 'fancybox-is-sliding' ); - - }; - - // Limit panning from edges - // ======================== - - Guestures.prototype.endPanning = function() { - - var self = this; - var newOffsetX, newOffsetY, newPos; - - if ( !self.contentLastPos ) { - return; - } - - if ( self.instance.current.opts.touch.momentum === false ) { - newOffsetX = self.contentLastPos.left; - newOffsetY = self.contentLastPos.top; - - } else { - - // Continue movement - newOffsetX = self.contentLastPos.left + ( self.velocityX * self.speed ); - newOffsetY = self.contentLastPos.top + ( self.velocityY * self.speed ); - } - - 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, 330 ); - }; - - - 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 ); - - // Switch from scale() to width/height or animation will not work correctly - $.fancybox.setTranslate( self.content, $.fancybox.getTranslate( self.$content ) ); - - $.fancybox.animate( self.$content, newPos, 150 ); - } - - }; - - Guestures.prototype.onTap = function(e) { - var self = this; - var $target = $( e.target ); + $(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"; + } - var instance = self.instance; - var current = instance.current; + if (self.isSwiping === "y" && $.fancybox.isMobile && self.isScrollable) { + self.isScrolling = true; - var endPoints = ( e && pointers( e ) ) || self.startPoints; + return; + } - var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0; - var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top : 0; + instance.isDragging = self.isSwiping; - var where; + // Reset points to avoid jumping, because we dropped first swipes to calculate the angle + self.startPoints = self.newPoints; - var process = function ( prefix ) { + $.each(instance.slides, function (index, slide) { + var slidePos, stagePos; - var action = current.opts[ prefix ]; + $.fancybox.stop(slide.$slide); - if ( $.isFunction( action ) ) { - action = action.apply( instance, [ current, e ] ); - } + slidePos = $.fancybox.getTranslate(slide.$slide); + stagePos = $.fancybox.getTranslate(instance.$refs.stage); - if ( !action) { - return; - } + slide.$slide + .css({ + transform: "", + opacity: "", + "transition-duration": "" + }) + .removeClass("fancybox-animated") + .removeClass(function (index, className) { + return (className.match(/(^|\s)fancybox-fx-\S+/g) || []).join(" "); + }); - switch ( action ) { + if (slide.pos === instance.current.pos) { + self.sliderStartPos.top = slidePos.top - stagePos.top; + self.sliderStartPos.left = slidePos.left - stagePos.left; + } - case "close" : + $.fancybox.setTranslate(slide.$slide, { + top: slidePos.top - stagePos.top, + left: slidePos.left - stagePos.left + }); + }); - instance.close( self.startEvent ); + // 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; + } - break; + // \/ + if (distanceY > 0 && newOffsetY > minTranslateY) { + newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8) || 0; + } - case "toggleControls" : + // /\ + if (distanceY < 0 && newOffsetY < maxTranslateY) { + newOffsetY = maxTranslateY + 1 - Math.pow(maxTranslateY - currentOffsetY - distanceY, 0.8) || 0; + } - instance.toggleControls( true ); + return { + top: newOffsetY, + left: newOffsetX + }; + }; - break; + Guestures.prototype.limitPosition = function (newOffsetX, newOffsetY, newWidth, newHeight) { + var self = this; - case "next" : + var canvasWidth = self.canvasWidth; + var canvasHeight = self.canvasHeight; - instance.next(); + 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); + } - break; + 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); + } - case "nextOrClose" : + return { + top: newOffsetY, + left: newOffsetX + }; + }; - if ( instance.group.length > 1 ) { - instance.next(); + Guestures.prototype.onZoom = function () { + var self = this; - } else { - instance.close( self.startEvent ); - } + // Calculate current distance between points to get pinch ratio and new width and height + var contentStartPos = self.contentStartPos; - break; + var currentWidth = contentStartPos.width; + var currentHeight = contentStartPos.height; - case "zoom" : + var currentOffsetX = contentStartPos.left; + var currentOffsetY = contentStartPos.top; - if ( current.type == 'image' && ( current.isLoaded || current.$ghost ) ) { + var endDistanceBetweenFingers = distance(self.newPoints[0], self.newPoints[1]); - if ( instance.canPan() ) { - instance.scaleToFit(); + var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; - } else if ( instance.isScaledDown() ) { - instance.scaleToActual( tapX, tapY ); + var newWidth = Math.floor(currentWidth * pinchRatio); + var newHeight = Math.floor(currentHeight * pinchRatio); - } else if ( instance.group.length < 2 ) { - instance.close( self.startEvent ); - } - } + // This is the translation due to pinch-zooming + var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; + var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; - break; - } + // 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; - // Ignore right click - if ( e.originalEvent && e.originalEvent.button == 2 ) { - return; - } + self.instance.isDragging = false; - // Skip if current slide is not in the center - if ( instance.isSliding ) { - return; - } + if (self.canTap) { + return self.onTap(e); + } - // Skip if clicked on the scrollbar - if ( tapX > $target[0].clientWidth + $target.offset().left ) { - return; - } + self.speed = 100; - // Check where is clicked - if ( $target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container' ) ) { - where = 'Outside'; + // Speed in px/ms + self.velocityX = (self.distanceX / self.dMs) * 0.5; + self.velocityY = (self.distanceY / self.dMs) * 0.5; - } else if ( $target.is( '.fancybox-slide' ) ) { - where = 'Slide'; + 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); + } - } else if ( instance.current.$content && instance.current.$content.has( e.target ).length ) { - where = 'Content'; + self.$container.removeClass("fancybox-is-sliding"); + }; - } else { - return; - } + // Limit panning from edges + // ======================== + Guestures.prototype.endPanning = function () { + var self = this, + newOffsetX, + newOffsetY, + newPos; - // Check if this is a double tap - if ( self.tapped ) { + if (!self.contentLastPos) { + return; + } - // Stop previously created single tap - clearTimeout( self.tapped ); - self.tapped = null; + 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; + } - // Skip if distance between taps is too big - if ( Math.abs( tapX - self.tapX ) > 50 || Math.abs( tapY - self.tapY ) > 50 || instance.isSliding ) { - return this; - } + newPos = self.limitPosition(newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height); - // OK, now we assume that this is a double-tap - process( 'dblclick' + where ); + newPos.width = self.contentStartPos.width; + newPos.height = self.contentStartPos.height; - } else { + $.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]; - // 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 ($.isFunction(action)) { + action = action.apply(instance, [current, e]); + } - if ( current.opts[ 'dblclick' + where ] && current.opts[ 'dblclick' + where ] !== current.opts[ 'click' + where ] ) { - self.tapped = setTimeout(function() { - self.tapped = null; + if (!action) { + return; + } - process( 'click' + where ); + switch (action) { + case "close": + instance.close(self.startEvent); - }, 300); + break; - } else { - process( 'click' + where ); - } + case "toggleControls": + instance.toggleControls(); - } + break; - return this; - }; + case "next": + instance.next(); - $(document).on('onActivate.fb', function (e, instance) { - if ( instance && !instance.Guestures ) { - instance.Guestures = new Guestures( instance ); - } - }); + break; - $(document).on('beforeClose.fb', function (e, instance) { - if ( instance && instance.Guestures ) { - instance.Guestures.destroy(); - } - }); + case "nextOrClose": + if (instance.group.length > 1) { + instance.next(); + } else { + instance.close(self.startEvent); + } + break; -}(window, document, window.jQuery)); + 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); \ No newline at end of file diff --git a/src/js/hash.js b/src/js/hash.js index 8cfc5bfa..2c1d8679 100644 --- a/src/js/hash.js +++ b/src/js/hash.js @@ -4,220 +4,207 @@ // Enables linking to each modal // // ========================================================================== -;(function (document, window, $) { - '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 ); - }; - } - - // Variable containing last hash value set by fancyBox - // It will be used to determine if fancyBox needs to close after hash change is detected - var currentHash = null; - - // Throtlling the history change - var timerID = null; - - // Get info about gallery name and current index from url - function parseUrl() { - var hash = window.location.hash.substr( 1 ); - var rez = hash.split( '-' ); - var index = rez.length > 1 && /^\+?\d+$/.test( rez[ rez.length - 1 ] ) ? parseInt( rez.pop( -1 ), 10 ) || 1 : 1; - var gallery = rez.join( '-' ); - - // Index is starting from 1 - if ( index < 1 ) { - index = 1; - } - - return { - hash : hash, - index : index, - gallery : gallery - }; - } - - // Trigger click evnt on links to open new fancyBox instance - function triggerFromUrl( url ) { - var $el; - - if ( url.gallery !== '' ) { - - // If we can find element matching 'data-fancybox' atribute, then trigger click event for that .. - $el = $( "[data-fancybox='" + $.escapeSelector( url.gallery ) + "']" ).eq( url.index - 1 ); - - if ( $el.length ) { - $el.trigger( 'click' ); - - } else { - - // .. if not, try finding element by ID - $( "#" + $.escapeSelector( url.gallery ) + "" ).trigger( 'click' ); - - } - +(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) + " "; } - } - - // Get gallery name from current instance - function getGallery( instance ) { - var opts; - - if ( !instance ) { - return false; - } - - opts = instance.current ? instance.current.opts : instance.opts; - - return opts.$orig ? opts.$orig.data( 'fancybox' ) : ( opts.hash || '' ); - } - - // Star when DOM becomes ready - $(function() { - - // Small delay is used to allow other scripts to process "dom ready" event - setTimeout(function() { - - // Check if this module is not disabled - 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 = getGallery( 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.opts.hash === false ) { - return; - } - - gallery = getGallery( instance ); - - // Update window hash - if ( gallery && gallery !== '' ) { - - if ( window.location.hash.indexOf( gallery ) < 0 ) { - instance.opts.origHash = window.location.hash; - } - - currentHash = gallery + ( instance.group.length > 1 ? '-' + ( current.index + 1 ) : '' ); - - if ( 'replaceState' in window.history ) { - if ( timerID ) { - clearTimeout( timerID ); - } + // 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"); + } + } - timerID = setTimeout(function() { - window.history[ firstRun ? 'pushState' : 'replaceState' ]( {} , document.title, window.location.pathname + window.location.search + '#' + currentHash ); + // Get gallery name from current instance + function getGalleryID(instance) { + var opts, ret; - timerID = null; + if (!instance) { + return false; + } - }, 300); + opts = instance.current ? instance.current.opts : instance.opts; + ret = opts.hash || (opts.$orig ? opts.$orig.data("fancybox") || opts.$orig.data("fancybox-trigger") : ""); - } else { - window.location.hash = currentHash; - } + 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; - 'beforeClose.fb' : function( e, instance, current ) { - var gallery, origHash; + if (instance.group[instance.currIndex].opts.hash === false) { + return; + } - if ( timerID ) { - clearTimeout( timerID ); - } + url = parseUrl(); + gallery = getGalleryID(instance); - if ( current.opts.hash === false ) { - return; - } + // Make sure gallery start index matches index from hash + if (gallery && url.gallery && gallery == url.gallery) { + instance.currIndex = url.index - 1; + } + }, - gallery = getGallery( instance ); - origHash = instance && instance.opts.origHash ? instance.opts.origHash : ''; + "beforeShow.fb": function (e, instance, current, firstRun) { + var gallery; - // Remove hash from location bar - if ( gallery && gallery !== '' ) { + if (!current || current.opts.hash === false) { + return; + } - if ( 'replaceState' in history ) { - window.history.replaceState( {} , document.title, window.location.pathname + window.location.search + origHash ); + // Check if need to update window hash + gallery = getGalleryID(instance); - } else { - window.location.hash = origHash; + if (!gallery) { + return; + } - // Keep original scroll position - $( window ).scrollTop( instance.scrollTop ).scrollLeft( instance.scrollLeft ); - } - } + // 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) : ""); - currentHash = null; - } - }); + // If current hash is the same (this instance most likely is opened by hashchange), then do nothing + if (window.location.hash === "#" + instance.currentHash) { + return; + } - // Check if need to close after url has changed - $(window).on('hashchange.fb', function() { - var url = parseUrl(); + if (firstRun && !instance.origHash) { + instance.origHash = window.location.hash; + } - if ( $.fancybox.getInstance() ) { - if ( currentHash && currentHash !== url.gallery + '-' + url.index && !( url.index === 1 && currentHash == url.gallery ) ) { - currentHash = null; + if (instance.hashTimer) { + clearTimeout(instance.hashTimer); + } - $.fancybox.close(); - } + // 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; + } - } else if ( url.gallery !== '' ) { - triggerFromUrl( url ); - } - }); + 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; + } + } - // If navigating away from current page - $(window).one('unload.fb popstate.fb', function() { - $.fancybox.getInstance( 'close', true, 0 ); - }); + instance.currentHash = null; + } + }); - // Check current hash and trigger click event on matching element to start fancyBox, if needed - triggerFromUrl( parseUrl() ); + // 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; + } + } + ); - }, 50); + 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); + } }); - -}(document, window, window.jQuery)); + // 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); \ No newline at end of file diff --git a/src/js/media.js b/src/js/media.js index cd83ba0a..d01692a1 100644 --- a/src/js/media.js +++ b/src/js/media.js @@ -4,208 +4,287 @@ // Adds additional media type support // // ========================================================================== -;(function ($) { - - 'use strict'; - - // 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; - }; - - // 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 : '//www.youtube.com/embed/$4', - thumb : '//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, - api : 1 - }, - paramPlace : 3, - type : 'iframe', - url : '//player.vimeo.com/video/$2' - }, - - metacafe : { - matcher : /metacafe.com\/watch\/(\d+)\/(.*)?/, - type : 'iframe', - url : '//www.metacafe.com/embed/$1/?ap=1' - }, - - dailymotion : { - matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, - params : { - additionalInfos : 0, - autoStart : 1 - }, - type : 'iframe', - url : '//www.dailymotion.com/embed/video/$1' - }, - - vine : { - matcher : /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/, - type : 'iframe', - url : '//vine.co/v/$1/embed/simple' - }, - - 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 - // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 - // https://www.google.lv/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 - google_maps : { - 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] ) + '&output=' + ( rez[12] && rez[12].indexOf('layer=c') > 0 ? 'svembed' : 'embed' ); - } - } - }; - - $(document).on('onInit.fb', function (e, instance) { - - $.each(instance.group, function( i, item ) { - - var url = item.src || '', - type = false, - media, - thumb, - rez, - params, - urlParams, - o, - provider; - - // Skip items that already have content type - if ( item.type ) { - return; - } - - media = $.extend( true, {}, defaults, item.opts.media ); - - // Look for any matching media type - $.each(media, function ( n, el ) { - rez = url.match(el.matcher); - o = {}; - provider = n; - - if (!rez) { - return; - } - - type = el.type; - - if ( el.paramPlace && rez[ el.paramPlace ] ) { - urlParams = rez[ el.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 ) { - o[ p[0] ] = decodeURIComponent( p[1].replace(/\+/g, " ") ); - } - } - } - - params = $.extend( true, {}, el.params, item.opts[ n ], o ); - - url = $.type(el.url) === "function" ? el.url.call(this, rez, params, item) : format(el.url, rez, params); - thumb = $.type(el.thumb) === "function" ? el.thumb.call(this, rez, params, item) : format(el.thumb, rez); - - if ( provider === 'vimeo' ) { - url = url.replace('&%23', '#'); - } - - return false; - }); - - // If it is found, then change content type and update the url - - if ( type ) { - item.src = url; - item.type = type; - - if ( !item.opts.thumb && !( item.opts.$thumb && item.opts.$thumb.length ) ) { - item.opts.thumb = thumb; - } - - if ( type === 'iframe' ) { - $.extend(true, item.opts, { - iframe : { - preload : false, - attr : { - scrolling : "no" - } - } - }); - - item.contentProvider = provider; - - item.opts.slideClass += ' fancybox-slide--' + ( provider == 'google_maps' ? 'map' : 'video' ); - } - - } else { - - // If no content type is found, then set it to `image` as fallback - item.type = 'image'; - } - - }); - - }); - -}(window.jQuery)); +(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); \ No newline at end of file diff --git a/src/js/share.js b/src/js/share.js new file mode 100644 index 00000000..5850dd97 --- /dev/null +++ b/src/js/share.js @@ -0,0 +1,104 @@ +//// ========================================================================== +// +// 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); \ No newline at end of file diff --git a/src/js/slideshow.js b/src/js/slideshow.js index b839cf41..1ffaa742 100644 --- a/src/js/slideshow.js +++ b/src/js/slideshow.js @@ -7,164 +7,199 @@ // $.fancybox.getInstance().SlideShow.start() // // ========================================================================== -;(function (document, $) { - 'use strict'; - - var SlideShow = function( instance ) { - this.instance = instance; - this.init(); - }; - - $.extend( SlideShow.prototype, { - timer : null, - isActive : false, - $button : null, - speed : 3000, - - init : function() { - var self = this; - - self.$button = self.instance.$refs.toolbar.find('[data-fancybox-play]').on('click', function() { - self.toggle(); - }); - - if ( self.instance.group.length < 2 || !self.instance.group[ self.instance.currIndex ].opts.slideShow ) { - self.$button.hide(); - } - }, - - set : function() { - var self = this; - - // Check if reached last element - if ( self.instance && self.instance.current && (self.instance.current.opts.loop || self.instance.currIndex < self.instance.group.length - 1 )) { - self.timer = setTimeout(function() { - self.instance.next(); - - }, self.instance.current.opts.slideShow.speed || self.speed); - - } else { - self.stop(); - self.instance.idleSecondsCounter = 0; - self.instance.showControls(); - } - - }, - - clear : function() { - var self = this; - - clearTimeout( self.timer ); - - self.timer = null; - }, - - start : function() { - var self = this; - var current = self.instance.current; - - if ( self.instance && current && ( current.opts.loop || current.index < self.instance.group.length - 1 )) { - - self.isActive = true; - - self.$button - .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_STOP ) - .addClass( 'fancybox-button--pause' ); - - if ( current.isComplete ) { - self.set(); - } - } - }, - - stop : function() { - var self = this; - var current = self.instance.current; - - self.clear(); - - self.$button - .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_START ) - .removeClass( 'fancybox-button--pause' ); - - self.isActive = false; - }, - - 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(); - var SlideShow = instance && instance.SlideShow; - - if ( SlideShow && SlideShow.isActive ) { - if ( document.hidden ) { - SlideShow.clear(); - - } else { - SlideShow.set(); - } - } - }); - -}(document, window.jQuery)); +(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); \ No newline at end of file diff --git a/src/js/thumbs.js b/src/js/thumbs.js index abbd2f47..55645c12 100644 --- a/src/js/thumbs.js +++ b/src/js/thumbs.js @@ -4,228 +4,248 @@ // Displays thumbnails in a grid // // ========================================================================== -;(function (document, $) { - 'use strict'; - - var FancyThumbs = function( instance ) { - this.instance = instance; - this.init(); - }; - - $.extend( FancyThumbs.prototype, { - - $button : null, - $grid : null, - $list : null, - isVisible : false, - - init : function() { - var self = this; - - var first = self.instance.group[0], - second = self.instance.group[1]; - - self.$button = self.instance.$refs.toolbar.find( '[data-fancybox-thumbs]' ); - - if ( self.instance.group.length > 1 && self.instance.group[ self.instance.currIndex ].opts.thumbs && ( - ( first.type == 'image' || first.opts.thumb || first.opts.$thumb ) && - ( second.type == 'image' || second.opts.thumb || second.opts.$thumb ) - )) { - - self.$button.on('click', function() { - self.toggle(); - }); - - self.isActive = true; - - } else { - self.$button.hide(); - - self.isActive = false; - } - - }, - - create : function() { - var instance = this.instance, - list, - src; - - this.$grid = $('
').appendTo( instance.$refs.container ); - - list = '
    '; - - $.each(instance.group, function( i, item ) { - - src = item.opts.thumb || ( item.opts.$thumb ? item.opts.$thumb.attr('src') : null ); - - if ( !src && item.type === 'image' ) { - src = item.src; - } - - if ( src && src.length ) { - list += '
  • '; - } - - }); - - list += '
'; - - this.$list = $( list ).appendTo( this.$grid ).on('click', 'li', function() { - instance.jumpTo( $(this).data('index') ); - }); - - this.$list.find('img').hide().one('load', function() { - - var $parent = $(this).parent().removeClass('fancybox-thumbs-loading'), - thumbWidth = $parent.outerWidth(), - thumbHeight = $parent.outerHeight(), - width, - height, - widthRatio, - heightRatio; - - width = this.naturalWidth || this.width; - height = this.naturalHeight || this.height; - - //Calculate thumbnail width/height and center it - - widthRatio = width / thumbWidth; - heightRatio = height / thumbHeight; - - if (widthRatio >= 1 && heightRatio >= 1) { - if (widthRatio > heightRatio) { - width = width / heightRatio; - height = thumbHeight; - - } else { - width = thumbWidth; - height = height / widthRatio; - } - } - - $(this).css({ - width : Math.floor(width), - height : Math.floor(height), - 'margin-top' : Math.min( 0, Math.floor(thumbHeight * 0.3 - height * 0.3 ) ), - 'margin-left' : Math.min( 0, Math.floor(thumbWidth * 0.5 - width * 0.5 ) ) - }).show(); - - }) - .each(function() { - this.src = $( this ).data( 'src' ); - }); - - }, - - focus : function() { - - if ( this.instance.current ) { - this.$list - .children() - .removeClass('fancybox-thumbs-active') - .filter('[data-index="' + this.instance.current.index + '"]') - .addClass('fancybox-thumbs-active') - .focus(); - } - - }, - - close : function() { - this.$grid.hide(); - }, - - update : function() { - - this.instance.$refs.container.toggleClass( 'fancybox-show-thumbs', this.isVisible ); - - if ( this.isVisible ) { - - if ( !this.$grid ) { - this.create(); - } - - this.instance.trigger( 'onThumbsShow' ); - - this.focus(); - - } else if ( this.$grid ) { - this.instance.trigger( 'onThumbsHide' ); - } - - // Update content position - this.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) { - if ( instance && !instance.Thumbs ) { - instance.Thumbs = new FancyThumbs( instance ); - } - }, - - 'beforeShow.fb' : function(e, instance, item, firstRun) { - var Thumbs = instance && instance.Thumbs; - - if ( !Thumbs || !Thumbs.isActive ) { - return; - } - - if ( item.modal ) { - Thumbs.$button.hide(); - - Thumbs.hide(); - - return; - } - - if ( firstRun && instance.opts.thumbs.autoStart === true ) { - Thumbs.show(); - } - - if ( Thumbs.isVisible ) { - Thumbs.focus(); - } - }, - - '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 && instance.opts.thumbs.hideOnClose !== false ) { - Thumbs.close(); - } - } - - }); - -}(document, window.jQuery)); +(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); \ No newline at end of file diff --git a/src/js/wheel.js b/src/js/wheel.js new file mode 100644 index 00000000..446e1e6d --- /dev/null +++ b/src/js/wheel.js @@ -0,0 +1,41 @@ +// ========================================================================== +// +// 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); \ No newline at end of file