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