1// ================================================== 2// fancyBox v3.1.20 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; 19 } 20 21 // Check if fancyBox is already initialized 22 // ======================================== 23 24 if ( $.fn.fancybox ) { 25 26 $.error('fancyBox already initialized'); 27 28 return; 29 } 30 31 // Private default settings 32 // ======================== 33 34 var defaults = { 35 36 // Enable infinite gallery navigation 37 loop : true, 38 39 // Space around image, ignored if zoomed-in or viewport smaller than 800px 40 margin : [44, 0], 41 42 // Horizontal space between slides 43 gutter : 50, 44 45 // Enable keyboard navigation 46 keyboard : true, 47 48 // Should display navigation arrows at the screen edges 49 arrows : true, 50 51 // Should display infobar (counter and arrows at the top) 52 infobar : false, 53 54 // Should display toolbar (buttons at the top) 55 toolbar : true, 56 57 // What buttons should appear in the top right corner. 58 // Buttons will be created using templates from `btnTpl` option 59 // and they will be placed into toolbar (class="fancybox-toolbar"` element) 60 buttons : [ 61 'close' 62 ], 63 64 // Detect "idle" time in seconds 65 idleTime : 4, 66 67 // Should display buttons at top right corner of the content 68 // If 'auto' - they will be created for content having type 'html', 'inline' or 'ajax' 69 // Use template from `btnTpl.smallBtn` for customization 70 smallBtn : 'auto', 71 72 // Disable right-click and use simple image protection for images 73 protect : false, 74 75 // Shortcut to make content "modal" - disable keyboard navigtion, hide buttons, etc 76 modal : false, 77 78 image : { 79 80 // Wait for images to load before displaying 81 // Requires predefined image dimensions 82 // If 'auto' - will zoom in thumbnail if 'width' and 'height' attributes are found 83 preload : "auto", 84 85 }, 86 87 ajax : { 88 89 // Object containing settings for ajax request 90 settings : { 91 92 // This helps to indicate that request comes from the modal 93 // Feel free to change naming 94 data : { 95 fancybox : true 96 } 97 } 98 99 }, 100 101 iframe : { 102 103 // Iframe template 104 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>', 105 106 // Preload iframe before displaying it 107 // This allows to calculate iframe content width and height 108 // (note: Due to "Same Origin Policy", you can't get cross domain data). 109 preload : true, 110 111 // Custom CSS styling for iframe wrapping element 112 // You can use this to set custom iframe dimensions 113 css : {}, 114 115 // Iframe tag attributes 116 attr : { 117 scrolling : 'auto' 118 } 119 120 }, 121 122 // Open/close animation type 123 // Possible values: 124 // false - disable 125 // "zoom" - zoom images from/to thumbnail 126 // "fade" 127 // "zoom-in-out" 128 // 129 animationEffect : "disable", 130 131 // Duration in ms for open/close animation 132 animationDuration : 366, 133 134 // Should image change opacity while zooming 135 // If opacity is 'auto', then opacity will be changed if image and thumbnail have different aspect ratios 136 zoomOpacity : 'auto', 137 138 // Transition effect between slides 139 // 140 // Possible values: 141 // false - disable 142 // "fade' 143 // "slide' 144 // "circular' 145 // "tube' 146 // "zoom-in-out' 147 // "rotate' 148 // 149 transitionEffect : "fade", 150 151 // Duration in ms for transition animation 152 transitionDuration : 366, 153 154 // Custom CSS class for slide element 155 slideClass : '', 156 157 // Custom CSS class for layout 158 baseClass : '', 159 160 // Base template for layout 161 baseTpl : 162 '<div class="fancybox-container" role="dialog" tabindex="-1">' + 163 '<div class="fancybox-bg"></div>' + 164 '<div class="fancybox-inner">' + 165 '<div class="fancybox-infobar">' + 166 '<button data-fancybox-prev title="{{PREV}}" class="fancybox-button fancybox-button--left"></button>' + 167 '<div class="fancybox-infobar__body">' + 168 '<span data-fancybox-index></span> / <span data-fancybox-count></span>' + 169 '</div>' + 170 '<button data-fancybox-next title="{{NEXT}}" class="fancybox-button fancybox-button--right"></button>' + 171 '</div>' + 172 '<div class="fancybox-toolbar">' + 173 '{{BUTTONS}}' + 174 '</div>' + 175 '<div class="fancybox-navigation">' + 176 '<button data-fancybox-prev title="{{PREV}}" class="fancybox-arrow fancybox-arrow--left" />' + 177 '<button data-fancybox-next title="{{NEXT}}" class="fancybox-arrow fancybox-arrow--right" />' + 178 '</div>' + 179 '<div class="fancybox-stage"></div>' + 180 '<div class="fancybox-caption-wrap">' + 181 '<div class="fancybox-caption"></div>' + 182 '</div>' + 183 '</div>' + 184 '</div>', 185 186 // Loading indicator template 187 spinnerTpl : '<div class="fancybox-loading"></div>', 188 189 // Error message template 190 errorTpl : '<div class="fancybox-error"><p>{{ERROR}}<p></div>', 191 192 btnTpl : { 193 slideShow : '<button data-fancybox-play class="fancybox-button fancybox-button--play" title="{{PLAY_START}}"></button>', 194 fullScreen : '<button data-fancybox-fullscreen class="fancybox-button fancybox-button--fullscreen" title="{{FULL_SCREEN}}"></button>', 195 thumbs : '<button data-fancybox-thumbs class="fancybox-button fancybox-button--thumbs" title="{{THUMBS}}"></button>', 196 close : '<button data-fancybox-close class="fancybox-button fancybox-button--close" title="{{CLOSE}}"></button>', 197 198 // This small close button will be appended to your html/inline/ajax content by default, 199 // if "smallBtn" option is not set to false 200 smallBtn : '<button data-fancybox-close class="fancybox-close-small" title="{{CLOSE}}"></button>' 201 }, 202 203 // Container is injected into this element 204 parentEl : 'body', 205 206 207 // Focus handling 208 // ============== 209 210 // Try to focus on the first focusable element after opening 211 autoFocus : true, 212 213 // Put focus back to active element after closing 214 backFocus : true, 215
216 // Do not let user to focus on element outside modal content 217 trapFocus : true, 218 219 220 // Module specific options 221 // ======================= 222 223 fullScreen : { 224 autoStart : false, 225 }, 226 227 touch : { 228 vertical : true, // Allow to drag content vertically 229 momentum : true // Continue movement after releasing mouse/touch when panning 230 }, 231 232 // Hash value when initializing manually, 233 // set `false` to disable hash change 234 hash : null, 235 236 // Customize or add new media types 237 // Example: 238 /* 239 media : { 240 youtube : { 241 params : { 242 autoplay : 0 243 } 244 } 245 } 246 */ 247 media : {}, 248 249 slideShow : { 250 autoStart : false, 251 speed : 4000 252 }, 253 254 thumbs : { 255 autoStart : false, // Display thumbnails on opening 256 hideOnClose : true // Hide thumbnail grid when closing animation starts 257 }, 258 259 // Callbacks 260 //========== 261 262 // See Documentation/API/Events for more information 263 // Example: 264 /* 265 afterShow: function( instance, current ) { 266 console.info( 'Clicked element:' ); 267 console.info( current.opts.$orig ); 268 } 269 */ 270 271 onInit : $.noop, // When instance has been initialized 272 273 beforeLoad : $.noop, // Before the content of a slide is being loaded 274 afterLoad : $.noop, // When the content of a slide is done loading 275 276 beforeShow : $.noop, // Before open animation starts 277 afterShow : $.noop, // When content is done loading and animating 278 279 beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close. 280 afterClose : $.noop, // After instance has been closed 281 282 onActivate : $.noop, // When instance is brought to front 283 onDeactivate : $.noop, // When other instance has been activated 284 285 286 // Interaction 287 // =========== 288 289 // Use options below to customize taken action when user clicks or double clicks on the fancyBox area, 290 // each option can be string or method that returns value. 291 // 292 // Possible values: 293 // "close" - close instance 294 // "next" - move to next gallery item 295 // "nextOrClose" - move to next gallery item or close if gallery has only one item 296 // "toggleControls" - show/hide controls 297 // "zoom" - zoom image (if loaded) 298 // false - do nothing 299 300 // Clicked on the content 301 dblclickContent : function( current, event ) { 302 303 return current.type === 'image' ? 'zoom' : false; 304 305 }, 306 clickContent : function( current, event ) { 307 308 309 $('.fancybox-caption-wrap').toggle(); //harrisonm 310 return (current.type === 'image' || current.type === 'iframe') ? 'toggleControls' : false; 311 312 return current.type === 'image' ? 'toggleControls' : false; 313 return current.type === 'iframe' ? 'toggleControls' : false; 314 }, 315 316 // Clicked on the slide 317 clickSlide : function( current, event ) { 318 319 320 321 $('.fancybox-caption-wrap').toggle(); 322 return (current.type === 'image' || current.type === 'iframe') ? 'toggleControls' : false; 323 324 }, 325 326 // Clicked on the background (backdrop) element 327 clickOutside : function( current, event ) { 328 329 330 $('.fancybox-caption-wrap').toggle(); 331 return (current.type === 'image' || current.type === 'iframe') ? 'toggleControls' : false; 332 333 }, 334 335 // Same as previous two, but for double click 336 // dblclickContent : false, 337 dblclickSlide : false, 338 dblclickOutside : false, 339 340 341 // Custom options when mobile device is detected 342 // ============================================= 343 344 mobile : { 345 clickContent : function( current, event ) { 346 347 348 $('.fancybox-caption-wrap').toggle(); 349 return (current.type === 'image' || current.type === 'iframe') ? 'toggleControls' : false;
350 351 return current.type === 'image' ? 'toggleControls' : false; 352 return current.type === 'iframe' ? 'toggleControls' : false; 353 }, 354 clickOutside : function( current, event ) { 355 356 357 $('.fancybox-caption-wrap').toggle(); 358 return (current.type === 'image' || current.type === 'iframe') ? 'toggleControls' : false; 359 }, 360 clickSlide : function( current, event ) { 361 362 $('.fancybox-caption-wrap').toggle(); 363 return (current.type === 'image' || current.type === 'iframe') ? 'toggleControls' : false; 364 }, 365 dblclickContent : function( current, event ) { 366 return current.type === 'image' ? 'zoom' : false; 367 }, 368 dblclickSlide : function( current, event ) { 369 return current.type === 'image' ? 'zoom' : false; 370 } 371 }, 372 373 374 // Internationalization 375 // ============ 376 377 lang : 'en', 378 i18n : { 379 'en' : { 380 CLOSE : 'Close', 381 NEXT : 'Next', 382 PREV : 'Previous', 383 ERROR : 'The requested content cannot be loaded. <br/> Please try again later.', 384 PLAY_START : 'Start slideshow', 385 PLAY_STOP : 'Pause slideshow', 386 FULL_SCREEN : 'Full screen', 387 THUMBS : 'Thumbnails' 388 }, 389 'de' : { 390 CLOSE : 'Schliessen', 391 NEXT : 'Weiter', 392 PREV : 'Zurück', 393 ERROR : 'Die angeforderten Daten konnten nicht geladen werden. <br/>
393 Bitte versuchen Sie es später nochmal.', 394 PLAY_START : 'Diaschau starten', 395 PLAY_STOP : 'Diaschau beenden', 396 FULL_SCREEN : 'Vollbild', 397 THUMBS : 'Vorschaubilder' 398 } 399 } 400 401 }; 402 403 // Few useful variables and methods 404 // ================================ 405 406 var $W = $(window); 407 var $D = $(document); 408 409 var called = 0; 410 411 412 // Check if an object is a jQuery object and not a native JavaScript object 413 // ======================================================================== 414 415 var isQuery = function ( obj ) { 416 return obj && obj.hasOwnProperty && obj instanceof $; 417 }; 418 419 420 // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame" 421 // =============================================================================== 422 423 var requestAFrame = (function () { 424 return window.requestAnimationFrame || 425 window.webkitRequestAnimationFrame || 426 window.mozRequestAnimationFrame || 427 window.oRequestAnimationFrame || 428 // if all else fails, use setTimeout 429 function (callback) { 430 return window.setTimeout(callback, 1000 / 60); 431 }; 432 })(); 433 434 435 // Detect the supported transition-end event property name 436 // ======================================================= 437 438 var transitionEnd = (function () { 439 var t, el = document.createElement("fakeelement"); 440 441 var transitions = { 442 "transition" : "transitionend", 443 "OTransition" : "oTransitionEnd", 444 "MozTransition" : "transitionend", 445 "WebkitTransition": "webkitTransitionEnd" 446 }; 447 448 for (t in transitions) { 449 if (el.style[t] !== undefined){ 450 return transitions[t]; 451 } 452 } 453 })(); 454 455 456 // Force redraw on an element. 457 // This helps in cases where the browser doesn't redraw an updated element properly. 458 // ================================================================================= 459 460 var forceRedraw = function( $el ) { 461 return ( $el && $el.length && $el[0].offsetHeight ); 462 }; 463 464 465 // Class definition 466 // ================ 467 468 var FancyBox = function( content, opts, index ) { 469 var self = this; 470 471 self.opts = $.extend( true, { index : index }, defaults, opts || {} ); 472 473 // Exclude buttons option from deep merging 474 if ( opts && $.isArray( opts.buttons ) ) { 475 self.opts.buttons = opts.buttons; 476 } 477 478 self.id = self.opts.id || ++called; 479 self.group = []; 480 481 self.currIndex = parseInt( self.opts.index, 10 ) || 0; 482 self.prevIndex = null; 483 484 self.prevPos = null; 485 self.currPos = 0; 486 487 self.firstRun = null; 488 489 // Create group elements from original item collection 490 self.createGroup( content ); 491 492 if ( !self.group.length ) { 493 return; 494 } 495 496 // Save last active element and current scroll position 497 self.$lastFocus = $(document.activeElement).blur(); 498 499 // Collection of gallery objects 500 self.slides = {}; 501 502 self.init( content ); 503 504 }; 505 506 $.extend(FancyBox.prototype, { 507 508 // Create DOM structure 509 // ==================== 510 511 init : function() { 512 var self = this; 513 514 var testWidth, $container, buttonStr; 515 516 var firstItemOpts = self.group[ self.currIndex ].opts; 517 518 self.scrollTop = $D.scrollTop(); 519 self.scrollLeft = $D.scrollLeft(); 520 521 522 // Hide scrollbars 523 // =============== 524 525 if ( !$.fancybox.getInstance() && !$.fancybox.isMobile && $( 'body' ).css('overflow') !== 'hidden' ) { 526 testWidth = $( 'body' ).width(); 527 528 $( 'html' ).addClass( 'fancybox-enabled' ); 529 530 // Compare body width after applying "overflow: hidden" 531 testWidth = $( 'body' ).width() - testWidth; 532 533 // If width has changed - compensate missing scrollbars by adding right margin 534 if ( testWidth > 1 ) { 535 $( 'head' ).append( '<style id="fancybox-style-noscroll" type="text/css">.compensate-for-scrollbar, .fancybox-enabled body { margin-right: ' + testWidth + 'px; }</style>' ); 536 } 537 } 538 539 540 // Build html markup and set references 541 // ==================================== 542 543 // Build html code for buttons and insert into main template 544 buttonStr = ''; 545 546 $.each( firstItemOpts.buttons, function( index, value ) { 547 buttonStr += ( firstItemOpts.btnTpl[ value ] || '' ); 548 }); 549 550 // Create markup from base template, it will be initially hidden to 551 // avoid unnecessary work like painting while initializing is not complete 552 $container = $( self.translate( self, firstItemOpts.baseTpl.replace( '\{\{BUTTONS\}\}', buttonStr ) ) ) 553 .addClass( 'fancybox-is-hidden' ) 554 .attr('id', 'fancybox-container-' + self.id) 555 .addClass( firstItemOpts.baseClass ) 556 .data( 'FancyBox', self ) 557 .prependTo( firstItemOpts.parentEl ); 558 559 // Create object holding references to jQuery wrapped nodes 560 self.$refs = { 561 container : $container 562 }; 563 564 [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption' ].forEach(function(item) { 565 self.$refs[ item ] = $container.find( '.fancybox-' + item ); 566 }); 567 568 // Check for redundant elements 569 if ( !firstItemOpts.arrows || self.group.length < 2 ) { 570 $container.find('.fancybox-navigation').remove(); 571 } 572 573 if ( !firstItemOpts.infobar ) { 574 self.$refs.infobar.remove(); 575 } 576 577 if ( !firstItemOpts.toolbar ) { 578 self.$refs.toolbar.remove(); 579 } 580 581 self.trigger( 'onInit' ); 582 583 // Bring to front and enable events 584 self.activate(); 585 586 // Build slides, load and reveal content 587 self.jumpTo( self.currIndex ); 588 }, 589 590 591 // Simple i18n support - replaces object keys found in template 592 // with corresponding values 593 // ============================================================ 594 595 translate : function( obj, str ) { 596 var arr = obj.opts.i18n[ obj.opts.lang ]; 597 598 return str.replace(/\{\{(\w+)\}\}/g, function(match, n) { 599 var value = arr[n]; 600 601 if ( value === undefined ) { 602 return match; 603 } 604 605 return value; 606 }); 607 }, 608 609 // Create array of gally item objects 610 // Check if each object has valid type and content 611 // =============================================== 612 613 createGroup : function ( content ) { 614 var self = this; 615 var items = $.makeArray( content ); 616 617 $.each(items, function( i, item ) { 618 var obj = {}, 619 opts = {}, 620 data = [], 621 $item, 622 type, 623 src, 624 srcParts; 625 626 // Step 1 - Make sure we have an object 627 // ==================================== 628 629 if ( $.isPlainObject( item ) ) { 630 631 // We probably have manual usage here, something like 632 // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] ) 633 634 obj = item; 635 opts = item.opts || item; 636 637 } else if ( $.type( item ) === 'object' && $( item ).length ) { 638 639 // Here we propbably have jQuery collection returned by some selector 640 641 $item = $( item ); 642 data = $item.data(); 643 644 opts = 'options' in data ? data.options : {}; 645 opts = $.type( opts ) === 'object' ? opts : {}; 646 647 obj.src = 'src' in data ? data.src : ( opts.src || $item.attr( 'href' ) ); 648
649 [ 'width', 'height', 'thumb', 'type', 'filter' ].forEach(function(item) { 650 if ( item in data ) { 651 opts[ item ] = data[ item ]; 652 } 653 }); 654 655 if ( 'srcset' in data ) { 656 opts.image = { srcset : data.srcset }; 657 } 658 659 opts.$orig = $item; 660 661 if ( !obj.type && !obj.src ) { 662 obj.type = 'inline'; 663 obj.src = item; 664 } 665 666 } else { 667 668 // Assume we have a simple html code, for example: 669 // $.fancybox.open( '<div><h1>Hi!</h1></div>' ); 670 671 obj = { 672 type : 'html', 673 src : item + '' 674 }; 675 676 } 677 678 // Each gallery object has full collection of options 679 obj.opts = $.extend( true, {}, self.opts, opts ); 680 681 if ( $.fancybox.isMobile ) { 682 obj.opts = $.extend( true, {}, obj.opts, obj.opts.mobile ); 683 } 684 685 686 // Step 2 - Make sure we have content type, if not - try to guess 687 // ============================================================== 688 689 type = obj.type || obj.opts.type; 690 src = obj.src || ''; 691 692 if ( !type && src ) { 693 if ( src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i) ) { 694 type = 'image'; 695 696 } else if ( src.match(/\.(pdf)((\?|#).*)?$/i) ) { 697 type = 'pdf'; 698 699 } else if ( src.charAt(0) === '#' ) { 700 type = 'inline'; 701 } 702 } 703 704 obj.type = type; 705 706 707 // Step 3 - Some adjustments 708 // ========================= 709 710 obj.index = self.group.length; 711 712 // Check if $orig and $thumb objects exist 713 if ( obj.opts.$orig && !obj.opts.$orig.length ) { 714 delete obj.opts.$orig; 715 } 716 717 if ( !obj.opts.$thumb && obj.opts.$orig ) { 718 obj.opts.$thumb = obj.opts.$orig.find( 'img:first' ); 719 } 720 721 if ( obj.opts.$thumb && !obj.opts.$thumb.length ) { 722 delete obj.opts.$thumb; 723 } 724 725 // Caption is a "special" option, it can be passed as a method 726 if ( $.type( obj.opts.caption ) === 'function' ) { 727 obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] ); 728 729 } else if ( 'caption' in data ) { 730 obj.opts.caption = data.caption; 731 } 732 733 // Make sure we have caption as a string 734 obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + ''; 735 736 // Check if url contains "filter" used to filter the content 737 // Example: "ajax.html #something" 738 if ( type === 'ajax' ) { 739 srcParts = src.split(/\s+/, 2); 740 741 if ( srcParts.length > 1 ) { 742 obj.src = srcParts.shift(); 743 744 obj.opts.filter = srcParts.shift(); 745 } 746 } 747 748 if ( obj.opts.smallBtn == 'auto' ) { 749 750 if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) { 751 obj.opts.toolbar = false; 752 obj.opts.smallBtn = true; 753 754 } else { 755 obj.opts.smallBtn = false; 756 } 757 758 } 759 760 // If the type is "pdf", then simply load file into iframe 761 if ( type === 'pdf' ) { 762 obj.type = 'iframe'; 763 764 obj.opts.iframe.preload = false; 765 } 766 767 // Hide all buttons and disable interactivity for modal items 768 if ( obj.opts.modal ) { 769 770 obj.opts = $.extend(true, obj.opts, { 771 // Remove buttons 772 infobar : 0, 773 toolbar : 0, 774 775 smallBtn : 0, 776 777 // Disable keyboard navigation 778 keyboard : 0, 779 780 // Disable some modules 781 slideShow : 0, 782 fullScreen : 0, 783 thumbs : 0, 784 touch : 0, 785 786 // Disable click event handlers 787 clickContent : false, 788 clickSlide : false, 789 clickOutside : false, 790 dblclickContent : false, 791 dblclickSlide : false, 792 dblclickOutside : false 793 }); 794 795 } 796 797 // Step 4 - Add processed object to group 798 // ====================================== 799 800 self.group.push( obj ); 801 802 }); 803 804 }, 805 806 807 // Attach an event handler functions for: 808 // - navigation buttons 809 // - browser scrolling, resizing; 810 // - focusing 811 // - keyboard 812 // - detect idle 813 // ====================================== 814 815 addEvents : function() { 816 var self = this; 817 818 self.removeEvents(); 819 820 // Make navigation elements clickable 821 self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function(e) { 822 e.stopPropagation(); 823 e.preventDefault(); 824 825 self.close( e ); 826 $( 'html' ).removeClass( 'fancybox-enabled' ); 827 828 }).on( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) { 829 e.stopPropagation(); 830 e.preventDefault(); 831 832 self.previous(); 833 834 }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) { 835 e.stopPropagation(); 836 e.preventDefault(); 837 838 self.next(); 839 840 }); 841 842 843 // Handle page scrolling and browser resizing 844 $W.on('orientationchange.fb resize.fb', function(e) { 845 846 if ( e && e.originalEvent && e.originalEvent.type === "resize" ) { 847 848 requestAFrame(function() {
849 self.update(); 850 }); 851 852 } else { 853 854 self.$refs.stage.hide(); 855 856 setTimeout(function() { 857 self.$refs.stage.show(); 858 859 self.update(); 860 }, 500); 861 862 } 863 864 }); 865 866 // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal 867 // (a.k.a. "escaping the modal") 868 $D.on('focusin.fb', function(e) { 869 var instance = $.fancybox ? $.fancybox.getInstance() : null; 870 871 if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) { 872 return; 873 } 874 875 if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) { 876 e.stopPropagation(); 877 878 instance.focus(); 879 880 // Sometimes page gets scrolled, set it back 881 $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); 882 } 883 }); 884 885 886 // Enable keyboard navigation 887 $D.on('keydown.fb', function (e) { 888 var current = self.current, 889 keycode = e.keyCode || e.which; 890 891 if ( !current || !current.opts.keyboard ) { 892 return; 893 } 894 895 if ( $(e.target).is('input') || $(e.target).is('textarea') ) { 896 return; 897 } 898 899 // Backspace and Esc keys 900 if ( keycode === 8 || keycode === 27 ) { 901 e.preventDefault(); 902 903 self.close( e ); 904 905 return; 906 } 907 908 // Left arrow and Up arrow 909 if ( keycode === 37 || keycode === 38 ) { 910 e.preventDefault(); 911 912 self.previous(); 913 914 return; 915 } 916 917 // Righ arrow and Down arrow 918 if ( keycode === 39 || keycode === 40 ) { 919 e.preventDefault(); 920 921 self.next(); 922 923 return; 924 } 925 926 self.trigger('afterKeydown', e, keycode); 927 }); 928 929 930 // Hide controls after some inactivity period 931 if ( self.group[ self.currIndex ].opts.idleTime ) { 932 self.idleSecondsCounter = 0; 933 934 $D.on('mousemove.fb-idle mouseenter.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function() { 935 self.idleSecondsCounter = 0; 936 937 if ( self.isIdle ) { 938 self.showControls(); 939 } 940 941 self.isIdle = false; 942 }); 943 944 self.idleInterval = window.setInterval(function() { 945 946 self.idleSecondsCounter++; 947 948 if ( self.idleSecondsCounter >= self.group[ self.currIndex ].opts.idleTime ) { 949 self.isIdle = true; 950 self.idleSecondsCounter = 0; 951 952 self.hideControls(); 953 } 954 955 }, 1000); 956 } 957 958 }, 959 960 961 // Remove events added by the core 962 // =============================== 963 964 removeEvents : function () { 965 var self = this; 966 967 $W.off( 'orientationchange.fb resize.fb' ); 968 $D.off( 'focusin.fb keydown.fb .fb-idle' ); 969 970 this.$refs.container.off( '.fb-close .fb-prev .fb-next' ); 971 972 if ( self.idleInterval ) { 973 window.clearInterval( self.idleInterval ); 974 975 self.idleInterval = null; 976 } 977 }, 978 979 980 // Change to previous gallery item 981 // =============================== 982 983 previous : function( duration ) { 984 $('.fancybox-caption-wrap').show(); 985 return this.jumpTo( this.currPos - 1, duration ); 986 }, 987 988 989 // Change to next gallery item 990 // =========================== 991 992 next : function( duration ) { 993 $('.fancybox-caption-wrap').show(); 994 return this.jumpTo( this.currPos + 1, duration ); 995 }, 996 997 998 // Switch to selected gallery item 999 // =============================== 1000 1001 jumpTo : function ( pos, duration, slide ) { 1002 var self = this, 1003 firstRun, 1004 loop, 1005 current, 1006 previous, 1007 canvasWidth, 1008 currentPos, 1009 transitionProps; 1010 1011 var groupLen = self.group.length; 1012 1013 if ( self.isSliding || self.isClosing || ( self.isAnimating && self.firstRun ) ) { 1014 return; 1015 } 1016 1017 pos = parseInt( pos, 10 ); 1018 loop = self.current ? self.current.opts.loop : self.opts.loop; 1019 1020 if ( !loop && ( pos < 0 || pos >= groupLen ) ) { 1021 return false;
1022 } 1023 1024 firstRun = self.firstRun = ( self.firstRun === null ); 1025 1026 if ( groupLen < 2 && !firstRun && !!self.isSliding ) { 1027 return; 1028 } 1029 1030 previous = self.current; 1031 1032 self.prevIndex = self.currIndex; 1033 self.prevPos = self.currPos; 1034 1035 // Create slides 1036 current = self.createSlide( pos ); 1037 1038 if ( groupLen > 1 ) { 1039 if ( loop || current.index > 0 ) { 1040 self.createSlide( pos - 1 ); 1041 } 1042 1043 if ( loop || current.index < groupLen - 1 ) { 1044 self.createSlide( pos + 1 ); 1045 } 1046 } 1047 1048 self.current = current; 1049 self.currIndex = current.index; 1050 self.currPos = current.pos; 1051 1052 self.trigger( 'beforeShow', firstRun ); 1053 1054 self.updateControls(); 1055 1056 currentPos = $.fancybox.getTranslate( current.$slide ); 1057 1058 current.isMoved = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' ); 1059 current.forcedDuration = undefined; 1060 1061 if ( $.isNumeric( duration ) ) { 1062 current.forcedDuration = duration; 1063 } else { 1064 duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ]; 1065 } 1066 1067 duration = parseInt( duration, 10 ); 1068 1069 // Fresh start - reveal container, current slide and start loading content 1070 if ( firstRun ) { 1071 1072 if ( current.opts.animationEffect && duration ) { 1073 self.$refs.container.css( 'transition-duration', duration + 'ms' ); 1074 } 1075 1076 self.$refs.container.removeClass( 'fancybox-is-hidden' ); 1077 1078 forceRedraw( self.$refs.container ); 1079 1080 self.$refs.container.addClass( 'fancybox-is-open' ); 1081 1082 // Make first slide visible (to display loading icon, if needed) 1083 current.$slide.addClass( 'fancybox-slide--current' ); 1084 1085 self.loadSlide( current ); 1086 1087 self.preload(); 1088 1089 return; 1090 } 1091 1092 // Clean up 1093 $.each(self.slides, function( index, slide ) { 1094 $.fancybox.stop( slide.$slide ); 1095 }); 1096 1097 // Make current that slide is visible even if content is still loading 1098 current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' ); 1099 1100 // If slides have been dragged, animate them to correct position 1101 if ( current.isMoved ) { 1102 canvasWidth = Math.round( current.$slide.width() ); 1103 1104 $.each(self.slides, function( index, slide ) { 1105 var pos = slide.pos - current.pos; 1106 1107 $.fancybox.animate( slide.$slide, { 1108 top : 0, 1109 left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ) 1110 }, duration, function() { 1111 1112 slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' ); 1113 1114 if ( slide.pos === self.currPos ) { 1115 current.isMoved = false; 1116 1117 self.complete(); 1118 } 1119 }); 1120 }); 1121 1122 } else { 1123 self.$refs.stage.children().removeAttr( 'style' ); 1124 } 1125 1126 // Start transition that reveals current content 1127 // or wait when it will be loaded 1128 1129 if ( current.isLoaded ) { 1130 self.revealContent( current ); 1131 1132 } else { 1133 self.loadSlide( current ); 1134 } 1135 1136 self.preload(); 1137 1138 if ( previous.pos === current.pos ) { 1139 return; 1140 } 1141 1142 // Handle previous slide 1143 // ===================== 1144 1145 transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' ); 1146 1147 previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ); 1148 1149 previous.isComplete = false;
1150 1151 if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) { 1152 return; 1153 } 1154 1155 if ( current.isMoved ) { 1156 previous.$slide.addClass( transitionProps ); 1157 1158 } else { 1159 1160 transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect; 1161 1162 $.fancybox.animate( previous.$slide, transitionProps, duration, function() { 1163 previous.$slide.removeClass( transitionProps ).removeAttr( 'style' ); 1164 }); 1165 1166 } 1167 1168 }, 1169 1170 1171 // Create new "slide" element 1172 // These are gallery items that are actually added to DOM 1173 // ======================================================= 1174 1175 createSlide : function( pos ) { 1176 1177 var self = this; 1178 var $slide; 1179 var index; 1180 1181 index = pos % self.group.length; 1182 index = index < 0 ? self.group.length + index : index; 1183 1184 if ( !self.slides[ pos ] && self.group[ index ] ) { 1185 $slide = $('<div class="fancybox-slide"></div>').appendTo( self.$refs.stage ); 1186 1187 self.slides[ pos ] = $.extend( true, {}, self.group[ index ], { 1188 pos : pos, 1189 $slide : $slide, 1190 isLoaded : false, 1191 }); 1192 1193 self.updateSlide( self.slides[ pos ] ); 1194 } 1195 1196 return self.slides[ pos ]; 1197 }, 1198 1199 1200 // Scale image to the actual size of the image 1201 // =========================================== 1202 1203 scaleToActual : function( x, y, duration ) { 1204 1205 var self = this; 1206 1207 var current = self.current; 1208 var $what = current.$content; 1209 1210 var imgPos, posX, posY, scaleX, scaleY; 1211 1212 var canvasWidth = parseInt( current.$slide.width(), 10 ); 1213 var canvasHeight = parseInt( current.$slide.height(), 10 ); 1214 1215 var newImgWidth = current.width; 1216 var newImgHeight = current.height; 1217 1218 if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating) { 1219 return; 1220 } 1221 1222 $.fancybox.stop( $what ); 1223 1224 self.isAnimating = true; 1225 1226 x = x === undefined ? canvasWidth * 0.5 : x; 1227 y = y === undefined ? canvasHeight * 0.5 : y; 1228 1229 imgPos = $.fancybox.getTranslate( $what ); 1230 1231 scaleX = newImgWidth / imgPos.width; 1232 scaleY = newImgHeight / imgPos.height; 1233 1234 // Get center position for original image 1235 posX = ( canvasWidth * 0.5 - newImgWidth * 0.5 ); 1236 posY = ( canvasHeight * 0.5 - newImgHeight * 0.5 ); 1237 1238 // Make sure image does not move away from edges 1239 if ( newImgWidth > canvasWidth ) { 1240 posX = imgPos.left * scaleX - ( ( x * scaleX ) - x ); 1241 1242 if ( posX > 0 ) { 1243 posX = 0; 1244 } 1245 1246 if ( posX < canvasWidth - newImgWidth ) { 1247 posX = canvasWidth - newImgWidth; 1248 } 1249 } 1250 1251 if ( newImgHeight > canvasHeight) { 1252 posY = imgPos.top * scaleY - ( ( y * scaleY ) - y ); 1253 1254 if ( posY > 0 ) { 1255 posY = 0; 1256 } 1257 1258 if ( posY < canvasHeight - newImgHeight ) { 1259 posY = canvasHeight - newImgHeight; 1260 } 1261 } 1262 1263 self.updateCursor( newImgWidth, newImgHeight ); 1264 1265 $.fancybox.animate( $what, { 1266 top : posY, 1267 left : posX, 1268 scaleX : scaleX, 1269 scaleY : scaleY 1270 }, duration || 330, function() { 1271 self.isAnimating = false; 1272 }); 1273 1274 // Stop slideshow 1275 if ( self.SlideShow && self.SlideShow.isActive ) { 1276 self.SlideShow.stop(); 1277 } 1278 }, 1279 1280 1281 // Scale image to fit inside parent element 1282 // ======================================== 1283 1284 scaleToFit : function( duration ) { 1285 1286 var self = this; 1287 1288 var current = self.current; 1289 var $what = current.$content; 1290 var end; 1291 1292 if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) { 1293 return; 1294 } 1295 1296 $.fancybox.stop( $what ); 1297 1298 self.isAnimating = true; 1299 1300 end = self.getFitPos( current ); 1301 1302 self.updateCursor( end.width, end.height ); 1303 1304 $.fancybox.animate( $what, { 1305 top : end.top, 1306 left : end.left, 1307 scaleX : end.width / $what.width(), 1308 scaleY : end.height / $what.height() 1309 }, duration || 330, function() { 1310 self.isAnimating = false;
1311 }); 1312 1313 }, 1314 1315 // Calculate image size to fit inside viewport 1316 // =========================================== 1317 1318 getFitPos : function( slide ) { 1319 var self = this; 1320 var $what = slide.$content; 1321 1322 var imgWidth = slide.width; 1323 var imgHeight = slide.height; 1324 1325 var margin = slide.opts.margin; 1326 1327 var canvasWidth, canvasHeight, minRatio, width, height; 1328 1329 if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) { 1330 return false; 1331 } 1332 1333 // Convert "margin to CSS style: [ top, right, bottom, left ] 1334 if ( $.type( margin ) === "number" ) { 1335 margin = [ margin, margin ]; 1336 } 1337 1338 if ( margin.length == 2 ) { 1339 margin = [ margin[0], margin[1], margin[0], margin[1] ]; 1340 } 1341 1342 if ( $W.width() < 800 ) { 1343 margin = [ 0, 0, 0, 0 ]; 1344 } 1345 1346 // We can not use $slide width here, because it can have different diemensions while in transiton 1347 canvasWidth = parseInt( self.$refs.stage.width(), 10 ) - ( margin[ 1 ] + margin[ 3 ] ); 1348 canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] ); 1349 1350 minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight ); 1351 1352 width = Math.floor( minRatio * imgWidth ); 1353 height = Math.floor( minRatio * imgHeight ); 1354 1355 // Use floor rounding to make sure it really fits 1356 return { 1357 top : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ], 1358 left : Math.floor( ( canvasWidth - width ) * 0.5 ) + margin[ 3 ], 1359 width : width, 1360 height : height 1361 }; 1362 1363 }, 1364 1365 1366 // Update position and content of all slides 1367 // ========================================= 1368 1369 update : function() { 1370 1371 var self = this; 1372 1373 $.each( self.slides, function( key, slide ) { 1374 self.updateSlide( slide ); 1375 }); 1376 1377 }, 1378 1379 1380 // Update slide position and scale content to fit 1381 // ============================================== 1382 1383 updateSlide : function( slide ) { 1384 1385 var self = this; 1386 var $what = slide.$content; 1387 1388 if ( $what && ( slide.width || slide.height ) ) { 1389 $.fancybox.stop( $what ); 1390 1391 $.fancybox.setTranslate( $what, self.getFitPos( slide ) ); 1392 1393 if ( slide.pos === self.currPos ) { 1394 self.updateCursor(); 1395 } 1396 } 1397 1398 slide.$slide.trigger( 'refresh' ); 1399 1400 self.trigger( 'onUpdate', slide ); 1401 1402 }, 1403 1404 // Update cursor style depending if content can be zoomed 1405 // ====================================================== 1406 1407 updateCursor : function( nextWidth, nextHeight ) { 1408 1409 var self = this; 1410 var isScaledDown; 1411 1412 var $container = self.$refs.container.removeClass('fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut'); 1413 1414 if ( !self.current || self.isClosing ) { 1415 return; 1416 } 1417 1418 if ( self.isZoomable() ) { 1419 1420 $container.addClass( 'fancybox-is-zoomable' ); 1421 1422 if ( nextWidth !== undefined && nextHeight !== undefined ) { 1423 isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height; 1424 1425 } else { 1426 isScaledDown = self.isScaledDown(); 1427 } 1428 1429 if ( isScaledDown ) { 1430 1431 // If image is scaled down, then, obviously, it can be zoomed to full size 1432 $container.addClass('fancybox-can-zoomIn'); 1433 1434 } else { 1435 1436 if ( self.current.opts.touch ) { 1437 1438 // If image size ir largen than available available and touch module is not disable, 1439 // then user can do panning 1440 $container.addClass('fancybox-can-drag'); 1441 1442 } else { 1443 $container.addClass('fancybox-can-zoomOut'); 1444 } 1445 1446 } 1447 1448 } else if ( self.current.opts.touch ) { 1449 $container.addClass('fancybox-can-drag'); 1450 } 1451 1452 }, 1453 1454 1455 // Check if current slide is zoomable 1456 // ================================== 1457 1458 isZoomable : function() { 1459 1460 var self = this; 1461 1462 var current = self.current; 1463 var fitPos; 1464 1465 if ( !current || self.isClosing ) { 1466 return; 1467 } 1468 1469 // Assume that slide is zoomable if 1470 // - image is loaded successfuly 1471 // - click action is "zoom" 1472 // - actual size of the image is smaller than available area 1473 if ( current.type === 'image' && current.isLoaded && !current.hasError && 1474 ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) === "zoom" ) ) 1475 ) { 1476 1477 fitPos = self.getFitPos( current ); 1478 1479 if ( current.width > fitPos.width || current.height > fitPos.height ) { 1480 return true; 1481 } 1482 1483 } 1484 1485 return false;
1486 1487 }, 1488 1489 1490 // Check if current image dimensions are smaller than actual 1491 // ========================================================= 1492 1493 isScaledDown : function() { 1494 1495 var self = this; 1496 1497 var current = self.current; 1498 var $what = current.$content; 1499 1500 var rez = false; 1501 1502 if ( $what ) { 1503 rez = $.fancybox.getTranslate( $what ); 1504 rez = rez.width < current.width || rez.height < current.height; 1505 } 1506 1507 return rez; 1508 1509 }, 1510 1511 1512 // Check if image dimensions exceed parent element 1513 // =============================================== 1514 1515 canPan : function() { 1516 1517 var self = this; 1518 1519 var current = self.current; 1520 var $what = current.$content; 1521 1522 var rez = false; 1523 1524 if ( $what ) { 1525 rez = self.getFitPos( current ); 1526 rez = Math.abs( $what.width() - rez.width ) > 1 || Math.abs( $what.height() - rez.height ) > 1; 1527 1528 } 1529 1530 return rez; 1531 1532 }, 1533 1534 1535 // Load content into the slide 1536 // =========================== 1537 1538 loadSlide : function( slide ) { 1539 1540 var self = this, type, $slide; 1541 var ajaxLoad; 1542 1543 if ( slide.isLoading ) { 1544 return; 1545 } 1546 1547 if ( slide.isLoaded ) { 1548 return; 1549 } 1550 1551 slide.isLoading = true; 1552 1553 self.trigger( 'beforeLoad', slide ); 1554 1555 type = slide.type; 1556 $slide = slide.$slide; 1557 1558 $slide 1559 .off( 'refresh' ) 1560 .trigger( 'onReset' ) 1561 .addClass( 'fancybox-slide--' + ( type || 'unknown' ) ) 1562 .addClass( slide.opts.slideClass ); 1563 1564 // Create content depending on the type 1565 1566 switch ( type ) { 1567 1568 case 'image': 1569 1570 self.setImage( slide ); 1571 1572 break; 1573 1574 case 'iframe': 1575 1576 self.setIframe( slide ); 1577 1578 break; 1579 1580 case 'html': 1581 1582 self.setContent( slide, slide.src || slide.content ); 1583 1584 break; 1585 1586 case 'inline': 1587 1588 if ( $( slide.src ).length ) { 1589 self.setContent( slide, $( slide.src ) ); 1590 1591 } else { 1592 self.setError( slide ); 1593 } 1594 1595 break; 1596 1597 case 'ajax': 1598 1599 self.showLoading( slide ); 1600 1601 ajaxLoad = $.ajax( $.extend( {}, slide.opts.ajax.settings, { 1602 url : slide.src, 1603 success : function ( data, textStatus ) { 1604 1605 if ( textStatus === 'success' ) { 1606 self.setContent( slide, data ); 1607 } 1608 1609 }, 1610 error : function ( jqXHR, textStatus ) { 1611 1612 if ( jqXHR && textStatus !== 'abort' ) { 1613 self.setError( slide ); 1614 } 1615 1616 } 1617 })); 1618 1619 $slide.one( 'onReset', function () { 1620 ajaxLoad.abort(); 1621 }); 1622 1623 break; 1624 1625 default: 1626 1627 self.setError( slide ); 1628 1629 break; 1630 1631 } 1632 1633 return true; 1634 1635 }, 1636 1637 1638 // Use thumbnail image, if possible 1639 // ================================ 1640 1641 setImage : function( slide ) { 1642 1643 var self = this; 1644 var srcset = slide.opts.image.srcset; 1645 1646 var found, temp, pxRatio, windowWidth; 1647 1648 // If we have "srcset", then we need to find matching "src" value. 1649 // This is necessary, because when you set an src attribute, the browser will preload the image 1650 // before any javascript or even CSS is applied. 1651 if ( srcset ) { 1652 pxRatio = window.devicePixelRatio || 1; 1653 windowWidth = window.innerWidth * pxRatio; 1654 1655 temp = srcset.split(',').map(function ( el ) { 1656 var ret = {}; 1657
1658 el.trim().split(/\s+/).forEach(function ( el, i ) { 1659 var value = parseInt( el.substring(0, el.length - 1), 10 ); 1660 1661 if ( i === 0 ) { 1662 return ( ret.url = el ); 1663 } 1664 1665 if ( value ) { 1666 ret.value = value; 1667 ret.postfix = el[ el.length - 1 ]; 1668 } 1669 1670 }); 1671 1672 return ret; 1673 }); 1674 1675 // Sort by value 1676 temp.sort(function (a, b) { 1677 return a.value - b.value; 1678 }); 1679 1680 // Ok, now we have an array of all srcset values 1681 for ( var j = 0; j < temp.length; j++ ) { 1682 var el = temp[ j ]; 1683 1684 if ( ( el.postfix === 'w' && el.value >= windowWidth ) || ( el.postfix === 'x' && el.value >= pxRatio ) ) { 1685 found = el; 1686 break; 1687 } 1688 } 1689 1690 // If not found, take the last one 1691 if ( !found && temp.length ) { 1692 found = temp[ temp.length - 1 ]; 1693 } 1694 1695 if ( found ) { 1696 slide.src = found.url; 1697 1698 // If we have default width/height values, we can calculate height for matching source 1699 if ( slide.width && slide.height && found.postfix == 'w' ) { 1700 slide.height = ( slide.width / slide.height ) * found.value; 1701 slide.width = found.value; 1702 } 1703 } 1704 } 1705 1706 // This will be wrapper containing both ghost and actual image 1707 slide.$content = $('<div class="fancybox-image-wrap"></div>') 1708 .addClass( 'fancybox-is-hidden' ) 1709 .appendTo( slide.$slide ); 1710 1711 1712 // If we have a thumbnail, we can display it while actual image is loading 1713 // Users will not stare at black screen and actual image will appear gradually 1714 if ( slide.opts.preload !== false && slide.opts.width && slide.opts.height && ( slide.opts.thumb || slide.opts.$thumb ) ) { 1715 1716 slide.width = slide.opts.width; 1717 slide.height = slide.opts.height; 1718 1719 slide.$ghost = $('<img />') 1720 .one('error', function() { 1721 1722 $(this).remove(); 1723 1724 slide.$ghost = null; 1725 1726 self.setBigImage( slide ); 1727 1728 }) 1729 .one('load', function() { 1730 1731 self.afterLoad( slide ); 1732 1733 self.setBigImage( slide ); 1734 1735 }) 1736 .addClass( 'fancybox-image' ) 1737 .appendTo( slide.$content ) 1738 .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) ); 1739 1740 } else { 1741 1742 self.setBigImage( slide ); 1743 1744 } 1745 1746 }, 1747 1748 1749 // Create full-size image 1750 // ====================== 1751 1752 setBigImage : function ( slide ) { 1753 var self = this; 1754 var $img = $('<img />'); 1755 1756 slide.$image = $img 1757 .one('error', function() { 1758 1759 self.setError( slide ); 1760 1761 }) 1762 .one('load', function() { 1763 1764 // Clear timeout that checks if loading icon needs to be displayed 1765 clearTimeout( slide.timouts ); 1766 1767 slide.timouts = null; 1768 1769 if ( self.isClosing ) { 1770 return; 1771 } 1772 1773 slide.width = this.naturalWidth; 1774 slide.height = this.naturalHeight; 1775 1776 if ( slide.opts.image.srcset ) { 1777 $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset ); 1778 } 1779 1780 self.hideLoading( slide ); 1781 1782 if ( slide.$ghost ) { 1783 1784 slide.timouts = setTimeout(function() { 1785 slide.timouts = null; 1786 1787 slide.$ghost.hide(); 1788 1789 }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) ); 1790 1791 } else { 1792 self.afterLoad( slide ); 1793 } 1794 1795 }) 1796 .addClass( 'fancybox-image' ) 1797 .attr('src', slide.src) 1798 .appendTo( slide.$content ); 1799 1800 if ( $img[0].complete ) { 1801 $img.trigger( 'load' ); 1802 1803 } else if( $img[0].error ) { 1804 $img.trigger( 'error' ); 1805 1806 } else { 1807 1808 slide.timouts = setTimeout(function() { 1809 if ( !$img[0].complete && !slide.hasError ) { 1810 self.showLoading( slide ); 1811 } 1812 1813 }, 100); 1814 1815 } 1816 1817 }, 1818 1819 1820 // Create iframe wrapper, iframe and bindings 1821 // ========================================== 1822 1823 setIframe : function( slide ) { 1824 var self = this, 1825 opts = slide.opts.iframe, 1826 $slide = slide.$slide, 1827 $iframe; 1828 1829 slide.$content = $('<div class="fancybox-content' + ( opts.preload ? ' fancybox-is-hidden' : '' ) + '"></div>') 1830 .css( opts.css ) 1831 .appendTo( $slide ); 1832 1833 $iframe = $( opts.tpl.replace(/\{rnd\}/g, new Date().getTime()) ) 1834 .attr( opts.attr ) 1835 .appendTo( slide.$content ); 1836 1837 if ( opts.preload ) { 1838 1839 self.showLoading( slide ); 1840 1841 // Unfortunately, it is not always possible to determine if iframe is successfully loaded 1842 // (due to browser security policy) 1843 1844 $iframe.on('load.fb error.fb', function(e) { 1845 this.isReady = 1; 1846 1847 slide.$slide.trigger( 'refresh' ); 1848 1849 self.afterLoad( slide ); 1850 }); 1851 1852 // Recalculate iframe content size 1853 // =============================== 1854 1855 $slide.on('refresh.fb', function() { 1856 var $wrap = slide.$content, 1857 $contents, 1858 $body, 1859 scrollWidth, 1860 frameWidth, 1861 frameHeight; 1862 1863 if ( $iframe[0].isReady !== 1 ) { 1864 return; 1865 } 1866 1867 // Check if content is accessible, 1868 // it will fail if frame is not with the same origin 1869 1870 try { 1871 $contents = $iframe.contents(); 1872 $body = $contents.find('body'); 1873 1874 } catch (ignore) {} 1875 1876 // Calculate dimensions for the wrapper 1877 if ( $body && $body.length && !( opts.css.width !== undefined && opts.css.height !== undefined ) ) { 1878 1879 scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth; 1880 1881 frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) );
1882 frameHeight = Math.ceil( $body.outerHeight(true) ); 1883 1884 // Resize wrapper to fit iframe content 1885 $wrap.css({ 1886 'width' : opts.css.width === undefined ? frameWidth + ( $wrap.outerWidth() - $wrap.innerWidth() ) : opts.css.width, 1887 'height' : opts.css.height === undefined ? frameHeight + ( $wrap.outerHeight() - $wrap.innerHeight() ) : opts.css.height 1888 }); 1889 1890 } 1891 1892 $wrap.removeClass( 'fancybox-is-hidden' ); 1893 1894 }); 1895 1896 } else { 1897 1898 this.afterLoad( slide ); 1899 1900 } 1901 1902 $iframe.attr( 'src', slide.src ); 1903 1904 if ( slide.opts.smallBtn === true ) { 1905 slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) ); 1906 } 1907 1908 // Remove iframe if closing or changing gallery item 1909 $slide.one( 'onReset', function () { 1910 1911 // This helps IE not to throw errors when closing 1912 try { 1913 1914 $( this ).find( 'iframe' ).hide().attr( 'src', '//about:blank' ); 1915 1916 } catch ( ignore ) {} 1917 1918 $( this ).empty(); 1919 1920 slide.isLoaded = false; 1921 1922 }); 1923 1924 }, 1925 1926 1927 // Wrap and append content to the slide 1928 // ====================================== 1929 1930 setContent : function ( slide, content ) { 1931 1932 var self = this; 1933 1934 if ( self.isClosing ) { 1935 return; 1936 } 1937 1938 self.hideLoading( slide ); 1939 1940 slide.$slide.empty(); 1941 1942 if ( isQuery( content ) && content.parent().length ) { 1943 1944 // If content is a jQuery object, then it will be moved to the slide. 1945 // The placeholder is created so we will know where to put it back. 1946 // If user is navigating gallery fast, then the content might be already inside fancyBox 1947 // ===================================================================================== 1948 1949 // Make sure content is not already moved to fancyBox 1950 content.parent( '.fancybox-slide--inline' ).trigger( 'onReset' ); 1951 1952 // Create temporary element marking original place of the content 1953 slide.$placeholder = $( '<div></div>' ).hide().insertAfter( content ); 1954 1955 // Make sure content is visible 1956 content.css('display', 'inline-block'); 1957 1958 } else if ( !slide.hasError ) { 1959 1960 // If content is just a plain text, try to convert it to html 1961 if ( $.type( content ) === 'string' ) { 1962 content = $('<div>').append( $.trim( content ) ).contents(); 1963 1964 // If we have text node, then add wrapping element to make vertical alignment work 1965 if ( content[0].nodeType === 3 ) { 1966 content = $('<div>').html( content ); 1967 } 1968 } 1969 1970 // If "filter" option is provided, then filter content 1971 if ( slide.opts.filter ) { 1972 content = $('<div>').html( content ).find( slide.opts.filter ); 1973 } 1974 1975 } 1976 1977 slide.$slide.one('onReset', function () { 1978 1979 // Put content back 1980 if ( slide.$placeholder ) { 1981 slide.$placeholder.after( content.hide() ).remove(); 1982 1983 slide.$placeholder = null; 1984 } 1985 1986 // Remove custom close button 1987 if ( slide.$smallBtn ) { 1988 slide.$smallBtn.remove(); 1989 1990 slide.$smallBtn = null; 1991 } 1992 1993 // Remove content and mark slide as not loaded 1994 if ( !slide.hasError ) { 1995 $(this).empty(); 1996 1997 slide.isLoaded = false; 1998 } 1999 2000 }); 2001 2002 slide.$content = $( content ).appendTo( slide.$slide ); 2003 2004 if ( slide.opts.smallBtn && !slide.$smallBtn ) { 2005 slide.$smallBtn = $( self.translate( slide, slide.opts.btnTpl.smallBtn ) ).appendTo( slide.$content ); 2006 } 2007 2008 this.afterLoad( slide ); 2009 }, 2010 2011 // Display error message 2012 // ===================== 2013 2014 setError : function ( slide ) { 2015 2016 slide.hasError = true; 2017 2018 slide.$slide.removeClass( 'fancybox-slide--' + slide.type ); 2019 2020 this.setContent( slide, this.translate( slide, slide.opts.errorTpl ) ); 2021 2022 }, 2023 2024 2025 // Show loading icon inside the slide 2026 // ================================== 2027 2028 showLoading : function( slide ) { 2029 2030 var self = this; 2031 2032 slide = slide || self.current; 2033 2034 if ( slide && !slide.$spinner ) { 2035 slide.$spinner = $( self.opts.spinnerTpl ).appendTo( slide.$slide ); 2036 } 2037 2038 }, 2039 2040 // Remove loading icon from the slide 2041 // ================================== 2042 2043 hideLoading : function( slide ) { 2044 2045 var self = this; 2046 2047 slide = slide || self.current; 2048 2049 if ( slide && slide.$spinner ) { 2050 slide.$spinner.remove(); 2051 2052 delete slide.$spinner; 2053 } 2054 2055 }, 2056 2057 2058 // Adjustments after slide content has been loaded 2059 // =============================================== 2060 2061 afterLoad : function( slide ) { 2062 2063 var self = this; 2064 2065 if ( self.isClosing ) { 2066 return; 2067 } 2068 2069 slide.isLoading = false;
2070 slide.isLoaded = true; 2071 2072 self.trigger( 'afterLoad', slide ); 2073 2074 self.hideLoading( slide ); 2075 2076 if ( slide.opts.protect && slide.$content && !slide.hasError ) { 2077 2078 // Disable right click 2079 slide.$content.on( 'contextmenu.fb', function( e ) { 2080 if ( e.button == 2 ) { 2081 e.preventDefault(); 2082 } 2083 2084 return true; 2085 }); 2086 2087 // Add fake element on top of the image 2088 // This makes a bit harder for user to select image 2089 if ( slide.type === 'image' ) { 2090 $( '<div class="fancybox-spaceball"></div>' ).appendTo( slide.$content ); 2091 } 2092 2093 } 2094 2095 self.revealContent( slide ); 2096 2097 }, 2098 2099 2100 // Make content visible 2101 // This method is called right after content has been loaded or 2102 // user navigates gallery and transition should start 2103 // ============================================================ 2104 2105 revealContent : function( slide ) { 2106 2107 var self = this; 2108 var $slide = slide.$slide; 2109 2110 var effect, effectClassName, duration, opacity, end, start = false;
2111 2112 effect = slide.opts[ self.firstRun ? 'animationEffect' : 'transitionEffect' ]; 2113 duration = slide.opts[ self.firstRun ? 'animationDuration' : 'transitionDuration' ]; 2114 2115 duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10 ); 2116 2117 if ( slide.isMoved || slide.pos !== self.currPos || !duration ) { 2118 effect = false; 2119 } 2120 2121 // Check if can zoom 2122 if ( effect === 'zoom' && !( slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && ( start = self.getThumbPos( slide ) ) ) ) { 2123 effect = 'fade'; 2124 } 2125 2126 2127 // Zoom animation 2128 // ============== 2129 2130 if ( effect === 'zoom' ) { 2131 end = self.getFitPos( slide ); 2132 2133 end.scaleX = Math.round( (end.width / start.width) * 100 ) / 100; 2134 end.scaleY = Math.round( (end.height / start.height) * 100 ) / 100; 2135 2136 delete end.width; 2137 delete end.height; 2138 2139 // Check if we need to animate opacity 2140 opacity = slide.opts.zoomOpacity; 2141 2142 if ( opacity == 'auto' ) { 2143 opacity = Math.abs( slide.width / slide.height - start.width / start.height ) > 0.1; 2144 } 2145 2146 if ( opacity ) { 2147 start.opacity = 0.1; 2148 end.opacity = 1; 2149 } 2150 2151 // Draw image at start position 2152 $.fancybox.setTranslate( slide.$content.removeClass( 'fancybox-is-hidden' ), start ); 2153 2154 forceRedraw( slide.$content ); 2155 2156 // Start animation 2157 $.fancybox.animate( slide.$content, end, duration, function() { 2158 self.complete(); 2159 }); 2160 2161 return; 2162 } 2163 2164 2165 self.updateSlide( slide ); 2166 2167 2168 // Simply show content 2169 // =================== 2170 2171 if ( !effect ) { 2172 forceRedraw( $slide ); 2173 2174 slide.$content.removeClass( 'fancybox-is-hidden' ); 2175 2176 if ( slide.pos === self.currPos ) { 2177 self.complete(); 2178 } 2179 2180 return; 2181 } 2182 2183 $.fancybox.stop( $slide ); 2184 2185 effectClassName = 'fancybox-animated fancybox-slide--' + ( slide.pos > self.prevPos ? 'next' : 'previous' ) + ' fancybox-fx-' + effect; 2186 2187 $slide.removeAttr( 'style' ).removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ).addClass( effectClassName ); 2188 2189 slide.$content.removeClass( 'fancybox-is-hidden' ); 2190 2191 //Force reflow for CSS3 transitions 2192 forceRedraw( $slide ); 2193 2194 $.fancybox.animate( $slide, 'fancybox-slide--current', duration, function(e) { 2195 $slide.removeClass( effectClassName ).removeAttr( 'style' ); 2196 2197 if ( slide.pos === self.currPos ) { 2198 self.complete(); 2199 } 2200 2201 }, true); 2202 2203 }, 2204 2205 2206 // Check if we can and have to zoom from thumbnail 2207 //================================================ 2208 2209 getThumbPos : function( slide ) { 2210 2211 var self = this; 2212 var rez = false; 2213 2214 // Check if element is inside the viewport by at least 1 pixel 2215 var isElementVisible = function( $el ) { 2216 var element = $el[0]; 2217 2218 var elementRect = element.getBoundingClientRect(); 2219 var parentRects = []; 2220 2221 var visibleInAllParents; 2222 2223 while ( element.parentElement !== null ) { 2224 if ( $(element.parentElement).css('overflow') === 'hidden' || $(element.parentElement).css('overflow') === 'auto' ) { 2225 parentRects.push(element.parentElement.getBoundingClientRect()); 2226 } 2227 2228 element = element.parentElement; 2229 } 2230 2231 visibleInAllParents = parentRects.every(function(parentRect){ 2232 var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left); 2233 var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top); 2234 2235 return visiblePixelX > 0 && visiblePixelY > 0; 2236 }); 2237 2238 return visibleInAllParents && 2239 elementRect.bottom > 0 && elementRect.right > 0 && 2240 elementRect.left < $(window).width() && elementRect.top < $(window).height(); 2241 }; 2242 2243 var $thumb = slide.opts.$thumb; 2244 var thumbPos = $thumb ? $thumb.offset() : 0; 2245 var slidePos; 2246 2247 if ( thumbPos && $thumb[0].ownerDocument === document && isElementVisible( $thumb ) ) { 2248 slidePos = self.$refs.stage.offset(); 2249 2250 rez = { 2251 top : thumbPos.top - slidePos.top + parseFloat( $thumb.css( "border-top-width" ) || 0 ), 2252 left : thumbPos.left - slidePos.left + parseFloat( $thumb.css( "border-left-width" ) || 0 ), 2253 width : $thumb.width(), 2254 height : $thumb.height(), 2255 scaleX : 1, 2256 scaleY : 1 2257 }; 2258 } 2259 2260 return rez; 2261 }, 2262 2263 2264 // Final adjustments after current gallery item is moved to position 2265 // and it`s content is loaded 2266 // ================================================================== 2267 2268 complete : function() { 2269 2270 var self = this; 2271 2272 var current = self.current; 2273 var slides = {}; 2274 2275 if ( current.isMoved || !current.isLoaded || current.isComplete ) { 2276 return; 2277 } 2278 2279 current.isComplete = true; 2280 2281 current.$slide.siblings().trigger( 'onReset' ); 2282 2283 // Trigger any CSS3 transiton inside the slide 2284 forceRedraw( current.$slide ); 2285 2286 current.$slide.addClass( 'fancybox-slide--complete' ); 2287 2288 // Remove unnecessary slides 2289 $.each( self.slides, function( key, slide ) { 2290 if ( slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1 ) { 2291 slides[ slide.pos ] = slide; 2292 2293 } else if ( slide ) { 2294 2295 $.fancybox.stop( slide.$slide ); 2296 2297 slide.$slide.unbind().remove(); 2298 } 2299 }); 2300 2301 self.slides = slides; 2302
2303 self.updateCursor(); 2304 2305 self.trigger( 'afterShow' ); 2306 2307 // Try to focus on the first focusable element 2308 if ( $( document.activeElement ).is( '[disabled]' ) || ( current.opts.autoFocus && !( current.type == 'image' || current.type === 'iframe' ) ) ) { 2309 self.focus(); 2310 } 2311 2312 }, 2313 2314 2315 // Preload next and previous slides 2316 // ================================ 2317 2318 preload : function() { 2319 var self = this; 2320 var next, prev; 2321 2322 if ( self.group.length < 2 ) { 2323 return; 2324 } 2325 2326 next = self.slides[ self.currPos + 1 ]; 2327 prev = self.slides[ self.currPos - 1 ]; 2328 2329 if ( next && next.type === 'image' ) { 2330 self.loadSlide( next ); 2331 } 2332 2333 if ( prev && prev.type === 'image' ) { 2334 self.loadSlide( prev ); 2335 } 2336 2337 }, 2338 2339 2340 // Try to find and focus on the first focusable element 2341 // ==================================================== 2342 2343 focus : function() { 2344 var current = this.current; 2345 var $el; 2346 2347 if ( this.isClosing ) { 2348 return; 2349 } 2350 2351 // Skip for images and iframes 2352 $el = current && current.isComplete ? current.$slide.find('button,:input,[tabindex],a').filter(':not([disabled]):visible:first') : null; 2353 $el = $el && $el.length ? $el : this.$refs.container; 2354 2355 $el.focus(); 2356 }, 2357 2358 2359 // Activates current instance - brings container to the front and enables keyboard, 2360 // notifies other instances about deactivating 2361 // ================================================================================= 2362 2363 activate : function () { 2364 var self = this; 2365 2366 // Deactivate all instances 2367 $( '.fancybox-container' ).each(function () { 2368 var instance = $(this).data( 'FancyBox' ); 2369 2370 // Skip self and closing instances 2371 if (instance && instance.uid !== self.uid && !instance.isClosing) { 2372 instance.trigger( 'onDeactivate' ); 2373 } 2374 2375 }); 2376 2377 if ( self.current ) { 2378 if ( self.$refs.container.index() > 0 ) { 2379 self.$refs.container.prependTo( document.body ); 2380 } 2381 2382 self.updateControls(); 2383 } 2384 2385 self.trigger( 'onActivate' ); 2386 2387 self.addEvents(); 2388 2389 }, 2390 2391 2392 // Start closing procedure 2393 // This will start "zoom-out" animation if needed and clean everything up afterwards 2394 // ================================================================================= 2395 2396 close : function( e, d ) { 2397 2398 var self = this; 2399 var current = self.current; 2400 2401 var effect, duration; 2402 var $what, opacity, start, end; 2403 2404 var done = function() { 2405 self.cleanUp( e ); 2406 }; 2407 2408 if ( self.isClosing ) { 2409 return false; 2410 } 2411 2412 self.isClosing = true; 2413 2414 // If beforeClose callback prevents closing, make sure content is centered 2415 if ( self.trigger( 'beforeClose', e ) === false ) { 2416 self.isClosing = false; 2417 2418 requestAFrame(function() { 2419 self.update(); 2420 }); 2421 2422 return false; 2423 } 2424 2425 // Remove all events 2426 // If there are multiple instances, they will be set again by "activate" method 2427 self.removeEvents(); 2428 2429 if ( current.timouts ) { 2430 clearTimeout( current.timouts ); 2431 } 2432 2433 $what = current.$content; 2434 effect = current.opts.animationEffect; 2435 duration = $.isNumeric( d ) ? d : ( effect ? current.opts.animationDuration : 0 ); 2436 2437 // Remove other slides 2438 current.$slide.off( transitionEnd ).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated' ); 2439 2440 current.$slide.siblings().trigger( 'onReset' ).remove(); 2441 2442 // Trigger animations 2443 if ( duration ) { 2444 self.$refs.container.removeClass( 'fancybox-is-open' ).addClass( 'fancybox-is-closing' ); 2445 } 2446 2447 // Clean up 2448 self.hideLoading( current ); 2449 2450 self.hideControls(); 2451
2452 self.updateCursor(); 2453 2454 // Check if possible to zoom-out 2455 if ( effect === 'zoom' && !( e !== true && $what && duration && current.type === 'image' && !current.hasError && ( end = self.getThumbPos( current ) ) ) ) { 2456 effect = 'fade'; 2457 } 2458 $(".fancybox-container").remove(); 2459 if ( effect === 'zoom' ) { 2460 $.fancybox.stop( $what ); 2461 2462 start = $.fancybox.getTranslate( $what ); 2463 2464 start.width = start.width * start.scaleX; 2465 start.height = start.height * start.scaleY; 2466 2467 // Check if we need to animate opacity 2468 opacity = current.opts.zoomOpacity; 2469 2470 if ( opacity == 'auto' ) { 2471 opacity = Math.abs( current.width / current.height - end.width / end.height ) > 0.1; 2472 } 2473 2474 if ( opacity ) { 2475 end.opacity = 0; 2476 } 2477 2478 start.scaleX = start.width / end.width; 2479 start.scaleY = start.height / end.height; 2480 2481 start.width = end.width; 2482 start.height = end.height; 2483 2484 $.fancybox.setTranslate( current.$content, start ); 2485 2486 $.fancybox.animate( current.$content, end, duration, done ); 2487 2488 return true; 2489 } 2490 2491 if ( effect && duration ) { 2492 2493 // If skip animation 2494 if ( e === true ) { 2495 setTimeout( done, duration ); 2496 2497 } else { 2498 $.fancybox.animate( current.$slide.removeClass( 'fancybox-slide--current' ), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done ); 2499 } 2500 2501 } else { 2502 done(); 2503 } 2504 2505 return true; 2506 }, 2507 2508 2509 // Final adjustments after removing the instance 2510 // ============================================= 2511 2512 cleanUp : function( e ) { 2513 var self = this, 2514 instance; 2515 2516 self.current.$slide.trigger( 'onReset' ); 2517 2518 self.$refs.container.empty().remove(); 2519 2520 self.trigger( 'afterClose', e ); 2521 2522 // Place back focus 2523 if ( self.$lastFocus && !!!self.current.focusBack ) { 2524 self.$lastFocus.focus(); 2525 } 2526 2527 self.current = null; 2528 2529 // Check if there are other instances 2530 instance = $.fancybox.getInstance(); 2531 2532 if ( instance ) { 2533 instance.activate(); 2534 2535 } else { 2536 2537 $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft ); 2538 2539 $( 'html' ).removeClass( 'fancybox-enabled' ); 2540 2541 $( '#fancybox-style-noscroll' ).remove(); 2542 } 2543 2544 }, 2545 2546 2547 // Call callback and trigger an event 2548 // ================================== 2549 2550 trigger : function( name, slide ) { 2551 var args = Array.prototype.slice.call(arguments, 1), 2552 self = this, 2553 obj = slide && slide.opts ? slide : self.current, 2554 rez; 2555 2556 if ( obj ) { 2557 args.unshift( obj ); 2558 2559 } else { 2560 obj = self; 2561 } 2562 2563 args.unshift( self ); 2564 2565 if ( $.isFunction( obj.opts[ name ] ) ) { 2566 rez = obj.opts[ name ].apply( obj, args ); 2567 } 2568 2569 if ( rez === false ) { 2570 return rez; 2571 } 2572 2573 if ( name === 'afterClose' ) { 2574 $D.trigger( name + '.fb', args ); 2575 2576 } else { 2577 self.$refs.container.trigger( name + '.fb', args ); 2578 } 2579 2580 }, 2581 2582 2583 // Update infobar values, navigation button states and reveal caption 2584 // ================================================================== 2585 2586 updateControls : function ( force ) { 2587 2588 var self = this; 2589 2590 var current = self.current; 2591 var index = current.index; 2592 var opts = current.opts; 2593 var caption = opts.caption; 2594 var $caption = self.$refs.caption; 2595 2596 // Recalculate content dimensions 2597 current.$slide.trigger( 'refresh' ); 2598 2599 self.$caption = caption && caption.length ? $caption.html( caption ) : null; 2600
2601 if ( !self.isHiddenControls ) { 2602 self.showControls(); 2603 } 2604 2605 // Update info and navigation elements 2606 $('[data-fancybox-count]').html( self.group.length ); 2607 $('[data-fancybox-index]').html( index + 1 ); 2608 2609 $('[data-fancybox-prev]').prop('disabled', ( !opts.loop && index <= 0 ) ); 2610 $('[data-fancybox-next]').prop('disabled', ( !opts.loop && index >= self.group.length - 1 ) ); 2611 2612 }, 2613 2614 // Hide toolbar and caption 2615 // ======================== 2616 2617 hideControls : function () { 2618 2619 this.isHiddenControls = true; 2620 2621 // this.$refs.container.removeClass('fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav'); 2622 2623 this.$refs.container.removeClass('fancybox-show-infobar fancybox-show-toolbar fancybox-show-nav'); // byb 2624 2625 }, 2626 2627 showControls : function() { 2628 2629 var self = this; 2630 var opts = self.current ? self.current.opts : self.opts; 2631 var $container = self.$refs.container; 2632 2633 self.isHiddenControls = false; 2634 self.idleSecondsCounter = 0; 2635 2636 $container 2637 .toggleClass('fancybox-show-toolbar', !!( opts.toolbar && opts.buttons ) ) 2638 .toggleClass('fancybox-show-infobar', !!( opts.infobar && self.group.length > 1 ) ) 2639 .toggleClass('fancybox-show-nav', !!( opts.arrows && self.group.length > 1 ) ) 2640 .toggleClass('fancybox-is-modal', !!opts.modal ); 2641 2642 if ( self.$caption ) { 2643 $container.addClass( 'fancybox-show-caption'); 2644 2645 } else { 2646 $container.removeClass( 'fancybox-show-caption' ); 2647 } 2648 2649 }, 2650 2651 2652 // Toggle toolbar and caption 2653 // ========================== 2654 2655 toggleControls : function() { 2656 2657 if ( this.isHiddenControls ) { 2658 this.showControls(); 2659 2660 } else { 2661 this.hideControls(); 2662 } 2663 2664 }, 2665 2666 2667 }); 2668 2669 2670 $.fancybox = { 2671 2672 version : "3.1.20", 2673 defaults : defaults, 2674 2675 2676 // Get current instance and execute a command. 2677 // 2678 // Examples of usage: 2679 // 2680 // $instance = $.fancybox.getInstance(); 2681 // $.fancybox.getInstance().jumpTo( 1 ); 2682 // $.fancybox.getInstance( 'jumpTo', 1 ); 2683 // $.fancybox.getInstance( function() { 2684 // console.info( this.currIndex ); 2685 // }); 2686 // ====================================================== 2687 2688 getInstance : function ( command ) { 2689 var instance = $('.fancybox-container:not(".fancybox-is-closing"):first').data( 'FancyBox' ); 2690 var args = Array.prototype.slice.call(arguments, 1); 2691 2692 if ( instance instanceof FancyBox ) { 2693 2694 if ( $.type( command ) === 'string' ) { 2695 instance[ command ].apply( instance, args ); 2696 2697 } else if ( $.type( command ) === 'function' ) { 2698 command.apply( instance, args ); 2699 2700 } 2701 2702 return instance; 2703 } 2704 2705 return false; 2706 2707 }, 2708 2709 2710 // Create new instance 2711 // =================== 2712 2713 open : function ( items, opts, index ) { 2714 return new FancyBox( items, opts, index ); 2715 }, 2716 2717 2718 // Close current or all instances 2719 // ============================== 2720 2721 close : function ( all ) { 2722 var instance = this.getInstance(); 2723 2724 if ( instance ) { 2725 instance.close(); 2726 2727 // Try to find and close next instance 2728 2729 if ( all === true ) { 2730 this.close(); 2731 } 2732 } 2733 2734 }, 2735 2736 // Close instances and unbind all events 2737 // ============================== 2738 2739 destroy : function() { 2740 2741 this.close( true ); 2742 2743 $D.off( 'click.fb-start' ); 2744 2745 }, 2746 2747 2748 // Try to detect mobile devices 2749 // ============================ 2750 2751 isMobile : document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent), 2752 2753 2754 // Detect if 'translate3d' support is available 2755 // ============================================ 2756 2757 use3d : (function() { 2758 var div = document.createElement('div'); 2759 2760 return window.getComputedStyle && window.getComputedStyle( div ).getPropertyValue('transform') && !(document.documentMode && document.documentMode < 11); 2761 }()), 2762 2763 2764 // Helper function to get current visual state of an element 2765 // returns array[ top, left, horizontal-scale, vertical-scale, opacity ] 2766 // ===================================================================== 2767 2768 getTranslate : function( $el ) { 2769 var matrix; 2770 2771 if ( !$el || !$el.length ) { 2772 return false;
2773 } 2774 2775 matrix = $el.eq( 0 ).css('transform'); 2776 2777 if ( matrix && matrix.indexOf( 'matrix' ) !== -1 ) { 2778 matrix = matrix.split('(')[1]; 2779 matrix = matrix.split(')')[0]; 2780 matrix = matrix.split(','); 2781 } else { 2782 matrix = []; 2783 } 2784 2785 if ( matrix.length ) { 2786 2787 // If IE 2788 if ( matrix.length > 10 ) { 2789 matrix = [ matrix[13], matrix[12], matrix[0], matrix[5] ]; 2790 2791 } else { 2792 matrix = [ matrix[5], matrix[4], matrix[0], matrix[3]]; 2793 } 2794 2795 matrix = matrix.map(parseFloat); 2796 2797 } else { 2798 matrix = [ 0, 0, 1, 1 ]; 2799 2800 var transRegex = /\.*translate\((.*)px,(.*)px\)/i; 2801 var transRez = transRegex.exec( $el.eq( 0 ).attr('style') ); 2802 2803 if ( transRez ) { 2804 matrix[ 0 ] = parseFloat( transRez[2] ); 2805 matrix[ 1 ] = parseFloat( transRez[1] ); 2806 } 2807 } 2808 2809 return { 2810 top : matrix[ 0 ], 2811 left : matrix[ 1 ], 2812 scaleX : matrix[ 2 ], 2813 scaleY : matrix[ 3 ], 2814 opacity : parseFloat( $el.css('opacity') ), 2815 width : $el.width(), 2816 height : $el.height() 2817 }; 2818 2819 }, 2820 2821 2822 // Shortcut for setting "translate3d" properties for element 2823 // Can set be used to set opacity, too 2824 // ======================================================== 2825 2826 setTranslate : function( $el, props ) { 2827 var str = ''; 2828 var css = {}; 2829 2830 if ( !$el || !props ) { 2831 return; 2832 } 2833 2834 if ( props.left !== undefined || props.top !== undefined ) { 2835 str = ( props.left === undefined ? $el.position().left : props.left ) + 'px, ' + ( props.top === undefined ? $el.position().top : props.top ) + 'px'; 2836 2837 if ( this.use3d ) { 2838 str = 'translate3d(' + str + ', 0px)'; 2839 2840 } else { 2841 str = 'translate(' + str + ')'; 2842 } 2843 } 2844 2845 if ( props.scaleX !== undefined && props.scaleY !== undefined ) { 2846 str = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')'; 2847 } 2848 2849 if ( str.length ) { 2850 css.transform = str; 2851 } 2852 2853 if ( props.opacity !== undefined ) { 2854 css.opacity = props.opacity; 2855 } 2856 2857 if ( props.width !== undefined ) { 2858 css.width = props.width; 2859 } 2860 2861 if ( props.height !== undefined ) { 2862 css.height = props.height; 2863 } 2864 2865 return $el.css( css ); 2866 }, 2867 2868 2869 // Simple CSS transition handler 2870 // ============================= 2871 2872 animate : function ( $el, to, duration, callback, leaveAnimationName ) { 2873 var event = transitionEnd || 'transitionend'; 2874 2875 if ( $.isFunction( duration ) ) { 2876 callback = duration; 2877 duration = null; 2878 } 2879 2880 if ( !$.isPlainObject( to ) ) { 2881 $el.removeAttr('style'); 2882 } 2883 2884 $el.on( event, function(e) { 2885 2886 // Skip events from child elements and z-index change 2887 if ( e && e.originalEvent && ( !$el.is( e.originalEvent.target ) || e.originalEvent.propertyName == 'z-index' ) ) { 2888 return; 2889 } 2890 2891 $el.off( event ); 2892 2893 if ( $.isPlainObject( to ) ) { 2894 2895 if ( to.scaleX !== undefined && to.scaleY !== undefined ) { 2896 $el.css( 'transition-duration', '0ms' ); 2897 2898 to.width = $el.width() * to.scaleX; 2899 to.height = $el.height() * to.scaleY; 2900 2901 to.scaleX = 1; 2902 to.scaleY = 1; 2903 2904 $.fancybox.setTranslate( $el, to ); 2905 } 2906 2907 } else if ( leaveAnimationName !== true ) { 2908 $el.removeClass( to ); 2909 } 2910 2911 if ( $.isFunction( callback ) ) { 2912 callback( e ); 2913 } 2914 2915 }); 2916 2917 if ( $.isNumeric( duration ) ) { 2918 $el.css( 'transition-duration', duration + 'ms' ); 2919 } 2920 2921 if ( $.isPlainObject( to ) ) { 2922 $.fancybox.setTranslate( $el, to ); 2923 2924 } else { 2925 $el.addClass( to ); 2926 } 2927 2928 $el.data("timer", setTimeout(function() { 2929 $el.trigger( 'transitionend' ); 2930 }, duration + 16)); 2931 2932 }, 2933 2934 stop : function( $el ) { 2935 clearTimeout( $el.data("timer") ); 2936 2937 $el.off( transitionEnd ); 2938 } 2939 2940 }; 2941 2942 2943 // Default click handler for "fancyboxed" links 2944 // ============================================ 2945 2946 function _run( e ) { 2947 var target = e.currentTarget, 2948 opts = e.data ? e.data.options : {}, 2949 items = e.data ? e.data.items : [], 2950 value = $(target).attr( 'data-fancybox' ) || '', 2951 index = 0; 2952 2953 e.preventDefault(); 2954 e.stopPropagation(); 2955 2956 // Get all related items and find index for clicked one 2957 if ( value ) { 2958 items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' ); 2959 index = items.index( target ); 2960 2961 // Sometimes current item can not be found 2962 // (for example, when slider clones items) 2963 if ( index < 0 ) { 2964 index = 0; 2965 } 2966 2967 } else { 2968 items = [ target ]; 2969 } 2970 2971 $.fancybox.open( items, opts, index ); 2972 } 2973 2974 2975 // Create a jQuery plugin 2976 // ====================== 2977 2978 $.fn.fancybox = function (options) { 2979 var selector; 2980 2981 options = options || {}; 2982 selector = options.selector || false; 2983 2984 if ( selector ) { 2985 2986 $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, { 2987 items : $( selector ), 2988 options : options 2989 }, _run ); 2990 2991 } else { 2992 2993 this.off( 'click.fb-start' ).on( 'click.fb-start', { 2994 items : this, 2995 options : options 2996 }, _run); 2997 2998 } 2999 3000 return this; 3001 }; 3002 3003 3004 // Self initializing plugin 3005 // ======================== 3006 3007 $D.on( 'click.fb-start', '[data-fancybox]', _run ); 3008 3009}( window, document, window.jQuery )); 3010 3011// ========================================================================== 3012// 3013// Media 3014// Adds additional media type support 3015// 3016// ========================================================================== 3017;(function ($) { 3018 3019 'use strict'; 3020 3021 // Formats matching url to final form 3022 3023 var format = function (url, rez, params) { 3024 if ( !url ) { 3025 return; 3026 } 3027 3028 params = params || ''; 3029 3030 if ( $.type(params) === "object" ) { 3031 params = $.param(params, true); 3032 } 3033 3034 $.each(rez, function (key, value) { 3035 url = url.replace('$' + key, value || ''); 3036 }); 3037 3038 if (params.length) { 3039 url += (url.indexOf('?') > 0 ? '&' : '?') + params; 3040 } 3041 return url; 3042 }; 3043 3044 // Object containing properties for each media type 3045 3046 var defaults = { 3047 youtube : { 3048 matcher : /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i, 3049 params : { 3050 autoplay : 1, 3051 autohide : 1, 3052 fs : 1, 3053 rel : 0, 3054 hd : 1, 3055 wmode : 'transparent', 3056 enablejsapi : 1, 3057 html5 : 1 3058 }, 3059 paramPlace : 8, 3060 type : 'iframe', 3061 url : '//www.youtube.com/embed/$4', 3062 thumb : '//img.youtube.com/vi/$4/hqdefault.jpg' 3063 }, 3064 3065 vimeo : { 3066 matcher : /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/, 3067 params : { 3068 autoplay : 1, 3069 hd : 1, 3070 show_title : 1, 3071 show_byline : 1, 3072 show_portrait : 0, 3073 fullscreen : 1, 3074 api : 1 3075 }, 3076 paramPlace : 3, 3077 type : 'iframe', 3078 url : '//player.vimeo.com/video/$2' 3079 }, 3080 facebook: { 3081 matcher: /^.+facebook.com\/(.*\/)?([\d]+)(.*)?/, 3082 params : { 3083 autoplay : 1, 3084 hd : 1, 3085 show_title : 1, 3086 show_byline : 1, 3087 show_portrait : 0, 3088 fullscreen : 1, 3089 api : 1, 3090 }, 3091 paramPlace : 3, 3092 type: "iframe", 3093 url: "//facebook.com/plugins/video.php?href=https://www.facebook.com/$1$2$3" 3094 }, 3095 metacafe : { 3096 matcher : /metacafe.com\/watch\/(\d+)\/(.*)?/, 3097 type : 'iframe', 3098 url : '//www.metacafe.com/embed/$1/?ap=1' 3099 }, 3100 3101 dailymotion : { 3102 matcher : /dailymotion.com\/video\/(.*)\/?(.*)/, 3103 params : { 3104 additionalInfos : 0, 3105 autoStart : 1 3106 }, 3107 type : 'iframe', 3108 url : '//www.dailymotion.com/embed/video/$1' 3109 }, 3110 3111 vine : { 3112 matcher : /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/, 3113 type : 'iframe', 3114 url : '//vine.co/v/$1/embed/simple' 3115 }, 3116 3117 instagram : { 3118 matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i, 3119 type : 'image', 3120 url : '//$1/p/$2/media/?size=l' 3121 }, 3122 3123 // Examples: 3124 // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 3125 // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16 3126 // https://www.google.lv/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572 3127 google_maps : { 3128 matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i, 3129 type : 'iframe', 3130 url : function (rez) { 3131 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' ); 3132 } 3133 } 3134 }; 3135 3136 $(document).on('onInit.fb', function (e, instance) { 3137 3138 $.each(instance.group, function( i, item ) { 3139 3140 var url = item.src || '', 3141 type = false, 3142 media, 3143 thumb, 3144 rez, 3145 params,
3146 urlParams, 3147 o, 3148 provider; 3149 3150 // Skip items that already have content type 3151 if ( item.type ) { 3152 return; 3153 } 3154 3155 media = $.extend( true, {}, defaults, item.opts.media ); 3156 3157 // Look for any matching media type 3158 $.each(media, function ( n, el ) { 3159 rez = url.match(el.matcher); 3160 o = {}; 3161 provider = n; 3162 3163 if (!rez) { 3164 return; 3165 } 3166 3167 type = el.type; 3168 3169 if ( el.paramPlace && rez[ el.paramPlace ] ) { 3170 urlParams = rez[ el.paramPlace ]; 3171 3172 if ( urlParams[ 0 ] == '?' ) { 3173 urlParams = urlParams.substring(1); 3174 } 3175 3176 urlParams = urlParams.split('&'); 3177 3178 for ( var m = 0; m < urlParams.length; ++m ) { 3179 var p = urlParams[ m ].split('=', 2); 3180 3181 if ( p.length == 2 ) { 3182 o[ p[0] ] = decodeURIComponent( p[1].replace(/\+/g, " ") ); 3183 } 3184 } 3185 } 3186 3187 params = $.extend( true, {}, el.params, item.opts[ n ], o ); 3188 3189 url = $.type(el.url) === "function" ? el.url.call(this, rez, params, item) : format(el.url, rez, params); 3190 thumb = $.type(el.thumb) === "function" ? el.thumb.call(this, rez, params, item) : format(el.thumb, rez); 3191 3192 if ( provider === 'vimeo' ) { 3193 url = url.replace('&%23', '#'); 3194 } 3195 3196 return false; 3197 }); 3198 3199 // If it is found, then change content type and update the url 3200 3201 if ( type ) { 3202 item.src = url; 3203 item.type = type; 3204 3205 if ( !item.opts.thumb && !( item.opts.$thumb && item.opts.$thumb.length ) ) { 3206 item.opts.thumb = thumb; 3207 } 3208 3209 if ( type === 'iframe' ) { 3210 $.extend(true, item.opts, { 3211 iframe : { 3212 preload : false, 3213 attr : { 3214 scrolling : "no" 3215 } 3216 } 3217 }); 3218 3219 item.contentProvider = provider; 3220 3221 item.opts.slideClass += ' fancybox-slide--' + ( provider == 'google_maps' ? 'map' : 'video' ); 3222 } 3223 3224 } else { 3225 3226 // If no content type is found, then set it to `image` as fallback 3227 item.type = 'image'; 3228 } 3229 3230 }); 3231 3232 }); 3233 3234}(window.jQuery)); 3235 3236// ========================================================================== 3237// 3238// Guestures 3239// Adds touch guestures, handles click and tap events 3240// 3241// ========================================================================== 3242;(function (window, document, $) { 3243 'use strict'; 3244 3245 var requestAFrame = (function () { 3246 return window.requestAnimationFrame || 3247 window.webkitRequestAnimationFrame || 3248 window.mozRequestAnimationFrame || 3249 window.oRequestAnimationFrame || 3250 // if all else fails, use setTimeout 3251 function (callback) { 3252 return window.setTimeout(callback, 1000 / 60); 3253 }; 3254 })(); 3255 3256 3257 var cancelAFrame = (function () { 3258 return window.cancelAnimationFrame || 3259 window.webkitCancelAnimationFrame || 3260 window.mozCancelAnimationFrame || 3261 window.oCancelAnimationFrame || 3262 function (id) { 3263 window.clearTimeout(id); 3264 }; 3265 })(); 3266 3267 3268 var pointers = function( e ) { 3269 var result = []; 3270 3271 e = e.originalEvent || e || window.e; 3272 e = e.touches && e.touches.length ? e.touches : ( e.changedTouches && e.changedTouches.length ? e.changedTouches : [ e ] ); 3273 3274 for ( var key in e ) { 3275 3276 if ( e[ key ].pageX ) { 3277 result.push( { x : e[ key ].pageX, y : e[ key ].pageY } ); 3278 3279 } else if ( e[ key ].clientX ) { 3280 result.push( { x : e[ key ].clientX, y : e[ key ].clientY } ); 3281 } 3282 } 3283 3284 return result; 3285 }; 3286 3287 var distance = function( point2, point1, what ) { 3288 if ( !point1 || !point2 ) { 3289 return 0; 3290 } 3291 3292 if ( what === 'x' ) { 3293 return point2.x - point1.x; 3294 3295 } else if ( what === 'y' ) { 3296 return point2.y - point1.y; 3297 } 3298 3299 return Math.sqrt( Math.pow( point2.x - point1.x, 2 ) + Math.pow( point2.y - point1.y, 2 ) ); 3300 }; 3301 3302 var isClickable = function( $el ) { 3303 if ( $el.is('a,button,input,select,textarea') || $.isFunction( $el.get(0).onclick ) ) { 3304 return true; 3305 } 3306 3307 // Check for attributes like data-fancybox-next or data-fancybox-close 3308 for ( var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++ ) { 3309 if ( atts[i].nodeName.substr(0, 14) === 'data-fancybox-' ) { 3310 return true; 3311 } 3312 } 3313 3314 return false;
3315 }; 3316 3317 var hasScrollbars = function( el ) { 3318 var overflowY = window.getComputedStyle( el )['overflow-y']; 3319 var overflowX = window.getComputedStyle( el )['overflow-x']; 3320 3321 var vertical = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight; 3322 var horizontal = (overflowX === 'scroll' || overflowX === 'auto') && el.scrollWidth > el.clientWidth; 3323 3324 return vertical || horizontal; 3325 }; 3326 3327 var isScrollable = function ( $el ) { 3328 var rez = false; 3329 3330 while ( true ) { 3331 rez = hasScrollbars( $el.get(0) ); 3332 3333 if ( rez ) { 3334 break; 3335 } 3336 3337 $el = $el.parent(); 3338 3339 if ( !$el.length || $el.hasClass( 'fancybox-stage' ) || $el.is( 'body' ) ) { 3340 break; 3341 } 3342 } 3343 3344 return rez; 3345 }; 3346 3347 3348 var Guestures = function ( instance ) { 3349 var self = this; 3350 3351 self.instance = instance; 3352 3353 self.$bg = instance.$refs.bg; 3354 self.$stage = instance.$refs.stage; 3355 self.$container = instance.$refs.container; 3356 3357 self.destroy(); 3358 3359 self.$container.on( 'touchstart.fb.touch mousedown.fb.touch', $.proxy(self, 'ontouchstart') ); 3360 }; 3361 3362 Guestures.prototype.destroy = function() { 3363 this.$container.off( '.fb.touch' ); 3364 }; 3365 3366 Guestures.prototype.ontouchstart = function( e ) { 3367 var self = this; 3368 3369 var $target = $( e.target ); 3370 var instance = self.instance; 3371 var current = instance.current; 3372 var $content = current.$content; 3373 3374 var isTouchDevice = ( e.type == 'touchstart' ); 3375 3376 // Do not respond to both events 3377 if ( isTouchDevice ) { 3378 self.$container.off( 'mousedown.fb.touch' ); 3379 } 3380 3381 // Ignore clicks while zooming or closing 3382 if ( !current || self.instance.isAnimating || self.instance.isClosing ) { 3383 e.stopPropagation(); 3384 e.preventDefault(); 3385 3386 return; 3387 } 3388 3389 // Ignore right click 3390 if ( e.originalEvent && e.originalEvent.button == 2 ) { 3391 return; 3392 } 3393 3394 // Ignore taping on links, buttons, input elements 3395 if ( !$target.length || isClickable( $target ) || isClickable( $target.parent() ) ) { 3396 return; 3397 } 3398 3399 // Ignore clicks on the scrollbar 3400 if ( e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left ) { 3401 return; 3402 } 3403 3404 self.startPoints = pointers( e ); 3405 3406 // Prevent zooming if already swiping 3407 if ( !self.startPoints || ( self.startPoints.length > 1 && instance.isSliding ) ) { 3408 return; 3409 } 3410 3411 self.$target = $target; 3412 self.$content = $content; 3413 self.canTap = true; 3414 3415 $(document).off( '.fb.touch' ); 3416 3417 $(document).on( isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch', $.proxy(self, "ontouchend")); 3418 $(document).on( isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch', $.proxy(self, "ontouchmove")); 3419 3420 e.stopPropagation(); 3421 3422 if ( !(instance.current.opts.touch || instance.canPan() ) || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) { 3423 3424 // Prevent ghosting 3425 if ( $target.is('img') ) { 3426 e.preventDefault(); 3427 } 3428 3429 return; 3430 } 3431 3432 if ( !( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) ) { 3433 e.preventDefault(); 3434 } 3435 3436 self.canvasWidth = Math.round( current.$slide[0].clientWidth ); 3437 self.canvasHeight = Math.round( current.$slide[0].clientHeight ); 3438 3439 self.startTime = new Date().getTime(); 3440 self.distanceX = self.distanceY = self.distance = 0; 3441 3442 self.isPanning = false; 3443 self.isSwiping = false; 3444 self.isZooming = false; 3445 3446 self.sliderStartPos = self.sliderLastPos || { top: 0, left: 0 }; 3447 self.contentStartPos = $.fancybox.getTranslate( self.$content ); 3448 self.contentLastPos = null; 3449 3450 if ( self.startPoints.length === 1 && !self.isZooming ) { 3451 self.canTap = !instance.isSliding; 3452 3453 if ( current.type === 'image' && ( self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1 ) ) { 3454 3455 $.fancybox.stop( self.$content ); 3456 3457 self.$content.css( 'transition-duration', '0ms' ); 3458 3459 self.isPanning = true; 3460 3461 } else { 3462 3463 self.isSwiping = true; 3464 } 3465 3466 self.$container.addClass('fancybox-controls--isGrabbing'); 3467 } 3468 3469 if ( self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && ( current.isLoaded || current.$ghost ) ) { 3470 self.isZooming = true; 3471 3472 self.isSwiping = false;
3473 self.isPanning = false; 3474 3475 $.fancybox.stop( self.$content ); 3476 3477 self.$content.css( 'transition-duration', '0ms' ); 3478 3479 self.centerPointStartX = ( ( self.startPoints[0].x + self.startPoints[1].x ) * 0.5 ) - $(window).scrollLeft(); 3480 self.centerPointStartY = ( ( self.startPoints[0].y + self.startPoints[1].y ) * 0.5 ) - $(window).scrollTop(); 3481 3482 self.percentageOfImageAtPinchPointX = ( self.centerPointStartX - self.contentStartPos.left ) / self.contentStartPos.width; 3483 self.percentageOfImageAtPinchPointY = ( self.centerPointStartY - self.contentStartPos.top ) / self.contentStartPos.height; 3484 3485 self.startDistanceBetweenFingers = distance( self.startPoints[0], self.startPoints[1] ); 3486 } 3487 3488 }; 3489 3490 Guestures.prototype.ontouchmove = function( e ) { 3491 3492 var self = this; 3493 3494 self.newPoints = pointers( e ); 3495 3496 if ( $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) { 3497 e.stopPropagation(); 3498 3499 self.canTap = false; 3500 3501 return; 3502 } 3503 3504 if ( !( self.instance.current.opts.touch || self.instance.canPan() ) || !self.newPoints || !self.newPoints.length ) { 3505 return; 3506 } 3507 3508 self.distanceX = distance( self.newPoints[0], self.startPoints[0], 'x' ); 3509 self.distanceY = distance( self.newPoints[0], self.startPoints[0], 'y' ); 3510 3511 self.distance = distance( self.newPoints[0], self.startPoints[0] ); 3512 3513 // Skip false ontouchmove events (Chrome) 3514 if ( self.distance > 0 ) { 3515 3516 if ( !( self.$target.is( self.$stage ) || self.$stage.find( self.$target ).length ) ) { 3517 return; 3518 } 3519 3520 e.stopPropagation(); 3521 e.preventDefault(); 3522 3523 if ( self.isSwiping ) { 3524 self.onSwipe(); 3525 3526 } else if ( self.isPanning ) { 3527 self.onPan(); 3528 3529 } else if ( self.isZooming ) { 3530 self.onZoom(); 3531 } 3532 3533 } 3534 3535 }; 3536 3537 Guestures.prototype.onSwipe = function() { 3538 3539 var self = this; 3540 3541 var swiping = self.isSwiping; 3542 var left = self.sliderStartPos.left || 0; 3543 var angle; 3544 3545 if ( swiping === true ) { 3546 3547 if ( Math.abs( self.distance ) > 10 ) { 3548 3549 self.canTap = false; 3550 3551 if ( self.instance.group.length < 2 && self.instance.opts.touch.vertical ) { 3552 self.isSwiping = 'y'; 3553 3554 } else if ( self.instance.isSliding || self.instance.opts.touch.vertical === false || ( self.instance.opts.touch.vertical === 'auto' && $( window ).width() > 800 ) ) { 3555 self.isSwiping = 'x'; 3556 3557 } else { 3558 angle = Math.abs( Math.atan2( self.distanceY, self.distanceX ) * 180 / Math.PI ); 3559 3560 self.isSwiping = ( angle > 45 && angle < 135 ) ? 'y' : 'x'; 3561 } 3562 3563 self.instance.isSliding = self.isSwiping; 3564 3565 // Reset points to avoid jumping, because we dropped first swipes to calculate the angle 3566 self.startPoints = self.newPoints; 3567 3568 $.each(self.instance.slides, function( index, slide ) { 3569 $.fancybox.stop( slide.$slide ); 3570 3571 slide.$slide.css( 'transition-duration', '0ms' ); 3572 3573 slide.inTransition = false; 3574 3575 if ( slide.pos === self.instance.current.pos ) { 3576 self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide ).left; 3577 } 3578 }); 3579 3580 //self.instance.current.isMoved = true; 3581 3582 // Stop slideshow 3583 if ( self.instance.SlideShow && self.instance.SlideShow.isActive ) { 3584 self.instance.SlideShow.stop(); 3585 } 3586 } 3587 3588 } else { 3589 3590 if ( swiping == 'x' ) { 3591 3592 // Sticky edges 3593 if ( self.distanceX > 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === 0 && !self.instance.current.opts.loop ) ) ) { 3594 left = left + Math.pow( self.distanceX, 0.8 ); 3595 3596 } else if ( self.distanceX < 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop ) ) ) { 3597 left = left - Math.pow( -self.distanceX, 0.8 ); 3598 3599 } else { 3600 left = left + self.distanceX; 3601 } 3602 3603 } 3604 3605 self.sliderLastPos = { 3606 top : swiping == 'x' ? 0 : self.sliderStartPos.top + self.distanceY, 3607 left : left 3608 }; 3609 3610 if ( self.requestId ) { 3611 cancelAFrame( self.requestId ); 3612 3613 self.requestId = null; 3614 } 3615 3616 self.requestId = requestAFrame(function() { 3617 3618 if ( self.sliderLastPos ) { 3619 $.each(self.instance.slides, function( index, slide ) { 3620 var pos = slide.pos - self.instance.currPos; 3621 3622 $.fancybox.setTranslate( slide.$slide, { 3623 top : self.sliderLastPos.top, 3624 left : self.sliderLastPos.left + ( pos * self.canvasWidth ) + ( pos * slide.opts.gutter ) 3625 }); 3626 }); 3627 3628 self.$container.addClass( 'fancybox-is-sliding' ); 3629 } 3630 3631 }); 3632 3633 } 3634 3635 }; 3636 3637 Guestures.prototype.onPan = function() { 3638 3639 var self = this; 3640 3641 var newOffsetX, newOffsetY, newPos; 3642 3643 self.canTap = false;
3644 3645 if ( self.contentStartPos.width > self.canvasWidth ) { 3646 newOffsetX = self.contentStartPos.left + self.distanceX; 3647 3648 } else { 3649 newOffsetX = self.contentStartPos.left; 3650 } 3651 3652 newOffsetY = self.contentStartPos.top + self.distanceY; 3653 3654 newPos = self.limitMovement( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); 3655 3656 newPos.scaleX = self.contentStartPos.scaleX; 3657 newPos.scaleY = self.contentStartPos.scaleY; 3658 3659 self.contentLastPos = newPos; 3660 3661 if ( self.requestId ) { 3662 cancelAFrame( self.requestId ); 3663 3664 self.requestId = null; 3665 } 3666 3667 self.requestId = requestAFrame(function() { 3668 $.fancybox.setTranslate( self.$content, self.contentLastPos ); 3669 }); 3670 }; 3671 3672 // Make panning sticky to the edges 3673 Guestures.prototype.limitMovement = function( newOffsetX, newOffsetY, newWidth, newHeight ) { 3674 3675 var self = this; 3676 3677 var minTranslateX, minTranslateY, maxTranslateX, maxTranslateY; 3678 3679 var canvasWidth = self.canvasWidth; 3680 var canvasHeight = self.canvasHeight; 3681 3682 var currentOffsetX = self.contentStartPos.left; 3683 var currentOffsetY = self.contentStartPos.top; 3684 3685 var distanceX = self.distanceX; 3686 var distanceY = self.distanceY; 3687 3688 // Slow down proportionally to traveled distance 3689 3690 minTranslateX = Math.max(0, canvasWidth * 0.5 - newWidth * 0.5 ); 3691 minTranslateY = Math.max(0, canvasHeight * 0.5 - newHeight * 0.5 ); 3692 3693 maxTranslateX = Math.min( canvasWidth - newWidth, canvasWidth * 0.5 - newWidth * 0.5 ); 3694 maxTranslateY = Math.min( canvasHeight - newHeight, canvasHeight * 0.5 - newHeight * 0.5 ); 3695 3696 if ( newWidth > canvasWidth ) { 3697 3698 // -> 3699 if ( distanceX > 0 && newOffsetX > minTranslateX ) { 3700 newOffsetX = minTranslateX - 1 + Math.pow( -minTranslateX + currentOffsetX + distanceX, 0.8 ) || 0; 3701 } 3702 3703 // <- 3704 if ( distanceX < 0 && newOffsetX < maxTranslateX ) { 3705 newOffsetX = maxTranslateX + 1 - Math.pow( maxTranslateX - currentOffsetX - distanceX, 0.8 ) || 0; 3706 } 3707 3708 } 3709 3710 if ( newHeight > canvasHeight ) { 3711 3712 // \/ 3713 if ( distanceY > 0 && newOffsetY > minTranslateY ) { 3714 newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8 ) || 0; 3715 } 3716 3717 // /\ 3718 if ( distanceY < 0 && newOffsetY < maxTranslateY ) { 3719 newOffsetY = maxTranslateY + 1 - Math.pow ( maxTranslateY - currentOffsetY - distanceY, 0.8 ) || 0; 3720 } 3721 3722 } 3723 3724 return { 3725 top : newOffsetY, 3726 left : newOffsetX 3727 }; 3728 3729 }; 3730 3731 3732 Guestures.prototype.limitPosition = function( newOffsetX, newOffsetY, newWidth, newHeight ) { 3733 3734 var self = this; 3735 3736 var canvasWidth = self.canvasWidth; 3737 var canvasHeight = self.canvasHeight; 3738 3739 if ( newWidth > canvasWidth ) { 3740 newOffsetX = newOffsetX > 0 ? 0 : newOffsetX; 3741 newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX; 3742 3743 } else { 3744 3745 // Center horizontally 3746 newOffsetX = Math.max( 0, canvasWidth / 2 - newWidth / 2 ); 3747 3748 } 3749 3750 if ( newHeight > canvasHeight ) { 3751 newOffsetY = newOffsetY > 0 ? 0 : newOffsetY; 3752 newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY; 3753 3754 } else { 3755 3756 // Center vertically 3757 newOffsetY = Math.max( 0, canvasHeight / 2 - newHeight / 2 ); 3758 3759 } 3760 3761 return { 3762 top : newOffsetY, 3763 left : newOffsetX 3764 }; 3765 3766 }; 3767 3768 Guestures.prototype.onZoom = function() { 3769 3770 var self = this; 3771 3772 // Calculate current distance between points to get pinch ratio and new width and height 3773 3774 var currentWidth = self.contentStartPos.width; 3775 var currentHeight = self.contentStartPos.height; 3776 3777 var currentOffsetX = self.contentStartPos.left; 3778 var currentOffsetY = self.contentStartPos.top; 3779 3780 var endDistanceBetweenFingers = distance( self.newPoints[0], self.newPoints[1] ); 3781 3782 var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers; 3783 3784 var newWidth = Math.floor( currentWidth * pinchRatio ); 3785 var newHeight = Math.floor( currentHeight * pinchRatio ); 3786 3787 // This is the translation due to pinch-zooming 3788 var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX; 3789 var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY; 3790 3791 //Point between the two touches 3792 3793 var centerPointEndX = ((self.newPoints[0].x + self.newPoints[1].x) / 2) - $(window).scrollLeft(); 3794 var centerPointEndY = ((self.newPoints[0].y + self.newPoints[1].y) / 2) - $(window).scrollTop(); 3795 3796 // And this is the translation due to translation of the centerpoint 3797 // between the two fingers 3798 3799 var translateFromTranslatingX = centerPointEndX - self.centerPointStartX; 3800 var translateFromTranslatingY = centerPointEndY - self.centerPointStartY; 3801 3802 // The new offset is the old/current one plus the total translation 3803 3804 var newOffsetX = currentOffsetX + ( translateFromZoomingX + translateFromTranslatingX ); 3805 var newOffsetY = currentOffsetY + ( translateFromZoomingY + translateFromTranslatingY ); 3806 3807 var newPos = { 3808 top : newOffsetY, 3809 left : newOffsetX, 3810 scaleX : self.contentStartPos.scaleX * pinchRatio, 3811 scaleY : self.contentStartPos.scaleY * pinchRatio 3812 }; 3813 3814 self.canTap = false;
3815 3816 self.newWidth = newWidth; 3817 self.newHeight = newHeight; 3818 3819 self.contentLastPos = newPos; 3820 3821 if ( self.requestId ) { 3822 cancelAFrame( self.requestId ); 3823 3824 self.requestId = null; 3825 } 3826 3827 self.requestId = requestAFrame(function() { 3828 $.fancybox.setTranslate( self.$content, self.contentLastPos ); 3829 }); 3830 3831 }; 3832 3833 Guestures.prototype.ontouchend = function( e ) { 3834 3835 var self = this; 3836 var dMs = Math.max( (new Date().getTime() ) - self.startTime, 1); 3837 3838 var swiping = self.isSwiping; 3839 var panning = self.isPanning; 3840 var zooming = self.isZooming; 3841 3842 self.endPoints = pointers( e ); 3843 3844 self.$container.removeClass( 'fancybox-controls--isGrabbing' ); 3845 3846 $(document).off( '.fb.touch' ); 3847 3848 if ( self.requestId ) { 3849 cancelAFrame( self.requestId ); 3850 3851 self.requestId = null; 3852 } 3853 3854 self.isSwiping = false; 3855 self.isPanning = false; 3856 self.isZooming = false; 3857 3858 if ( self.canTap ) { 3859 return self.onTap( e ); 3860 } 3861 3862 self.speed = 366; 3863 3864 // Speed in px/ms 3865 self.velocityX = self.distanceX / dMs * 0.5; 3866 self.velocityY = self.distanceY / dMs * 0.5; 3867 3868 self.speedX = Math.max( self.speed * 0.5, Math.min( self.speed * 1.5, ( 1 / Math.abs( self.velocityX ) ) * self.speed ) ); 3869 3870 if ( panning ) { 3871 self.endPanning(); 3872 3873 } else if ( zooming ) { 3874 self.endZooming(); 3875 3876 } else { 3877 self.endSwiping( swiping ); 3878 } 3879 3880 return; 3881 }; 3882 3883 Guestures.prototype.endSwiping = function( swiping ) { 3884 3885 var self = this; 3886 var ret = false;
3887 3888 self.instance.isSliding = false; 3889 self.sliderLastPos = null; 3890 3891 // Close if swiped vertically / navigate if horizontally 3892 if ( swiping == 'y' && Math.abs( self.distanceY ) > 50 ) { 3893 3894 // Continue vertical movement 3895 $.fancybox.animate( self.instance.current.$slide, { 3896 top : self.sliderStartPos.top + self.distanceY + ( self.velocityY * 150 ), 3897 opacity : 0 3898 }, 150 ); 3899 3900 ret = self.instance.close( true, 300 ); 3901 3902 } else if ( swiping == 'x' && self.distanceX > 50 && self.instance.group.length > 1 ) { 3903 ret = self.instance.previous( self.speedX ); 3904 3905 } else if ( swiping == 'x' && self.distanceX < -50 && self.instance.group.length > 1 ) { 3906 ret = self.instance.next( self.speedX ); 3907 } 3908 3909 if ( ret === false && ( swiping == 'x' || swiping == 'y' ) ) { 3910 self.instance.jumpTo( self.instance.current.index, 150 ); 3911 } 3912 3913 self.$container.removeClass( 'fancybox-is-sliding' ); 3914 3915 }; 3916 3917 // Limit panning from edges 3918 // ======================== 3919 3920 Guestures.prototype.endPanning = function() { 3921 3922 var self = this; 3923 var newOffsetX, newOffsetY, newPos; 3924 3925 if ( !self.contentLastPos ) { 3926 return; 3927 } 3928 3929 if ( self.instance.current.opts.touch.momentum === false ) { 3930 newOffsetX = self.contentLastPos.left; 3931 newOffsetY = self.contentLastPos.top; 3932 3933 } else { 3934 3935 // Continue movement 3936 newOffsetX = self.contentLastPos.left + ( self.velocityX * self.speed ); 3937 newOffsetY = self.contentLastPos.top + ( self.velocityY * self.speed ); 3938 } 3939 3940 newPos = self.limitPosition( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height ); 3941 3942 newPos.width = self.contentStartPos.width; 3943 newPos.height = self.contentStartPos.height; 3944 3945 $.fancybox.animate( self.$content, newPos, 330 ); 3946 }; 3947 3948 3949 Guestures.prototype.endZooming = function() { 3950 3951 var self = this; 3952 3953 var current = self.instance.current; 3954 3955 var newOffsetX, newOffsetY, newPos, reset; 3956 3957 var newWidth = self.newWidth; 3958 var newHeight = self.newHeight; 3959 3960 if ( !self.contentLastPos ) { 3961 return; 3962 } 3963 3964 newOffsetX = self.contentLastPos.left; 3965 newOffsetY = self.contentLastPos.top; 3966 3967 reset = { 3968 top : newOffsetY, 3969 left : newOffsetX, 3970 width : newWidth, 3971 height : newHeight, 3972 scaleX : 1, 3973 scaleY : 1 3974 }; 3975 3976 // Reset scalex/scaleY values; this helps for perfomance and does not break animation 3977 $.fancybox.setTranslate( self.$content, reset ); 3978 3979 if ( newWidth < self.canvasWidth && newHeight < self.canvasHeight ) { 3980 self.instance.scaleToFit( 150 ); 3981 3982 } else if ( newWidth > current.width || newHeight > current.height ) { 3983 self.instance.scaleToActual( self.centerPointStartX, self.centerPointStartY, 150 ); 3984 3985 } else { 3986 3987 newPos = self.limitPosition( newOffsetX, newOffsetY, newWidth, newHeight ); 3988 3989 // Switch from scale() to width/height or animation will not work correctly 3990 $.fancybox.setTranslate( self.content, $.fancybox.getTranslate( self.$content ) ); 3991 3992 $.fancybox.animate( self.$content, newPos, 150 ); 3993 } 3994 3995 }; 3996 3997 Guestures.prototype.onTap = function(e) { 3998 var self = this; 3999 var $target = $( e.target ); 4000 4001 var instance = self.instance; 4002 var current = instance.current; 4003 4004 var endPoints = ( e && pointers( e ) ) || self.startPoints; 4005 4006 var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0; 4007 var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top : 0; 4008 4009 var where; 4010 4011 var process = function ( prefix ) { 4012 4013 var action = current.opts[ prefix ]; 4014 4015 if ( $.isFunction( action ) ) { 4016 action = action.apply( instance, [ current, e ] ); 4017 } 4018 4019 if ( !action) { 4020 return; 4021 } 4022 4023 switch ( action ) { 4024 4025 case "close" : 4026 4027 instance.close( self.startEvent ); 4028 4029 break; 4030 4031 case "toggleControls" : 4032 4033 instance.toggleControls( true ); 4034 4035 break; 4036 4037 case "next" : 4038 4039 instance.next(); 4040 4041 break; 4042 4043 case "nextOrClose" : 4044 4045 if ( instance.group.length > 1 ) { 4046 instance.next(); 4047 4048 } else { 4049 instance.close( self.startEvent ); 4050 } 4051 4052 break; 4053 4054 case "zoom" : 4055 4056 if ( current.type == 'image' && ( current.isLoaded || current.$ghost ) ) { 4057 4058 if ( instance.canPan() ) { 4059 instance.scaleToFit(); 4060 4061 } else if ( instance.isScaledDown() ) { 4062 instance.scaleToActual( tapX, tapY ); 4063 4064 } else if ( instance.group.length < 2 ) { 4065 instance.close( self.startEvent ); 4066 } 4067 } 4068 4069 break; 4070 } 4071 4072 }; 4073 4074 // Ignore right click 4075 if ( e.originalEvent && e.originalEvent.button == 2 ) { 4076 return; 4077 } 4078 4079 // Skip if current slide is not in the center 4080 if ( instance.isSliding ) { 4081 return; 4082 } 4083 4084 // Skip if clicked on the scrollbar 4085 if ( tapX > $target[0].clientWidth + $target.offset().left ) { 4086 return; 4087 } 4088 4089 // Check where is clicked 4090 if ( $target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container' ) ) { 4091 where = 'Outside'; 4092 4093 } else if ( $target.is( '.fancybox-slide' ) ) { 4094 where = 'Slide'; 4095 4096 } else if ( instance.current.$content && instance.current.$content.has( e.target ).length ) { 4097 where = 'Content'; 4098 4099 } else { 4100 return; 4101 } 4102 4103 // Check if this is a double tap 4104 if ( self.tapped ) { 4105 4106 // Stop previously created single tap 4107 clearTimeout( self.tapped ); 4108 self.tapped = null; 4109 4110 // Skip if distance between taps is too big 4111 if ( Math.abs( tapX - self.tapX ) > 50 || Math.abs( tapY - self.tapY ) > 50 || instance.isSliding ) { 4112 return this; 4113 } 4114 4115 // OK, now we assume that this is a double-tap
4116 process( 'dblclick' + where ); 4117 4118 } else { 4119 4120 // Single tap will be processed if user has not clicked second time within 300ms 4121 // or there is no need to wait for double-tap 4122 self.tapX = tapX; 4123 self.tapY = tapY; 4124 4125 if ( current.opts[ 'dblclick' + where ] && current.opts[ 'dblclick' + where ] !== current.opts[ 'click' + where ] ) { 4126 self.tapped = setTimeout(function() { 4127 self.tapped = null; 4128 4129 process( 'click' + where ); 4130 4131 }, 300); 4132 4133 } else { 4134 process( 'click' + where ); 4135 } 4136 4137 } 4138 4139 return this; 4140 }; 4141 4142 $(document).on('onActivate.fb', function (e, instance) { 4143 if ( instance && !instance.Guestures ) { 4144 instance.Guestures = new Guestures( instance ); 4145 } 4146 }); 4147 4148 $(document).on('beforeClose.fb', function (e, instance) { 4149 if ( instance && instance.Guestures ) { 4150 instance.Guestures.destroy(); 4151 } 4152 }); 4153 4154 4155}(window, document, window.jQuery)); 4156 4157// ========================================================================== 4158// 4159// SlideShow 4160// Enables slideshow functionality 4161// 4162// Example of usage: 4163// $.fancybox.getInstance().SlideShow.start() 4164// 4165// ========================================================================== 4166;(function (document, $) { 4167 'use strict'; 4168 4169 var SlideShow = function( instance ) { 4170 this.instance = instance; 4171 this.init(); 4172 }; 4173 4174 $.extend( SlideShow.prototype, { 4175 timer : null, 4176 isActive : false, 4177 $button : null, 4178 speed : 3000, 4179 4180 init : function() { 4181 var self = this; 4182 4183 self.$button = self.instance.$refs.toolbar.find('[data-fancybox-play]').on('click', function() { 4184 self.toggle(); 4185 }); 4186 4187 if ( self.instance.group.length < 2 || !self.instance.group[ self.instance.currIndex ].opts.slideShow ) { 4188 self.$button.hide(); 4189 } 4190 }, 4191 4192 set : function() { 4193 var self = this; 4194 4195 // Check if reached last element 4196 if ( self.instance && self.instance.current && (self.instance.current.opts.loop || self.instance.currIndex < self.instance.group.length - 1 )) { 4197 self.timer = setTimeout(function() { 4198 self.instance.next(); 4199 4200 }, self.instance.current.opts.slideShow.speed || self.speed); 4201 4202 } else { 4203 self.stop(); 4204 self.instance.idleSecondsCounter = 0; 4205 self.instance.showControls(); 4206 } 4207 4208 }, 4209 4210 clear : function() { 4211 var self = this; 4212 4213 clearTimeout( self.timer ); 4214 4215 self.timer = null; 4216 }, 4217 4218 start : function() { 4219 var self = this; 4220 var current = self.instance.current; 4221 4222 if ( self.instance && current && ( current.opts.loop || current.index < self.instance.group.length - 1 )) { 4223 4224 self.isActive = true; 4225 4226 self.$button 4227 .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_STOP ) 4228 .addClass( 'fancybox-button--pause' ); 4229 4230 if ( current.isComplete ) { 4231 self.set(); 4232 } 4233 } 4234 }, 4235 4236 stop : function() { 4237 var self = this; 4238 var current = self.instance.current; 4239 4240 self.clear(); 4241 4242 self.$button 4243 .attr( 'title', current.opts.i18n[ current.opts.lang ].PLAY_START ) 4244 .removeClass( 'fancybox-button--pause' ); 4245 4246 self.isActive = false; 4247 }, 4248 4249 toggle : function() { 4250 var self = this; 4251 4252 if ( self.isActive ) { 4253 self.stop(); 4254 4255 } else { 4256 self.start(); 4257 } 4258 } 4259 4260 }); 4261 4262 $(document).on({ 4263 'onInit.fb' : function(e, instance) { 4264 if ( instance && !instance.SlideShow ) { 4265 instance.SlideShow = new SlideShow( instance ); 4266 } 4267 }, 4268 4269 'beforeShow.fb' : function(e, instance, current, firstRun) { 4270 var SlideShow = instance && instance.SlideShow; 4271 4272 if ( firstRun ) { 4273 4274 if ( SlideShow && current.opts.slideShow.autoStart ) { 4275 SlideShow.start(); 4276 } 4277 4278 } else if ( SlideShow && SlideShow.isActive ) { 4279 SlideShow.clear(); 4280 } 4281 }, 4282 4283 'afterShow.fb' : function(e, instance, current) { 4284 var SlideShow = instance && instance.SlideShow; 4285 4286 if ( SlideShow && SlideShow.isActive ) { 4287 SlideShow.set(); 4288 } 4289 }, 4290 4291 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4292 var SlideShow = instance && instance.SlideShow; 4293 4294 // "P" or Spacebar 4295 if ( SlideShow && current.opts.slideShow && ( keycode === 80 || keycode === 32 ) && !$(document.activeElement).is( 'button,a,input' ) ) { 4296 keypress.preventDefault(); 4297 4298 SlideShow.toggle(); 4299 } 4300 }, 4301 4302 'beforeClose.fb onDeactivate.fb' : function(e, instance) { 4303 var SlideShow = instance && instance.SlideShow; 4304 4305 if ( SlideShow ) { 4306 SlideShow.stop(); 4307 } 4308 } 4309 }); 4310 4311 // Page Visibility API to pause slideshow when window is not active 4312 $(document).on("visibilitychange", function() { 4313 var instance = $.fancybox.getInstance(); 4314 var SlideShow = instance && instance.SlideShow; 4315 4316 if ( SlideShow && SlideShow.isActive ) { 4317 if ( document.hidden ) { 4318 SlideShow.clear(); 4319 4320 } else { 4321 SlideShow.set(); 4322 } 4323 } 4324 }); 4325 4326}(document, window.jQuery)); 4327 4328// ========================================================================== 4329// 4330// FullScreen 4331// Adds fullscreen functionality 4332// 4333// ========================================================================== 4334;(function (document, $) { 4335 'use strict'; 4336 4337 // Collection of methods supported by user browser 4338 var fn = (function () { 4339 4340 var fnMap = [ 4341 [ 4342 'requestFullscreen', 4343 'exitFullscreen', 4344 'fullscreenElement', 4345 'fullscreenEnabled', 4346 'fullscreenchange', 4347 'fullscreenerror' 4348 ], 4349 // new WebKit 4350 [ 4351 'webkitRequestFullscreen', 4352 'webkitExitFullscreen', 4353 'webkitFullscreenElement', 4354 'webkitFullscreenEnabled', 4355 'webkitfullscreenchange', 4356 'webkitfullscreenerror' 4357 4358 ], 4359 // old WebKit (Safari 5.1) 4360 [ 4361 'webkitRequestFullScreen', 4362 'webkitCancelFullScreen', 4363 'webkitCurrentFullScreenElement', 4364 'webkitCancelFullScreen', 4365 'webkitfullscreenchange', 4366 'webkitfullscreenerror' 4367 4368 ], 4369 [ 4370 'mozRequestFullScreen', 4371 'mozCancelFullScreen', 4372 'mozFullScreenElement', 4373 'mozFullScreenEnabled', 4374 'mozfullscreenchange', 4375 'mozfullscreenerror' 4376 ], 4377 [ 4378 'msRequestFullscreen', 4379 'msExitFullscreen', 4380 'msFullscreenElement', 4381 'msFullscreenEnabled', 4382 'MSFullscreenChange', 4383 'MSFullscreenError' 4384 ] 4385 ]; 4386 4387 var val; 4388 var ret = {}; 4389 var i, j; 4390 4391 for ( i = 0; i < fnMap.length; i++ ) { 4392 val = fnMap[ i ]; 4393 4394 if ( val && val[ 1 ] in document ) { 4395 for ( j = 0; j < val.length; j++ ) { 4396 ret[ fnMap[ 0 ][ j ] ] = val[ j ]; 4397 } 4398 4399 return ret; 4400 } 4401 } 4402 4403 return false;
4404 })(); 4405 4406 // If browser does not have Full Screen API, then simply unset default button template and stop 4407 if ( !fn ) { 4408 $.fancybox.defaults.btnTpl.fullScreen = false; 4409 4410 return; 4411 } 4412 4413 var FullScreen = { 4414 request : function ( elem ) { 4415 4416 elem = elem || document.documentElement; 4417 4418 elem[ fn.requestFullscreen ]( elem.ALLOW_KEYBOARD_INPUT ); 4419 4420 }, 4421 exit : function () { 4422 4423 document[ fn.exitFullscreen ](); 4424 4425 }, 4426 toggle : function ( elem ) { 4427 4428 elem = elem || document.documentElement; 4429 4430 if ( this.isFullscreen() ) { 4431 this.exit(); 4432 4433 } else { 4434 this.request( elem ); 4435 } 4436 4437 }, 4438 isFullscreen : function() { 4439 4440 return Boolean( document[ fn.fullscreenElement ] ); 4441 4442 }, 4443 enabled : function() { 4444 4445 return Boolean( document[ fn.fullscreenEnabled ] ); 4446 4447 } 4448 }; 4449 4450 $(document).on({ 4451 'onInit.fb' : function(e, instance) { 4452 var $container; 4453 4454 var $button = instance.$refs.toolbar.find('[data-fancybox-fullscreen]'); 4455 4456 if ( instance && !instance.FullScreen && instance.group[ instance.currIndex ].opts.fullScreen ) { 4457 $container = instance.$refs.container; 4458 4459 $container.on('click.fb-fullscreen', '[data-fancybox-fullscreen]', function(e) { 4460 4461 e.stopPropagation(); 4462 e.preventDefault(); 4463 4464 FullScreen.toggle( $container[ 0 ] ); 4465 4466 }); 4467 4468 if ( instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true ) { 4469 FullScreen.request( $container[ 0 ] ); 4470 } 4471 4472 // Expose API 4473 instance.FullScreen = FullScreen; 4474 4475 } else { 4476 $button.hide(); 4477 } 4478 4479 }, 4480 4481 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4482 4483 // "P" or Spacebar 4484 if ( instance && instance.FullScreen && keycode === 70 ) { 4485 keypress.preventDefault(); 4486 4487 instance.FullScreen.toggle( instance.$refs.container[ 0 ] ); 4488 } 4489 4490 }, 4491 4492 'beforeClose.fb' : function( instance ) { 4493 if ( instance && instance.FullScreen ) { 4494 FullScreen.exit(); 4495 } 4496 } 4497 }); 4498 4499 $(document).on(fn.fullscreenchange, function() { 4500 var instance = $.fancybox.getInstance(); 4501 4502 // If image is zooming, then force to stop and reposition properly 4503 if ( instance.current && instance.current.type === 'image' && instance.isAnimating ) { 4504 instance.current.$content.css( 'transition', 'none' ); 4505 4506 instance.isAnimating = false; 4507 4508 instance.update( true, true, 0 ); 4509 } 4510 4511 }); 4512 4513}(document, window.jQuery)); 4514 4515// ========================================================================== 4516// 4517// Thumbs 4518// Displays thumbnails in a grid 4519// 4520// ========================================================================== 4521;(function (document, $) { 4522 'use strict'; 4523 4524 var FancyThumbs = function( instance ) { 4525 this.instance = instance; 4526 this.init(); 4527 }; 4528 4529 $.extend( FancyThumbs.prototype, { 4530 4531 $button : null, 4532 $grid : null, 4533 $list : null, 4534 isVisible : false, 4535 4536 init : function() { 4537 var self = this; 4538 4539 var first = self.instance.group[0], 4540 second = self.instance.group[1]; 4541 4542 self.$button = self.instance.$refs.toolbar.find( '[data-fancybox-thumbs]' ); 4543 4544 if ( self.instance.group.length > 1 && self.instance.group[ self.instance.currIndex ].opts.thumbs && ( 4545 ( first.type == 'image' || first.opts.thumb || first.opts.$thumb ) && 4546 ( second.type == 'image' || second.opts.thumb || second.opts.$thumb ) 4547 )) { 4548 4549 self.$button.on('click', function() { 4550 self.toggle(); 4551 }); 4552 4553 self.isActive = true; 4554 4555 } else { 4556 self.$button.hide(); 4557 4558 self.isActive = false; 4559 } 4560 4561 }, 4562 4563 create : function() { 4564 var instance = this.instance, 4565 list, 4566 src; 4567 4568 this.$grid = $('<div class="fancybox-thumbs"></div>').appendTo( instance.$refs.container ); 4569 4570 list = '<ul>'; 4571 4572 $.each(instance.group, function( i, item ) { 4573 4574 src = item.opts.thumb || ( item.opts.$thumb ? item.opts.$thumb.attr('src') : null ); 4575 4576 if ( !src && item.type === 'image' ) { 4577 src = item.src; 4578 } 4579 4580 if ( src && src.length ) { 4581 list += '<li data-index="' + i + '" tabindex="0" class="fancybox-thumbs-loading"><img data-src="' + src + '" /></li>'; 4582 } 4583 4584 }); 4585 4586 list += '</ul>'; 4587 4588 this.$list = $( list ).appendTo( this.$grid ).on('click', 'li', function() { 4589 instance.jumpTo( $(this).data('index') ); 4590 }); 4591 4592 this.$list.find('img').hide().one('load', function() { 4593 4594 var $parent = $(this).parent().removeClass('fancybox-thumbs-loading'), 4595 thumbWidth = $parent.outerWidth(),
4596 thumbHeight = $parent.outerHeight(), 4597 width, 4598 height, 4599 widthRatio, 4600 heightRatio; 4601 4602 width = this.naturalWidth || this.width; 4603 height = this.naturalHeight || this.height; 4604 4605 //Calculate thumbnail width/height and center it 4606 4607 widthRatio = width / thumbWidth; 4608 heightRatio = height / thumbHeight; 4609 4610 if (widthRatio >= 1 && heightRatio >= 1) { 4611 if (widthRatio > heightRatio) { 4612 width = width / heightRatio; 4613 height = thumbHeight; 4614 4615 } else { 4616 width = thumbWidth; 4617 height = height / widthRatio; 4618 } 4619 } 4620 4621 $(this).css({ 4622 width : Math.floor(width), 4623 height : Math.floor(height), 4624 'margin-top' : Math.min( 0, Math.floor(thumbHeight * 0.3 - height * 0.3 ) ), 4625 'margin-left' : Math.min( 0, Math.floor(thumbWidth * 0.5 - width * 0.5 ) ) 4626 }).show(); 4627 4628 }) 4629 .each(function() { 4630 this.src = $( this ).data( 'src' ); 4631 }); 4632 4633 }, 4634 4635 focus : function() { 4636 4637 if ( this.instance.current ) { 4638 this.$list 4639 .children() 4640 .removeClass('fancybox-thumbs-active') 4641 .filter('[data-index="' + this.instance.current.index + '"]') 4642 .addClass('fancybox-thumbs-active') 4643 .focus(); 4644 } 4645 4646 }, 4647 4648 close : function() { 4649 this.$grid.hide(); 4650 }, 4651 4652 update : function() { 4653 4654 this.instance.$refs.container.toggleClass( 'fancybox-show-thumbs', this.isVisible ); 4655 4656 if ( this.isVisible ) { 4657 4658 if ( !this.$grid ) { 4659 this.create(); 4660 } 4661 4662 this.instance.trigger( 'onThumbsShow' ); 4663 4664 this.focus(); 4665 4666 } else if ( this.$grid ) { 4667 this.instance.trigger( 'onThumbsHide' ); 4668 } 4669 4670 // Update content position 4671 this.instance.update(); 4672 4673 }, 4674 4675 hide : function() { 4676 this.isVisible = false; 4677 this.update(); 4678 }, 4679 4680 show : function() { 4681 this.isVisible = true; 4682 this.update(); 4683 }, 4684 4685 toggle : function() { 4686 this.isVisible = !this.isVisible; 4687 this.update(); 4688 } 4689 4690 }); 4691 4692 $(document).on({ 4693 4694 'onInit.fb' : function(e, instance) { 4695 if ( instance && !instance.Thumbs ) { 4696 instance.Thumbs = new FancyThumbs( instance ); 4697 } 4698 }, 4699 4700 'beforeShow.fb' : function(e, instance, item, firstRun) { 4701 var Thumbs = instance && instance.Thumbs; 4702 4703 if ( !Thumbs || !Thumbs.isActive ) { 4704 return; 4705 } 4706 4707 if ( item.modal ) { 4708 Thumbs.$button.hide(); 4709 4710 Thumbs.hide(); 4711 4712 return; 4713 } 4714 4715 if ( firstRun && instance.opts.thumbs.autoStart === true ) { 4716 Thumbs.show(); 4717 } 4718 4719 if ( Thumbs.isVisible ) { 4720 Thumbs.focus(); 4721 } 4722 }, 4723 4724 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) { 4725 var Thumbs = instance && instance.Thumbs; 4726 4727 // "G" 4728 if ( Thumbs && Thumbs.isActive && keycode === 71 ) { 4729 keypress.preventDefault(); 4730 4731 Thumbs.toggle(); 4732 } 4733 }, 4734 4735 'beforeClose.fb' : function( e, instance ) { 4736 var Thumbs = instance && instance.Thumbs; 4737 4738 if ( Thumbs && Thumbs.isVisible && instance.opts.thumbs.hideOnClose !== false ) { 4739 Thumbs.close(); 4740 } 4741 } 4742 4743 }); 4744 4745}(document, window.jQuery)); 4746 4747// ========================================================================== 4748// 4749// Hash 4750// Enables linking to each modal 4751// 4752// ========================================================================== 4753;(function (document, window, $) { 4754 'use strict'; 4755 4756 // Simple $.escapeSelector polyfill (for jQuery prior v3) 4757 if ( !$.escapeSelector ) { 4758 $.escapeSelector = function( sel ) { 4759 var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; 4760 var fcssescape = function( ch, asCodePoint ) { 4761 if ( asCodePoint ) { 4762 // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER 4763 if ( ch === "\0" ) { 4764 return "\uFFFD"; 4765 } 4766 4767 // Control characters and (dependent upon position) numbers get escaped as code points 4768 return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; 4769 } 4770 4771 // Other potentially-special ASCII characters get backslash-escaped 4772 return "\\" + ch; 4773 }; 4774 4775 return ( sel + "" ).replace( rcssescape, fcssescape ); 4776 }; 4777 } 4778 4779 // Variable containing last hash value set by fancyBox 4780 // It will be used to determine if fancyBox needs to close after hash change is detected 4781 var currentHash = null; 4782 4783 // Throtlling the history change 4784 var timerID = null; 4785 4786 // Get info about gallery name and current index from url 4787 function parseUrl() { 4788 var hash = window.location.hash.substr( 1 ); 4789 var rez = hash.split( '-' ); 4790 var index = rez.length > 1 && /^\+?\d+$/.test( rez[ rez.length - 1 ] ) ? parseInt( rez.pop( -1 ), 10 ) || 1 : 1; 4791 var gallery = rez.join( '-' ); 4792 4793 // Index is starting from 1 4794 if ( index < 1 ) { 4795 index = 1; 4796 } 4797 4798 return { 4799 hash : hash, 4800 index : index, 4801 gallery : gallery 4802 }; 4803 } 4804 4805 // Trigger click evnt on links to open new fancyBox instance 4806 function triggerFromUrl( url ) { 4807 var $el; 4808 4809 if ( url.gallery !== '' ) { 4810 4811 // If we can find element matching 'data-fancybox' atribute, then trigger click event for that .. 4812 $el = $( "[data-fancybox='" + $.escapeSelector( url.gallery ) + "']" ).eq( url.index - 1 ); 4813 4814 if ( $el.length ) { 4815 $el.trigger( 'click' ); 4816 4817 } else { 4818 4819 // .. if not, try finding element by ID 4820 $( "#" + $.escapeSelector( url.gallery ) + "" ).trigger( 'click' ); 4821 4822 } 4823 4824 } 4825 } 4826 4827 // Get gallery name from current instance 4828 function getGallery( instance ) { 4829 var opts; 4830 4831 if ( !instance ) { 4832 return false;
vendor: 23,449 bytes, lines 4833-5782
4833 } 4834 4835 opts = instance.current ? instance.current.opts : instance.opts; 4836 4837 return opts.$orig ? opts.$orig.data( 'fancybox' ) : ( opts.hash || '' ); 4838 } 4839 4840 // Star when DOM becomes ready 4841 $(function() { 4842 4843 // Small delay is used to allow other scripts to process "dom ready" event 4844 setTimeout(function() {/* 4845 4846 // Check if this module is not disabled 4847 if ( $.fancybox.defaults.hash === false ) { 4848 return; 4849 } 4850 4851 // Update hash when opening/closing fancyBox 4852 $(document).on({ 4853 'onInit.fb' : function( e, instance ) { 4854 var url, gallery; 4855 4856 if ( instance.group[ instance.currIndex ].opts.hash === false ) { 4857 return; 4858 } 4859 4860 url = parseUrl(); 4861 gallery = getGallery( instance ); 4862 4863 // Make sure gallery start index matches index from hash 4864 if ( gallery && url.gallery && gallery == url.gallery ) { 4865 instance.currIndex = url.index - 1; 4866 } 4867 4868 }, 4869 4870 'beforeShow.fb' : function( e, instance, current, firstRun ) { 4871 var gallery; 4872 4873 if ( current.opts.hash === false ) { 4874 return; 4875 } 4876 4877 gallery = getGallery( instance ); 4878 4879 // Update window hash 4880 if ( gallery && gallery !== '' ) { 4881 4882 if ( window.location.hash.indexOf( gallery ) < 0 ) { 4883 instance.opts.origHash = window.location.hash; 4884 } 4885 4886 currentHash = gallery + ( instance.group.length > 1 ? '-' + ( current.index + 1 ) : '' ); 4887 4888 if ( 'replaceState' in window.history ) { 4889 if ( timerID ) { 4890 clearTimeout( timerID ); 4891 } 4892 4893 timerID = setTimeout(function() { 4894 window.history[ firstRun ? 'pushState' : 'replaceState' ]( {} , document.title, window.location.pathname + window.location.search + '#' + currentHash ); 4895 4896 timerID = null; 4897 4898 }, 300); 4899 4900 } else { 4901 window.location.hash = currentHash; 4902 } 4903 4904 } 4905 4906 }, 4907 4908 'beforeClose.fb' : function( e, instance, current ) { 4909 var gallery, origHash; 4910 4911 if ( timerID ) { 4912 clearTimeout( timerID ); 4913 } 4914 4915 if ( current.opts.hash === false ) { 4916 return; 4917 } 4918 4919 gallery = getGallery( instance ); 4920 origHash = instance && instance.opts.origHash ? instance.opts.origHash : ''; 4921 4922 // Remove hash from location bar 4923 if ( gallery && gallery !== '' ) { 4924 4925 if ( 'replaceState' in history ) { 4926 window.history.replaceState( {} , document.title, window.location.pathname + window.location.search + origHash ); 4927 4928 } else { 4929 window.location.hash = origHash; 4930 4931 // Keep original scroll position 4932 $( window ).scrollTop( instance.scrollTop ).scrollLeft( instance.scrollLeft ); 4933 } 4934 } 4935 4936 currentHash = null; 4937 } 4938 }); 4939 4940 // Check if need to close after url has changed 4941 $(window).on('hashchange.fb', function() { 4942 var url = parseUrl(); 4943 4944 if ( $.fancybox.getInstance() ) { 4945 if ( currentHash && currentHash !== url.gallery + '-' + url.index && !( url.index === 1 && currentHash == url.gallery ) ) { 4946 currentHash = null; 4947 4948 $.fancybox.close(); 4949 } 4950 4951 } else if ( url.gallery !== '' ) { 4952 triggerFromUrl( url ); 4953 } 4954 }); 4955 4956 // If navigating away from current page 4957 $(window).one('unload.fb popstate.fb', function() { 4958 $.fancybox.getInstance( 'close', true, 0 ); 4959 }); 4960 4961 4962 4963 // Check current hash and trigger click event on matching element to start fancyBox, if needed 4964 triggerFromUrl( parseUrl() ); 4965 4966 Comment by Bhaveh */ 4967 4968 $(window).on('mousemove.fancybox-stage', function() { 4969 $.fancybox.getInstance( 'showControls', true, 0 ); 4970 self.idleSecondsCounter = 4; 4971 }); }, 50); 4972 4973 }); 4974 4975 4976}(document, window, window.jQuery)); 4977/*! 4978 * Masonry PACKAGED v4.0.0 4979 * Cascading grid layout library 4980 * http://masonry.desandro.com 4981 * MIT License 4982 * by David DeSandro 4983 */ 4984 4985/** 4986 * Bridget makes jQuery widgets 4987 * v2.0.0 4988 * MIT license 4989 */ 4990 4991/* jshint browser: true, strict: true, undef: true, unused: true */ 4992 4993( function( window, factory ) { 4994 'use strict'; 4995 /* globals define: false, module: false, require: false */ 4996 4997 if ( typeof define == 'function' && define.amd ) { 4998 // AMD 4999 define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) { 5000 factory( window, jQuery ); 5001 }); 5002 } else if ( typeof module == 'object' && module.exports ) { 5003 // CommonJS 5004 module.exports = factory( 5005 window, 5006 require('jquery') 5007 ); 5008 } else { 5009 // browser global 5010 window.jQueryBridget = factory( 5011 window, 5012 window.jQuery 5013 ); 5014 } 5015 5016}( window, function factory( window, jQuery ) { 5017'use strict'; 5018 5019// ----- utils ----- // 5020 5021var arraySlice = Array.prototype.slice; 5022 5023// helper function for logging errors 5024// $.error breaks jQuery chaining 5025var console = window.console; 5026var logError = typeof console == 'undefined' ? function() {} : 5027 function( message ) { 5028 console.error( message ); 5029 }; 5030 5031// ----- jQueryBridget ----- // 5032 5033function jQueryBridget( namespace, PluginClass, $ ) { 5034 $ = $ || jQuery || window.jQuery; 5035 if ( !$ ) { 5036 return; 5037 } 5038 5039 // add option method -> $().plugin('option', {...}) 5040 if ( !PluginClass.prototype.option ) { 5041 // option setter 5042 PluginClass.prototype.option = function( opts ) { 5043 // bail out if not an object 5044 if ( !$.isPlainObject( opts ) ){ 5045 return; 5046 } 5047 this.options = $.extend( true, this.options, opts ); 5048 }; 5049 } 5050 5051 // make jQuery plugin 5052 $.fn[ namespace ] = function( arg0 /*, arg1 */ ) { 5053 if ( typeof arg0 == 'string' ) { 5054 // method call $().plugin( 'methodName', { options } ) 5055 // shift arguments by 1 5056 var args = arraySlice.call( arguments, 1 ); 5057 return methodCall( this, arg0, args ); 5058 } 5059 // just $().plugin({ options }) 5060 plainCall( this, arg0 ); 5061 return this; 5062 }; 5063 5064 // $().plugin('methodName') 5065 function methodCall( $elems, methodName, args ) { 5066 var returnValue; 5067 var pluginMethodStr = '$().' + namespace + '("' + methodName + '")'; 5068 5069 $elems.each( function( i, elem ) { 5070 // get instance 5071 var instance = $.data( elem, namespace ); 5072 if ( !instance ) { 5073 logError( namespace + ' not initialized. Cannot call methods, i.e. ' + 5074 pluginMethodStr ); 5075 return; 5076 } 5077 5078 var method = instance[ methodName ]; 5079 if ( !method || methodName.charAt(0) == '_' ) { 5080 logError( pluginMethodStr + ' is not a valid method' ); 5081 return; 5082 } 5083 5084 // apply method, get return value 5085 var value = method.apply( instance, args ); 5086 // set return value if value is returned, use only first value 5087 returnValue = returnValue === undefined ? value : returnValue; 5088 }); 5089 5090 return returnValue !== undefined ? returnValue : $elems; 5091 } 5092 5093 function plainCall( $elems, options ) { 5094 $elems.each( function( i, elem ) { 5095 var instance = $.data( elem, namespace ); 5096 if ( instance ) { 5097 // set options & init 5098 instance.option( options ); 5099 instance._init(); 5100 } else { 5101 // initialize new instance 5102 instance = new PluginClass( elem, options ); 5103 $.data( elem, namespace, instance ); 5104 } 5105 }); 5106 } 5107 5108 updateJQuery( $ ); 5109 5110} 5111 5112// ----- updateJQuery ----- // 5113 5114// set $.bridget for v1 backwards compatibility 5115function updateJQuery( $ ) { 5116 if ( !$ || ( $ && $.bridget ) ) { 5117 return; 5118 } 5119 $.bridget = jQueryBridget; 5120} 5121 5122updateJQuery( jQuery || window.jQuery ); 5123 5124// ----- ----- // 5125 5126return jQueryBridget; 5127 5128})); 5129 5130/** 5131 * EvEmitter v1.0.1 5132 * Lil' event emitter 5133 * MIT License 5134 */ 5135 5136/* jshint unused: true, undef: true, strict: true */ 5137 5138( function( global, factory ) { 5139 // universal module definition 5140 /* jshint strict: false */ /* globals define, module */ 5141 if ( typeof define == 'function' && define.amd ) { 5142 // AMD - RequireJS 5143 define( 'ev-emitter/ev-emitter',factory ); 5144 } else if ( typeof module == 'object' && module.exports ) { 5145 // CommonJS - Browserify, Webpack 5146 module.exports = factory(); 5147 } else { 5148 // Browser globals 5149 global.EvEmitter = factory(); 5150 } 5151 5152}( this, function() { 5153 5154 5155 5156function EvEmitter() {} 5157 5158var proto = EvEmitter.prototype; 5159 5160proto.on = function( eventName, listener ) { 5161 if ( !eventName || !listener ) { 5162 return; 5163 } 5164 // set events hash 5165 var events = this._events = this._events || {}; 5166 // set listeners array 5167 var listeners = events[ eventName ] = events[ eventName ] || []; 5168 // only add once 5169 if ( listeners.indexOf( listener ) == -1 ) { 5170 listeners.push( listener ); 5171 } 5172 5173 return this; 5174}; 5175 5176proto.once = function( eventName, listener ) { 5177 if ( !eventName || !listener ) { 5178 return; 5179 } 5180 // add event 5181 this.on( eventName, listener ); 5182 // set once flag 5183 // set onceEvents hash 5184 var onceEvents = this._onceEvents = this._onceEvents || {}; 5185 // set onceListeners array 5186 var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || []; 5187 // set flag 5188 onceListeners[ listener ] = true; 5189 5190 return this; 5191}; 5192 5193proto.off = function( eventName, listener ) { 5194 var listeners = this._events && this._events[ eventName ]; 5195 if ( !listeners || !listeners.length ) { 5196 return; 5197 } 5198 var index = listeners.indexOf( listener ); 5199 if ( index != -1 ) { 5200 listeners.splice( index, 1 ); 5201 } 5202 5203 return this; 5204}; 5205 5206proto.emitEvent = function( eventName, args ) { 5207 var listeners = this._events && this._events[ eventName ]; 5208 if ( !listeners || !listeners.length ) { 5209 return; 5210 } 5211 var i = 0; 5212 var listener = listeners[i]; 5213 args = args || []; 5214 // once stuff 5215 var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; 5216 5217 while ( listener ) { 5218 var isOnce = onceListeners && onceListeners[ listener ]; 5219 if ( isOnce ) { 5220 // remove listener 5221 // remove before trigger to prevent recursion 5222 this.off( eventName, listener ); 5223 // unset once flag 5224 delete onceListeners[ listener ]; 5225 } 5226 // trigger listener 5227 listener.apply( this, args ); 5228 // get next listener 5229 i += isOnce ? 0 : 1; 5230 listener = listeners[i]; 5231 } 5232 5233 return this; 5234}; 5235 5236return EvEmitter; 5237 5238})); 5239 5240/*! 5241 * getSize v2.0.2 5242 * measure size of elements 5243 * MIT license 5244 */ 5245 5246/*jshint browser: true, strict: true, undef: true, unused: true */ 5247/*global define: false, module: false, console: false */ 5248 5249( function( window, factory ) { 5250 'use strict'; 5251 5252 if ( typeof define == 'function' && define.amd ) { 5253 // AMD 5254 define( 'get-size/get-size',[],function() { 5255 return factory(); 5256 }); 5257 } else if ( typeof module == 'object' && module.exports ) { 5258 // CommonJS 5259 module.exports = factory(); 5260 } else { 5261 // browser global 5262 window.getSize = factory(); 5263 } 5264 5265})( window, function factory() { 5266'use strict'; 5267 5268// -------------------------- helpers -------------------------- // 5269 5270// get a number from a string, not a percentage 5271function getStyleSize( value ) { 5272 var num = parseFloat( value ); 5273 // not a percent like '100%', and a number 5274 var isValid = value.indexOf('%') == -1 && !isNaN( num ); 5275 return isValid && num; 5276} 5277 5278function noop() {} 5279 5280var logError = typeof console == 'undefined' ? noop : 5281 function( message ) { 5282 console.error( message ); 5283 }; 5284 5285// -------------------------- measurements -------------------------- // 5286 5287var measurements = [ 5288 'paddingLeft', 5289 'paddingRight', 5290 'paddingTop', 5291 'paddingBottom', 5292 'marginLeft', 5293 'marginRight', 5294 'marginTop', 5295 'marginBottom', 5296 'borderLeftWidth', 5297 'borderRightWidth', 5298 'borderTopWidth', 5299 'borderBottomWidth' 5300]; 5301 5302var measurementsLength = measurements.length; 5303 5304function getZeroSize() { 5305 var size = { 5306 width: 0, 5307 height: 0, 5308 innerWidth: 0, 5309 innerHeight: 0, 5310 outerWidth: 0, 5311 outerHeight: 0 5312 }; 5313 for ( var i=0; i < measurementsLength; i++ ) { 5314 var measurement = measurements[i]; 5315 size[ measurement ] = 0; 5316 } 5317 return size; 5318} 5319 5320// -------------------------- getStyle -------------------------- // 5321 5322/** 5323 * getStyle, get style of element, check for Firefox bug 5324 * https://bugzilla.mozilla.org/show_bug.cgi?id=548397 5325 */ 5326function getStyle( elem ) { 5327 var style = getComputedStyle( elem ); 5328 if ( !style ) { 5329 logError( 'Style returned ' + style + 5330 '. Are you running this code in a hidden iframe on Firefox? ' + 5331 'See http://bit.ly/getsizebug1' ); 5332 } 5333 return style; 5334} 5335 5336// -------------------------- setup -------------------------- // 5337 5338var isSetup = false; 5339 5340var isBoxSizeOuter; 5341 5342/** 5343 * setup 5344 * check isBoxSizerOuter 5345 * do on first getSize() rather than on page load for Firefox bug 5346 */ 5347function setup() { 5348 // setup once 5349 if ( isSetup ) { 5350 return; 5351 } 5352 isSetup = true; 5353 5354 // -------------------------- box sizing -------------------------- // 5355 5356 /** 5357 * WebKit measures the outer-width on style.width on border-box elems 5358 * IE & Firefox<29 measures the inner-width 5359 */ 5360 var div = document.createElement('div'); 5361 div.style.width = '200px'; 5362 div.style.padding = '1px 2px 3px 4px'; 5363 div.style.borderStyle = 'solid'; 5364 div.style.borderWidth = '1px 2px 3px 4px'; 5365 div.style.boxSizing = 'border-box'; 5366 5367 var body = document.body || document.documentElement; 5368 body.appendChild( div ); 5369 var style = getStyle( div ); 5370 5371 getSize.isBoxSizeOuter = isBoxSizeOuter = getStyleSize( style.width ) == 200; 5372 body.removeChild( div ); 5373 5374} 5375 5376// -------------------------- getSize -------------------------- // 5377 5378function getSize( elem ) { 5379 setup(); 5380 5381 // use querySeletor if elem is string 5382 if ( typeof elem == 'string' ) { 5383 elem = document.querySelector( elem ); 5384 } 5385 5386 // do not proceed on non-objects 5387 if ( !elem || typeof elem != 'object' || !elem.nodeType ) { 5388 return; 5389 } 5390 5391 var style = getStyle( elem ); 5392 5393 // if hidden, everything is 0 5394 if ( style.display == 'none' ) { 5395 return getZeroSize(); 5396 } 5397 5398 var size = {}; 5399 size.width = elem.offsetWidth; 5400 size.height = elem.offsetHeight; 5401 5402 var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box'; 5403 5404 // get all measurements 5405 for ( var i=0; i < measurementsLength; i++ ) { 5406 var measurement = measurements[i]; 5407 var value = style[ measurement ]; 5408 var num = parseFloat( value ); 5409 // any 'auto', 'medium' value will be 0 5410 size[ measurement ] = !isNaN( num ) ? num : 0; 5411 } 5412 5413 var paddingWidth = size.paddingLeft + size.paddingRight; 5414 var paddingHeight = size.paddingTop + size.paddingBottom; 5415 var marginWidth = size.marginLeft + size.marginRight; 5416 var marginHeight = size.marginTop + size.marginBottom; 5417 var borderWidth = size.borderLeftWidth + size.borderRightWidth; 5418 var borderHeight = size.borderTopWidth + size.borderBottomWidth; 5419 5420 var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; 5421 5422 // overwrite width and height if we can get it from style 5423 var styleWidth = getStyleSize( style.width ); 5424 if ( styleWidth !== false ) { 5425 size.width = styleWidth + 5426 // add padding and border unless it's already including it 5427 ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth ); 5428 } 5429 5430 var styleHeight = getStyleSize( style.height ); 5431 if ( styleHeight !== false ) { 5432 size.height = styleHeight + 5433 // add padding and border unless it's already including it 5434 ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight ); 5435 } 5436 5437 size.innerWidth = size.width - ( paddingWidth + borderWidth ); 5438 size.innerHeight = size.height - ( paddingHeight + borderHeight ); 5439 5440 size.outerWidth = size.width + marginWidth; 5441 size.outerHeight = size.height + marginHeight; 5442 5443 return size; 5444} 5445 5446return getSize; 5447 5448}); 5449 5450/** 5451 * matchesSelector v2.0.1 5452 * matchesSelector( element, '.selector' ) 5453 * MIT license 5454 */ 5455 5456/*jshint browser: true, strict: true, undef: true, unused: true */ 5457 5458( function( window, factory ) { 5459 /*global define: false, module: false */ 5460 'use strict'; 5461 // universal module definition 5462 if ( typeof define == 'function' && define.amd ) { 5463 // AMD 5464 define( 'matches-selector/matches-selector',factory ); 5465 } else if ( typeof module == 'object' && module.exports ) { 5466 // CommonJS 5467 module.exports = factory(); 5468 } else { 5469 // browser global 5470 window.matchesSelector = factory(); 5471 } 5472 5473}( window, function factory() { 5474 'use strict'; 5475 5476 var matchesMethod = ( function() { 5477 var ElemProto = Element.prototype; 5478 // check for the standard method name first 5479 if ( ElemProto.matches ) { 5480 return 'matches'; 5481 } 5482 // check un-prefixed 5483 if ( ElemProto.matchesSelector ) { 5484 return 'matchesSelector'; 5485 } 5486 // check vendor prefixes 5487 var prefixes = [ 'webkit', 'moz', 'ms', 'o' ]; 5488 5489 for ( var i=0; i < prefixes.length; i++ ) { 5490 var prefix = prefixes[i]; 5491 var method = prefix + 'MatchesSelector'; 5492 if ( ElemProto[ method ] ) { 5493 return method; 5494 } 5495 } 5496 })(); 5497 5498 return function matchesSelector( elem, selector ) { 5499 return elem[ matchesMethod ]( selector ); 5500 }; 5501 5502})); 5503 5504/** 5505 * Fizzy UI utils v2.0.0 5506 * MIT license 5507 */ 5508 5509/*jshint browser: true, undef: true, unused: true, strict: true */ 5510 5511( function( window, factory ) { 5512 /*global define: false, module: false, require: false */ 5513 'use strict'; 5514 // universal module definition 5515 5516 if ( typeof define == 'function' && define.amd ) { 5517 // AMD 5518 define( 'fizzy-ui-utils/utils',[ 5519 'matches-selector/matches-selector' 5520 ], function( matchesSelector ) { 5521 return factory( window, matchesSelector ); 5522 }); 5523 } else if ( typeof module == 'object' && module.exports ) { 5524 // CommonJS 5525 module.exports = factory( 5526 window, 5527 require('desandro-matches-selector') 5528 ); 5529 } else { 5530 // browser global 5531 window.fizzyUIUtils = factory( 5532 window, 5533 window.matchesSelector 5534 ); 5535 } 5536 5537}( window, function factory( window, matchesSelector ) { 5538 5539 5540 5541var utils = {}; 5542 5543// ----- extend ----- // 5544 5545// extends objects 5546utils.extend = function( a, b ) { 5547 for ( var prop in b ) { 5548 a[ prop ] = b[ prop ]; 5549 } 5550 return a; 5551}; 5552 5553// ----- modulo ----- // 5554 5555utils.modulo = function( num, div ) { 5556 return ( ( num % div ) + div ) % div; 5557}; 5558 5559// ----- makeArray ----- // 5560 5561// turn element or nodeList into an array 5562utils.makeArray = function( obj ) { 5563 var ary = []; 5564 if ( Array.isArray( obj ) ) { 5565 // use object if already an array 5566 ary = obj; 5567 } else if ( obj && typeof obj.length == 'number' ) { 5568 // convert nodeList to array 5569 for ( var i=0; i < obj.length; i++ ) { 5570 ary.push( obj[i] ); 5571 } 5572 } else { 5573 // array of single index 5574 ary.push( obj ); 5575 } 5576 return ary; 5577}; 5578 5579// ----- removeFrom ----- // 5580 5581utils.removeFrom = function( ary, obj ) { 5582 var index = ary.indexOf( obj ); 5583 if ( index != -1 ) { 5584 ary.splice( index, 1 ); 5585 } 5586}; 5587 5588// ----- getParent ----- // 5589 5590utils.getParent = function( elem, selector ) { 5591 while ( elem != document.body ) { 5592 elem = elem.parentNode; 5593 if ( matchesSelector( elem, selector ) ) { 5594 return elem; 5595 } 5596 } 5597}; 5598 5599// ----- getQueryElement ----- // 5600 5601// use element as selector string 5602utils.getQueryElement = function( elem ) { 5603 if ( typeof elem == 'string' ) { 5604 return document.querySelector( elem ); 5605 } 5606 return elem; 5607}; 5608 5609// ----- handleEvent ----- // 5610 5611// enable .ontype to trigger from .addEventListener( elem, 'type' ) 5612utils.handleEvent = function( event ) { 5613 var method = 'on' + event.type; 5614 if ( this[ method ] ) { 5615 this[ method ]( event ); 5616 } 5617}; 5618 5619// ----- filterFindElements ----- // 5620 5621utils.filterFindElements = function( elems, selector ) { 5622 // make array of elems 5623 elems = utils.makeArray( elems ); 5624 var ffElems = []; 5625 5626 elems.forEach( function( elem ) { 5627 // check that elem is an actual element 5628 if ( !( elem instanceof HTMLElement ) ) { 5629 return; 5630 } 5631 // add elem if no selector 5632 if ( !selector ) { 5633 ffElems.push( elem ); 5634 return; 5635 } 5636 // filter & find items if we have a selector 5637 // filter 5638 if ( matchesSelector( elem, selector ) ) { 5639 ffElems.push( elem ); 5640 } 5641 // find children 5642 var childElems = elem.querySelectorAll( selector ); 5643 // concat childElems to filterFound array 5644 for ( var i=0; i < childElems.length; i++ ) { 5645 ffElems.push( childElems[i] ); 5646 } 5647 }); 5648 5649 return ffElems; 5650}; 5651 5652// ----- debounceMethod ----- // 5653 5654utils.debounceMethod = function( _class, methodName, threshold ) { 5655 // original method 5656 var method = _class.prototype[ methodName ]; 5657 var timeoutName = methodName + 'Timeout'; 5658 5659 _class.prototype[ methodName ] = function() { 5660 var timeout = this[ timeoutName ]; 5661 if ( timeout ) { 5662 clearTimeout( timeout ); 5663 } 5664 var args = arguments; 5665 5666 var _this = this; 5667 this[ timeoutName ] = setTimeout( function() { 5668 method.apply( _this, args ); 5669 delete _this[ timeoutName ]; 5670 }, threshold || 100 ); 5671 }; 5672}; 5673 5674// ----- docReady ----- // 5675 5676utils.docReady = function( callback ) { 5677 if ( document.readyState == 'complete' ) { 5678 callback(); 5679 } else { 5680 document.addEventListener( 'DOMContentLoaded', callback ); 5681 } 5682}; 5683 5684// ----- htmlInit ----- // 5685 5686// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/ 5687utils.toDashed = function( str ) { 5688 return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) { 5689 return $1 + '-' + $2; 5690 }).toLowerCase(); 5691}; 5692 5693var console = window.console; 5694/** 5695 * allow user to initialize classes via [data-namespace] or .js-namespace class 5696 * htmlInit( Widget, 'widgetName' ) 5697 * options are parsed from data-namespace-options 5698 */ 5699utils.htmlInit = function( WidgetClass, namespace ) { 5700 utils.docReady( function() { 5701 var dashedNamespace = utils.toDashed( namespace ); 5702 var dataAttr = 'data-' + dashedNamespace; 5703 var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' ); 5704 var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace ); 5705 var elems = utils.makeArray( dataAttrElems ) 5706 .concat( utils.makeArray( jsDashElems ) ); 5707 var dataOptionsAttr = dataAttr + '-options'; 5708 var jQuery = window.jQuery; 5709 5710 elems.forEach( function( elem ) { 5711 var attr = elem.getAttribute( dataAttr ) || 5712 elem.getAttribute( dataOptionsAttr ); 5713 var options; 5714 try { 5715 options = attr && JSON.parse( attr ); 5716 } catch ( error ) { 5717 // log error, do not initialize 5718 if ( console ) { 5719 console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className + 5720 ': ' + error ); 5721 } 5722 return; 5723 } 5724 // initialize 5725 var instance = new WidgetClass( elem, options ); 5726 // make available via $().data('layoutname') 5727 if ( jQuery ) { 5728 jQuery.data( elem, namespace, instance ); 5729 } 5730 }); 5731 5732 }); 5733}; 5734 5735// ----- ----- // 5736 5737return utils; 5738 5739})); 5740 5741/** 5742 * Outlayer Item 5743 */ 5744 5745( function( window, factory ) { 5746 // universal module definition 5747 /* jshint strict: false */ /* globals define, module, require */ 5748 if ( typeof define == 'function' && define.amd ) { 5749 // AMD - RequireJS 5750 define( 'outlayer/item',[ 5751 'ev-emitter/ev-emitter', 5752 'get-size/get-size' 5753 ], 5754 function( EvEmitter, getSize ) { 5755 return factory( window, EvEmitter, getSize ); 5756 } 5757 ); 5758 } else if ( typeof module == 'object' && module.exports ) { 5759 // CommonJS - Browserify, Webpack 5760 module.exports = factory( 5761 window, 5762 require('ev-emitter'), 5763 require('get-size') 5764 ); 5765 } else { 5766 // browser global 5767 window.Outlayer = {}; 5768 window.Outlayer.Item = factory( 5769 window, 5770 window.EvEmitter, 5771 window.getSize 5772 ); 5773 } 5774 5775}( window, function factory( window, EvEmitter, getSize ) { 5776'use strict'; 5777 5778// ----- helpers ----- // 5779 5780function isEmptyObj( obj ) { 5781 for ( var prop in obj ) { 5782 return false;
vendor: 4,257 bytes, lines 5783-5938
5783 } 5784 prop = null; 5785 return true; 5786} 5787 5788// -------------------------- CSS3 support -------------------------- // 5789 5790 5791var docElemStyle = document.documentElement.style; 5792 5793var transitionProperty = typeof docElemStyle.transition == 'string' ? 5794 'transition' : 'WebkitTransition'; 5795var transformProperty = typeof docElemStyle.transform == 'string' ? 5796 'transform' : 'WebkitTransform'; 5797 5798var transitionEndEvent = { 5799 WebkitTransition: 'webkitTransitionEnd', 5800 transition: 'transitionend' 5801}[ transitionProperty ]; 5802 5803// cache all vendor properties 5804var vendorProperties = [ 5805 transformProperty, 5806 transitionProperty, 5807 transitionProperty + 'Duration', 5808 transitionProperty + 'Property' 5809]; 5810 5811// -------------------------- Item -------------------------- // 5812 5813function Item( element, layout ) { 5814 if ( !element ) { 5815 return; 5816 } 5817 5818 this.element = element; 5819 // parent layout class, i.e. Masonry, Isotope, or Packery 5820 this.layout = layout; 5821 this.position = { 5822 x: 0, 5823 y: 0 5824 }; 5825 5826 this._create(); 5827} 5828 5829// inherit EvEmitter 5830var proto = Item.prototype = Object.create( EvEmitter.prototype ); 5831proto.constructor = Item; 5832 5833proto._create = function() { 5834 // transition objects 5835 this._transn = { 5836 ingProperties: {}, 5837 clean: {}, 5838 onEnd: {} 5839 }; 5840 5841 this.css({ 5842 position: 'absolute' 5843 }); 5844}; 5845 5846// trigger specified handler for event type 5847proto.handleEvent = function( event ) { 5848 var method = 'on' + event.type; 5849 if ( this[ method ] ) { 5850 this[ method ]( event ); 5851 } 5852}; 5853 5854proto.getSize = function() { 5855 this.size = getSize( this.element ); 5856}; 5857 5858/** 5859 * apply CSS styles to element 5860 * @param {Object} style 5861 */ 5862proto.css = function( style ) { 5863 var elemStyle = this.element.style; 5864 5865 for ( var prop in style ) { 5866 // use vendor property if available 5867 var supportedProp = vendorProperties[ prop ] || prop; 5868 elemStyle[ supportedProp ] = style[ prop ]; 5869 } 5870}; 5871 5872 // measure position, and sets it 5873proto.getPosition = function() { 5874 var style = getComputedStyle( this.element ); 5875 var isOriginLeft = this.layout._getOption('originLeft'); 5876 var isOriginTop = this.layout._getOption('originTop'); 5877 var xValue = style[ isOriginLeft ? 'left' : 'right' ]; 5878 var yValue = style[ isOriginTop ? 'top' : 'bottom' ]; 5879 // convert percent to pixels 5880 var layoutSize = this.layout.size; 5881 var x = xValue.indexOf('%') != -1 ? 5882 ( parseFloat( xValue ) / 100 ) * layoutSize.width : parseInt( xValue, 10 ); 5883 var y = yValue.indexOf('%') != -1 ? 5884 ( parseFloat( yValue ) / 100 ) * layoutSize.height : parseInt( yValue, 10 ); 5885 5886 // clean up 'auto' or other non-integer values 5887 x = isNaN( x ) ? 0 : x; 5888 y = isNaN( y ) ? 0 : y; 5889 // remove padding from measurement 5890 x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight; 5891 y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom; 5892 5893 this.position.x = x; 5894 this.position.y = y; 5895}; 5896 5897// set settled position, apply padding 5898proto.layoutPosition = function() { 5899 var layoutSize = this.layout.size; 5900 var style = {}; 5901 var isOriginLeft = this.layout._getOption('originLeft'); 5902 var isOriginTop = this.layout._getOption('originTop'); 5903 5904 // x 5905 var xPadding = isOriginLeft ? 'paddingLeft' : 'paddingRight'; 5906 var xProperty = isOriginLeft ? 'left' : 'right'; 5907 var xResetProperty = isOriginLeft ? 'right' : 'left'; 5908 5909 var x = this.position.x + layoutSize[ xPadding ]; 5910 // set in percentage or pixels 5911 style[ xProperty ] = this.getXValue( x ); 5912 // reset other property 5913 style[ xResetProperty ] = ''; 5914 5915 // y 5916 var yPadding = isOriginTop ? 'paddingTop' : 'paddingBottom'; 5917 var yProperty = isOriginTop ? 'top' : 'bottom'; 5918 var yResetProperty = isOriginTop ? 'bottom' : 'top'; 5919 5920 var y = this.position.y + layoutSize[ yPadding ]; 5921 // set in percentage or pixels 5922 style[ yProperty ] = this.getYValue( y ); 5923 // reset other property 5924 style[ yResetProperty ] = ''; 5925 5926 this.css( style ); 5927 this.emitEvent( 'layout', [ this ] ); 5928}; 5929 5930proto.getXValue = function( x ) { 5931 var isHorizontal = this.layout._getOption('horizontal'); 5932 return this.layout.options.percentPosition && !isHorizontal ? 5933 ( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px'; 5934}; 5935 5936proto.getYValue = function( y ) { 5937 var isHorizontal = this.layout._getOption('horizontal'); 5938 return this.layout.options.percentPosition && isHorizontal ?
5939 ( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px'; 5940}; 5941 5942proto._transitionTo = function( x, y ) { 5943 this.getPosition(); 5944 // get current x & y from top/left 5945 var curX = this.position.x; 5946 var curY = this.position.y; 5947 5948 var compareX = parseInt( x, 10 ); 5949 var compareY = parseInt( y, 10 ); 5950 var didNotMove = compareX === this.position.x && compareY === this.position.y; 5951 5952 // save end position 5953 this.setPosition( x, y ); 5954 5955 // if did not move and not transitioning, just go to layout 5956 if ( didNotMove && !this.isTransitioning ) { 5957 this.layoutPosition(); 5958 return; 5959 } 5960 5961 var transX = x - curX; 5962 var transY = y - curY; 5963 var transitionStyle = {}; 5964 transitionStyle.transform = this.getTranslate( transX, transY ); 5965 5966 this.transition({ 5967 to: transitionStyle, 5968 onTransitionEnd: { 5969 transform: this.layoutPosition 5970 }, 5971 isCleaning: true 5972 }); 5973}; 5974 5975proto.getTranslate = function( x, y ) { 5976 // flip cooridinates if origin on right or bottom 5977 var isOriginLeft = this.layout._getOption('originLeft'); 5978 var isOriginTop = this.layout._getOption('originTop'); 5979 x = isOriginLeft ? x : -x; 5980 y = isOriginTop ? y : -y; 5981 return 'translate3d(' + x + 'px, ' + y + 'px, 0)'; 5982}; 5983 5984// non transition + transform support 5985proto.goTo = function( x, y ) { 5986 this.setPosition( x, y ); 5987 this.layoutPosition(); 5988}; 5989 5990proto.moveTo = proto._transitionTo; 5991 5992proto.setPosition = function( x, y ) { 5993 this.position.x = parseInt( x, 10 ); 5994 this.position.y = parseInt( y, 10 ); 5995}; 5996 5997// ----- transition ----- // 5998 5999/** 6000 * @param {Object} style - CSS 6001 * @param {Function} onTransitionEnd 6002 */ 6003 6004// non transition, just trigger callback 6005proto._nonTransition = function( args ) { 6006 this.css( args.to ); 6007 if ( args.isCleaning ) { 6008 this._removeStyles( args.to ); 6009 } 6010 for ( var prop in args.onTransitionEnd ) { 6011 args.onTransitionEnd[ prop ].call( this ); 6012 } 6013}; 6014 6015/** 6016 * proper transition 6017 * @param {Object} args - arguments 6018 * @param {Object} to - style to transition to 6019 * @param {Object} from - style to start transition from 6020 * @param {Boolean} isCleaning - removes transition styles after transition 6021 * @param {Function} onTransitionEnd - callback 6022 */ 6023proto._transition = function( args ) { 6024 // redirect to nonTransition if no transition duration 6025 if ( !parseFloat( this.layout.options.transitionDuration ) ) { 6026 this._nonTransition( args ); 6027 return; 6028 } 6029 6030 var _transition = this._transn; 6031 // keep track of onTransitionEnd callback by css property 6032 for ( var prop in args.onTransitionEnd ) { 6033 _transition.onEnd[ prop ] = args.onTransitionEnd[ prop ]; 6034 } 6035 // keep track of properties that are transitioning 6036 for ( prop in args.to ) { 6037 _transition.ingProperties[ prop ] = true; 6038 // keep track of properties to clean up when transition is done 6039 if ( args.isCleaning ) { 6040 _transition.clean[ prop ] = true; 6041 } 6042 } 6043 6044 // set from styles 6045 if ( args.from ) { 6046 this.css( args.from ); 6047 // force redraw. http://blog.alexmaccaw.com/css-transitions 6048 var h = this.element.offsetHeight; 6049 // hack for JSHint to hush about unused var 6050 h = null; 6051 } 6052 // enable transition 6053 this.enableTransition( args.to ); 6054 // set styles that are transitioning 6055 this.css( args.to ); 6056 6057 this.isTransitioning = true; 6058 6059}; 6060 6061// dash before all cap letters, including first for 6062// WebkitTransform => -webkit-transform 6063function toDashedAll( str ) { 6064 return str.replace( /([A-Z])/g, function( $1 ) { 6065 return '-' + $1.toLowerCase(); 6066 }); 6067} 6068 6069var transitionProps = 'opacity,' + 6070 toDashedAll( vendorProperties.transform || 'transform' ); 6071 6072proto.enableTransition = function(/* style */) { 6073 // HACK changing transitionProperty during a transition 6074 // will cause transition to jump 6075 if ( this.isTransitioning ) { 6076 return; 6077 } 6078 6079 // make `transition: foo, bar, baz` from style object 6080 // HACK un-comment this when enableTransition can work 6081 // while a transition is happening 6082 // var transitionValues = []; 6083 // for ( var prop in style ) { 6084 // // dash-ify camelCased properties like WebkitTransition 6085 // prop = vendorProperties[ prop ] || prop; 6086 // transitionValues.push( toDashedAll( prop ) ); 6087 // } 6088 // enable transition styles 6089 this.css({ 6090 transitionProperty: transitionProps, 6091 transitionDuration: this.layout.options.transitionDuration 6092 }); 6093 // listen for transition end event 6094 this.element.addEventListener( transitionEndEvent, this, false ); 6095}; 6096 6097proto.transition = Item.prototype[ transitionProperty ? '_transition' : '_nonTransition' ]; 6098 6099// ----- events ----- // 6100 6101proto.onwebkitTransitionEnd = function( event ) { 6102 this.ontransitionend( event ); 6103}; 6104 6105proto.onotransitionend = function( event ) { 6106 this.ontransitionend( event ); 6107}; 6108 6109// properties that I munge to make my life easier 6110var dashedVendorProperties = { 6111 '-webkit-transform': 'transform' 6112}; 6113 6114proto.ontransitionend = function( event ) { 6115 // disregard bubbled events from children 6116 if ( event.target !== this.element ) { 6117 return; 6118 } 6119 var _transition = this._transn; 6120 // get property name of transitioned property, convert to prefix-free 6121 var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName; 6122 6123 // remove property that has completed transitioning 6124 delete _transition.ingProperties[ propertyName ]; 6125 // check if any properties are still transitioning 6126 if ( isEmptyObj( _transition.ingProperties ) ) { 6127 // all properties have completed transitioning 6128 this.disableTransition(); 6129 } 6130 // clean style 6131 if ( propertyName in _transition.clean ) { 6132 // clean up style 6133 this.element.style[ event.propertyName ] = ''; 6134 delete _transition.clean[ propertyName ]; 6135 } 6136 // trigger onTransitionEnd callback 6137 if ( propertyName in _transition.onEnd ) { 6138 var onTransitionEnd = _transition.onEnd[ propertyName ]; 6139 onTransitionEnd.call( this ); 6140 delete _transition.onEnd[ propertyName ]; 6141 } 6142 6143 this.emitEvent( 'transitionEnd', [ this ] ); 6144}; 6145 6146proto.disableTransition = function() { 6147 this.removeTransitionStyles(); 6148 this.element.removeEventListener( transitionEndEvent, this, false ); 6149 this.isTransitioning = false;
vendor: 8,827 bytes, lines 6150-6532
6150}; 6151 6152/** 6153 * removes style property from element 6154 * @param {Object} style 6155**/ 6156proto._removeStyles = function( style ) { 6157 // clean up transition styles 6158 var cleanStyle = {}; 6159 for ( var prop in style ) { 6160 cleanStyle[ prop ] = ''; 6161 } 6162 this.css( cleanStyle ); 6163}; 6164 6165var cleanTransitionStyle = { 6166 transitionProperty: '', 6167 transitionDuration: '' 6168}; 6169 6170proto.removeTransitionStyles = function() { 6171 // remove transition 6172 this.css( cleanTransitionStyle ); 6173}; 6174 6175// ----- show/hide/remove ----- // 6176 6177// remove element from DOM 6178proto.removeElem = function() { 6179 this.element.parentNode.removeChild( this.element ); 6180 // remove display: none 6181 this.css({ display: '' }); 6182 this.emitEvent( 'remove', [ this ] ); 6183}; 6184 6185proto.remove = function() { 6186 // just remove element if no transition support or no transition 6187 if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) { 6188 this.removeElem(); 6189 return; 6190 } 6191 6192 // start transition 6193 this.once( 'transitionEnd', function() { 6194 this.removeElem(); 6195 }); 6196 this.hide(); 6197}; 6198 6199proto.reveal = function() { 6200 delete this.isHidden; 6201 // remove display: none 6202 this.css({ display: '' }); 6203 6204 var options = this.layout.options; 6205 6206 var onTransitionEnd = {}; 6207 var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle'); 6208 onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd; 6209 6210 this.transition({ 6211 from: options.hiddenStyle, 6212 to: options.visibleStyle, 6213 isCleaning: true, 6214 onTransitionEnd: onTransitionEnd 6215 }); 6216}; 6217 6218proto.onRevealTransitionEnd = function() { 6219 // check if still visible 6220 // during transition, item may have been hidden 6221 if ( !this.isHidden ) { 6222 this.emitEvent('reveal'); 6223 } 6224}; 6225 6226/** 6227 * get style property use for hide/reveal transition end 6228 * @param {String} styleProperty - hiddenStyle/visibleStyle 6229 * @returns {String} 6230 */ 6231proto.getHideRevealTransitionEndProperty = function( styleProperty ) { 6232 var optionStyle = this.layout.options[ styleProperty ]; 6233 // use opacity 6234 if ( optionStyle.opacity ) { 6235 return 'opacity'; 6236 } 6237 // get first property 6238 for ( var prop in optionStyle ) { 6239 return prop; 6240 } 6241}; 6242 6243proto.hide = function() { 6244 // set flag 6245 this.isHidden = true; 6246 // remove display: none 6247 this.css({ display: '' }); 6248 6249 var options = this.layout.options; 6250 6251 var onTransitionEnd = {}; 6252 var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle'); 6253 onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd; 6254 6255 this.transition({ 6256 from: options.visibleStyle, 6257 to: options.hiddenStyle, 6258 // keep hidden stuff hidden 6259 isCleaning: true, 6260 onTransitionEnd: onTransitionEnd 6261 }); 6262}; 6263 6264proto.onHideTransitionEnd = function() { 6265 // check if still hidden 6266 // during transition, item may have been un-hidden 6267 if ( this.isHidden ) { 6268 this.css({ display: 'none' }); 6269 this.emitEvent('hide'); 6270 } 6271}; 6272 6273proto.destroy = function() { 6274 this.css({ 6275 position: '', 6276 left: '', 6277 right: '', 6278 top: '', 6279 bottom: '', 6280 transition: '', 6281 transform: '' 6282 }); 6283}; 6284 6285return Item; 6286 6287})); 6288 6289/*! 6290 * Outlayer v2.0.0 6291 * the brains and guts of a layout library 6292 * MIT license 6293 */ 6294 6295( function( window, factory ) { 6296 'use strict'; 6297 // universal module definition 6298 /* jshint strict: false */ /* globals define, module, require */ 6299 if ( typeof define == 'function' && define.amd ) { 6300 // AMD - RequireJS 6301 define( 'outlayer/outlayer',[ 6302 'ev-emitter/ev-emitter', 6303 'get-size/get-size', 6304 'fizzy-ui-utils/utils', 6305 './item' 6306 ], 6307 function( EvEmitter, getSize, utils, Item ) { 6308 return factory( window, EvEmitter, getSize, utils, Item); 6309 } 6310 ); 6311 } else if ( typeof module == 'object' && module.exports ) { 6312 // CommonJS - Browserify, Webpack 6313 module.exports = factory( 6314 window, 6315 require('ev-emitter'), 6316 require('get-size'), 6317 require('fizzy-ui-utils'), 6318 require('./item') 6319 ); 6320 } else { 6321 // browser global 6322 window.Outlayer = factory( 6323 window, 6324 window.EvEmitter, 6325 window.getSize, 6326 window.fizzyUIUtils, 6327 window.Outlayer.Item 6328 ); 6329 } 6330 6331}( window, function factory( window, EvEmitter, getSize, utils, Item ) { 6332'use strict'; 6333 6334// ----- vars ----- // 6335 6336var console = window.console; 6337var jQuery = window.jQuery; 6338var noop = function() {}; 6339 6340// -------------------------- Outlayer -------------------------- // 6341 6342// globally unique identifiers 6343var GUID = 0; 6344// internal store of all Outlayer intances 6345var instances = {}; 6346 6347 6348/** 6349 * @param {Element, String} element 6350 * @param {Object} options 6351 * @constructor 6352 */ 6353function Outlayer( element, options ) { 6354 var queryElement = utils.getQueryElement( element ); 6355 if ( !queryElement ) { 6356 if ( console ) { 6357 console.error( 'Bad element for ' + this.constructor.namespace + 6358 ': ' + ( queryElement || element ) ); 6359 } 6360 return; 6361 } 6362 this.element = queryElement; 6363 // add jQuery 6364 if ( jQuery ) { 6365 this.$element = jQuery( this.element ); 6366 } 6367 6368 // options 6369 this.options = utils.extend( {}, this.constructor.defaults ); 6370 this.option( options ); 6371 6372 // add id for Outlayer.getFromElement 6373 var id = ++GUID; 6374 this.element.outlayerGUID = id; // expando 6375 instances[ id ] = this; // associate via id 6376 6377 // kick it off 6378 this._create(); 6379 6380 var isInitLayout = this._getOption('initLayout'); 6381 if ( isInitLayout ) { 6382 this.layout(); 6383 } 6384} 6385 6386// settings are for internal use only 6387Outlayer.namespace = 'outlayer'; 6388Outlayer.Item = Item; 6389 6390// default options 6391Outlayer.defaults = { 6392 containerStyle: { 6393 position: 'relative' 6394 }, 6395 initLayout: true, 6396 originLeft: true, 6397 originTop: true, 6398 resize: true, 6399 resizeContainer: true, 6400 // item options 6401 transitionDuration: '0.4s', 6402 hiddenStyle: { 6403 opacity: 0, 6404 transform: 'scale(0.001)' 6405 }, 6406 visibleStyle: { 6407 opacity: 1, 6408 transform: 'scale(1)' 6409 } 6410}; 6411 6412var proto = Outlayer.prototype; 6413// inherit EvEmitter 6414utils.extend( proto, EvEmitter.prototype ); 6415 6416/** 6417 * set options 6418 * @param {Object} opts 6419 */ 6420proto.option = function( opts ) { 6421 utils.extend( this.options, opts ); 6422}; 6423 6424/** 6425 * get backwards compatible option value, check old name 6426 */ 6427proto._getOption = function( option ) { 6428 var oldOption = this.constructor.compatOptions[ option ]; 6429 return oldOption && this.options[ oldOption ] !== undefined ? 6430 this.options[ oldOption ] : this.options[ option ]; 6431}; 6432 6433Outlayer.compatOptions = { 6434 // currentName: oldName 6435 initLayout: 'isInitLayout', 6436 horizontal: 'isHorizontal', 6437 layoutInstant: 'isLayoutInstant', 6438 originLeft: 'isOriginLeft', 6439 originTop: 'isOriginTop', 6440 resize: 'isResizeBound', 6441 resizeContainer: 'isResizingContainer' 6442}; 6443 6444proto._create = function() { 6445 // get items from children 6446 this.reloadItems(); 6447 // elements that affect layout, but are not laid out 6448 this.stamps = []; 6449 this.stamp( this.options.stamp ); 6450 // set container style 6451 utils.extend( this.element.style, this.options.containerStyle ); 6452 6453 // bind resize method 6454 var canBindResize = this._getOption('resize'); 6455 if ( canBindResize ) { 6456 this.bindResize(); 6457 } 6458}; 6459 6460// goes through all children again and gets bricks in proper order 6461proto.reloadItems = function() { 6462 // collection of item elements 6463 this.items = this._itemize( this.element.children ); 6464}; 6465 6466 6467/** 6468 * turn elements into Outlayer.Items to be used in layout 6469 * @param {Array or NodeList or HTMLElement} elems 6470 * @returns {Array} items - collection of new Outlayer Items 6471 */ 6472proto._itemize = function( elems ) { 6473 6474 var itemElems = this._filterFindItemElements( elems ); 6475 var Item = this.constructor.Item; 6476 6477 // create new Outlayer Items for collection 6478 var items = []; 6479 for ( var i=0; i < itemElems.length; i++ ) { 6480 var elem = itemElems[i]; 6481 var item = new Item( elem, this ); 6482 items.push( item ); 6483 } 6484 6485 return items; 6486}; 6487 6488/** 6489 * get item elements to be used in layout 6490 * @param {Array or NodeList or HTMLElement} elems 6491 * @returns {Array} items - item elements 6492 */ 6493proto._filterFindItemElements = function( elems ) { 6494 return utils.filterFindElements( elems, this.options.itemSelector ); 6495}; 6496 6497/** 6498 * getter method for getting item elements 6499 * @returns {Array} elems - collection of item elements 6500 */ 6501proto.getItemElements = function() { 6502 return this.items.map( function( item ) { 6503 return item.element; 6504 }); 6505}; 6506 6507// ----- init & layout ----- // 6508 6509/** 6510 * lays out all items 6511 */ 6512proto.layout = function() { 6513 this._resetLayout(); 6514 this._manageStamps(); 6515 6516 // don't animate first layout 6517 var layoutInstant = this._getOption('layoutInstant'); 6518 var isInstant = layoutInstant !== undefined ? 6519 layoutInstant : !this._isLayoutInited; 6520 this.layoutItems( this.items, isInstant ); 6521 6522 // flag for initalized 6523 this._isLayoutInited = true; 6524}; 6525 6526// _init is alias for layout 6527proto._init = proto.layout; 6528 6529/** 6530 * logic before any new layout 6531 */ 6532proto._resetLayout = function() {
vendor: 2,902 bytes, lines 6533-6655
6533 this.getSize(); 6534}; 6535 6536 6537proto.getSize = function() { 6538 this.size = getSize( this.element ); 6539}; 6540 6541/** 6542 * get measurement from option, for columnWidth, rowHeight, gutter 6543 * if option is String -> get element from selector string, & get size of element 6544 * if option is Element -> get size of element 6545 * else use option as a number 6546 * 6547 * @param {String} measurement 6548 * @param {String} size - width or height 6549 * @private 6550 */ 6551proto._getMeasurement = function( measurement, size ) { 6552 var option = this.options[ measurement ]; 6553 var elem; 6554 if ( !option ) { 6555 // default to 0 6556 this[ measurement ] = 0; 6557 } else { 6558 // use option as an element 6559 if ( typeof option == 'string' ) { 6560 elem = this.element.querySelector( option ); 6561 } else if ( option instanceof HTMLElement ) { 6562 elem = option; 6563 } 6564 // use size of element, if element 6565 this[ measurement ] = elem ? getSize( elem )[ size ] : option; 6566 } 6567}; 6568 6569/** 6570 * layout a collection of item elements 6571 * @api public 6572 */ 6573proto.layoutItems = function( items, isInstant ) { 6574 items = this._getItemsForLayout( items ); 6575 6576 this._layoutItems( items, isInstant ); 6577 6578 this._postLayout(); 6579}; 6580 6581/** 6582 * get the items to be laid out 6583 * you may want to skip over some items 6584 * @param {Array} items 6585 * @returns {Array} items 6586 */ 6587proto._getItemsForLayout = function( items ) { 6588 return items.filter( function( item ) { 6589 return !item.isIgnored; 6590 }); 6591}; 6592 6593/** 6594 * layout items 6595 * @param {Array} items 6596 * @param {Boolean} isInstant 6597 */ 6598proto._layoutItems = function( items, isInstant ) { 6599 this._emitCompleteOnItems( 'layout', items ); 6600 6601 if ( !items || !items.length ) { 6602 // no items, emit event with empty array 6603 return; 6604 } 6605 6606 var queue = []; 6607 6608 items.forEach( function( item ) { 6609 // get x/y object from method 6610 var position = this._getItemLayoutPosition( item ); 6611 // enqueue 6612 position.item = item; 6613 position.isInstant = isInstant || item.isLayoutInstant; 6614 queue.push( position ); 6615 }, this ); 6616 6617 this._processLayoutQueue( queue ); 6618}; 6619 6620/** 6621 * get item layout position 6622 * @param {Outlayer.Item} item 6623 * @returns {Object} x and y position 6624 */ 6625proto._getItemLayoutPosition = function( /* item */ ) { 6626 return { 6627 x: 0, 6628 y: 0 6629 }; 6630}; 6631 6632/** 6633 * iterate over array and position each item 6634 * Reason being - separating this logic prevents 'layout invalidation' 6635 * thx @paul_irish 6636 * @param {Array} queue 6637 */ 6638proto._processLayoutQueue = function( queue ) { 6639 queue.forEach( function( obj ) { 6640 this._positionItem( obj.item, obj.x, obj.y, obj.isInstant ); 6641 }, this ); 6642}; 6643 6644/** 6645 * Sets position of item in DOM 6646 * @param {Outlayer.Item} item 6647 * @param {Number} x - horizontal position 6648 * @param {Number} y - vertical position 6649 * @param {Boolean} isInstant - disables transitions 6650 */ 6651proto._positionItem = function( item, x, y, isInstant ) { 6652 if ( isInstant ) { 6653 // if not transition, just set CSS 6654 item.goTo( x, y ); 6655 } else {
vendor: 6,326 bytes, lines 6655-6918
6655 6656 item.moveTo( x, y ); 6657 } 6658}; 6659 6660/** 6661 * Any logic you want to do after each layout, 6662 * i.e. size the container 6663 */ 6664proto._postLayout = function() { 6665 this.resizeContainer(); 6666}; 6667 6668proto.resizeContainer = function() { 6669 var isResizingContainer = this._getOption('resizeContainer'); 6670 if ( !isResizingContainer ) { 6671 return; 6672 } 6673 var size = this._getContainerSize(); 6674 if ( size ) { 6675 this._setContainerMeasure( size.width, true ); 6676 this._setContainerMeasure( size.height, false ); 6677 } 6678}; 6679 6680/** 6681 * Sets width or height of container if returned 6682 * @returns {Object} size 6683 * @param {Number} width 6684 * @param {Number} height 6685 */ 6686proto._getContainerSize = noop; 6687 6688/** 6689 * @param {Number} measure - size of width or height 6690 * @param {Boolean} isWidth 6691 */ 6692proto._setContainerMeasure = function( measure, isWidth ) { 6693 if ( measure === undefined ) { 6694 return; 6695 } 6696 6697 var elemSize = this.size; 6698 // add padding and border width if border box 6699 if ( elemSize.isBorderBox ) { 6700 measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight + 6701 elemSize.borderLeftWidth + elemSize.borderRightWidth : 6702 elemSize.paddingBottom + elemSize.paddingTop + 6703 elemSize.borderTopWidth + elemSize.borderBottomWidth; 6704 } 6705 6706 measure = Math.max( measure, 0 ); 6707 this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px'; 6708}; 6709 6710/** 6711 * emit eventComplete on a collection of items events 6712 * @param {String} eventName 6713 * @param {Array} items - Outlayer.Items 6714 */ 6715proto._emitCompleteOnItems = function( eventName, items ) { 6716 var _this = this; 6717 function onComplete() { 6718 _this.dispatchEvent( eventName + 'Complete', null, [ items ] ); 6719 } 6720 6721 var count = items.length; 6722 if ( !items || !count ) { 6723 onComplete(); 6724 return; 6725 } 6726 6727 var doneCount = 0; 6728 function tick() { 6729 doneCount++; 6730 if ( doneCount == count ) { 6731 onComplete(); 6732 } 6733 } 6734 6735 // bind callback 6736 items.forEach( function( item ) { 6737 item.once( eventName, tick ); 6738 }); 6739}; 6740 6741/** 6742 * emits events via EvEmitter and jQuery events 6743 * @param {String} type - name of event 6744 * @param {Event} event - original event 6745 * @param {Array} args - extra arguments 6746 */ 6747proto.dispatchEvent = function( type, event, args ) { 6748 // add original event to arguments 6749 var emitArgs = event ? [ event ].concat( args ) : args; 6750 this.emitEvent( type, emitArgs ); 6751 6752 if ( jQuery ) { 6753 // set this.$element 6754 this.$element = this.$element || jQuery( this.element ); 6755 if ( event ) { 6756 // create jQuery event 6757 var $event = jQuery.Event( event ); 6758 $event.type = type; 6759 this.$element.trigger( $event, args ); 6760 } else { 6761 // just trigger with type if no event available 6762 this.$element.trigger( type, args ); 6763 } 6764 } 6765}; 6766 6767// -------------------------- ignore & stamps -------------------------- // 6768 6769 6770/** 6771 * keep item in collection, but do not lay it out 6772 * ignored items do not get skipped in layout 6773 * @param {Element} elem 6774 */ 6775proto.ignore = function( elem ) { 6776 var item = this.getItem( elem ); 6777 if ( item ) { 6778 item.isIgnored = true; 6779 } 6780}; 6781 6782/** 6783 * return item to layout collection 6784 * @param {Element} elem 6785 */ 6786proto.unignore = function( elem ) { 6787 var item = this.getItem( elem ); 6788 if ( item ) { 6789 delete item.isIgnored; 6790 } 6791}; 6792 6793/** 6794 * adds elements to stamps 6795 * @param {NodeList, Array, Element, or String} elems 6796 */ 6797proto.stamp = function( elems ) { 6798 elems = this._find( elems ); 6799 if ( !elems ) { 6800 return; 6801 } 6802 6803 this.stamps = this.stamps.concat( elems ); 6804 // ignore 6805 elems.forEach( this.ignore, this ); 6806}; 6807 6808/** 6809 * removes elements to stamps 6810 * @param {NodeList, Array, or Element} elems 6811 */ 6812proto.unstamp = function( elems ) { 6813 elems = this._find( elems ); 6814 if ( !elems ){ 6815 return; 6816 } 6817 6818 elems.forEach( function( elem ) { 6819 // filter out removed stamp elements 6820 utils.removeFrom( this.stamps, elem ); 6821 this.unignore( elem ); 6822 }, this ); 6823}; 6824 6825/** 6826 * finds child elements 6827 * @param {NodeList, Array, Element, or String} elems 6828 * @returns {Array} elems 6829 */ 6830proto._find = function( elems ) { 6831 if ( !elems ) { 6832 return; 6833 } 6834 // if string, use argument as selector string 6835 if ( typeof elems == 'string' ) { 6836 elems = this.element.querySelectorAll( elems ); 6837 } 6838 elems = utils.makeArray( elems ); 6839 return elems; 6840}; 6841 6842proto._manageStamps = function() { 6843 if ( !this.stamps || !this.stamps.length ) { 6844 return; 6845 } 6846 6847 this._getBoundingRect(); 6848 6849 this.stamps.forEach( this._manageStamp, this ); 6850}; 6851 6852// update boundingLeft / Top 6853proto._getBoundingRect = function() { 6854 // get bounding rect for container element 6855 var boundingRect = this.element.getBoundingClientRect(); 6856 var size = this.size; 6857 this._boundingRect = { 6858 left: boundingRect.left + size.paddingLeft + size.borderLeftWidth, 6859 top: boundingRect.top + size.paddingTop + size.borderTopWidth, 6860 right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ), 6861 bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth ) 6862 }; 6863}; 6864 6865/** 6866 * @param {Element} stamp 6867**/ 6868proto._manageStamp = noop; 6869 6870/** 6871 * get x/y position of element relative to container element 6872 * @param {Element} elem 6873 * @returns {Object} offset - has left, top, right, bottom 6874 */ 6875proto._getElementOffset = function( elem ) { 6876 var boundingRect = elem.getBoundingClientRect(); 6877 var thisRect = this._boundingRect; 6878 var size = getSize( elem ); 6879 var offset = { 6880 left: boundingRect.left - thisRect.left - size.marginLeft, 6881 top: boundingRect.top - thisRect.top - size.marginTop, 6882 right: thisRect.right - boundingRect.right - size.marginRight, 6883 bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom 6884 }; 6885 return offset; 6886}; 6887 6888// -------------------------- resize -------------------------- // 6889 6890// enable event handlers for listeners 6891// i.e. resize -> onresize 6892proto.handleEvent = utils.handleEvent; 6893 6894/** 6895 * Bind layout to window resizing 6896 */ 6897proto.bindResize = function() { 6898 window.addEventListener( 'resize', this ); 6899 this.isResizeBound = true; 6900}; 6901 6902/** 6903 * Unbind layout to window resizing 6904 */ 6905proto.unbindResize = function() { 6906 window.removeEventListener( 'resize', this ); 6907 this.isResizeBound = false; 6908}; 6909 6910proto.onresize = function() { 6911 this.resize(); 6912}; 6913 6914utils.debounceMethod( Outlayer, 'onresize', 100 ); 6915 6916proto.resize = function() { 6917 // don't trigger if size did not change 6918 // or if resize was unbound. See #9
vendor: 4,167 bytes, lines 6918-7101
6918 6919 if ( !this.isResizeBound || !this.needsResizeLayout() ) { 6920 return; 6921 } 6922 6923 this.layout(); 6924}; 6925 6926/** 6927 * check if layout is needed post layout 6928 * @returns Boolean 6929 */ 6930proto.needsResizeLayout = function() { 6931 var size = getSize( this.element ); 6932 // check that this.size and size are there 6933 // IE8 triggers resize on body size change, so they might not be 6934 var hasSizes = this.size && size; 6935 return hasSizes && size.innerWidth !== this.size.innerWidth; 6936}; 6937 6938// -------------------------- methods -------------------------- // 6939 6940/** 6941 * add items to Outlayer instance 6942 * @param {Array or NodeList or Element} elems 6943 * @returns {Array} items - Outlayer.Items 6944**/ 6945proto.addItems = function( elems ) { 6946 var items = this._itemize( elems ); 6947 // add items to collection 6948 if ( items.length ) { 6949 this.items = this.items.concat( items ); 6950 } 6951 return items; 6952}; 6953 6954/** 6955 * Layout newly-appended item elements 6956 * @param {Array or NodeList or Element} elems 6957 */ 6958proto.appended = function( elems ) { 6959 var items = this.addItems( elems ); 6960 if ( !items.length ) { 6961 return; 6962 } 6963 // layout and reveal just the new items 6964 this.layoutItems( items, true ); 6965 this.reveal( items ); 6966}; 6967 6968/** 6969 * Layout prepended elements 6970 * @param {Array or NodeList or Element} elems 6971 */ 6972proto.prepended = function( elems ) { 6973 var items = this._itemize( elems ); 6974 if ( !items.length ) { 6975 return; 6976 } 6977 // add items to beginning of collection 6978 var previousItems = this.items.slice(0); 6979 this.items = items.concat( previousItems ); 6980 // start new layout 6981 this._resetLayout(); 6982 this._manageStamps(); 6983 // layout new stuff without transition 6984 this.layoutItems( items, true ); 6985 this.reveal( items ); 6986 // layout previous items 6987 this.layoutItems( previousItems ); 6988}; 6989 6990/** 6991 * reveal a collection of items 6992 * @param {Array of Outlayer.Items} items 6993 */ 6994proto.reveal = function( items ) { 6995 this._emitCompleteOnItems( 'reveal', items ); 6996 if ( !items || !items.length ) { 6997 return; 6998 } 6999 items.forEach( function( item ) { 7000 item.reveal(); 7001 }); 7002}; 7003 7004/** 7005 * hide a collection of items 7006 * @param {Array of Outlayer.Items} items 7007 */ 7008proto.hide = function( items ) { 7009 this._emitCompleteOnItems( 'hide', items ); 7010 if ( !items || !items.length ) { 7011 return; 7012 } 7013 items.forEach( function( item ) { 7014 item.hide(); 7015 }); 7016}; 7017 7018/** 7019 * reveal item elements 7020 * @param {Array}, {Element}, {NodeList} items 7021 */ 7022proto.revealItemElements = function( elems ) { 7023 var items = this.getItems( elems ); 7024 this.reveal( items ); 7025}; 7026 7027/** 7028 * hide item elements 7029 * @param {Array}, {Element}, {NodeList} items 7030 */ 7031proto.hideItemElements = function( elems ) { 7032 var items = this.getItems( elems ); 7033 this.hide( items ); 7034}; 7035 7036/** 7037 * get Outlayer.Item, given an Element 7038 * @param {Element} elem 7039 * @param {Function} callback 7040 * @returns {Outlayer.Item} item 7041 */ 7042proto.getItem = function( elem ) { 7043 // loop through items to get the one that matches 7044 for ( var i=0; i < this.items.length; i++ ) { 7045 var item = this.items[i]; 7046 if ( item.element == elem ) { 7047 // return item 7048 return item; 7049 } 7050 } 7051}; 7052 7053/** 7054 * get collection of Outlayer.Items, given Elements 7055 * @param {Array} elems 7056 * @returns {Array} items - Outlayer.Items 7057 */ 7058proto.getItems = function( elems ) { 7059 elems = utils.makeArray( elems ); 7060 var items = []; 7061 elems.forEach( function( elem ) { 7062 var item = this.getItem( elem ); 7063 if ( item ) { 7064 items.push( item ); 7065 } 7066 }, this ); 7067 7068 return items; 7069}; 7070 7071/** 7072 * remove element(s) from instance and DOM 7073 * @param {Array or NodeList or Element} elems 7074 */ 7075proto.remove = function( elems ) { 7076 var removeItems = this.getItems( elems ); 7077 7078 this._emitCompleteOnItems( 'remove', removeItems ); 7079 7080 // bail if no items to remove 7081 if ( !removeItems || !removeItems.length ) { 7082 return; 7083 } 7084 7085 removeItems.forEach( function( item ) { 7086 item.remove(); 7087 // remove item from collection 7088 utils.removeFrom( this.items, item ); 7089 }, this ); 7090}; 7091 7092// ----- destroy ----- // 7093 7094// remove and disable Outlayer instance 7095proto.destroy = function() { 7096 // clean up dynamic styles 7097 var style = this.element.style; 7098 style.height = ''; 7099 style.position = ''; 7100 style.width = ''; 7101 // destroy items
vendor: 2,836 bytes, lines 7102-7227
7102 this.items.forEach( function( item ) { 7103 item.destroy(); 7104 }); 7105 7106 this.unbindResize(); 7107 7108 var id = this.element.outlayerGUID; 7109 delete instances[ id ]; // remove reference to instance by id 7110 delete this.element.outlayerGUID; 7111 // remove data for jQuery 7112 if ( jQuery ) { 7113 jQuery.removeData( this.element, this.constructor.namespace ); 7114 } 7115 7116}; 7117 7118// -------------------------- data -------------------------- // 7119 7120/** 7121 * get Outlayer instance from element 7122 * @param {Element} elem 7123 * @returns {Outlayer} 7124 */ 7125Outlayer.data = function( elem ) { 7126 elem = utils.getQueryElement( elem ); 7127 var id = elem && elem.outlayerGUID; 7128 return id && instances[ id ]; 7129}; 7130 7131 7132// -------------------------- create Outlayer class -------------------------- // 7133 7134/** 7135 * create a layout class 7136 * @param {String} namespace 7137 */ 7138Outlayer.create = function( namespace, options ) { 7139 // sub-class Outlayer 7140 var Layout = subclass( Outlayer ); 7141 // apply new options and compatOptions 7142 Layout.defaults = utils.extend( {}, Outlayer.defaults ); 7143 utils.extend( Layout.defaults, options ); 7144 Layout.compatOptions = utils.extend( {}, Outlayer.compatOptions ); 7145 7146 Layout.namespace = namespace; 7147 7148 Layout.data = Outlayer.data; 7149 7150 // sub-class Item 7151 Layout.Item = subclass( Item ); 7152 7153 // -------------------------- declarative -------------------------- // 7154 7155 utils.htmlInit( Layout, namespace ); 7156 7157 // -------------------------- jQuery bridge -------------------------- // 7158 7159 // make into jQuery plugin 7160 if ( jQuery && jQuery.bridget ) { 7161 jQuery.bridget( namespace, Layout ); 7162 } 7163 7164 return Layout; 7165}; 7166 7167function subclass( Parent ) { 7168 function SubClass() { 7169 Parent.apply( this, arguments ); 7170 } 7171 7172 SubClass.prototype = Object.create( Parent.prototype ); 7173 SubClass.prototype.constructor = SubClass; 7174 7175 return SubClass; 7176} 7177 7178// ----- fin ----- // 7179 7180// back in global 7181Outlayer.Item = Item; 7182 7183return Outlayer; 7184 7185})); 7186 7187/*! 7188 * Masonry v4.0.0 7189 * Cascading grid layout library 7190 * http://masonry.desandro.com 7191 * MIT License 7192 * by David DeSandro 7193 */ 7194 7195( function( window, factory ) { 7196 // universal module definition 7197 /* jshint strict: false */ /*globals define, module, require */ 7198 if ( typeof define == 'function' && define.amd ) { 7199 // AMD 7200 define( [ 7201 'outlayer/outlayer', 7202 'get-size/get-size' 7203 ], 7204 factory ); 7205 } else if ( typeof module == 'object' && module.exports ) { 7206 // CommonJS 7207 module.exports = factory( 7208 require('outlayer'), 7209 require('get-size') 7210 ); 7211 } else { 7212 // browser global 7213 window.Masonry = factory( 7214 window.Outlayer, 7215 window.getSize 7216 ); 7217 } 7218 7219}( window, function factory( Outlayer, getSize ) { 7220 7221 7222 7223// -------------------------- masonryDefinition -------------------------- // 7224 7225 // create an Outlayer layout class 7226 var Masonry = Outlayer.create('masonry'); 7227 // isFitWidth -> fitWidth
7228 Masonry.compatOptions.fitWidth = 'isFitWidth'; 7229 7230 Masonry.prototype._resetLayout = function() { 7231 this.getSize(); 7232 this._getMeasurement( 'columnWidth', 'outerWidth' ); 7233 this._getMeasurement( 'gutter', 'outerWidth' ); 7234 this.measureColumns(); 7235 7236 // reset column Y 7237 this.colYs = []; 7238 for ( var i=0; i < this.cols; i++ ) { 7239 this.colYs.push( 0 ); 7240 } 7241 7242 this.maxY = 0; 7243 }; 7244 7245 Masonry.prototype.measureColumns = function() { 7246 this.getContainerWidth(); 7247 // if columnWidth is 0, default to outerWidth of first item 7248 if ( !this.columnWidth ) { 7249 var firstItem = this.items[0]; 7250 var firstItemElem = firstItem && firstItem.element; 7251 // columnWidth fall back to item of first element 7252 this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth || 7253 // if first elem has no width, default to size of container 7254 this.containerWidth; 7255 } 7256 7257 var columnWidth = this.columnWidth += this.gutter; 7258 7259 // calculate columns 7260 var containerWidth = this.containerWidth + this.gutter; 7261 var cols = containerWidth / columnWidth; 7262 // fix rounding errors, typically with gutters 7263 var excess = columnWidth - containerWidth % columnWidth; 7264 // if overshoot is less than a pixel, round up, otherwise floor it 7265 var mathMethod = excess && excess < 1 ? 'round' : 'floor'; 7266 cols = Math[ mathMethod ]( cols ); 7267 this.cols = Math.max( cols, 1 ); 7268 }; 7269 7270 Masonry.prototype.getContainerWidth = function() { 7271 // container is parent if fit width 7272 var isFitWidth = this._getOption('fitWidth'); 7273 var container = isFitWidth ? this.element.parentNode : this.element; 7274 // check that this.size and size are there 7275 // IE8 triggers resize on body size change, so they might not be 7276 var size = getSize( container ); 7277 this.containerWidth = size && size.innerWidth; 7278 }; 7279 7280 Masonry.prototype._getItemLayoutPosition = function( item ) { 7281 item.getSize(); 7282 // how many columns does this brick span 7283 var remainder = item.size.outerWidth % this.columnWidth; 7284 var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil'; 7285 // round if off by 1 pixel, otherwise use ceil 7286 var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth ); 7287 colSpan = Math.min( colSpan, this.cols ); 7288 7289 var colGroup = this._getColGroup( colSpan ); 7290 // get the minimum Y value from the columns 7291 var minimumY = Math.min.apply( Math, colGroup ); 7292 var shortColIndex = colGroup.indexOf( minimumY ); 7293 7294 // position the brick 7295 var position = { 7296 x: this.columnWidth * shortColIndex, 7297 y: minimumY 7298 }; 7299 7300 // apply setHeight to necessary columns 7301 var setHeight = minimumY + item.size.outerHeight; 7302 var setSpan = this.cols + 1 - colGroup.length; 7303 for ( var i = 0; i < setSpan; i++ ) { 7304 this.colYs[ shortColIndex + i ] = setHeight; 7305 } 7306 7307 return position; 7308 }; 7309 7310 /** 7311 * @param {Number} colSpan - number of columns the element spans 7312 * @returns {Array} colGroup 7313 */ 7314 Masonry.prototype._getColGroup = function( colSpan ) { 7315 if ( colSpan < 2 ) { 7316 // if brick spans only one column, use all the column Ys 7317 return this.colYs; 7318 } 7319 7320 var colGroup = []; 7321 // how many different places could this brick fit horizontally 7322 var groupCount = this.cols + 1 - colSpan; 7323 // for each group potential horizontal position 7324 for ( var i = 0; i < groupCount; i++ ) { 7325 // make an array of colY values for that one group 7326 var groupColYs = this.colYs.slice( i, i + colSpan ); 7327 // and get the max value of the array 7328 colGroup[i] = Math.max.apply( Math, groupColYs ); 7329 } 7330 return colGroup; 7331 }; 7332 7333 Masonry.prototype._manageStamp = function( stamp ) { 7334 var stampSize = getSize( stamp ); 7335 var offset = this._getElementOffset( stamp ); 7336 // get the columns that this stamp affects 7337 var isOriginLeft = this._getOption('originLeft'); 7338 var firstX = isOriginLeft ? offset.left : offset.right; 7339 var lastX = firstX + stampSize.outerWidth; 7340 var firstCol = Math.floor( firstX / this.columnWidth ); 7341 firstCol = Math.max( 0, firstCol ); 7342 var lastCol = Math.floor( lastX / this.columnWidth ); 7343 // lastCol should not go over if multiple of columnWidth #425 7344 lastCol -= lastX % this.columnWidth ? 0 : 1; 7345 lastCol = Math.min( this.cols - 1, lastCol ); 7346 // set colYs to bottom of the stamp 7347 7348 var isOriginTop = this._getOption('originTop'); 7349 var stampMaxY = ( isOriginTop ? offset.top : offset.bottom ) + 7350 stampSize.outerHeight; 7351 for ( var i = firstCol; i <= lastCol; i++ ) { 7352 this.colYs[i] = Math.max( stampMaxY, this.colYs[i] ); 7353 } 7354 }; 7355 7356 Masonry.prototype._getContainerSize = function() { 7357 this.maxY = Math.max.apply( Math, this.colYs ); 7358 var size = { 7359 height: this.maxY 7360 }; 7361 7362 if ( this._getOption('fitWidth') ) { 7363 size.width = this._getContainerFitWidth(); 7364 } 7365 7366 return size; 7367 }; 7368 7369 Masonry.prototype._getContainerFitWidth = function() { 7370 var unusedCols = 0; 7371 // count unused columns 7372 var i = this.cols; 7373 while ( --i ) { 7374 if ( this.colYs[i] !== 0 ) { 7375 break; 7376 } 7377 unusedCols++; 7378 } 7379 // fit container to columns that have been used 7380 return ( this.cols - unusedCols ) * this.columnWidth - this.gutter; 7381 }; 7382 7383 Masonry.prototype.needsResizeLayout = function() { 7384 var previousWidth = this.containerWidth; 7385 this.getContainerWidth(); 7386 return previousWidth != this.containerWidth; 7387 }; 7388 7389 return Masonry; 7390 7391})); 7392 7393 7394// Menu toggle 7395$(document).ready(function(){ 7396 $(".layout4menu ul").slideUp(); 7397 $(".layout4menulink").click(function(e){ 7398 e.stopPropagation(); 7399 $(this).addClass('opendropdownmenu'); 7400 $(".layout4menu ul").slideToggle(); 7401 }); 7402 $(document).click(function () { 7403 if($(".layout4menulink").hasClass('opendropdownmenu')) 7404 { 7405 $(".layout4menulink").removeClass('opendropdownmenu'); 7406 $(".layout4menu ul").slideToggle(); 7407 } 7408 }); 7409}); 7410// Open Fancybox Content 7411$("a[rel='portfolio']").each(function(e) { 7412 var title = $(this).attr('title'); 7413 7414 $(this).mouseover(function() { 7415 $(this).removeAttr('title',''); 7416 }).mouseout(function() { 7417 $(this).removeAttr('title', title); 7418 }); 7419 $(this).click(function() { 7420 $(this).attr('title', title); 7421 }); 7422}); 7423$( document ).ready(function() { 7424 jQuery(".ResponsiveFancyGallery2").fancybox({ 7425 titlePosition: "over", 7426 beforeShow : function(){ 7427 7428 if($(this.element).data("title")!=undefined && $(this.element).data("caption")!=undefined) 7429 { 7430 this.title = $(this.element).data("title") + " <br> " + $(this.element).data("caption"); 7431 } 7432 else if($(this.element).data("title")!=undefined && $(this.element).data("caption")==undefined) 7433 { 7434 this.title = $(this.element).data("title"); 7435 } 7436 else if($(this.element).data("title")==undefined && $(this.element).data("caption")!=undefined) 7437 { 7438 this.title = $(this.element).data("caption"); 7439 } 7440 else 7441 { 7442 this.title = ''; 7443 } 7444 }, 7445 onUpdate:function(){ 7446 7447 var posXY = ''; 7448 $('.fancybox-skin').draggable({ 7449 axis: "x", 7450 drag: function(event,ui){ 7451 7452 posXY = ui.position.left; 7453 7454 if(posXY > 150){return false;} 7455 if(posXY < -150){return false;} 7456 }, 7457 stop: function(){ 7458 7459 if(posXY > 110){$.fancybox.prev();} 7460 if(posXY < -110){$.fancybox.next();} 7461 } 7462 }); 7463 }, 7464 afterClose : function() 7465 { 7466 $( window ).focus(); 7467 }, 7468 7469 }); 7470}); 7471 7472
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.