fancyBox3 Documentation
- --
-
- Homepage -
- Documentation -
- Store -
- Github -
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: '
{{ERROR}}
{{ERROR}}
" + + '' + + '' + + "Facebook" + + "" + + '' + + '' + + "Twitter" + + "" + + '' + + '' + + "Pinterest" + + "" + + "
" + + '' + + "{{ERROR}}
{{ERROR}}
- 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. -
- -
- -- jQuery 3+ is preferred, but fancyBox works with jQuery 1.9.1+ and jQuery 2+ -
- -- 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: -
- -
- 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> + +++ ++ ++++- - - - + 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: '++ ++++
+- + Introduction +
+- + Setup +
+- + How to Use +
+- + Media types + + +
+- + Options +
+- + Api +
+- + Modules +
+- + FAQ +
++ ++// Customize buttons +$( '[data-fancybox="images"]' ).fancybox({ + buttons : [ + 'fb', + 'close' + ] +}); + + ++ +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 thedata-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
+ -- 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, useselector
option. Example: -<a href="image_1.jpg" data-fancybox="gallery" data-caption="Caption #1"> + <img src="thumbnail_1.jpg" alt="" /> +</a> -+ + ++<a href="image_2.jpg" data-fancybox="gallery" data-caption="Caption #2"> + <img src="thumbnail_2.jpg" alt="" /> +</a> +$().fancybox({ - selector : '[data-fancybox="images"]', - loop : true -});
+ 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> +
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. +
- Example of displaying a simple message: - + -$('[data-fancybox="gallery"]').fancybox({ + // Options will go here +}); +
+$.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
+ -- 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>');
+ 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!' ); + } + } +});
+ 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>
- 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>
+ 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; + } + } +});
- 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>
- 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 });
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 href="https://vimeo.com/191947042"> + Vimeo video +</a> -<a data-fancybox data-src="#hidden-content" href="javascript:;"> - Hidden div -</a>
- The script will append small close button (if you have not disabled by
+<a data-fancybox data-width="640" data-height="360" href="video.mp4"> + Direct link to MP4 video +</a> -smallBtn:false
) - and will not apply any styles except for centering. Therefore you can easily set custom dimensions using CSS. -Ajax
+<a data-fancybox href="#myVideo"> + HTML5 video element +</a> -- To load content via AJAX, you need to add a
+<video width="640" height="320" controls id="myVideo" style="display:none;"> + <source src="https://www.html5rocks.com/en/tutorials/video/basics/Chrome_ImF.mp4" type="video/mp4"> + <source src="https://www.html5rocks.com/en/tutorials/video/basics/Chrome_ImF.webm" type="video/webm"> + <source src="https://www.html5rocks.com/en/tutorials/video/basics/Chrome_ImF.ogv" type="video/ogg"> + Your browser doesn't support HTML5 video tag. +</video>data-type="ajax"
attribute to your link: -+ Controlling YouTube & Vimeo video via URL parameters: +
+ +<a data-fancybox href="https://www.youtube.com/watch?v=_sI_Ps7JSEk&autoplay=1&rel=0&controls=0&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>
+ Via JavaScript: +
+ ++ -$('[data-fancybox]').fancybox({ + youtube : { + controls : 0, + showinfo : 0 + }, + vimeo : { + color : 'f00' + } +});
- 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> -
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
anddata-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>
- 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({ + + +
+ 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="Edited">balloon rides at dawn ✨🎈<br>was such a magical experience floating over napa valley as the golden light hit the hills.<br><a href="https://www.instagram.com/jamesrelfdyer/">@jamesrelfdyer</a></span>"> - Instagram photo -</a>
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; }
- 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&autoplay=1&rel=0&controls=0&showinfo=0"> - YouTube video - hide controls and info -</a> - -<a data-fancybox href="https://vimeo.com/191947042?color=f00"> - Vimeo video - custom color -</a>
- Via JavaScript: -
- -$('[data-fancybox]').fancybox({ - youtube : { - controls : 0, - showinfo : 0 - }, - vimeo : { - color : 'f00' - } +
- - - -$("[data-fancybox]").fancybox({ + iframe : { + css : { + width : '600px' + } + } });
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' - ], +- // See Documentation/API/Events for more information - // Example: - /* - afterShow: function( instance, current ) { - console.info( 'Clicked element:' ); - console.info( current.opts.$orig ); - } - */ +// 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> / <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(); +
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 +- beforeShow : $.noop, // Before open animation starts - afterShow : $.noop, // When content is done loading and animating +<div style="display: none;" id="hidden-content"> + <h2>Hello</h2> + <p>You are awesome.</p> +</div>
+ Then simply create a link having +
- beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close. - afterClose : $.noop, // After instance has been closed +data-src
attribute that matches ID of the element you want to open (preceded by a hash mark + (#); in this example -#hidden-content
): ++ - onActivate : $.noop, // When instance is brought to front - onDeactivate : $.noop, // When other instance has been activated +<a data-fancybox data-src="#hidden-content" href="javascript:;"> + Trigger the fancybox +</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. ++ 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: ++ - // 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' - } - } +<a data-fancybox data-type="ajax" data-src="my_page.com/path/to/ajax/" href="javascript:;"> + AJAX content +</a>
+ 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> +
+ +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> / <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. +- This attribute should contain the properly formatted JSON object: - +$.fancybox.defaults.animationEffect = "fade";
+ Custom options for each element individually can be set by adding a +
+data-options
attribute to the element. + This attribute should contain the properly formatted JSON object + (remember, strings should be wrapped in double quotes). ++ It is also possible to quickly set any option using + parameterized name of the selected option, for example, +
-animationEffect
would bedata-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> +- 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. -
-- +// Close all instances and unbind all events +$.fancybox.destroy(); +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; -});
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 });
- 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
} });- 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. -
+ + ++ 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>');
+ 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 + }); +});
- +$.fancybox.open('<div class="message"><h2>Hello!</h2><p>You are awesome!</p></div>');
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 returnfalse
: -+ 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/orcss
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/orcss
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 } -})
+ 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; + } + } +});
- 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 formargin-right
property. - Therefore, if your element haswidth:100%
, you should positon it usingleft
andright
properties instead. Example: -+ 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; + } });
+ 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>'; + } });
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(); } +});
+ #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'); }); -
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 -
- -+ #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 ++ #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 +}); -
' + + '' + + '", + + // Loading indicator template + spinnerTpl: '', + + // Error message template + errorTpl: '' + + '" + + " ' + + ' ' + + ' ' + + '' + + '' + + "', + + 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.{{ERROR}}
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 : '' + - '' + - ' ' + - ' ' + - ' ' + - '' + - '' + - '' + - '' + - '', - - 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.{{ERROR}}
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( '' ); + 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 = $('Hi!
') - .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: '' + + "" + } + }); + + 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 = '{{SHARE}}
" + + "" + + '' + + '' + + "Facebook" + + "" + + '' + + '' + + "Twitter" + + "" + + '' + + '' + + "Pinterest" + + "" + + "
" + + '' + + "'; - - $.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 += '
'; - - 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 = $('- '; - } - - }); - - 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