1/** 2 * Galleria v1.6.1 3 * 4 * Copyright (c) 2010 - 2019 worse is better UG 5 * Licensed under the MIT license 6 * 7 */ 8 9( function( window, factory ) { 10 if ( typeof define == 'function' && define.amd ) { 11 define( [ 'jquery' ], function( jQuery ) { 12 return factory( window, jQuery ); 13 }); 14 } else if ( typeof module == 'object' && module.exports ) { 15 module.exports = factory( 16 window, 17 require('jquery') 18 ); 19 } else { 20 // browser global 21 window.Galleria = factory( 22 window, 23 window.jQuery 24 ); 25 } 26 27}( window, function factory( window, $, Galleria, undef ) { 28 29/*global jQuery, navigator, Image, module, define */ 30 31// some references 32var doc = window.document, 33 $doc = $( doc ), 34 $win = $( window ), 35 jQuery = $, 36 37// native prototypes 38 protoArray = Array.prototype, 39 40// internal constants 41 VERSION = 1.61, 42 DEBUG = fals
vendor: 207,307 bytes, lines 42-6630
42e, 43 TIMEOUT = 30000, 44 DUMMY = false, 45 NAV = navigator.userAgent.toLowerCase(), 46 HASH = window.location.hash.replace(/#\//, ''), 47 PROT = window.location.protocol == "file:" ? "http:" : window.location.protocol, 48 M = Math, 49 F = function(){}, 50 FALSE = function() { return false; }, 51 MOBILE = !( 52 ( window.screen.width > 1279 && window.devicePixelRatio == 1 ) || // there are not so many mobile devices with more than 1280px and pixelRatio equal to 1 (i.e. retina displays are equal to 2...) 53 ( window.screen.width > 1000 && window.innerWidth < (window.screen.width * .9) ) // this checks in the end if a user is using a resized browser window which is not common on mobile devices 54 ), 55 IE = (function() { 56 57 var v = 3, 58 div = doc.createElement( 'div' ), 59 all = div.getElementsByTagName( 'i' ); 60 61 do { 62 div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->'; 63 } while ( all[0] ); 64 65 return v > 4 ? v : doc.documentMode || undef; 66 67 }() ), 68 DOM = function() { 69 return { 70 html: doc.documentElement, 71 body: doc.body, 72 head: doc.getElementsByTagName('head')[0], 73 title: doc.title 74 }; 75 }, 76 IFRAME = window.parent !== window.self, 77 78 // list of Galleria events 79 _eventlist = 'data ready thumbnail loadstart loadfinish image play pause progress ' + 80 'fullscreen_enter fullscreen_exit idle_enter idle_exit rescale ' + 81 'lightbox_open lightbox_close lightbox_image', 82 83 _events = (function() { 84 85 var evs = []; 86 87 $.each( _eventlist.split(' '), function( i, ev ) { 88 evs.push( ev ); 89 90 // legacy events 91 if ( /_/.test( ev ) ) { 92 evs.push( ev.replace( /_/g, '' ) ); 93 } 94 }); 95 96 return evs; 97 98 }()), 99 100 // legacy options 101 // allows the old my_setting syntax and converts it to camel case 102 103 _legacyOptions = function( options ) { 104 105 var n; 106 107 if ( typeof options !== 'object' ) { 108 109 // return whatever it was... 110 return options; 111 } 112 113 $.each( options, function( key, value ) { 114 if ( /^[a-z]+_/.test( key ) ) { 115 n = ''; 116 $.each( key.split('_'), function( i, k ) { 117 n += i > 0 ? k.substr( 0, 1 ).toUpperCase() + k.substr( 1 ) : k; 118 }); 119 options[ n ] = value; 120 delete options[ key ]; 121 } 122 }); 123 124 return options; 125 }, 126 127 _patchEvent = function( type ) { 128 129 // allow 'image' instead of Galleria.IMAGE 130 if ( $.inArray( type, _events ) > -1 ) { 131 return Galleria[ type.toUpperCase() ]; 132 } 133 134 return type; 135 }, 136 137 // video providers 138 _video = { 139 youtube: { 140 reg: /https?:\/\/(?:[a-zA_Z]{2,3}.)?(?:youtube\.com\/watch\?)((?:[\w\d\-\_\=]+&(?:amp;)?)*v(?:<[A-Z]+>)?=([0-9a-zA-Z\-\_]+))/i, 141 embed: function() { 142 return PROT + '//www.youtube.com/embed/' + this.id; 143 }, 144 get_thumb: function( data ) { 145 return PROT + '//img.youtube.com/vi/'+this.id+'/default.jpg'; 146 }, 147 get_image: function( data ) { 148 return PROT + '//img.youtube.com/vi/'+this.id+'/maxresdefault.jpg'; } 149 }, 150 vimeo: { 151 reg: /https?:\/\/(?:www\.)?(vimeo\.com)\/(?:hd#)?([0-9]+)/i, 152 embed: function() { 153 return PROT + '//player.vimeo.com/video/' + this.id; 154 }, 155 getUrl: function() { 156 return PROT + '//vimeo.com/api/v2/video/' + this.id + '.json?callback=?'; 157 }, 158 get_thumb: function( data ) { 159 return data[0].thumbnail_medium; 160 }, 161 get_image: function( data ) { 162 return data[0].thumbnail_large; 163 } 164 }, 165 dailymotion: { 166 reg: /https?:\/\/(?:www\.)?(dailymotion\.com)\/video\/([^_]+)/, 167 embed: function() { 168 return PROT + '//www.dailymotion.com/embed/video/' + this.id; 169 }, 170 getUrl: function() { 171 return 'https://api.dailymotion.com/video/' + this.id + '?fields=thumbnail_240_url,thumbnail_720_url&callback=?'; 172 }, 173 get_thumb: function( data ) { 174 return data.thumbnail_240_url; 175 }, 176 get_image: function( data ) { 177 return data.thumbnail_720_url; 178 } 179 }, 180 _inst: [] 181 }, 182 Video = function( type, id ) { 183 184 for( var i=0; i<_video._inst.length; i++ ) { 185 if ( _video._inst[i].id === id && _video._inst[i].type == type ) { 186 return _video._inst[i]; 187 } 188 } 189 190 this.type = type; 191 this.id = id; 192 this.readys = []; 193 194 _video._inst.push(this); 195 196 var self = this; 197 198 $.extend( this, _video[type] ); 199 200 _videoThumbs = function(data) { 201 self.data = data; 202 $.each( self.readys, function( i, fn ) { 203 fn( self.data ); 204 }); 205 self.readys = []; 206 }; 207 208 if ( this.hasOwnProperty('getUrl') ) { 209 $.getJSON( this.getUrl(), _videoThumbs); 210 } else { 211 window.setTimeout(_videoThumbs, 400); 212 } 213 214 this.getMedia = function( type, callback, fail ) { 215 fail = fail || F; 216 var self = this; 217 var success = function( data ) { 218 callback( self['get_'+type]( data ) ); 219 }; 220 try { 221 if ( self.data ) { 222 success( self.data ); 223 } else { 224 self.readys.push( success ); 225 } 226 } catch(e) { 227 fail(); 228 } 229 }; 230 }, 231 232 // utility for testing the video URL and getting the video ID 233 _videoTest = function( url ) { 234 var match; 235 for ( var v in _video ) { 236 match = url && _video[v].reg && url.match( _video[v].reg ); 237 if( match && match.length ) { 238 return { 239 id: match[2], 240 provider: v 241 }; 242 } 243 } 244 return false; 245 }, 246 247 // native fullscreen handler 248 _nativeFullscreen = { 249 250 support: (function() { 251 var html = DOM().html; 252 return !IFRAME && ( html.requestFullscreen || html.msRequestFullscreen || html.mozRequestFullScreen || html.webkitRequestFullScreen ); 253 }()), 254 255 callback: F, 256 257 enter: function( instance, callback, elem ) { 258 259 this.instance = instance; 260 261 this.callback = callback || F; 262 263 elem = elem || DOM().html; 264 if ( elem.requestFullscreen ) { 265 elem.requestFullscreen(); 266 } 267 else if ( elem.msRequestFullscreen ) { 268 elem.msRequestFullscreen(); 269 } 270 else if ( elem.mozRequestFullScreen ) { 271 elem.mozRequestFullScreen(); 272 } 273 else if ( elem.webkitRequestFullScreen ) { 274 elem.webkitRequestFullScreen(); 275 } 276 }, 277 278 exit: function( callback ) { 279 280 this.callback = callback || F; 281 282 if ( doc.exitFullscreen ) { 283 doc.exitFullscreen(); 284 } 285 else if ( doc.msExitFullscreen ) { 286 doc.msExitFullscreen(); 287 } 288 else if ( doc.mozCancelFullScreen ) { 289 doc.mozCancelFullScreen(); 290 } 291 else if ( doc.webkitCancelFullScreen ) { 292 doc.webkitCancelFullScreen(); 293 } 294 }, 295 296 instance: null, 297 298 listen: function() { 299 300 if ( !this.support ) { 301 return; 302 } 303 304 var handler = function() { 305 306 if ( !_nativeFullscreen.instance ) { 307 return; 308 } 309 var fs = _nativeFullscreen.instance._fullscreen; 310 311 if ( doc.fullscreen || doc.mozFullScreen || doc.webkitIsFullScreen || ( doc.msFullscreenElement && doc.msFullscreenElement !== null ) ) { 312 fs._enter( _nativeFullscreen.callback ); 313 } else { 314 fs._exit( _nativeFullscreen.callback ); 315 } 316 }; 317 doc.addEventListener( 'fullscreenchange', handler, false ); 318 doc.addEventListener( 'MSFullscreenChange', handler, false ); 319 doc.addEventListener( 'mozfullscreenchange', handler, false ); 320 doc.addEventListener( 'webkitfullscreenchange', handler, false ); 321 } 322 }, 323 324 // the internal gallery holder 325 _galleries = [], 326 327 // the internal instance holder 328 _instances = [], 329 330 // flag for errors 331 _hasError = false, 332 333 // canvas holder 334 _canvas = false, 335 336 // instance pool, holds the galleries until themeLoad is triggered 337 _pool = [], 338 339 // Run galleries from theme trigger 340 _loadedThemes = [], 341 _themeLoad = function( theme ) { 342 343 _loadedThemes.push(theme); 344 345 // run the instances we have in the pool 346 // and apply the last theme if not specified 347 $.each( _pool, function( i, instance ) { 348 if ( instance._options.theme == theme.name || (!instance._initialized && !instance._options.theme) ) { 349 instance.theme = theme; 350 instance._init.call( instance ); 351 } 352 }); 353 }, 354 355 // the Utils singleton 356 Utils = (function() { 357 358 return { 359 360 // legacy support for clearTimer 361 clearTimer: function( id ) { 362 $.each( Galleria.get(), function() { 363 this.clearTimer( id ); 364 }); 365 }, 366 367 // legacy support for addTimer 368 addTimer: function( id ) { 369 $.each( Galleria.get(), function() { 370 this.addTimer( id ); 371 }); 372 }, 373 374 array : function( obj ) { 375 return protoArray.slice.call(obj, 0); 376 }, 377 378 create : function( className, nodeName ) { 379 nodeName = nodeName || 'div'; 380 var elem = doc.createElement( nodeName ); 381 elem.className = className; 382 return elem; 383 }, 384 385 removeFromArray : function( arr, elem ) { 386 $.each(arr, function(i, el) { 387 if ( el == elem ) { 388 arr.splice(i, 1); 389 return false; 390 } 391 }); 392 return arr; 393 }, 394 395 getScriptPath : function( src ) { 396 397 // the currently executing script is always the last 398 src = src || $('script:last').attr('src'); 399 var slices = src.split('/'); 400 401 if (slices.length == 1) { 402 return ''; 403 } 404 405 slices.pop(); 406 407 return slices.join('/') + '/'; 408 }, 409 410 // CSS3 transitions, added in 1.2.4 411 animate : (function() { 412 413 // detect transition 414 var transition = (function( style ) { 415 var props = 'transition WebkitTransition MozTransition OTransition'.split(' '), 416 i; 417 418 // disable css3 animations in opera until stable 419 if ( window.opera ) { 420 return false; 421 } 422 423 for ( i = 0; props[i]; i++ ) { 424 if ( typeof style[ props[ i ] ] !== 'undefined' ) { 425 return props[ i ]; 426 } 427 } 428 return false; 429 }(( doc.body || doc.documentElement).style )); 430 431 // map transitionend event 432 var endEvent = { 433 MozTransition: 'transitionend', 434 OTransition: 'oTransitionEnd', 435 WebkitTransition: 'webkitTransitionEnd', 436 transition: 'transitionend' 437 }[ transition ]; 438 439 // map bezier easing conversions 440 var easings = { 441 _default: [0.25, 0.1, 0.25, 1], 442 galleria: [0.645, 0.045, 0.355, 1], 443 galleriaIn: [0.55, 0.085, 0.68, 0.53], 444 galleriaOut: [0.25, 0.46, 0.45, 0.94], 445 ease: [0.25, 0, 0.25, 1], 446 linear: [0.25, 0.25, 0.75, 0.75], 447 'ease-in': [0.42, 0, 1, 1], 448 'ease-out': [0, 0, 0.58, 1], 449 'ease-in-out': [0.42, 0, 0.58, 1] 450 }; 451 452 // function for setting transition css for all browsers 453 var setStyle = function( elem, value, suffix ) { 454 var css = {}; 455 suffix = suffix || 'transition'; 456 $.each( 'webkit moz ms o'.split(' '), function() { 457 css[ '-' + this + '-' + suffix ] = value; 458 }); 459 elem.css( css ); 460 }; 461 462 // clear styles 463 var clearStyle = function( elem ) { 464 setStyle( elem, 'none', 'transition' ); 465 if ( Galleria.WEBKIT && Galleria.TOUCH ) { 466 setStyle( elem, 'translate3d(0,0,0)', 'transform' ); 467 if ( elem.data('revert') ) { 468 elem.css( elem.data('revert') ); 469 elem.data('revert', null); 470 } 471 } 472 }; 473 474 // various variables 475 var change, strings, easing, syntax, revert, form, css; 476 477 // the actual animation method 478 return function( elem, to, options ) { 479 480 // extend defaults 481 options = $.extend({ 482 duration: 400, 483 complete: F, 484 stop: false 485 }, options); 486 487 // cache jQuery instance 488 elem = $( elem ); 489 490 if ( !options.duration ) { 491 elem.css( to ); 492 options.complete.call( elem[0] ); 493 return; 494 } 495 496 // fallback to jQuery's animate if transition is not supported 497 if ( !transition ) { 498 elem.animate(to, options); 499 return; 500 } 501 502 // stop 503 if ( options.stop ) { 504 // clear the animation 505 elem.off( endEvent ); 506 clearStyle( elem ); 507 } 508 509 // see if there is a change 510 change = false; 511 $.each( to, function( key, val ) { 512 css = elem.css( key ); 513 if ( Utils.parseValue( css ) != Utils.parseValue( val ) ) { 514 change = true; 515 } 516 // also add computed styles for FF 517 elem.css( key, css ); 518 }); 519 if ( !change ) { 520 window.setTimeout( function() { 521 options.complete.call( elem[0] ); 522 }, options.duration ); 523 return; 524 } 525 526 // the css strings to be applied 527 strings = []; 528 529 // the easing bezier 530 easing = options.easing in easings ? easings[ options.easing ] : easings._default; 531 532 // the syntax 533 syntax = ' ' + options.duration + 'ms' + ' cubic-bezier(' + easing.join(',') + ')'; 534 535 // add a tiny timeout so that the browsers catches any css changes before animating 536 window.setTimeout( (function(elem, endEvent, to, syntax) { 537 return function() { 538 539 // attach the end event 540 elem.one(endEvent, (function( elem ) { 541 return function() { 542 543 // clear the animation 544 clearStyle(elem); 545 546 // run the complete method 547 options.complete.call(elem[0]); 548 }; 549 }( elem ))); 550 551 // do the webkit translate3d for better performance on iOS 552 if( Galleria.WEBKIT && Galleria.TOUCH ) { 553 554 revert = {}; 555 form = [0,0,0]; 556 557 $.each( ['left', 'top'], function(i, m) { 558 if ( m in to ) { 559 form[ i ] = ( Utils.parseValue( to[ m ] ) - Utils.parseValue(elem.css( m )) ) + 'px'; 560 revert[ m ] = to[ m ]; 561 delete to[ m ]; 562 } 563 }); 564 565 if ( form[0] || form[1]) { 566 567 elem.data('revert', revert); 568 569 strings.push('-webkit-transform' + syntax); 570 571 // 3d animate 572 setStyle( elem, 'translate3d(' + form.join(',') + ')', 'transform'); 573 } 574 } 575 576 // push the animation props 577 $.each(to, function( p, val ) { 578 strings.push(p + syntax); 579 }); 580 581 // set the animation styles 582 setStyle( elem, strings.join(',') ); 583 584 // animate 585 elem.css( to ); 586 587 }; 588 }(elem, endEvent, to, syntax)), 2); 589 }; 590 }()), 591 592 removeAlpha : function( elem ) { 593 if ( elem instanceof jQuery ) { 594 elem = elem[0]; 595 } 596 if ( IE < 9 && elem ) { 597 598 var style = elem.style, 599 currentStyle = elem.currentStyle, 600 filter = currentStyle && currentStyle.filter || style.filter || ""; 601 602 if ( /alpha/.test( filter ) ) { 603 style.filter = filter.replace( /alpha\([^)]*\)/i, '' ); 604 } 605 } 606 }, 607 608 forceStyles : function( elem, styles ) { 609 elem = $(elem); 610 if ( elem.attr( 'style' ) ) { 611 elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' ); 612 } 613 elem.css( styles ); 614 }, 615 616 revertStyles : function() { 617 $.each( Utils.array( arguments ), function( i, elem ) { 618 619 elem = $( elem ); 620 elem.removeAttr( 'style' ); 621 622 elem.attr('style',''); // "fixes" webkit bug 623 624 if ( elem.data( 'styles' ) ) { 625 elem.attr( 'style', elem.data('styles') ).data( 'styles', null ); 626 } 627 }); 628 }, 629 630 moveOut : function( elem ) { 631 Utils.forceStyles( elem, { 632 position: 'absolute', 633 left: -10000 634 }); 635 }, 636 637 moveIn : function() { 638 Utils.revertStyles.apply( Utils, Utils.array( arguments ) ); 639 }, 640 641 hide : function( elem, speed, callback ) { 642 643 callback = callback || F; 644 645 var $elem = $(elem); 646 elem = $elem[0]; 647 648 // save the value if not exist 649 if (! $elem.data('opacity') ) { 650 $elem.data('opacity', $elem.css('opacity') ); 651 } 652 653 // always hide 654 var style = { opacity: 0 }; 655 656 if (speed) { 657 658 var complete = IE < 9 && elem ? function() { 659 Utils.removeAlpha( elem ); 660 elem.style.visibility = 'hidden'; 661 callback.call( elem ); 662 } : callback; 663 664 Utils.animate( elem, style, { 665 duration: speed, 666 complete: complete, 667 stop: true 668 }); 669 } else { 670 if ( IE < 9 && elem ) { 671 Utils.removeAlpha( elem ); 672 elem.style.visibility = 'hidden'; 673 } else { 674 $elem.css( style ); 675 } 676 } 677 }, 678 679 show : function( elem, speed, callback ) { 680 681 callback = callback || F; 682 683 var $elem = $(elem); 684 elem = $elem[0]; 685 686 // bring back saved opacity 687 var saved = parseFloat( $elem.data('opacity') ) || 1, 688 style = { opacity: saved }; 689 690 // animate or toggle 691 if (speed) { 692 693 if ( IE < 9 ) { 694 $elem.css('opacity', 0); 695 elem.style.visibility = 'visible'; 696 } 697 698 var complete = IE < 9 && elem ? function() { 699 if ( style.opacity == 1 ) { 700 Utils.removeAlpha( elem ); 701 } 702 callback.call( elem ); 703 } : callback; 704 705 Utils.animate( elem, style, { 706 duration: speed, 707 complete: complete, 708 stop: true 709 }); 710 } else { 711 if ( IE < 9 && style.opacity == 1 && elem ) { 712 Utils.removeAlpha( elem ); 713 elem.style.visibility = 'visible'; 714 } else { 715 $elem.css( style ); 716 } 717 } 718 }, 719 720 wait : function(options) { 721 722 Galleria._waiters = Galleria._waiters || []; 723 724 options = $.extend({ 725 until : FALSE, 726 success : F, 727 error : function() { Galleria.raise('Could not complete wait function.'); }, 728 timeout: 3000 729 }, options); 730 731 var start = Utils.timestamp(), 732 elapsed, 733 now, 734 tid, 735 fn = function() { 736 now = Utils.timestamp(); 737 elapsed = now - start; 738 Utils.removeFromArray( Galleria._waiters, tid ); 739 if ( options.until( elapsed ) ) { 740 options.success(); 741 return false; 742 } 743 if (typeof options.timeout == 'number' && now >= start + options.timeout) { 744 options.error(); 745 return false; 746 } 747 Galleria._waiters.push( tid = window.setTimeout(fn, 10) ); 748 }; 749 Galleria._waiters.push( tid = window.setTimeout(fn, 10) ); 750 }, 751 752 toggleQuality : function( img, force ) { 753 754 if ( ( IE !== 7 && IE !== 8 ) || !img || img.nodeName.toUpperCase() != 'IMG' ) { 755 return; 756 } 757 758 if ( typeof force === 'undefined' ) { 759 force = img.style.msInterpolationMode === 'nearest-neighbor'; 760 } 761 762 img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor'; 763 }, 764 765 insertStyleTag : function( styles, id ) { 766 767 if ( id && $( '#'+id ).length ) { 768 return; 769 } 770 771 var style = doc.createElement( 'style' ); 772 if ( id ) { 773 style.id = id; 774 } 775 776 DOM().head.appendChild( style ); 777 778 if ( style.styleSheet ) { // IE 779 style.styleSheet.cssText = styles; 780 } else { 781 var cssText = doc.createTextNode( styles ); 782 style.appendChild( cssText ); 783 } 784 }, 785 786 // a loadscript method that works for local scripts 787 loadScript: function( url, callback ) { 788 789 var done = false, 790 script = $('<scr'+'ipt>').attr({ 791 src: url, 792 async: true 793 }).get(0); 794 795 // Attach handlers for all browsers 796 script.onload = script.onreadystatechange = function() { 797 if ( !done && (!this.readyState || 798 this.readyState === 'loaded' || this.readyState === 'complete') ) { 799 800 done = true; 801 802 // Handle memory leak in IE 803 script.onload = script.onreadystatechange = null; 804 805 if (typeof callback === 'function') { 806 callback.call( this, this ); 807 } 808 } 809 }; 810 811 DOM().head.appendChild( script ); 812 }, 813 814 // parse anything into a number 815 parseValue: function( val ) { 816 if (typeof val === 'number') { 817 return val; 818 } else if (typeof val === 'string') { 819 var arr = val.match(/\-?\d|\./g); 820 return arr && arr.constructor === Array ? arr.join('')*1 : 0; 821 } else { 822 return 0; 823 } 824 }, 825 826 // timestamp abstraction 827 timestamp: function() { 828 return new Date().getTime(); 829 }, 830 831 loadCSS : function( href, id, callback ) { 832 833 var link, 834 length; 835 836 // look for manual css 837 $('link[rel=stylesheet]').each(function() { 838 if ( new RegExp( href ).test( this.href ) ) { 839 link = this; 840 return false; 841 } 842 }); 843 844 if ( typeof id === 'function' ) { 845 callback = id; 846 id = undef; 847 } 848 849 callback = callback || F; // dirty 850 851 // if already present, return 852 if ( link ) { 853 callback.call( link, link ); 854 return link; 855 } 856 857 // save the length of stylesheets to check against 858 length = doc.styleSheets.length; 859 860 // check for existing id 861 if( $( '#' + id ).length ) { 862 863 $( '#' + id ).attr( 'href', href ); 864 length--; 865 866 } else { 867 link = $( '<link>' ).attr({ 868 rel: 'stylesheet', 869 href: href, 870 id: id 871 }).get(0); 872 873 var styles = $('link[rel="stylesheet"], style'); 874 if ( styles.length ) { 875 styles.get(0).parentNode.insertBefore( link, styles[0] ); 876 } else { 877 DOM().head.appendChild( link ); 878 } 879 880 if ( IE && length >= 31 ) { 881 Galleria.raise( 'You have reached the browser stylesheet limit (31)', true ); 882 return; 883 } 884 } 885 886 if ( typeof callback === 'function' ) { 887 888 // First check for dummy element (new in 1.2.8) 889 var $loader = $('<s>').attr( 'id', 'galleria-loader' ).hide().appendTo( DOM().body ); 890 891 Utils.wait({ 892 until: function() { 893 return $loader.height() > 0; 894 }, 895 success: function() { 896 $loader.remove(); 897 callback.call( link, link ); 898 }, 899 error: function() { 900 $loader.remove(); 901 902 // If failed, tell the dev to download the latest theme 903 Galleria.raise( 'Theme CSS could not load after 20 sec. ' + ( Galleria.QUIRK ? 904 'Your browser is in Quirks Mode, please add a correct doctype.' : 905 'Please download the latest theme.' ), true ); 906 }, 907 timeout: 5000 908 }); 909 } 910 return link; 911 } 912 }; 913 }()), 914 915 // play icon 916 _playIcon = function( container ) { 917 918 var css = '.galleria-videoicon{width:60px;height:60px;position:absolute;top:50%;left:50%;z-index:1;' + 919 'margin:-30px 0 0 -30px;cursor:pointer;background:#000;background:rgba(0,0,0,.8);border-radius:3px;-webkit-transition:all 150ms}' + 920 '.galleria-videoicon i{width:0px;height:0px;border-style:solid;border-width:10px 0 10px 16px;display:block;' + 921 'border-color:transparent transparent transparent #ffffff;margin:20px 0 0 22px}.galleria-image:hover .galleria-videoicon{background:#000}'; 922 923 Utils.insertStyleTag( css, 'galleria-videoicon' ); 924 925 return $( Utils.create( 'galleria-videoicon' ) ).html( '<i></i>' ).appendTo( container ) 926 .click( function() { $( this ).siblings( 'img' ).mouseup(); }); 927 }, 928 929 // the transitions holder 930 _transitions = (function() { 931 932 var _slide = function(params, complete, fade, door) { 933 934 var easing = this.getOptions('easing'), 935 distance = this.getStageWidth(), 936 from = { left: distance * ( params.rewind ? -1 : 1 ) }, 937 to = { left: 0 }; 938 939 if ( fade ) { 940 from.opacity = 0; 941 to.opacity = 1; 942 } else { 943 from.opacity = 1; 944 } 945 946 $(params.next).css(from); 947 948 Utils.animate(params.next, to, { 949 duration: params.speed, 950 complete: (function( elems ) { 951 return function() { 952 complete(); 953 elems.css({ 954 left: 0 955 }); 956 }; 957 }( $( params.next ).add( params.prev ) )), 958 queue: false, 959 easing: easing 960 }); 961 962 if (door) { 963 params.rewind = !params.rewind; 964 } 965 966 if (params.prev) { 967 968 from = { left: 0 }; 969 to = { left: distance * ( params.rewind ? 1 : -1 ) }; 970 971 if ( fade ) { 972 from.opacity = 1; 973 to.opacity = 0; 974 } 975 976 $(params.prev).css(from); 977 Utils.animate(params.prev, to, { 978 duration: params.speed, 979 queue: false, 980 easing: easing, 981 complete: function() { 982 $(this).css('opacity', 0); 983 } 984 }); 985 } 986 }; 987 988 return { 989 990 active: false, 991 992 init: function( effect, params, complete ) { 993 if ( _transitions.effects.hasOwnProperty( effect ) ) { 994 _transitions.effects[ effect ].call( this, params, complete ); 995 } 996 }, 997 998 effects: { 999 1000 fade: function(params, complete) { 1001 $(params.next).css({ 1002 opacity: 0, 1003 left: 0 1004 }); 1005 Utils.animate(params.next, { 1006 opacity: 1 1007 },{ 1008 duration: params.speed, 1009 complete: complete 1010 }); 1011 if (params.prev) { 1012 $(params.prev).css('opacity',1).show(); 1013 Utils.animate(params.prev, { 1014 opacity: 0 1015 },{ 1016 duration: params.speed 1017 }); 1018 } 1019 }, 1020 1021 flash: function(params, complete) { 1022 $(params.next).css({ 1023 opacity: 0, 1024 left: 0 1025 }); 1026 if (params.prev) { 1027 Utils.animate( params.prev, { 1028 opacity: 0 1029 },{ 1030 duration: params.speed/2, 1031 complete: function() { 1032 Utils.animate( params.next, { 1033 opacity:1 1034 },{ 1035 duration: params.speed, 1036 complete: complete 1037 }); 1038 } 1039 }); 1040 } else { 1041 Utils.animate( params.next, { 1042 opacity: 1 1043 },{ 1044 duration: params.speed, 1045 complete: complete 1046 }); 1047 } 1048 }, 1049 1050 pulse: function(params, complete) { 1051 if (params.prev) { 1052 $(params.prev).hide(); 1053 } 1054 $(params.next).css({ 1055 opacity: 0, 1056 left: 0 1057 }).show(); 1058 Utils.animate(params.next, { 1059 opacity:1 1060 },{ 1061 duration: params.speed, 1062 complete: complete 1063 }); 1064 }, 1065 1066 slide: function(params, complete) { 1067 _slide.apply( this, Utils.array( arguments ) ); 1068 }, 1069 1070 fadeslide: function(params, complete) { 1071 _slide.apply( this, Utils.array( arguments ).concat( [true] ) ); 1072 }, 1073 1074 doorslide: function(params, complete) { 1075 _slide.apply( this, Utils.array( arguments ).concat( [false, true] ) ); 1076 } 1077 } 1078 }; 1079 }()); 1080 1081// listen to fullscreen 1082_nativeFullscreen.listen(); 1083 1084// create special click:fast event for fast touch interaction 1085$.event.special['click:fast'] = { 1086 propagate: true, 1087 add: function(handleObj) { 1088 1089 var getCoords = function(e) { 1090 if ( e.touches && e.touches.length ) { 1091 var touch = e.touches[0]; 1092 return { 1093 x: touch.pageX, 1094 y: touch.pageY 1095 }; 1096 } 1097 }; 1098 1099 var def = { 1100 touched: false, 1101 touchdown: false, 1102 coords: { x:0, y:0 }, 1103 evObj: {} 1104 }; 1105 1106 $(this).data({ 1107 clickstate: def, 1108 timer: 0 1109 }).on('touchstart.fast', function(e) { 1110 window.clearTimeout($(this).data('timer')); 1111 $(this).data('clickstate', { 1112 touched: true, 1113 touchdown: true, 1114 coords: getCoords(e.originalEvent), 1115 evObj: e 1116 }); 1117 }).on('touchmove.fast', function(e) { 1118 var coords = getCoords(e.originalEvent), 1119 state = $(this).data('clickstate'), 1120 distance = Math.max( 1121 Math.abs(state.coords.x - coords.x), 1122 Math.abs(state.coords.y - coords.y) 1123 ); 1124 if ( distance > 6 ) { 1125 $(this).data('clickstate', $.extend(state, { 1126 touchdown: false 1127 })); 1128 } 1129 }).on('touchend.fast', function(e) { 1130 var $this = $(this), 1131 state = $this.data('clickstate'); 1132 if(state.touchdown) { 1133 handleObj.handler.call(this, e); 1134 } 1135 $this.data('timer', window.setTimeout(function() { 1136 $this.data('clickstate', def); 1137 }, 400)); 1138 }).on('click.fast', function(e) { 1139 var state = $(this).data('clickstate'); 1140 if ( state.touched ) { 1141 return false; 1142 } 1143 $(this).data('clickstate', def); 1144 handleObj.handler.call(this, e); 1145 }); 1146 }, 1147 remove: function() { 1148 $(this).off('touchstart.fast touchmove.fast touchend.fast click.fast'); 1149 } 1150}; 1151 1152// trigger resize on orientationchange (IOS7) 1153$win.on( 'orientationchange', function() { 1154 $(this).resize(); 1155}); 1156 1157/** 1158 The main Galleria class 1159 1160 @class 1161 @constructor 1162 1163 @example var gallery = new Galleria(); 1164 1165 @author http://wib.io 1166 1167 @requires jQuery 1168 1169*/ 1170 1171Galleria = function() { 1172 1173 var self = this; 1174 1175 // internal options 1176 this._options = {}; 1177 1178 // flag for controlling play/pause 1179 this._playing = false; 1180 1181 // internal interval for slideshow 1182 this._playtime = 5000; 1183 1184 // internal variable for the currently active image 1185 this._active = null; 1186 1187 // the internal queue, arrayified 1188 this._queue = { length: 0 }; 1189 1190 // the internal data array 1191 this._data = []; 1192 1193 // the internal dom collection 1194 this._dom = {}; 1195 1196 // the internal thumbnails array 1197 this._thumbnails = []; 1198 1199 // the internal layers array 1200 this._layers = []; 1201 1202 // internal init flag 1203 this._initialized = false; 1204 1205 // internal firstrun flag 1206 this._firstrun = false; 1207 1208 // global stagewidth/height 1209 this._stageWidth = 0; 1210 this._stageHeight = 0; 1211 1212 // target holder 1213 this._target = undef; 1214 1215 // bind hashes 1216 this._binds = []; 1217 1218 // instance id 1219 this._id = parseInt(M.random()*10000, 10); 1220 1221 // add some elements 1222 var divs = 'container stage images image-nav image-nav-left image-nav-right ' + 1223 'info info-text info-title info-description ' + 1224 'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' + 1225 'loader counter tooltip', 1226 spans = 'current total'; 1227 1228 $.each( divs.split(' '), function( i, elemId ) { 1229 self._dom[ elemId ] = Utils.create( 'galleria-' + elemId ); 1230 }); 1231 1232 $.each( spans.split(' '), function( i, elemId ) { 1233 self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' ); 1234 }); 1235 1236 // the internal keyboard object 1237 // keeps reference of the keybinds and provides helper methods for binding keys 1238 var keyboard = this._keyboard = { 1239 1240 keys : { 1241 'UP': 38, 1242 'DOWN': 40, 1243 'LEFT': 37, 1244 'RIGHT': 39, 1245 'RETURN': 13, 1246 'ESCAPE': 27, 1247 'BACKSPACE': 8, 1248 'SPACE': 32 1249 }, 1250 1251 map : {}, 1252 1253 bound: false, 1254 1255 press: function(e) { 1256 var key = e.keyCode || e.which; 1257 if ( key in keyboard.map && typeof keyboard.map[key] === 'function' ) { 1258 keyboard.map[key].call(self, e); 1259 } 1260 }, 1261 1262 attach: function(map) { 1263 1264 var key, up; 1265 1266 for( key in map ) { 1267 if ( map.hasOwnProperty( key ) ) { 1268 up = key.toUpperCase(); 1269 if ( up in keyboard.keys ) { 1270 keyboard.map[ keyboard.keys[up] ] = map[key]; 1271 } else { 1272 keyboard.map[ up ] = map[key]; 1273 } 1274 } 1275 } 1276 if ( !keyboard.bound ) { 1277 keyboard.bound = true; 1278 $doc.on('keydown', keyboard.press); 1279 } 1280 }, 1281 1282 detach: function() { 1283 keyboard.bound = false; 1284 keyboard.map = {}; 1285 $doc.off('keydown', keyboard.press); 1286 } 1287 }; 1288 1289 // internal controls for keeping track of active / inactive images 1290 var controls = this._controls = { 1291 1292 0: undef, 1293 1294 1: undef, 1295 1296 active : 0, 1297 1298 swap : function() { 1299 controls.active = controls.active ? 0 : 1; 1300 }, 1301 1302 getActive : function() { 1303 return self._options.swipe ? controls.slides[ self._active ] : controls[ controls.active ]; 1304 }, 1305 1306 getNext : function() { 1307 return self._options.swipe ? controls.slides[ self.getNext( self._active ) ] : controls[ 1 - controls.active ]; 1308 }, 1309 1310 slides : [], 1311 1312 frames: [], 1313 1314 layers: [] 1315 }; 1316 1317 // internal carousel object 1318 var carousel = this._carousel = { 1319 1320 // shortcuts 1321 next: self.$('thumb-nav-right'), 1322 prev: self.$('thumb-nav-left'), 1323 1324 // cache the width 1325 width: 0, 1326 1327 // track the current position 1328 current: 0, 1329 1330 // cache max value 1331 max: 0, 1332 1333 // save all hooks for each width in an array 1334 hooks: [], 1335 1336 // update the carousel 1337 // you can run this method anytime, f.ex on window.resize 1338 update: function() { 1339 var w = 0, 1340 h = 0, 1341 hooks = [0]; 1342 1343 $.each( self._thumbnails, function( i, thumb ) { 1344 if ( thumb.ready ) { 1345 w += thumb.outerWidth || $( thumb.container ).outerWidth( true ); 1346 // Due to a bug in jquery, outerwidth() returns the floor of the actual outerwidth, 1347 // if the browser is zoom to a value other than 100%. height() returns the floating point value. 1348 var containerWidth = $( thumb.container).width(); 1349 w += containerWidth - M.floor(containerWidth); 1350 1351 hooks[ i+1 ] = w; 1352 h = M.max( h, thumb.outerHeight || $( thumb.container).outerHeight( true ) ); 1353 } 1354 }); 1355 1356 self.$( 'thumbnails' ).css({ 1357 width: w, 1358 height: h 1359 }); 1360 1361 carousel.max = w; 1362 carousel.hooks = hooks; 1363 carousel.width = self.$( 'thumbnails-list' ).width(); 1364 carousel.setClasses(); 1365 1366 self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > carousel.width ); 1367 1368 // one extra calculation 1369 carousel.width = self.$( 'thumbnails-list' ).width(); 1370 1371 // todo: fix so the carousel moves to the left 1372 }, 1373 1374 bindControls: function() { 1375 1376 var i; 1377 1378 carousel.next.on( 'click:fast', function(e) { 1379 e.preventDefault(); 1380 1381 if ( self._options.carouselSteps === 'auto' ) { 1382 1383 for ( i = carousel.current; i < carousel.hooks.length; i++ ) { 1384 if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) { 1385 carousel.set(i - 2); 1386 break; 1387 } 1388 } 1389 1390 } else { 1391 carousel.set( carousel.current + self._options.carouselSteps); 1392 } 1393 }); 1394 1395 carousel.prev.on( 'click:fast', function(e) { 1396 e.preventDefault(); 1397 1398 if ( self._options.carouselSteps === 'auto' ) { 1399 1400 for ( i = carousel.current; i >= 0; i-- ) { 1401 if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) { 1402 carousel.set( i + 2 ); 1403 break; 1404 } else if ( i === 0 ) { 1405 carousel.set( 0 ); 1406 break; 1407 } 1408 } 1409 } else { 1410 carousel.set( carousel.current - self._options.carouselSteps ); 1411 } 1412 }); 1413 }, 1414 1415 // calculate and set positions 1416 set: function( i ) { 1417 i = M.max( i, 0 ); 1418 while ( carousel.hooks[i - 1] + carousel.width >= carousel.max && i >= 0 ) { 1419 i--; 1420 } 1421 carousel.current = i; 1422 carousel.animate(); 1423 }, 1424 1425 // get the last position 1426 getLast: function(i) { 1427 return ( i || carousel.current ) - 1; 1428 }, 1429 1430 // follow the active image 1431 follow: function(i) { 1432 1433 //don't follow if position fits 1434 if ( i === 0 || i === carousel.hooks.length - 2 ) { 1435 carousel.set( i ); 1436 return; 1437 } 1438 1439 // calculate last position 1440 var last = carousel.current; 1441 while( carousel.hooks[last] - carousel.hooks[ carousel.current ] < 1442 carousel.width && last <= carousel.hooks.length ) { 1443 last ++; 1444 } 1445 1446 // set position 1447 if ( i - 1 < carousel.current ) { 1448 carousel.set( i - 1 ); 1449 } else if ( i + 2 > last) { 1450 carousel.set( i - last + carousel.current + 2 ); 1451 } 1452 }, 1453 1454 // helper for setting disabled classes 1455 setClasses: function() { 1456 carousel.prev.toggleClass( 'disabled', !carousel.current ); 1457 carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width >= carousel.max ); 1458 }, 1459 1460 // the animation method 1461 animate: function(to) { 1462 carousel.setClasses(); 1463 var num = carousel.hooks[ carousel.current ] * -1; 1464 1465 if ( isNaN( num ) ) { 1466 return; 1467 } 1468 1469 // FF 24 bug 1470 self.$( 'thumbnails' ).css('left', function() { 1471 return $(this).css('left'); 1472 }); 1473 1474 Utils.animate(self.get( 'thumbnails' ), { 1475 left: num 1476 },{ 1477 duration: self._options.carouselSpeed, 1478 easing: self._options.easing, 1479 queue: false 1480 }); 1481 } 1482 }; 1483 1484 // tooltip control 1485 // added in 1.2 1486 var tooltip = this._tooltip = { 1487 1488 initialized : false, 1489 1490 open: false, 1491 1492 timer: 'tooltip' + self._id, 1493 1494 swapTimer: 'swap' + self._id, 1495 1496 init: function() { 1497 1498 tooltip.initialized = true; 1499 1500 var css = '.galleria-tooltip{padding:3px 8px;max-width:50%;background:#ffe;color:#000;z-index:3;position:absolute;font-size:11px;line-height:1.3;' + 1501 'opacity:0;box-shadow:0 0 2px rgba(0,0,0,.4);-moz-box-shadow:0 0 2px rgba(0,0,0,.4);-webkit-box-shadow:0 0 2px rgba(0,0,0,.4);}'; 1502 1503 Utils.insertStyleTag( css, 'galleria-tooltip' ); 1504 1505 self.$( 'tooltip' ).css({ 1506 opacity: 0.8, 1507 visibility: 'visible', 1508 display: 'none' 1509 }); 1510 1511 }, 1512 1513 // move handler 1514 move: function( e ) { 1515 var mouseX = self.getMousePosition(e).x, 1516 mouseY = self.getMousePosition(e).y, 1517 $elem = self.$( 'tooltip' ), 1518 x = mouseX, 1519 y = mouseY, 1520 height = $elem.outerHeight( true ) + 1, 1521 width = $elem.outerWidth( true ), 1522 limitY = height + 15; 1523 1524 var maxX = self.$( 'container' ).width() - width - 2, 1525 maxY = self.$( 'container' ).height() - height - 2; 1526 1527 if ( !isNaN(x) && !isNaN(y) ) { 1528 1529 x += 10; 1530 y -= ( height+8 ); 1531 1532 x = M.max( 0, M.min( maxX, x ) ); 1533 y = M.max( 0, M.min( maxY, y ) ); 1534 1535 if( mouseY < limitY ) { 1536 y = limitY; 1537 } 1538 1539 $elem.css({ left: x, top: y }); 1540 } 1541 }, 1542 1543 // bind elements to the tooltip 1544 // you can bind multiple elementIDs using { elemID : function } or { elemID : string } 1545 // you can also bind single DOM elements using bind(elem, string) 1546 bind: function( elem, value ) { 1547 1548 // todo: revise if alternative tooltip is needed for mobile devices 1549 if (Galleria.TOUCH) { 1550 return; 1551 } 1552 1553 if (! tooltip.initialized ) { 1554 tooltip.init(); 1555 } 1556 1557 var mouseout = function() { 1558 self.$( 'container' ).off( 'mousemove', tooltip.move ); 1559 self.clearTimer( tooltip.timer ); 1560 1561 self.$( 'tooltip' ).stop().animate({ 1562 opacity: 0 1563 }, 200, function() { 1564 1565 self.$( 'tooltip' ).hide(); 1566 1567 self.addTimer( tooltip.swapTimer, function() { 1568 tooltip.open = false; 1569 }, 1000); 1570 }); 1571 }; 1572 1573 var hover = function( elem, value) { 1574 1575 tooltip.define( elem, value ); 1576 1577 $( elem ).hover(function() { 1578 1579 self.clearTimer( tooltip.swapTimer ); 1580 self.$('container').off( 'mousemove', tooltip.move ).on( 'mousemove', tooltip.move ).trigger( 'mousemove' ); 1581 tooltip.show( elem ); 1582 1583 self.addTimer( tooltip.timer, function() { 1584 self.$( 'tooltip' ).stop().show().animate({ 1585 opacity: 1 1586 }); 1587 tooltip.open = true; 1588 1589 }, tooltip.open ? 0 : 500); 1590 1591 }, mouseout).click(mouseout); 1592 }; 1593 1594 if ( typeof value === 'string' ) { 1595 hover( ( elem in self._dom ? self.get( elem ) : elem ), value ); 1596 } else { 1597 // asume elemID here 1598 $.each( elem, function( elemID, val ) { 1599 hover( self.get(elemID), val ); 1600 }); 1601 } 1602 }, 1603 1604 show: function( elem ) { 1605 1606 elem = $( elem in self._dom ? self.get(elem) : elem ); 1607 1608 var text = elem.data( 'tt' ), 1609 mouseup = function( e ) { 1610 1611 // attach a tiny settimeout to make sure the new tooltip is filled 1612 window.setTimeout( (function( ev ) { 1613 return function() { 1614 tooltip.move( ev ); 1615 }; 1616 }( e )), 10); 1617 1618 elem.off( 'mouseup', mouseup ); 1619 1620 }; 1621 1622 text = typeof text === 'function' ? text() : text; 1623 1624 if ( ! text ) { 1625 return; 1626 } 1627 1628 self.$( 'tooltip' ).html( text.replace(/\s/, ' ') ); 1629 1630 // trigger mousemove on mouseup in case of click 1631 elem.on( 'mouseup', mouseup ); 1632 }, 1633 1634 define: function( elem, value ) { 1635 1636 // we store functions, not strings 1637 if (typeof value !== 'function') { 1638 var s = value; 1639 value = function() { 1640 return s; 1641 }; 1642 } 1643 1644 elem = $( elem in self._dom ? self.get(elem) : elem ).data('tt', value); 1645 1646 tooltip.show( elem ); 1647 1648 } 1649 }; 1650 1651 // internal fullscreen control 1652 var fullscreen = this._fullscreen = { 1653 1654 scrolled: 0, 1655 1656 crop: undef, 1657 1658 active: false, 1659 1660 prev: $(), 1661 1662 beforeEnter: function(fn){ fn(); }, 1663 beforeExit: function(fn){ fn(); }, 1664 1665 keymap: self._keyboard.map, 1666 1667 parseCallback: function( callback, enter ) { 1668 1669 return _transitions.active ? function() { 1670 if ( typeof callback == 'function' ) { 1671 callback.call(self); 1672 } 1673 var active = self._controls.getActive(), 1674 next = self._controls.getNext(); 1675 1676 self._scaleImage( next ); 1677 self._scaleImage( active ); 1678 1679 if ( enter && self._options.trueFullscreen ) { 1680 // Firefox bug, revise later 1681 $( active.container ).add( next.container ).trigger( 'transitionend' ); 1682 } 1683 1684 } : callback; 1685 1686 }, 1687 1688 enter: function( callback ) { 1689 1690 fullscreen.beforeEnter(function() { 1691 1692 callback = fullscreen.parseCallback( callback, true ); 1693 1694 if ( self._options.trueFullscreen && _nativeFullscreen.support ) { 1695 1696 // do some stuff prior animation for wmoother transitions 1697 1698 fullscreen.active = true; 1699 1700 Utils.forceStyles( self.get('container'), { 1701 width: '100%', 1702 height: '100%' 1703 }); 1704 1705 self.rescale(); 1706 1707 if ( Galleria.MAC ) { 1708 if ( !( Galleria.SAFARI && /version\/[1-5]/.test(NAV)) ) { 1709 self.$('container').css('opacity', 0).addClass('fullscreen'); 1710 window.setTimeout(function() { 1711 fullscreen.scale(); 1712 self.$('container').css('opacity', 1); 1713 }, 50); 1714 } else { 1715 self.$('stage').css('opacity', 0); 1716 window.setTimeout(function() { 1717 fullscreen.scale(); 1718 self.$('stage').css('opacity', 1); 1719 },4); 1720 } 1721 } else { 1722 self.$('container').addClass('fullscreen'); 1723 } 1724 1725 $win.resize( fullscreen.scale ); 1726 1727 _nativeFullscreen.enter( self, callback, self.get('container') ); 1728 1729 } else { 1730 1731 fullscreen.scrolled = $win.scrollTop(); 1732 if( !Galleria.TOUCH ) { 1733 window.scrollTo(0, 0); 1734 } 1735 1736 fullscreen._enter( callback ); 1737 } 1738 }); 1739 1740 }, 1741 1742 _enter: function( callback ) { 1743 1744 fullscreen.active = true; 1745 1746 if ( IFRAME ) { 1747 1748 fullscreen.iframe = (function() { 1749 1750 var elem, 1751 refer = doc.referrer, 1752 test = doc.createElement('a'), 1753 loc = window.location; 1754 1755 test.href = refer; 1756 1757 if( test.protocol != loc.protocol || 1758 test.hostname != loc.hostname || 1759 test.port != loc.port ) { 1760 Galleria.raise('Parent fullscreen not available. Iframe protocol, domains and ports must match.'); 1761 return false; 1762 } 1763 1764 fullscreen.pd = window.parent.document; 1765 1766 $( fullscreen.pd ).find('iframe').each(function() { 1767 var idoc = this.contentDocument || this.contentWindow.document; 1768 if ( idoc === doc ) { 1769 elem = this; 1770 return false; 1771 } 1772 }); 1773 1774 return elem; 1775 }()); 1776 1777 } 1778 1779 // hide the image until rescale is complete 1780 Utils.hide( self.getActiveImage() ); 1781 1782 if ( IFRAME && fullscreen.iframe ) { 1783 fullscreen.iframe.scrolled = $( window.parent ).scrollTop(); 1784 window.parent.scrollTo(0, 0); 1785 } 1786 1787 var data = self.getData(), 1788 options = self._options, 1789 inBrowser = !self._options.trueFullscreen || !_nativeFullscreen.support, 1790 htmlbody = { 1791 height: '100%', 1792 overflow: 'hidden', 1793 margin:0, 1794 padding:0 1795 }; 1796 1797 if (inBrowser) { 1798 1799 self.$('container').addClass('fullscreen'); 1800 fullscreen.prev = self.$('container').prev(); 1801 1802 if ( !fullscreen.prev.length ) { 1803 fullscreen.parent = self.$( 'container' ).parent(); 1804 } 1805 1806 // move 1807 self.$( 'container' ).appendTo( 'body' ); 1808 1809 // begin styleforce 1810 1811 Utils.forceStyles(self.get('container'), { 1812 position: Galleria.TOUCH ? 'absolute' : 'fixed', 1813 top: 0, 1814 left: 0, 1815 width: '100%', 1816 height: '100%', 1817 zIndex: 10000 1818 }); 1819 Utils.forceStyles( DOM().html, htmlbody ); 1820 Utils.forceStyles( DOM().body, htmlbody ); 1821 } 1822 1823 if ( IFRAME && fullscreen.iframe ) { 1824 Utils.forceStyles( fullscreen.pd.documentElement, htmlbody ); 1825 Utils.forceStyles( fullscreen.pd.body, htmlbody ); 1826 Utils.forceStyles( fullscreen.iframe, $.extend( htmlbody, { 1827 width: '100%', 1828 height: '100%', 1829 top: 0, 1830 left: 0, 1831 position: 'fixed', 1832 zIndex: 10000, 1833 border: 'none' 1834 })); 1835 } 1836 1837 // temporarily attach some keys 1838 // save the old ones first in a cloned object 1839 fullscreen.keymap = $.extend({}, self._keyboard.map); 1840 1841 self.attachKeyboard({ 1842 escape: self.exitFullscreen, 1843 right: self.next, 1844 left: self.prev 1845 }); 1846 1847 // temporarily save the crop 1848 fullscreen.crop = options.imageCrop; 1849 1850 // set fullscreen options 1851 if ( options.fullscreenCrop != undef ) { 1852 options.imageCrop = options.fullscreenCrop; 1853 } 1854 1855 // swap to big image if it's different from the display image 1856 if ( data && data.big && data.image !== data.big ) { 1857 var big = new Galleria.Picture(), 1858 cached = big.isCached( data.big ), 1859 index = self.getIndex(), 1860 thumb = self._thumbnails[ index ]; 1861 1862 self.trigger( { 1863 type: Galleria.LOADSTART, 1864 cached: cached, 1865 rewind: false, 1866 index: index, 1867 imageTarget: self.getActiveImage(), 1868 thumbTarget: thumb, 1869 galleriaData: data 1870 }); 1871 1872 big.load( data.big, function( big ) { 1873 self._scaleImage( big, { 1874 complete: function( big ) { 1875 self.trigger({ 1876 type: Galleria.LOADFINISH, 1877 cached: cached, 1878 index: index, 1879 rewind: false, 1880 imageTarget: big.image, 1881 thumbTarget: thumb 1882 }); 1883 var image = self._controls.getActive().image; 1884 if ( image ) { 1885 $( image ).width( big.image.width ).height( big.image.height ) 1886 .attr( 'style', $( big.image ).attr('style') ); 1887 if (big.image.src.srcset) { 1888 $( image ).attr( 'srcset', big.image.src.srcset ); 1889 } 1890 if (big.image.src.sizes) { 1891 $( image ).attr( 'sizes', big.image.src.sizes ); 1892 } 1893 $( image ).attr( 'src', big.image.src ); 1894 } 1895 } 1896 }); 1897 }); 1898 1899 var n = self.getNext(index), 1900 p = new Galleria.Picture(), 1901 ndata = self.getData( n ); 1902 p.preload( self.isFullscreen() && ndata.big ? ndata.big : ndata.image ); 1903 } 1904 1905 // init the first rescale and attach callbacks 1906 1907 self.rescale(function() { 1908 1909 self.addTimer(false, function() { 1910 // show the image after 50 ms 1911 if ( inBrowser ) { 1912 Utils.show( self.getActiveImage() ); 1913 } 1914 1915 if (typeof callback === 'function') { 1916 callback.call( self ); 1917 } 1918 self.rescale(); 1919 1920 }, 100); 1921 1922 self.trigger( Galleria.FULLSCREEN_ENTER ); 1923 }); 1924 1925 if ( !inBrowser ) { 1926 Utils.show( self.getActiveImage() ); 1927 } else { 1928 $win.resize( fullscreen.scale ); 1929 } 1930 1931 }, 1932 1933 scale : function() { 1934 self.rescale(); 1935 }, 1936 1937 exit: function( callback ) { 1938 1939 fullscreen.beforeExit(function() { 1940 1941 callback = fullscreen.parseCallback( callback ); 1942 1943 if ( self._options.trueFullscreen && _nativeFullscreen.support ) { 1944 _nativeFullscreen.exit( callback ); 1945 } else { 1946 fullscreen._exit( callback ); 1947 } 1948 }); 1949 }, 1950 1951 _exit: function( callback ) { 1952 1953 fullscreen.active = false; 1954 1955 var inBrowser = !self._options.trueFullscreen || !_nativeFullscreen.support, 1956 $container = self.$( 'container' ).removeClass( 'fullscreen' ); 1957 1958 // move back 1959 if ( fullscreen.parent ) { 1960 fullscreen.parent.prepend( $container ); 1961 } else { 1962 $container.insertAfter( fullscreen.prev ); 1963 } 1964 1965 if ( inBrowser ) { 1966 Utils.hide( self.getActiveImage() ); 1967 1968 // revert all styles 1969 Utils.revertStyles( self.get('container'), DOM().html, DOM().body ); 1970 1971 // scroll back 1972 if( !Galleria.TOUCH ) { 1973 window.scrollTo(0, fullscreen.scrolled); 1974 } 1975 1976 // reload iframe src manually 1977 var frame = self._controls.frames[ self._controls.active ]; 1978 if ( frame && frame.image ) { 1979 frame.image.src = frame.image.src; 1980 } 1981 } 1982 1983 if ( IFRAME && fullscreen.iframe ) { 1984 Utils.revertStyles( fullscreen.pd.documentElement, fullscreen.pd.body, fullscreen.iframe ); 1985 if ( fullscreen.iframe.scrolled ) { 1986 window.parent.scrollTo(0, fullscreen.iframe.scrolled ); 1987 } 1988 } 1989 1990 // detach all keyboard events and apply the old keymap 1991 self.detachKeyboard(); 1992 self.attachKeyboard( fullscreen.keymap ); 1993 1994 // bring back cached options 1995 self._options.imageCrop = fullscreen.crop; 1996 1997 // return to original image 1998 var big = self.getData().big, 1999 image = self._controls.getActive().image; 2000 2001 if ( !self.getData().iframe && image && big && big == image.src ) { 2002 2003 window.setTimeout(function(src) { 2004 return function() { 2005 image.src = src; 2006 }; 2007 }( self.getData().image ), 1 ); 2008 2009 } 2010 2011 self.rescale(function() { 2012 self.addTimer(false, function() { 2013 2014 // show the image after 50 ms 2015 if ( inBrowser ) { 2016 Utils.show( self.getActiveImage() ); 2017 } 2018 2019 if ( typeof callback === 'function' ) { 2020 callback.call( self ); 2021 } 2022 2023 $win.trigger( 'resize' ); 2024 2025 }, 50); 2026 self.trigger( Galleria.FULLSCREEN_EXIT ); 2027 }); 2028 2029 $win.off('resize', fullscreen.scale); 2030 } 2031 }; 2032 2033 // the internal idle object for controlling idle states 2034 var idle = this._idle = { 2035 2036 trunk: [], 2037 2038 bound: false, 2039 2040 active: false, 2041 2042 add: function(elem, to, from, hide) { 2043 if ( !elem || Galleria.TOUCH ) { 2044 return; 2045 } 2046 if (!idle.bound) { 2047 idle.addEvent(); 2048 } 2049 elem = $(elem); 2050 2051 if ( typeof from == 'boolean' ) { 2052 hide = from; 2053 from = {}; 2054 } 2055 2056 from = from || {}; 2057 2058 var extract = {}, 2059 style; 2060 2061 for ( style in to ) { 2062 if ( to.hasOwnProperty( style ) ) { 2063 extract[ style ] = elem.css( style ); 2064 } 2065 } 2066 2067 elem.data('idle', { 2068 from: $.extend( extract, from ), 2069 to: to, 2070 complete: true, 2071 busy: false 2072 }); 2073 2074 if ( !hide ) { 2075 idle.addTimer(); 2076 } else { 2077 elem.css( to ); 2078 } 2079 idle.trunk.push(elem); 2080 }, 2081 2082 remove: function(elem) { 2083 2084 elem = $(elem); 2085 2086 $.each(idle.trunk, function(i, el) { 2087 if ( el && el.length && !el.not(elem).length ) { 2088 elem.css( elem.data( 'idle' ).from ); 2089 idle.trunk.splice(i, 1); 2090 } 2091 }); 2092 2093 if (!idle.trunk.length) { 2094 idle.removeEvent(); 2095 self.clearTimer( idle.timer ); 2096 } 2097 }, 2098 2099 addEvent : function() { 2100 idle.bound = true; 2101 self.$('container').on( 'mousemove click', idle.showAll ); 2102 if ( self._options.idleMode == 'hover' ) { 2103 self.$('container').on( 'mouseleave', idle.hide ); 2104 } 2105 }, 2106 2107 removeEvent : function() { 2108 idle.bound = false; 2109 self.$('container').on( 'mousemove click', idle.showAll ); 2110 if ( self._options.idleMode == 'hover' ) { 2111 self.$('container').off( 'mouseleave', idle.hide ); 2112 } 2113 }, 2114 2115 addTimer : function() { 2116 if( self._options.idleMode == 'hover' ) { 2117 return; 2118 } 2119 self.addTimer( 'idle', function() { 2120 idle.hide(); 2121 }, self._options.idleTime ); 2122 }, 2123 2124 hide : function() { 2125 2126 if ( !self._options.idleMode || self.getIndex() === false ) { 2127 return; 2128 } 2129 2130 self.trigger( Galleria.IDLE_ENTER ); 2131 2132 var len = idle.trunk.length; 2133 2134 $.each( idle.trunk, function(i, elem) { 2135 2136 var data = elem.data('idle'); 2137 2138 if (! data) { 2139 return; 2140 } 2141 2142 elem.data('idle').complete = false; 2143 2144 Utils.animate( elem, data.to, { 2145 duration: self._options.idleSpeed, 2146 complete: function() { 2147 if ( i == len-1 ) { 2148 idle.active = false; 2149 } 2150 } 2151 }); 2152 }); 2153 }, 2154 2155 showAll : function() { 2156 2157 self.clearTimer( 'idle' ); 2158 2159 $.each( idle.trunk, function( i, elem ) { 2160 idle.show( elem ); 2161 }); 2162 }, 2163 2164 show: function(elem) { 2165 2166 var data = elem.data('idle'); 2167 2168 if ( !idle.active || ( !data.busy && !data.complete ) ) { 2169 2170 data.busy = true; 2171 2172 self.trigger( Galleria.IDLE_EXIT ); 2173 2174 self.clearTimer( 'idle' ); 2175 2176 Utils.animate( elem, data.from, { 2177 duration: self._options.idleSpeed/2, 2178 complete: function() { 2179 idle.active = true; 2180 $(elem).data('idle').busy = false; 2181 $(elem).data('idle').complete = true; 2182 } 2183 }); 2184 2185 } 2186 idle.addTimer(); 2187 } 2188 }; 2189 2190 // internal lightbox object 2191 // creates a predesigned lightbox for simple popups of images in galleria 2192 var lightbox = this._lightbox = { 2193 2194 width : 0, 2195 2196 height : 0, 2197 2198 initialized : false, 2199 2200 active : null, 2201 2202 image : null, 2203 2204 elems : {}, 2205 2206 keymap: false, 2207 2208 init : function() { 2209 2210 if ( lightbox.initialized ) { 2211 return; 2212 } 2213 lightbox.initialized = true; 2214 2215 // create some elements to work with 2216 var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image', 2217 el = {}, 2218 op = self._options, 2219 css = '', 2220 abs = 'position:absolute;', 2221 prefix = 'lightbox-', 2222 cssMap = { 2223 overlay: 'position:fixed;display:none;opacity:'+op.overlayOpacity+';filter:alpha(opacity='+(op.overlayOpacity*100)+ 2224 ');top:0;left:0;width:100%;height:100%;background:'+op.overlayBackground+';z-index:99990', 2225 box: 'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991', 2226 shadow: abs+'background:#000;width:100%;height:100%;', 2227 content: abs+'background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden', 2228 info: abs+'bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px', 2229 close: abs+'top:10px;right:10px;height:20px;width:20px;background:#fff;text-align:center;cursor:pointer;color:#444;font:16px/22px arial,sans-serif;z-index:99999', 2230 image: abs+'top:10px;left:10px;right:10px;bottom:30px;overflow:hidden;display:block;', 2231 prevholder: abs+'width:50%;top:0;bottom:40px;cursor:pointer;', 2232 nextholder: abs+'width:50%;top:0;bottom:40px;right:-1px;cursor:pointer;', 2233 prev: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;left:20px;display:none;text-align:center;color:#000;font:bold 16px/36px arial,sans-serif', 2234 next: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;right:20px;left:auto;display:none;font:bold 16px/36px arial,sans-serif;text-align:center;color:#000', 2235 title: 'float:left', 2236 counter: 'float:right;margin-left:8px;' 2237 }, 2238 hover = function(elem) { 2239 return elem.hover( 2240 function() { $(this).css( 'color', '#bbb' ); }, 2241 function() { $(this).css( 'color', '#444' ); } 2242 ); 2243 }, 2244 appends = {}; 2245 2246 // fix for navigation hovers transparent background event "feature" 2247 var exs = ''; 2248 if ( IE > 7 ) { 2249 exs = IE < 9 ? 'background:#000;filter:alpha(opacity=0);' : 'background:rgba(0,0,0,0);'; 2250 } else { 2251 exs = 'z-index:99999'; 2252 } 2253 2254 cssMap.nextholder += exs; 2255 cssMap.prevholder += exs; 2256 2257 // create and insert CSS 2258 $.each(cssMap, function( key, value ) { 2259 css += '.galleria-'+prefix+key+'{'+value+'}'; 2260 }); 2261 2262 css += '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'prevholder,'+ 2263 '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'nextholder{'+ 2264 'width:100px;height:100px;top:50%;margin-top:-70px}'; 2265 2266 Utils.insertStyleTag( css, 'galleria-lightbox' ); 2267 2268 // create the elements 2269 $.each(elems.split(' '), function( i, elemId ) { 2270 self.addElement( 'lightbox-' + elemId ); 2271 el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId ); 2272 }); 2273 2274 // initiate the image 2275 lightbox.image = new Galleria.Picture(); 2276 2277 // append the elements 2278 $.each({ 2279 box: 'shadow content close prevholder nextholder', 2280 info: 'title counter', 2281 content: 'info image', 2282 prevholder: 'prev', 2283 nextholder: 'next' 2284 }, function( key, val ) { 2285 var arr = []; 2286 $.each( val.split(' '), function( i, prop ) { 2287 arr.push( prefix + prop ); 2288 }); 2289 appends[ prefix+key ] = arr; 2290 }); 2291 2292 self.append( appends ); 2293 2294 $( el.image ).append( lightbox.image.container ); 2295 2296 $( DOM().body ).append( el.overlay, el.box ); 2297 2298 // add the prev/next nav and bind some controls 2299 2300 hover( $( el.close ).on( 'click:fast', lightbox.hide ).html('×') ); 2301 2302 $.each( ['Prev','Next'], function(i, dir) { 2303 2304 var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '‹ ' : ' ›' ), 2305 $e = $( el[ dir.toLowerCase()+'holder'] ); 2306 2307 $e.on( 'click:fast', function() { 2308 lightbox[ 'show' + dir ](); 2309 }); 2310 2311 // IE7 and touch devices will simply show the nav 2312 if ( IE < 8 || Galleria.TOUCH ) { 2313 $d.show(); 2314 return; 2315 } 2316 2317 $e.hover( function() { 2318 $d.show(); 2319 }, function(e) { 2320 $d.stop().fadeOut( 200 ); 2321 }); 2322 2323 }); 2324 $( el.overlay ).on( 'click:fast', lightbox.hide ); 2325 2326 // the lightbox animation is slow on ipad 2327 if ( Galleria.IPAD ) { 2328 self._options.lightboxTransitionSpeed = 0; 2329 } 2330 2331 }, 2332 2333 rescale: function(event) { 2334 2335 // calculate 2336 var width = M.min( $win.width()-40, lightbox.width ), 2337 height = M.min( $win.height()-60, lightbox.height ), 2338 ratio = M.min( width / lightbox.width, height / lightbox.height ), 2339 destWidth = M.round( lightbox.width * ratio ) + 40, 2340 destHeight = M.round( lightbox.height * ratio ) + 60, 2341 to = { 2342 width: destWidth, 2343 height: destHeight, 2344 'margin-top': M.ceil( destHeight / 2 ) *- 1, 2345 'margin-left': M.ceil( destWidth / 2 ) *- 1 2346 }; 2347 2348 // if rescale event, don't animate 2349 if ( event ) { 2350 $( lightbox.elems.box ).css( to ); 2351 } else { 2352 $( lightbox.elems.box ).animate( to, { 2353 duration: self._options.lightboxTransitionSpeed, 2354 easing: self._options.easing, 2355 complete: function() { 2356 var image = lightbox.image, 2357 speed = self._options.lightboxFadeSpeed; 2358 2359 self.trigger({ 2360 type: Galleria.LIGHTBOX_IMAGE, 2361 imageTarget: image.image 2362 }); 2363 2364 $( image.container ).show(); 2365 2366 $( image.image ).animate({ opacity: 1 }, speed); 2367 Utils.show( lightbox.elems.info, speed ); 2368 } 2369 }); 2370 } 2371 }, 2372 2373 hide: function() { 2374 2375 // remove the image 2376 lightbox.image.image = null; 2377 2378 $win.off('resize', lightbox.rescale); 2379 2380 $( lightbox.elems.box ).hide().find( 'iframe' ).remove(); 2381 2382 Utils.hide( lightbox.elems.info ); 2383 2384 self.detachKeyboard(); 2385 self.attachKeyboard( lightbox.keymap ); 2386 2387 lightbox.keymap = false; 2388 2389 Utils.hide( lightbox.elems.overlay, 200, function() { 2390 $( this ).hide().css( 'opacity', self._options.overlayOpacity ); 2391 self.trigger( Galleria.LIGHTBOX_CLOSE ); 2392 }); 2393 }, 2394 2395 showNext: function() { 2396 lightbox.show( self.getNext( lightbox.active ) ); 2397 }, 2398 2399 showPrev: function() { 2400 lightbox.show( self.getPrev( lightbox.active ) ); 2401 }, 2402 2403 show: function(index) { 2404 2405 lightbox.active = index = typeof index === 'number' ? index : self.getIndex() || 0; 2406 2407 if ( !lightbox.initialized ) { 2408 lightbox.init(); 2409 } 2410 2411 // trigger the event 2412 self.trigger( Galleria.LIGHTBOX_OPEN ); 2413 2414 // temporarily attach some keys 2415 // save the old ones first in a cloned object 2416 if ( !lightbox.keymap ) { 2417 2418 lightbox.keymap = $.extend({}, self._keyboard.map); 2419 2420 self.attachKeyboard({ 2421 escape: lightbox.hide, 2422 right: lightbox.showNext, 2423 left: lightbox.showPrev 2424 }); 2425 } 2426 2427 $win.off('resize', lightbox.rescale ); 2428 2429 var data = self.getData(index), 2430 total = self.getDataLength(), 2431 n = self.getNext( index ), 2432 ndata, p, i; 2433 2434 Utils.hide( lightbox.elems.info ); 2435 2436 try { 2437 for ( i = self._options.preload; i > 0; i-- ) { 2438 p = new Galleria.Picture(); 2439 ndata = self.getData( n ); 2440 p.preload( ndata.big ? ndata.big : ndata.image ); 2441 n = self.getNext( n ); 2442 } 2443 } catch(e) {} 2444 2445 lightbox.image.isIframe = ( data.iframe && !data.image ); 2446 2447 $( lightbox.elems.box ).toggleClass( 'iframe', lightbox.image.isIframe ); 2448 2449 $( lightbox.image.container ).find( '.galleria-videoicon' ).remove(); 2450 2451 lightbox.image.load( data.big || data.image || data.iframe, function( image ) { 2452 2453 if ( image.isIframe ) { 2454 2455 var cw = $(window).width(), 2456 ch = $(window).height(); 2457 2458 if ( image.video && self._options.maxVideoSize ) { 2459 var r = M.min( self._options.maxVideoSize/cw, self._options.maxVideoSize/ch ); 2460 if ( r < 1 ) { 2461 cw *= r; 2462 ch *= r; 2463 } 2464 } 2465 lightbox.width = cw; 2466 lightbox.height = ch; 2467 2468 } else { 2469 lightbox.width = image.original.width; 2470 lightbox.height = image.original.height; 2471 } 2472 2473 $( image.image ).css({ 2474 width: image.isIframe ? '100%' : '100.1%', 2475 height: image.isIframe ? '100%' : '100.1%', 2476 top: 0, 2477 bottom: 0, 2478 zIndex: 99998, 2479 opacity: 0, 2480 visibility: 'visible' 2481 }).parent().height('100%'); 2482 2483 lightbox.elems.title.innerHTML = data.title || ''; 2484 lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total; 2485 $win.resize( lightbox.rescale ); 2486 lightbox.rescale(); 2487 2488 if( data.image && data.iframe ) { 2489 2490 $( lightbox.elems.box ).addClass('iframe'); 2491 2492 if ( data.video ) { 2493 var $icon = _playIcon( image.container ).hide(); 2494 window.setTimeout(function() { 2495 $icon.fadeIn(200); 2496 }, 200); 2497 } 2498 2499 $( image.image ).css( 'cursor', 'pointer' ).mouseup((function(data, image) { 2500 return function(e) { 2501 $( lightbox.image.container ).find( '.galleria-videoicon' ).remove(); 2502 e.preventDefault(); 2503 image.isIframe = true; 2504 image.load( data.iframe + ( data.video ? '&autoplay=1' : '' ), { 2505 width: '100%', 2506 height: IE < 8 ? $( lightbox.image.container ).height() : '100%' 2507 }); 2508 }; 2509 }(data, image))); 2510 } 2511 }); 2512 2513 $( lightbox.elems.overlay ).show().css( 'visibility', 'visible' ); 2514 $( lightbox.elems.box ).show(); 2515 } 2516 }; 2517 2518 // the internal timeouts object 2519 // provides helper methods for controlling timeouts 2520 2521 var _timer = this._timer = { 2522 2523 trunk: {}, 2524 2525 add: function( id, fn, delay, loop ) { 2526 id = id || new Date().getTime(); 2527 loop = loop || false; 2528 this.clear( id ); 2529 if ( loop ) { 2530 var old = fn; 2531 fn = function() { 2532 old(); 2533 _timer.add( id, fn, delay ); 2534 }; 2535 } 2536 this.trunk[ id ] = window.setTimeout( fn, delay ); 2537 }, 2538 2539 clear: function( id ) { 2540 2541 var del = function( i ) { 2542 window.clearTimeout( this.trunk[ i ] ); 2543 delete this.trunk[ i ]; 2544 }, i; 2545 2546 if ( !!id && id in this.trunk ) { 2547 del.call( this, id ); 2548 2549 } else if ( typeof id === 'undefined' ) { 2550 for ( i in this.trunk ) { 2551 if ( this.trunk.hasOwnProperty( i ) ) { 2552 del.call( this, i ); 2553 } 2554 } 2555 } 2556 } 2557 }; 2558 2559 return this; 2560}; 2561 2562// end Galleria constructor 2563 2564Galleria.prototype = { 2565 2566 // bring back the constructor reference 2567 2568 constructor: Galleria, 2569 2570 /** 2571 Use this function to initialize the gallery and start loading. 2572 Should only be called once per instance. 2573 2574 @param {HTMLElement} target The target element 2575 @param {Object} options The gallery options 2576 2577 @returns Instance 2578 */ 2579 2580 init: function( target, options ) { 2581 2582 options = _legacyOptions( options ); 2583 2584 // save the original ingredients 2585 this._original = { 2586 target: target, 2587 options: options, 2588 data: null 2589 }; 2590 2591 // save the target here 2592 this._target = this._dom.target = target.nodeName ? target : $( target ).get(0); 2593 2594 // save the original content for destruction 2595 this._original.html = this._target.innerHTML; 2596 2597 // push the instance 2598 _instances.push( this ); 2599 2600 // raise error if no target is detected 2601 if ( !this._target ) { 2602 Galleria.raise('Target not found', true); 2603 return; 2604 } 2605 2606 // apply options 2607 this._options = { 2608 autoplay: false, 2609 carousel: true, 2610 carouselFollow: true, // legacy, deprecate at 1.3 2611 carouselSpeed: 400, 2612 carouselSteps: 'auto', 2613 clicknext: false, 2614 dailymotion: { 2615 foreground: '%23EEEEEE', 2616 highlight: '%235BCEC5', 2617 background: '%23222222', 2618 logo: 0, 2619 hideInfos: 1 2620 }, 2621 dataConfig : function( elem ) { return {}; }, 2622 dataSelector: 'img', 2623 dataSort: false, 2624 dataSource: this._target, 2625 debug: undef, 2626 dummy: undef, // 1.2.5 2627 easing: 'galleria', 2628 extend: function(options) {}, 2629 fullscreenCrop: undef, // 1.2.5 2630 fullscreenDoubleTap: true, // 1.2.4 toggles fullscreen on double-tap for touch devices 2631 fullscreenTransition: undef, // 1.2.6 2632 height: 0, 2633 idleMode: true, // 1.2.4 toggles idleMode 2634 idleTime: 3000, 2635 idleSpeed: 200, 2636 imageCrop: false, 2637 imageMargin: 0, 2638 imagePan: false, 2639 imagePanSmoothness: 12, 2640 imagePosition: '50%', 2641 imageTimeout: undef, // 1.2.5 2642 initialTransition: undef, // 1.2.4, replaces transitionInitial 2643 keepSource: false, 2644 layerFollow: true, // 1.2.5 2645 lightbox: false, // 1.2.3 2646 lightboxFadeSpeed: 200, 2647 lightboxTransitionSpeed: 200, 2648 linkSourceImages: true, 2649 maxScaleRatio: undef, 2650 maxVideoSize: undef, // 1.2.9 2651 minScaleRatio: undef, // deprecated in 1.2.9 2652 overlayOpacity: 0.85, 2653 overlayBackground: '#0b0b0b', 2654 pauseOnInteraction: true, 2655 popupLinks: false, 2656 preload: 2, 2657 queue: true, 2658 responsive: true, 2659 show: 0, 2660 showInfo: true, 2661 showCounter: true, 2662 showImagenav: true, 2663 swipe: 'auto', // 1.2.4 -> revised in 1.3 -> changed type in 1.3.5 2664 theme: null, 2665 thumbCrop: true, 2666 thumbEventType: 'click:fast', 2667 thumbMargin: 0, 2668 thumbQuality: 'auto', 2669 thumbDisplayOrder: true, // 1.2.8 2670 thumbPosition: '50%', // 1.3 2671 thumbnails: true, 2672 touchTransition: undef, // 1.2.6 2673 transition: 'fade', 2674 transitionInitial: undef, // legacy, deprecate in 1.3. Use initialTransition instead. 2675 transitionSpeed: 400, 2676 trueFullscreen: true, // 1.2.7 2677 useCanvas: false, // 1.2.4 2678 variation: '', // 1.3.2 2679 videoPoster: true, // 1.3 2680 vimeo: { 2681 title: 0, 2682 byline: 0, 2683 portrait: 0, 2684 color: 'aaaaaa' 2685 }, 2686 wait: 5000, // 1.2.7 2687 width: 'auto', 2688 youtube: { 2689 modestbranding: 1, 2690 autohide: 1, 2691 color: 'white', 2692 hd: 1, 2693 rel: 0, 2694 showinfo: 0 2695 } 2696 }; 2697 2698 // legacy support for transitionInitial 2699 this._options.initialTransition = this._options.initialTransition || this._options.transitionInitial; 2700 2701 if ( options ) { 2702 2703 // turn off debug 2704 if ( options.debug === false ) { 2705 DEBUG = false; 2706 } 2707 2708 // set timeout 2709 if ( typeof options.imageTimeout === 'number' ) { 2710 TIMEOUT = options.imageTimeout; 2711 } 2712 2713 // set dummy 2714 if ( typeof options.dummy === 'string' ) { 2715 DUMMY = options.dummy; 2716 } 2717 2718 // set theme 2719 if ( typeof options.theme == 'string' ) { 2720 this._options.theme = options.theme; 2721 } 2722 } 2723 2724 // hide all content 2725 $( this._target ).children().hide(); 2726 2727 // Warn for quirks mode 2728 if ( Galleria.QUIRK ) { 2729 Galleria.raise('Your page is in Quirks mode, Galleria may not render correctly. Please validate your HTML and add a correct doctype.'); 2730 } 2731 2732 // now we just have to wait for the theme... 2733 // first check if it has already loaded 2734 if ( _loadedThemes.length ) { 2735 if ( this._options.theme ) { 2736 for ( var i=0; i<_loadedThemes.length; i++ ) { 2737 if( this._options.theme === _loadedThemes[i].name ) { 2738 this.theme = _loadedThemes[i]; 2739 break; 2740 } 2741 } 2742 } else { 2743 // if no theme sepcified, apply the first loaded theme 2744 this.theme = _loadedThemes[0]; 2745 } 2746 } 2747 2748 if ( typeof this.theme == 'object' ) { 2749 this._init(); 2750 } else { 2751 // if no theme is loaded yet, push the instance into a pool and run it when the theme is ready 2752 _pool.push( this ); 2753 } 2754 2755 return this; 2756 }, 2757 2758 // this method should only be called once per instance 2759 // for manipulation of data, use the .load method 2760 2761 _init: function() { 2762 2763 var self = this, 2764 options = this._options; 2765 2766 if ( this._initialized ) { 2767 Galleria.raise( 'Init failed: Gallery instance already initialized.' ); 2768 return this; 2769 } 2770 2771 this._initialized = true; 2772 2773 if ( !this.theme ) { 2774 Galleria.raise( 'Init failed: No theme found.', true ); 2775 return this; 2776 } 2777 2778 // merge the theme & caller options 2779 $.extend( true, options, this.theme.defaults, this._original.options, Galleria.configure.options ); 2780 2781 // internally we use boolean for swipe 2782 options.swipe = (function(s) { 2783 2784 if ( s == 'enforced' ) { return true; } 2785 2786 // legacy patch 2787 if( s === false || s == 'disabled' ) { return false; } 2788 2789 return !!Galleria.TOUCH; 2790 2791 }( options.swipe )); 2792 2793 // disable options that arent compatible with swipe 2794 if ( options.swipe ) { 2795 options.clicknext = false; 2796 options.imagePan = false; 2797 } 2798 2799 // check for canvas support 2800 (function( can ) { 2801 if ( !( 'getContext' in can ) ) { 2802 can = null; 2803 return; 2804 } 2805 _canvas = _canvas || { 2806 elem: can, 2807 context: can.getContext( '2d' ), 2808 cache: {}, 2809 length: 0 2810 }; 2811 }( doc.createElement( 'canvas' ) ) ); 2812 2813 // bind the gallery to run when data is ready 2814 this.bind( Galleria.DATA, function() { 2815 2816 // remove big if total pixels are less than 1024 (most phones) 2817 if ( window.screen && window.screen.width && Array.prototype.forEach ) { 2818 2819 this._data.forEach(function(data) { 2820 2821 var density = 'devicePixelRatio' in window ? window.devicePixelRatio : 1, 2822 m = M.max( window.screen.width, window.screen.height ); 2823 2824 if ( m*density < 1024 ) { 2825 data.big = data.image; 2826 } 2827 }); 2828 } 2829 2830 // save the new data 2831 this._original.data = this._data; 2832 2833 // lets show the counter here 2834 this.get('total').innerHTML = this.getDataLength(); 2835 2836 // cache the container 2837 var $container = this.$( 'container' ); 2838 2839 // set ratio if height is < 2 2840 if ( self._options.height < 2 ) { 2841 self._userRatio = self._ratio = self._options.height; 2842 } 2843 2844 // the gallery is ready, let's just wait for the css 2845 var num = { width: 0, height: 0 }; 2846 var testHeight = function() { 2847 return self.$( 'stage' ).height(); 2848 }; 2849 2850 // check container and thumbnail height 2851 Utils.wait({ 2852 until: function() { 2853 2854 // keep trying to get the value 2855 num = self._getWH(); 2856 $container.width( num.width ).height( num.height ); 2857 return testHeight() && num.width && num.height > 50; 2858 2859 }, 2860 success: function() { 2861 2862 self._width = num.width; 2863 self._height = num.height; 2864 self._ratio = self._ratio || num.height/num.width; 2865 2866 // for some strange reason, webkit needs a single setTimeout to play ball 2867 if ( Galleria.WEBKIT ) { 2868 window.setTimeout( function() { 2869 self._run(); 2870 }, 1); 2871 } else { 2872 self._run(); 2873 } 2874 }, 2875 error: function() { 2876 2877 // Height was probably not set, raise hard errors 2878 2879 if ( testHeight() ) { 2880 Galleria.raise('Could not extract sufficient width/height of the gallery container. Traced measures: width:' + num.width + 'px, height: ' + num.height + 'px.', true); 2881 } else { 2882 Galleria.raise('Could not extract a stage height from the CSS. Traced height: ' + testHeight() + 'px.', true); 2883 } 2884 }, 2885 timeout: typeof this._options.wait == 'number' ? this._options.wait : false 2886 }); 2887 }); 2888 2889 // build the gallery frame 2890 this.append({ 2891 'info-text' : 2892 ['info-title', 'info-description'], 2893 'info' : 2894 ['info-text'], 2895 'image-nav' : 2896 ['image-nav-right', 'image-nav-left'], 2897 'stage' : 2898 ['images', 'loader', 'counter', 'image-nav'], 2899 'thumbnails-list' : 2900 ['thumbnails'], 2901 'thumbnails-container' : 2902 ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'], 2903 'container' : 2904 ['stage', 'thumbnails-container', 'info', 'tooltip'] 2905 }); 2906 2907 Utils.hide( this.$( 'counter' ).append( 2908 this.get( 'current' ), 2909 doc.createTextNode(' / '), 2910 this.get( 'total' ) 2911 ) ); 2912 2913 this.setCounter('–'); 2914 2915 Utils.hide( self.get('tooltip') ); 2916 2917 // add a notouch class on the container to prevent unwanted :hovers on touch devices 2918 this.$( 'container' ).addClass([ 2919 ( Galleria.TOUCH ? 'touch' : 'notouch' ), 2920 this._options.variation, 2921 'galleria-theme-'+this.theme.name 2922 ].join(' ')); 2923 2924 // add images to the controls 2925 if ( !this._options.swipe ) { 2926 $.each( new Array(2), function( i ) { 2927 2928 // create a new Picture instance 2929 var image = new Galleria.Picture(); 2930 2931 // apply some styles, create & prepend overlay 2932 $( image.container ).css({ 2933 position: 'absolute', 2934 top: 0, 2935 left: 0 2936 }).prepend( self._layers[i] = $( Utils.create('galleria-layer') ).css({ 2937 position: 'absolute', 2938 top:0, left:0, right:0, bottom:0, 2939 zIndex:2 2940 })[0] ); 2941 2942 // append the image 2943 self.$( 'images' ).append( image.container ); 2944 2945 // reload the controls 2946 self._controls[i] = image; 2947 2948 // build a frame 2949 var frame = new Galleria.Picture(); 2950 frame.isIframe = true; 2951 2952 $( frame.container ).attr('class', 'galleria-frame').css({ 2953 position: 'absolute', 2954 top: 0, 2955 left: 0, 2956 zIndex: 4, 2957 background: '#000', 2958 display: 'none' 2959 }).appendTo( image.container ); 2960 2961 self._controls.frames[i] = frame; 2962 2963 }); 2964 } 2965 2966 // some forced generic styling 2967 this.$( 'images' ).css({ 2968 position: 'relative', 2969 top: 0, 2970 left: 0, 2971 width: '100%', 2972 height: '100%' 2973 }); 2974 2975 if ( options.swipe ) { 2976 this.$( 'images' ).css({ 2977 position: 'absolute', 2978 top: 0, 2979 left: 0, 2980 width: 0, 2981 height: '100%' 2982 }); 2983 this.finger = new Galleria.Finger(this.get('stage'), { 2984 onchange: function(page) { 2985 self.pause().show(page); 2986 }, 2987 oncomplete: function(page) { 2988 2989 var index = M.max( 0, M.min( parseInt( page, 10 ), self.getDataLength() - 1 ) ), 2990 data = self.getData(index); 2991 2992 $( self._thumbnails[ index ].container ) 2993 .addClass( 'active' ) 2994 .siblings( '.active' ) 2995 .removeClass( 'active' ); 2996 2997 if ( !data ) { 2998 return; 2999 } 3000 3001 // remove video iframes 3002 self.$( 'images' ).find( '.galleria-frame' ).css('opacity', 0).hide().find( 'iframe' ).remove(); 3003 3004 if ( self._options.carousel && self._options.carouselFollow ) { 3005 self._carousel.follow( index ); 3006 } 3007 } 3008 }); 3009 this.bind( Galleria.RESCALE, function() { 3010 this.finger.setup(); 3011 }); 3012 this.$('stage').on('click', function(e) { 3013 var data = self.getData(); 3014 if ( !data ) { 3015 return; 3016 } 3017 if ( data.iframe ) { 3018 3019 if ( self.isPlaying() ) { 3020 self.pause(); 3021 } 3022 var frame = self._controls.frames[ self._active ], 3023 w = self._stageWidth, 3024 h = self._stageHeight; 3025 3026 if ( $( frame.container ).find( 'iframe' ).length ) { 3027 return; 3028 } 3029 3030 $( frame.container ).css({ 3031 width: w, 3032 height: h, 3033 opacity: 0 3034 }).show().animate({ 3035 opacity: 1 3036 }, 200); 3037 3038 window.setTimeout(function() { 3039 frame.load( data.iframe + ( data.video ? '&autoplay=1' : '' ), { 3040 width: w, 3041 height: h 3042 }, function( frame ) { 3043 self.$( 'container' ).addClass( 'videoplay' ); 3044 frame.scale({ 3045 width: self._stageWidth, 3046 height: self._stageHeight, 3047 iframelimit: data.video ? self._options.maxVideoSize : undef 3048 }); 3049 }); 3050 }, 100); 3051 3052 return; 3053 } 3054 3055 if ( data.link ) { 3056 if ( self._options.popupLinks ) { 3057 var win = window.open( data.link, '_blank' ); 3058 } else { 3059 window.location.href = data.link; 3060 } 3061 return; 3062 } 3063 }); 3064 this.bind( Galleria.IMAGE, function(e) { 3065 3066 self.setCounter( e.index ); 3067 self.setInfo( e.index ); 3068 3069 var next = this.getNext(), 3070 prev = this.getPrev(); 3071 3072 var preloads = [prev,next]; 3073 preloads.push(this.getNext(next), this.getPrev(prev), self._controls.slides.length-1); 3074 3075 var filtered = []; 3076 3077 $.each(preloads, function(i, val) { 3078 if ( $.inArray(val, filtered) == -1 ) { 3079 filtered.push(val); 3080 } 3081 }); 3082 3083 $.each(filtered, function(i, loadme) { 3084 var d = self.getData(loadme), 3085 img = self._controls.slides[loadme], 3086 src = self.isFullscreen() && d.big ? d.big : ( d.image || d.iframe ); 3087 3088 if ( d.iframe && !d.image ) { 3089 img.isIframe = true; 3090 } 3091 3092 if ( !img.ready ) { 3093 self._controls.slides[loadme].load(src, function(img) { 3094 if ( !img.isIframe ) { 3095 $(img.image).css('visibility', 'hidden'); 3096 } 3097 self._scaleImage(img, { 3098 complete: function(img) { 3099 if ( !img.isIframe ) { 3100 $(img.image).css({ 3101 opacity: 0, 3102 visibility: 'visible' 3103 }).animate({ 3104 opacity: 1 3105 }, 200); 3106 } 3107 } 3108 }); 3109 }); 3110 } 3111 }); 3112 }); 3113 } 3114 3115 this.$( 'thumbnails, thumbnails-list' ).css({ 3116 overflow: 'hidden', 3117 position: 'relative' 3118 }); 3119 3120 // bind image navigation arrows 3121 this.$( 'image-nav-right, image-nav-left' ).on( 'click:fast', function(e) { 3122 3123 // pause if options is set 3124 if ( options.pauseOnInteraction ) { 3125 self.pause(); 3126 } 3127 3128 // navigate 3129 var fn = /right/.test( this.className ) ? 'next' : 'prev'; 3130 self[ fn ](); 3131 3132 }).on('click', function(e) { 3133 3134 e.preventDefault(); 3135 3136 // tune the clicknext option 3137 if ( options.clicknext || options.swipe ) { 3138 e.stopPropagation(); 3139 } 3140 }); 3141 3142 // hide controls if chosen to 3143 $.each( ['info','counter','image-nav'], function( i, el ) { 3144 if ( options[ 'show' + el.substr(0,1).toUpperCase() + el.substr(1).replace(/-/,'') ] === false ) { 3145 Utils.moveOut( self.get( el.toLowerCase() ) ); 3146 } 3147 }); 3148 3149 // load up target content 3150 this.load(); 3151 3152 // now it's usually safe to remove the content 3153 // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway) 3154 if ( !options.keepSource && !IE ) { 3155 this._target.innerHTML = ''; 3156 } 3157 3158 // re-append the errors, if they happened before clearing 3159 if ( this.get( 'errors' ) ) { 3160 this.appendChild( 'target', 'errors' ); 3161 } 3162 3163 // append the gallery frame 3164 this.appendChild( 'target', 'container' ); 3165 3166 // parse the carousel on each thumb load 3167 if ( options.carousel ) { 3168 var count = 0, 3169 show = options.show; 3170 this.bind( Galleria.THUMBNAIL, function() { 3171 this.updateCarousel(); 3172 if ( ++count == this.getDataLength() && typeof show == 'number' && show > 0 ) { 3173 this._carousel.follow( show ); 3174 } 3175 }); 3176 } 3177 3178 // bind window resize for responsiveness 3179 if ( options.responsive ) { 3180 $win.on( 'resize', function() { 3181 if ( !self.isFullscreen() ) { 3182 self.resize(); 3183 } 3184 }); 3185 } 3186 3187 // double-tap/click fullscreen toggle 3188 3189 if ( options.fullscreenDoubleTap ) { 3190 3191 this.$( 'stage' ).on( 'touchstart', (function() { 3192 var last, cx, cy, lx, ly, now, 3193 getData = function(e) { 3194 return e.originalEvent.touches ? e.originalEvent.touches[0] : e; 3195 }; 3196 self.$( 'stage' ).on('touchmove', function() { 3197 last = 0; 3198 }); 3199 return function(e) { 3200 if( /(-left|-right)/.test(e.target.className) ) { 3201 return; 3202 } 3203 now = Utils.timestamp(); 3204 cx = getData(e).pageX; 3205 cy = getData(e).pageY; 3206 if ( e.originalEvent.touches.length < 2 && ( now - last < 300 ) && ( cx - lx < 20) && ( cy - ly < 20) ) { 3207 self.toggleFullscreen(); 3208 e.preventDefault(); 3209 return; 3210 } 3211 last = now; 3212 lx = cx; 3213 ly = cy; 3214 }; 3215 }())); 3216 } 3217 3218 // bind the ons 3219 $.each( Galleria.on.binds, function(i, bind) { 3220 // check if already bound 3221 if ( $.inArray( bind.hash, self._binds ) == -1 ) { 3222 self.bind( bind.type, bind.callback ); 3223 } 3224 }); 3225 3226 return this; 3227 }, 3228 3229 addTimer : function() { 3230 this._timer.add.apply( this._timer, Utils.array( arguments ) ); 3231 return this; 3232 }, 3233 3234 clearTimer : function() { 3235 this._timer.clear.apply( this._timer, Utils.array( arguments ) ); 3236 return this; 3237 }, 3238 3239 // parse width & height from CSS or options 3240 3241 _getWH : function() { 3242 3243 var $container = this.$( 'container' ), 3244 $target = this.$( 'target' ), 3245 self = this, 3246 num = {}, 3247 arr; 3248 3249 $.each(['width', 'height'], function( i, m ) { 3250 3251 // first check if options is set 3252 if ( self._options[ m ] && typeof self._options[ m ] === 'number') { 3253 num[ m ] = self._options[ m ]; 3254 } else { 3255 3256 arr = [ 3257 Utils.parseValue( $container.css( m ) ), // the container css height 3258 Utils.parseValue( $target.css( m ) ), // the target css height 3259 $container[ m ](), // the container jQuery method 3260 $target[ m ]() // the target jQuery method 3261 ]; 3262 3263 // if first time, include the min-width & min-height 3264 if ( !self[ '_'+m ] ) { 3265 arr.splice(arr.length, 3266 Utils.parseValue( $container.css( 'min-'+m ) ), 3267 Utils.parseValue( $target.css( 'min-'+m ) ) 3268 ); 3269 } 3270 3271 // else extract the measures from different sources and grab the highest value 3272 num[ m ] = M.max.apply( M, arr ); 3273 } 3274 }); 3275 3276 // allow setting a height ratio instead of exact value 3277 // useful when doing responsive galleries 3278 3279 if ( self._userRatio ) { 3280 num.height = num.width * self._userRatio; 3281 } 3282 3283 return num; 3284 }, 3285 3286 // Creates the thumbnails and carousel 3287 // can be used at any time, f.ex when the data object is manipulated 3288 // push is an optional argument with pushed images 3289 3290 _createThumbnails : function( push ) { 3291 3292 this.get( 'total' ).innerHTML = this.getDataLength(); 3293 3294 var src, 3295 thumb, 3296 data, 3297 3298 $container, 3299 3300 self = this, 3301 o = this._options, 3302 3303 i = push ? this._data.length - push.length : 0, 3304 chunk = i, 3305 3306 thumbchunk = [], 3307 loadindex = 0, 3308 3309 gif = IE < 8 ? 'http://upload.wikimedia.org/wikipedia/commons/c/c0/Blank.gif' : 3310 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D', 3311 3312 // get previously active thumbnail, if exists 3313 active = (function() { 3314 var a = self.$('thumbnails').find('.active'); 3315 if ( !a.length ) { 3316 return false; 3317 } 3318 return a.find('img').attr('src'); 3319 }()), 3320 3321 // cache the thumbnail option 3322 optval = typeof o.thumbnails === 'string' ? o.thumbnails.toLowerCase() : null, 3323 3324 // move some data into the instance 3325 // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery 3326 // so we resort to getComputedStyle for browsers who support it 3327 getStyle = function( prop ) { 3328 return doc.defaultView && doc.defaultView.getComputedStyle ? 3329 doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] : 3330 $container.css( prop ); 3331 }, 3332 3333 fake = function(image, index, container) { 3334 return function() { 3335 $( container ).append( image ); 3336 self.trigger({ 3337 type: Galleria.THUMBNAIL, 3338 thumbTarget: image, 3339 index: index, 3340 galleriaData: self.getData( index ) 3341 }); 3342 }; 3343 }, 3344 3345 onThumbEvent = function( e ) { 3346 3347 // pause if option is set 3348 if ( o.pauseOnInteraction ) { 3349 self.pause(); 3350 } 3351 3352 // extract the index from the data 3353 var index = $( e.currentTarget ).data( 'index' ); 3354 if ( self.getIndex() !== index ) { 3355 self.show( index ); 3356 } 3357 3358 e.preventDefault(); 3359 }, 3360 3361 thumbComplete = function( thumb, callback ) { 3362 3363 $( thumb.container ).css( 'visibility', 'visible' ); 3364 self.trigger({ 3365 type: Galleria.THUMBNAIL, 3366 thumbTarget: thumb.image, 3367 index: thumb.data.order, 3368 galleriaData: self.getData( thumb.data.order ) 3369 }); 3370 3371 if ( typeof callback == 'function' ) { 3372 callback.call( self, thumb ); 3373 } 3374 }, 3375 3376 onThumbLoad = function( thumb, callback ) { 3377 3378 // scale when ready 3379 thumb.scale({ 3380 width: thumb.data.width, 3381 height: thumb.data.height, 3382 crop: o.thumbCrop, 3383 margin: o.thumbMargin, 3384 canvas: o.useCanvas, 3385 position: o.thumbPosition, 3386 complete: function( thumb ) { 3387 3388 // shrink thumbnails to fit 3389 var top = ['left', 'top'], 3390 arr = ['Width', 'Height'], 3391 m, 3392 css, 3393 data = self.getData( thumb.index ); 3394 3395 // calculate shrinked positions 3396 $.each(arr, function( i, measure ) { 3397 m = measure.toLowerCase(); 3398 if ( (o.thumbCrop !== true || o.thumbCrop === m ) ) { 3399 css = {}; 3400 css[ m ] = thumb[ m ]; 3401 $( thumb.container ).css( css ); 3402 css = {}; 3403 css[ top[ i ] ] = 0; 3404 $( thumb.image ).css( css ); 3405 } 3406 3407 // cache outer measures 3408 thumb[ 'outer' + measure ] = $( thumb.container )[ 'outer' + measure ]( true ); 3409 }); 3410 3411 // set high quality if downscale is moderate 3412 Utils.toggleQuality( thumb.image, 3413 o.thumbQuality === true || 3414 ( o.thumbQuality === 'auto' && thumb.original.width < thumb.width * 3 ) 3415 ); 3416 3417 if ( o.thumbDisplayOrder && !thumb.lazy ) { 3418 3419 $.each( thumbchunk, function( i, th ) { 3420 if ( i === loadindex && th.ready && !th.displayed ) { 3421 3422 loadindex++; 3423 th.displayed = true; 3424 3425 thumbComplete( th, callback ); 3426 3427 return; 3428 } 3429 }); 3430 } else { 3431 thumbComplete( thumb, callback ); 3432 } 3433 } 3434 }); 3435 }; 3436 3437 if ( !push ) { 3438 this._thumbnails = []; 3439 this.$( 'thumbnails' ).empty(); 3440 } 3441 3442 // loop through data and create thumbnails 3443 for( ; this._data[ i ]; i++ ) { 3444 3445 data = this._data[ i ]; 3446 3447 // get source from thumb or image 3448 src = data.thumb || data.image; 3449 3450 if ( ( o.thumbnails === true || optval == 'lazy' ) && ( data.thumb || data.image ) ) { 3451 3452 // add a new Picture instance 3453 thumb = new Galleria.Picture(i); 3454 3455 // save the index 3456 thumb.index = i; 3457 3458 // flag displayed 3459 thumb.displayed = false; 3460 3461 // flag lazy 3462 thumb.lazy = false; 3463 3464 // flag video 3465 thumb.video = false; 3466 3467 // append the thumbnail 3468 this.$( 'thumbnails' ).append( thumb.container ); 3469 3470 // cache the container 3471 $container = $( thumb.container ); 3472 3473 // hide it 3474 $container.css( 'visibility', 'hidden' ); 3475 3476 thumb.data = { 3477 width : Utils.parseValue( getStyle( 'width' ) ), 3478 height : Utils.parseValue( getStyle( 'height' ) ), 3479 order : i, 3480 src : src 3481 }; 3482 3483 // grab & reset size for smoother thumbnail loads 3484 if ( o.thumbCrop !== true ) { 3485 $container.css( { width: 'auto', height: 'auto' } ); 3486 } else { 3487 $container.css( { width: thumb.data.width, height: thumb.data.height } ); 3488 } 3489 3490 // load the thumbnail 3491 if ( optval == 'lazy' ) { 3492 3493 $container.addClass( 'lazy' ); 3494 3495 thumb.lazy = true; 3496 3497 thumb.load( gif, { 3498 height: thumb.data.height, 3499 width: thumb.data.width 3500 }); 3501 3502 } else { 3503 thumb.load( src, onThumbLoad ); 3504 } 3505 3506 // preload all images here 3507 if ( o.preload === 'all' ) { 3508 thumb.preload( data.image ); 3509 } 3510 3511 // create empty spans if thumbnails is set to 'empty' 3512 } else if ( ( data.iframe && optval !== null ) || optval === 'empty' || optval === 'numbers' ) { 3513 thumb = { 3514 container: Utils.create( 'galleria-image' ), 3515 image: Utils.create( 'img', 'span' ), 3516 ready: true, 3517 data: { 3518 order: i 3519 } 3520 }; 3521 3522 // create numbered thumbnails 3523 if ( optval === 'numbers' ) { 3524 $( thumb.image ).text( i + 1 ); 3525 } 3526 3527 if ( data.iframe ) { 3528 $( thumb.image ).addClass( 'iframe' ); 3529 } 3530 3531 this.$( 'thumbnails' ).append( thumb.container ); 3532 3533 // we need to "fake" a loading delay before we append and trigger 3534 // 50+ should be enough 3535 3536 window.setTimeout( ( fake )( thumb.image, i, thumb.container ), 50 + ( i*20 ) ); 3537 3538 // create null object to silent errors 3539 } else { 3540 thumb = { 3541 container: null, 3542 image: null 3543 }; 3544 } 3545 3546 // add events for thumbnails 3547 // you can control the event type using thumb_event_type 3548 // we'll add the same event to the source if it's kept 3549 3550 $( thumb.container ).add( o.keepSource && o.linkSourceImages ? data.original : null ) 3551 .data('index', i).on( o.thumbEventType, onThumbEvent ) 3552 .data('thumbload', onThumbLoad); 3553 3554 if (active === src) { 3555 $( thumb.container ).addClass( 'active' ); 3556 } 3557 3558 this._thumbnails.push( thumb ); 3559 } 3560 3561 thumbchunk = this._thumbnails.slice( chunk ); 3562 3563 return this; 3564 }, 3565 3566 /** 3567 Lazy-loads thumbnails. 3568 You can call this method to load lazy thumbnails at run time 3569 3570 @param {Array|Number} index Index or array of indexes of thumbnails to be loaded 3571 @param {Function} complete Callback that is called when all lazy thumbnails have been loaded 3572 3573 @returns Instance 3574 */ 3575 3576 lazyLoad: function( index, complete ) { 3577 3578 var arr = index.constructor == Array ? index : [ index ], 3579 self = this, 3580 loaded = 0; 3581 3582 $.each( arr, function(i, ind) { 3583 3584 if ( ind > self._thumbnails.length - 1 ) { 3585 return; 3586 } 3587 3588 var thumb = self._thumbnails[ ind ], 3589 data = thumb.data, 3590 callback = function() { 3591 if ( ++loaded == arr.length && typeof complete == 'function' ) { 3592 complete.call( self ); 3593 } 3594 }, 3595 thumbload = $( thumb.container ).data( 'thumbload' ); 3596 if (thumbload) { 3597 if ( thumb.video ) { 3598 thumbload.call( self, thumb, callback ); 3599 } else { 3600 thumb.load( data.src , function( thumb ) { 3601 thumbload.call( self, thumb, callback ); 3602 }); 3603 } 3604 } 3605 }); 3606 3607 return this; 3608 3609 }, 3610 3611 /** 3612 Lazy-loads thumbnails in chunks. 3613 This method automatcally chops up the loading process of many thumbnails into chunks 3614 3615 @param {Number} size Size of each chunk to be loaded 3616 @param {Number} [delay] Delay between each loads 3617 3618 @returns Instance 3619 */ 3620 3621 lazyLoadChunks: function( size, delay ) { 3622 3623 var len = this.getDataLength(), 3624 i = 0, 3625 n = 0, 3626 arr = [], 3627 temp = [], 3628 self = this; 3629 3630 delay = delay || 0; 3631 3632 for( ; i<len; i++ ) { 3633 temp.push(i); 3634 if ( ++n == size || i == len-1 ) { 3635 arr.push( temp ); 3636 n = 0; 3637 temp = []; 3638 } 3639 } 3640 3641 var init = function( wait ) { 3642 var a = arr.shift(); 3643 if ( a ) { 3644 window.setTimeout(function() { 3645 self.lazyLoad(a, function() { 3646 init( true ); 3647 }); 3648 }, ( delay && wait ) ? delay : 0 ); 3649 } 3650 }; 3651 3652 init( false ); 3653 3654 return this; 3655 3656 }, 3657 3658 // the internal _run method should be called after loading data into galleria 3659 // makes sure the gallery has proper measurements before postrun & ready 3660 _run : function() { 3661 3662 var self = this; 3663 3664 self._createThumbnails(); 3665 3666 // make sure we have a stageHeight && stageWidth 3667 3668 Utils.wait({ 3669 3670 timeout: 10000, 3671 3672 until: function() { 3673 3674 // Opera crap 3675 if ( Galleria.OPERA ) { 3676 self.$( 'stage' ).css( 'display', 'inline-block' ); 3677 } 3678 3679 self._stageWidth = self.$( 'stage' ).width(); 3680 self._stageHeight = self.$( 'stage' ).height(); 3681 3682 return( self._stageWidth && 3683 self._stageHeight > 50 ); // what is an acceptable height? 3684 }, 3685 3686 success: function() { 3687 3688 // save the instance 3689 _galleries.push( self ); 3690 3691 // postrun some stuff after the gallery is ready 3692 3693 // create the touch slider 3694 if ( self._options.swipe ) { 3695 3696 var $images = self.$( 'images' ).width( self.getDataLength() * self._stageWidth ); 3697 $.each( new Array( self.getDataLength() ), function(i) { 3698 3699 var image = new Galleria.Picture(), 3700 data = self.getData(i); 3701 3702 $( image.container ).css({ 3703 position: 'absolute', 3704 top: 0, 3705 left: self._stageWidth*i 3706 }).prepend( self._layers[i] = $( Utils.create('galleria-layer') ).css({ 3707 position: 'absolute', 3708 top:0, left:0, right:0, bottom:0, 3709 zIndex:2 3710 })[0] ).appendTo( $images ); 3711 3712 if( data.video ) { 3713 _playIcon( image.container ); 3714 } 3715 3716 self._controls.slides.push(image); 3717 3718 var frame = new Galleria.Picture(); 3719 frame.isIframe = true; 3720 3721 $( frame.container ).attr('class', 'galleria-frame').css({ 3722 position: 'absolute', 3723 top: 0, 3724 left: 0, 3725 zIndex: 4, 3726 background: '#000', 3727 display: 'none' 3728 }).appendTo( image.container ); 3729 3730 self._controls.frames.push(frame); 3731 }); 3732 3733 self.finger.setup(); 3734 } 3735 3736 // show counter 3737 Utils.show( self.get('counter') ); 3738 3739 // bind carousel nav 3740 if ( self._options.carousel ) { 3741 self._carousel.bindControls(); 3742 } 3743 3744 // start autoplay 3745 if ( self._options.autoplay ) { 3746 3747 self.pause(); 3748 3749 if ( typeof self._options.autoplay === 'number' ) { 3750 self._playtime = self._options.autoplay; 3751 } 3752 3753 self._playing = true; 3754 } 3755 // if second load, just do the show and return 3756 if ( self._firstrun ) { 3757 3758 if ( self._options.autoplay ) { 3759 self.trigger( Galleria.PLAY ); 3760 } 3761 3762 if ( typeof self._options.show === 'number' ) { 3763 self.show( self._options.show ); 3764 } 3765 return; 3766 } 3767 3768 self._firstrun = true; 3769 3770 // initialize the History plugin 3771 if ( Galleria.History ) { 3772 3773 // bind the show method 3774 Galleria.History.change(function( value ) { 3775 3776 // if ID is NaN, the user pressed back from the first image 3777 // return to previous address 3778 if ( isNaN( value ) ) { 3779 window.history.go(-1); 3780 3781 // else show the image 3782 } else { 3783 self.show( value, undef, true ); 3784 } 3785 }); 3786 } 3787 3788 self.trigger( Galleria.READY ); 3789 3790 // call the theme init method 3791 self.theme.init.call( self, self._options ); 3792 3793 // Trigger Galleria.ready 3794 $.each( Galleria.ready.callbacks, function(i ,fn) { 3795 if ( typeof fn == 'function' ) { 3796 fn.call( self, self._options ); 3797 } 3798 }); 3799 3800 // call the extend option 3801 self._options.extend.call( self, self._options ); 3802 3803 // show the initial image 3804 // first test for permalinks in history 3805 if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) { 3806 self.show( HASH, undef, true ); 3807 3808 } else if( self._data[ self._options.show ] ) { 3809 self.show( self._options.show ); 3810 } 3811 3812 // play trigger 3813 if ( self._options.autoplay ) { 3814 self.trigger( Galleria.PLAY ); 3815 } 3816 }, 3817 3818 error: function() { 3819 Galleria.raise('Stage width or height is too small to show the gallery. Traced measures: width:' + self._stageWidth + 'px, height: ' + self._stageHeight + 'px.', true); 3820 } 3821 3822 }); 3823 }, 3824 3825 /** 3826 Loads data into the gallery. 3827 You can call this method on an existing gallery to reload the gallery with new data. 3828 3829 @param {Array|string} [source] Optional JSON array of data or selector of where to find data in the document. 3830 Defaults to the Galleria target or dataSource option. 3831 3832 @param {string} [selector] Optional element selector of what elements to parse. 3833 Defaults to 'img'. 3834 3835 @param {Function} [config] Optional function to modify the data extraction proceedure from the selector. 3836 See the dataConfig option for more information. 3837 3838 @returns Instance 3839 */ 3840 3841 load : function( source, selector, config ) { 3842 3843 var self = this, 3844 o = this._options; 3845 3846 // empty the data array 3847 this._data = []; 3848 3849 // empty the thumbnails 3850 this._thumbnails = []; 3851 this.$('thumbnails').empty(); 3852 3853 // shorten the arguments 3854 if ( typeof selector === 'function' ) { 3855 config = selector; 3856 selector = null; 3857 } 3858 3859 // use the source set by target 3860 source = source || o.dataSource; 3861 3862 // use selector set by option 3863 selector = selector || o.dataSelector; 3864 3865 // use the dataConfig set by option 3866 config = config || o.dataConfig; 3867 3868 // if source is a true object, make it into an array 3869 if( $.isPlainObject( source ) ) { 3870 source = [source]; 3871 } 3872 3873 // check if the data is an array already 3874 if ( $.isArray( source ) ) { 3875 if ( this.validate( source ) ) { 3876 this._data = source; 3877 } else { 3878 Galleria.raise( 'Load failed: JSON Array not valid.' ); 3879 } 3880 } else { 3881 3882 // add .video and .iframe to the selector (1.2.7) 3883 selector += ',.video,.iframe'; 3884 3885 // loop through images and set data 3886 $( source ).find( selector ).each( function( i, elem ) { 3887 3888 elem = $( elem ); 3889 var data = {}, 3890 parent = elem.parent(), 3891 href = parent.attr( 'href' ), 3892 rel = parent.attr( 'rel' ); 3893 3894 if( href && ( elem[0].nodeName == 'IMG' || elem.hasClass('video') ) && _videoTest( href ) ) { 3895 data.video = href; 3896 } else if( href && elem.hasClass('iframe') ) { 3897 data.iframe = href; 3898 } else { 3899 data.image = href; 3900 } 3901 3902 if ( rel ) { 3903 data.big = rel; 3904 } 3905 3906 data.imagesrcset = parent.data( 'srcset' ); 3907 data.imagesizes = parent.data( 'sizes' ); 3908 data.thumbsizes = elem.attr( 'sizes' ); 3909 data.thumbsrcset = elem.attr( 'srcset' ); 3910 3911 // alternative extraction from HTML5 data attribute 3912 $.each( 'big bigsrcset bigsizes title description link layer image imagesrcset imagesizes'.split(' '), function( i, val ) { 3913 if ( elem.data(val) ) { 3914 data[ val ] = elem.data(val).toString(); 3915 } 3916 }); 3917 3918 if (elem.data('srcset')) { 3919 data.imagesrcset = elem.data('srcset'); 3920 } 3921 3922 if (elem.data('sizes')) { 3923 data.imagesizes = elem.data('sizes'); 3924 } 3925 3926 if ( !data.big ) { 3927 data.big = data.image; 3928 data.bigsrcset = data.imagesrcset; 3929 data.bigsizes = data.imagesizes; 3930 } 3931 3932 // mix default extractions with the hrefs and config 3933 // and push it into the data array 3934 self._data.push( $.extend({ 3935 3936 title: elem.attr('title') || '', 3937 thumb: elem.attr('src'), 3938 image: elem.attr('src'), 3939 big: elem.attr('src'), 3940 description: elem.attr('alt') || '', 3941 link: elem.attr('longdesc'), 3942 original: elem.get(0) // saved as a reference 3943 3944 }, data, config( elem ) ) ); 3945 3946 }); 3947 } 3948 3949 if ( typeof o.dataSort == 'function' ) { 3950 protoArray.sort.call( this._data, o.dataSort ); 3951 } else if ( o.dataSort == 'random' ) { 3952 this._data.sort( function() { 3953 return M.round(M.random())-0.5; 3954 }); 3955 } 3956 3957 // trigger the DATA event and return 3958 if ( this.getDataLength() ) { 3959 this._parseData( function() { 3960 this.trigger( Galleria.DATA ); 3961 } ); 3962 } 3963 return this; 3964 3965 }, 3966 3967 // make sure the data works properly 3968 _parseData : function( callback ) { 3969 3970 var self = this, 3971 current, 3972 ready = false, 3973 onload = function() { 3974 var complete = true; 3975 $.each( self._data, function( i, data ) { 3976 if ( data.loading ) { 3977 complete = false; 3978 return false; 3979 } 3980 }); 3981 if ( complete && !ready ) { 3982 ready = true; 3983 callback.call( self ); 3984 } 3985 }; 3986 3987 $.each( this._data, function( i, data ) { 3988 3989 current = self._data[ i ]; 3990 3991 // q&d hack to attach srcset & sizes to src 3992 $.each( 'big image thumb'.split(' '), function( i, val ) { 3993 if ( data[ val] ) { 3994 data[val] = new String(data[val]); 3995 data[val].srcset = data [val + 'srcset']; 3996 data[val].sizes = data [val + 'sizes']; 3997 } 3998 }); 3999 4000 // copy image as thumb if no thumb exists 4001 if ( 'thumb' in data === false ) { 4002 current.thumb = data.image; 4003 } 4004 // copy image as big image if no biggie exists 4005 if ( !data.big ) { 4006 current.big = data.image; 4007 } 4008 // parse video 4009 if ( 'video' in data ) { 4010 var result = _videoTest( data.video ); 4011 4012 if ( result ) { 4013 current.iframe = new Video(result.provider, result.id ).embed() + (function() { 4014 4015 // add options 4016 if ( typeof self._options[ result.provider ] == 'object' ) { 4017 var str = '?', arr = []; 4018 $.each( self._options[ result.provider ], function( key, val ) { 4019 arr.push( key + '=' + val ); 4020 }); 4021 4022 // small youtube specifics, perhaps move to _video later 4023 if ( result.provider == 'youtube' ) { 4024 arr = ['wmode=opaque'].concat(arr); 4025 } 4026 return str + arr.join('&'); 4027 } 4028 return ''; 4029 }()); 4030 4031 // pre-fetch video providers media 4032 4033 if( !current.thumb || !current.image ) { 4034 $.each( ['thumb', 'image'], function( i, type ) { 4035 if ( type == 'image' && !self._options.videoPoster ) { 4036 current.image = undef; 4037 return; 4038 } 4039 var video = new Video( result.provider, result.id ); 4040 if ( !current[ type ] ) { 4041 current.loading = true; 4042 video.getMedia( type, (function(current, type) { 4043 return function(src) { 4044 current[ type ] = src; 4045 if ( type == 'image' && !current.big ) { 4046 current.big = current.image; 4047 } 4048 delete current.loading; 4049 onload(); 4050 }; 4051 }( current, type ))); 4052 } 4053 }); 4054 } 4055 } 4056 } 4057 }); 4058 4059 onload(); 4060 4061 return this; 4062 }, 4063 4064 /** 4065 Destroy the Galleria instance and recover the original content 4066 4067 @example this.destroy(); 4068 4069 @returns Instance 4070 */ 4071 4072 destroy : function() { 4073 this.$( 'target' ).data( 'galleria', null ); 4074 this.$( 'container' ).off( 'galleria' ); 4075 this.get( 'target' ).innerHTML = this._original.html; 4076 this.clearTimer(); 4077 Utils.removeFromArray( _instances, this ); 4078 Utils.removeFromArray( _galleries, this ); 4079 _video._inst = []; 4080 if ( Galleria._waiters !== undefined && Galleria._waiters.length ) { 4081 $.each( Galleria._waiters, function( i, w ) { 4082 if ( w ) window.clearTimeout( w ); 4083 }); 4084 } 4085 return this; 4086 }, 4087 4088 /** 4089 Adds and/or removes images from the gallery 4090 Works just like Array.splice 4091 https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice 4092 4093 @example this.splice( 2, 4 ); // removes 4 images after the second image 4094 4095 @returns Instance 4096 */ 4097 4098 splice : function() { 4099 var self = this, 4100 args = Utils.array( arguments ); 4101 window.setTimeout(function() { 4102 protoArray.splice.apply( self._data, args ); 4103 self._parseData( function() { 4104 self._createThumbnails(); 4105 }); 4106 },2); 4107 return self; 4108 }, 4109 4110 /** 4111 Append images to the gallery 4112 Works just like Array.push 4113 https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push 4114 4115 @example this.push({ image: 'image1.jpg' }); // appends the image to the gallery 4116 4117 @returns Instance 4118 */ 4119 4120 push : function() { 4121 var self = this, 4122 args = Utils.array( arguments ); 4123 4124 if ( args.length == 1 && args[0].constructor == Array ) { 4125 args = args[0]; 4126 } 4127 4128 window.setTimeout(function() { 4129 protoArray.push.apply( self._data, args ); 4130 self._parseData( function() { 4131 self._createThumbnails( args ); 4132 }); 4133 }, 2); 4134 return self; 4135 }, 4136 4137 _getActive : function() { 4138 return this._controls.getActive(); 4139 }, 4140 4141 validate : function( data ) { 4142 // todo: validate a custom data array 4143 return true; 4144 }, 4145 4146 /** 4147 Bind any event to Galleria 4148 4149 @param {string} type The Event type to listen for 4150 @param {Function} fn The function to execute when the event is triggered 4151 4152 @example this.bind( 'image', function() { Galleria.log('image shown') }); 4153 4154 @returns Instance 4155 */ 4156 4157 bind : function(type, fn) { 4158 4159 // allow 'image' instead of Galleria.IMAGE 4160 type = _patchEvent( type ); 4161 4162 this.$( 'container' ).on( type, this.proxy(fn) ); 4163 return this; 4164 }, 4165 4166 /** 4167 Unbind any event to Galleria 4168 4169 @param {string} type The Event type to forget 4170 4171 @returns Instance 4172 */ 4173 4174 unbind : function(type) { 4175 4176 type = _patchEvent( type ); 4177 4178 this.$( 'container' ).off( type ); 4179 return this; 4180 }, 4181 4182 /** 4183 Manually trigger a Galleria event 4184 4185 @param {string} type The Event to trigger 4186 4187 @returns Instance 4188 */ 4189 4190 trigger : function( type ) { 4191 4192 type = typeof type === 'object' ? 4193 $.extend( type, { scope: this } ) : 4194 { type: _patchEvent( type ), scope: this }; 4195 4196 this.$( 'container' ).trigger( type ); 4197 4198 return this; 4199 }, 4200 4201 /** 4202 Assign an "idle state" to any element. 4203 The idle state will be applied after a certain amount of idle time 4204 Useful to hide f.ex navigation when the gallery is inactive 4205 4206 @param {HTMLElement|string} elem The Dom node or selector to apply the idle state to 4207 @param {Object} styles the CSS styles to apply when in idle mode 4208 @param {Object} [from] the CSS styles to apply when in normal 4209 @param {Boolean} [hide] set to true if you want to hide it first 4210 4211 @example addIdleState( this.get('image-nav'), { opacity: 0 }); 4212 @example addIdleState( '.galleria-image-nav', { top: -200 }, true); 4213 4214 @returns Instance 4215 */ 4216 4217 addIdleState: function( elem, styles, from, hide ) { 4218 this._idle.add.apply( this._idle, Utils.array( arguments ) ); 4219 return this; 4220 }, 4221 4222 /** 4223 Removes any idle state previously set using addIdleState() 4224 4225 @param {HTMLElement|string} elem The Dom node or selector to remove the idle state from. 4226 4227 @returns Instance 4228 */ 4229 4230 removeIdleState: function( elem ) { 4231 this._idle.remove.apply( this._idle, Utils.array( arguments ) ); 4232 return this; 4233 }, 4234 4235 /** 4236 Force Galleria to enter idle mode. 4237 4238 @returns Instance 4239 */ 4240 4241 enterIdleMode: function() { 4242 this._idle.hide(); 4243 return this; 4244 }, 4245 4246 /** 4247 Force Galleria to exit idle mode. 4248 4249 @returns Instance 4250 */ 4251 4252 exitIdleMode: function() { 4253 this._idle.showAll(); 4254 return this; 4255 }, 4256 4257 /** 4258 Enter FullScreen mode 4259 4260 @param {Function} callback the function to be executed when the fullscreen mode is fully applied. 4261 4262 @returns Instance 4263 */ 4264 4265 enterFullscreen: function( callback ) { 4266 this._fullscreen.enter.apply( this, Utils.array( arguments ) ); 4267 return this; 4268 }, 4269 4270 /** 4271 Exits FullScreen mode 4272 4273 @param {Function} callback the function to be executed when the fullscreen mode is fully applied. 4274 4275 @returns Instance 4276 */ 4277 4278 exitFullscreen: function( callback ) { 4279 this._fullscreen.exit.apply( this, Utils.array( arguments ) ); 4280 return this; 4281 }, 4282 4283 /** 4284 Toggle FullScreen mode 4285 4286 @param {Function} callback the function to be executed when the fullscreen mode is fully applied or removed. 4287 4288 @returns Instance 4289 */ 4290 4291 toggleFullscreen: function( callback ) { 4292 this._fullscreen[ this.isFullscreen() ? 'exit' : 'enter'].apply( this, Utils.array( arguments ) ); 4293 return this; 4294 }, 4295 4296 /** 4297 Adds a tooltip to any element. 4298 You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples) 4299 4300 @param {HTMLElement} elem The DOM Node to attach the event to 4301 @param {string|Function} value The tooltip message. Can also be a function that returns a string. 4302 4303 @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails'); 4304 @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' }); 4305 @example this.bindTooltip( { image_nav: 'Navigation' }); 4306 4307 @returns Instance 4308 */ 4309 4310 bindTooltip: function( elem, value ) { 4311 this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) ); 4312 return this; 4313 }, 4314 4315 /** 4316 Note: this method is deprecated. Use refreshTooltip() instead. 4317 4318 Redefine a tooltip. 4319 Use this if you want to re-apply a tooltip value to an already bound tooltip element. 4320 4321 @param {HTMLElement} elem The DOM Node to attach the event to 4322 @param {string|Function} value The tooltip message. Can also be a function that returns a string. 4323 4324 @returns Instance 4325 */ 4326 4327 defineTooltip: function( elem, value ) { 4328 this._tooltip.define.apply( this._tooltip, Utils.array(arguments) ); 4329 return this; 4330 }, 4331 4332 /** 4333 Refresh a tooltip value. 4334 Use this if you want to change the tooltip value at runtime, f.ex if you have a play/pause toggle. 4335 4336 @param {HTMLElement} elem The DOM Node that has a tooltip that should be refreshed 4337 4338 @returns Instance 4339 */ 4340 4341 refreshTooltip: function( elem ) { 4342 this._tooltip.show.apply( this._tooltip, Utils.array(arguments) ); 4343 return this; 4344 }, 4345 4346 /** 4347 Open a pre-designed lightbox with the currently active image. 4348 You can control some visuals using gallery options. 4349 4350 @returns Instance 4351 */ 4352 4353 openLightbox: function() { 4354 this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) ); 4355 return this; 4356 }, 4357 4358 /** 4359 Close the lightbox. 4360 4361 @returns Instance 4362 */ 4363 4364 closeLightbox: function() { 4365 this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) ); 4366 return this; 4367 }, 4368 4369 /** 4370 Check if a variation exists 4371 4372 @returns {Boolean} If the variation has been applied 4373 */ 4374 4375 hasVariation: function( variation ) { 4376 return $.inArray( variation, this._options.variation.split(/\s+/) ) > -1; 4377 }, 4378 4379 /** 4380 Get the currently active image element. 4381 4382 @returns {HTMLElement} The image element 4383 */ 4384 4385 getActiveImage: function() { 4386 var active = this._getActive(); 4387 return active ? active.image : undef; 4388 }, 4389 4390 /** 4391 Get the currently active thumbnail element. 4392 4393 @returns {HTMLElement} The thumbnail element 4394 */ 4395 4396 getActiveThumb: function() { 4397 return this._thumbnails[ this._active ].image || undef; 4398 }, 4399 4400 /** 4401 Get the mouse position relative to the gallery container 4402 4403 @param e The mouse event 4404 4405 @example 4406 4407var gallery = this; 4408$(document).mousemove(function(e) { 4409 console.log( gallery.getMousePosition(e).x ); 4410}); 4411 4412 @returns {Object} Object with x & y of the relative mouse postion 4413 */ 4414 4415 getMousePosition : function(e) { 4416 return { 4417 x: e.pageX - this.$( 'container' ).offset().left, 4418 y: e.pageY - this.$( 'container' ).offset().top 4419 }; 4420 }, 4421 4422 /** 4423 Adds a panning effect to the image 4424 4425 @param [img] The optional image element. If not specified it takes the currently active image 4426 4427 @returns Instance 4428 */ 4429 4430 addPan : function( img ) { 4431 4432 if ( this._options.imageCrop === false ) { 4433 return; 4434 } 4435 4436 img = $( img || this.getActiveImage() ); 4437 4438 // define some variables and methods 4439 var self = this, 4440 x = img.width() / 2, 4441 y = img.height() / 2, 4442 destX = parseInt( img.css( 'left' ), 10 ), 4443 destY = parseInt( img.css( 'top' ), 10 ), 4444 curX = destX || 0, 4445 curY = destY || 0, 4446 distX = 0, 4447 distY = 0, 4448 active = false, 4449 ts = Utils.timestamp(), 4450 cache = 0, 4451 move = 0, 4452 4453 // positions the image 4454 position = function( dist, cur, pos ) { 4455 if ( dist > 0 ) { 4456 move = M.round( M.max( dist * -1, M.min( 0, cur ) ) ); 4457 if ( cache !== move ) { 4458 4459 cache = move; 4460 4461 if ( IE === 8 ) { // scroll is faster for IE 4462 img.parent()[ 'scroll' + pos ]( move * -1 ); 4463 } else { 4464 var css = {}; 4465 css[ pos.toLowerCase() ] = move; 4466 img.css(css); 4467 } 4468 } 4469 } 4470 }, 4471 4472 // calculates mouse position after 50ms 4473 calculate = function(e) { 4474 if (Utils.timestamp() - ts < 50) { 4475 return; 4476 } 4477 active = true; 4478 x = self.getMousePosition(e).x; 4479 y = self.getMousePosition(e).y; 4480 }, 4481 4482 // the main loop to check 4483 loop = function(e) { 4484 4485 if (!active) { 4486 return; 4487 } 4488 4489 distX = img.width() - self._stageWidth; 4490 distY = img.height() - self._stageHeight; 4491 destX = x / self._stageWidth * distX * -1; 4492 destY = y / self._stageHeight * distY * -1; 4493 curX += ( destX - curX ) / self._options.imagePanSmoothness; 4494 curY += ( destY - curY ) / self._options.imagePanSmoothness; 4495 4496 position( distY, curY, 'Top' ); 4497 position( distX, curX, 'Left' ); 4498 4499 }; 4500 4501 // we need to use scroll in IE8 to speed things up 4502 if ( IE === 8 ) { 4503 4504 img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 ); 4505 img.css({ 4506 top: 0, 4507 left: 0 4508 }); 4509 4510 } 4511 4512 // unbind and bind event 4513 this.$( 'stage' ).off( 'mousemove', calculate ).on( 'mousemove', calculate ); 4514 4515 // loop the loop 4516 this.addTimer( 'pan' + self._id, loop, 50, true); 4517 4518 return this; 4519 }, 4520 4521 /** 4522 Brings the scope into any callback 4523 4524 @param fn The callback to bring the scope into 4525 @param [scope] Optional scope to bring 4526 4527 @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) ) 4528 4529 @returns {Function} Return the callback with the gallery scope 4530 */ 4531 4532 proxy : function( fn, scope ) { 4533 if ( typeof fn !== 'function' ) { 4534 return F; 4535 } 4536 scope = scope || this; 4537 return function() { 4538 return fn.apply( scope, Utils.array( arguments ) ); 4539 }; 4540 }, 4541 4542 /** 4543 Tells you the theme name of the gallery 4544 4545 @returns {String} theme name 4546 */ 4547 4548 getThemeName : function() { 4549 return this.theme.name; 4550 }, 4551 4552 /** 4553 Removes the panning effect set by addPan() 4554 4555 @returns Instance 4556 */ 4557 4558 removePan: function() { 4559 4560 // todo: doublecheck IE8 4561 4562 this.$( 'stage' ).off( 'mousemove' ); 4563 4564 this.clearTimer( 'pan' + this._id ); 4565 4566 return this; 4567 }, 4568 4569 /** 4570 Adds an element to the Galleria DOM array. 4571 When you add an element here, you can access it using element ID in many API calls 4572 4573 @param {string} id The element ID you wish to use. You can add many elements by adding more arguments. 4574 4575 @example addElement('mybutton'); 4576 @example addElement('mybutton','mylink'); 4577 4578 @returns Instance 4579 */ 4580 4581 addElement : function( id ) { 4582 4583 var dom = this._dom; 4584 4585 $.each( Utils.array(arguments), function( i, blueprint ) { 4586 dom[ blueprint ] = Utils.create( 'galleria-' + blueprint ); 4587 }); 4588 4589 return this; 4590 }, 4591 4592 /** 4593 Attach keyboard events to Galleria 4594 4595 @param {Object} map The map object of events. 4596 Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'. 4597 4598 @example 4599 4600this.attachKeyboard({ 4601 right: this.next, 4602 left: this.prev, 4603 up: function() { 4604 console.log( 'up key pressed' ) 4605 } 4606}); 4607 4608 @returns Instance 4609 */ 4610 4611 attachKeyboard : function( map ) { 4612 this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) ); 4613 return this; 4614 }, 4615 4616 /** 4617 Detach all keyboard events to Galleria 4618 4619 @returns Instance 4620 */ 4621 4622 detachKeyboard : function() { 4623 this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) ); 4624 return this; 4625 }, 4626 4627 /** 4628 Fast helper for appending galleria elements that you added using addElement() 4629 4630 @param {string} parentID The parent element ID where the element will be appended 4631 @param {string} childID the element ID that should be appended 4632 4633 @example this.addElement('myElement'); 4634 this.appendChild( 'info', 'myElement' ); 4635 4636 @returns Instance 4637 */ 4638 4639 appendChild : function( parentID, childID ) { 4640 this.$( parentID ).append( this.get( childID ) || childID ); 4641 return this; 4642 }, 4643 4644 /** 4645 Fast helper for prepending galleria elements that you added using addElement() 4646 4647 @param {string} parentID The parent element ID where the element will be prepended 4648 @param {string} childID the element ID that should be prepended 4649 4650 @example 4651 4652this.addElement('myElement'); 4653this.prependChild( 'info', 'myElement' ); 4654 4655 @returns Instance 4656 */ 4657 4658 prependChild : function( parentID, childID ) { 4659 this.$( parentID ).prepend( this.get( childID ) || childID ); 4660 return this; 4661 }, 4662 4663 /** 4664 Remove an element by blueprint 4665 4666 @param {string} elemID The element to be removed. 4667 You can remove multiple elements by adding arguments. 4668 4669 @returns Instance 4670 */ 4671 4672 remove : function( elemID ) { 4673 this.$( Utils.array( arguments ).join(',') ).remove(); 4674 return this; 4675 }, 4676 4677 // a fast helper for building dom structures 4678 // leave this out of the API for now 4679 4680 append : function( data ) { 4681 var i, j; 4682 for( i in data ) { 4683 if ( data.hasOwnProperty( i ) ) { 4684 if ( data[i].constructor === Array ) { 4685 for( j = 0; data[i][j]; j++ ) { 4686 this.appendChild( i, data[i][j] ); 4687 } 4688 } else { 4689 this.appendChild( i, data[i] ); 4690 } 4691 } 4692 } 4693 return this; 4694 }, 4695 4696 // an internal helper for scaling according to options 4697 _scaleImage : function( image, options ) { 4698 4699 image = image || this._controls.getActive(); 4700 4701 // janpub (JH) fix: 4702 // image might be unselected yet 4703 // e.g. when external logics rescales the gallery on window resize events 4704 if( !image ) { 4705 return; 4706 } 4707 4708 var complete, 4709 4710 scaleLayer = function( img ) { 4711 $( img.container ).children(':first').css({ 4712 top: M.max(0, Utils.parseValue( img.image.style.top )), 4713 left: M.max(0, Utils.parseValue( img.image.style.left )), 4714 width: Utils.parseValue( img.image.width ), 4715 height: Utils.parseValue( img.image.height ) 4716 }); 4717 }; 4718 4719 options = $.extend({ 4720 width: this._stageWidth, 4721 height: this._stageHeight, 4722 crop: this._options.imageCrop, 4723 max: this._options.maxScaleRatio, 4724 min: this._options.minScaleRatio, 4725 margin: this._options.imageMargin, 4726 position: this._options.imagePosition, 4727 iframelimit: this._options.maxVideoSize 4728 }, options ); 4729 4730 if ( this._options.layerFollow && this._options.imageCrop !== true ) { 4731 4732 if ( typeof options.complete == 'function' ) { 4733 complete = options.complete; 4734 options.complete = function() { 4735 complete.call( image, image ); 4736 scaleLayer( image ); 4737 }; 4738 } else { 4739 options.complete = scaleLayer; 4740 } 4741 4742 } else { 4743 $( image.container ).children(':first').css({ top: 0, left: 0 }); 4744 } 4745 4746 image.scale( options ); 4747 return this; 4748 }, 4749 4750 /** 4751 Updates the carousel, 4752 useful if you resize the gallery and want to re-check if the carousel nav is needed. 4753 4754 @returns Instance 4755 */ 4756 4757 updateCarousel : function() { 4758 this._carousel.update(); 4759 return this; 4760 }, 4761 4762 /** 4763 Resize the entire gallery container 4764 4765 @param {Object} [measures] Optional object with width/height specified 4766 @param {Function} [complete] The callback to be called when the scaling is complete 4767 4768 @returns Instance 4769 */ 4770 4771 resize : function( measures, complete ) { 4772 4773 if ( typeof measures == 'function' ) { 4774 complete = measures; 4775 measures = undef; 4776 } 4777 4778 measures = $.extend( { width:0, height:0 }, measures ); 4779 4780 var self = this, 4781 $container = this.$( 'container' ); 4782 4783 $.each( measures, function( m, val ) { 4784 if ( !val ) { 4785 $container[ m ]( 'auto' ); 4786 measures[ m ] = self._getWH()[ m ]; 4787 } 4788 }); 4789 4790 $.each( measures, function( m, val ) { 4791 $container[ m ]( val ); 4792 }); 4793 4794 return this.rescale( complete ); 4795 4796 }, 4797 4798 /** 4799 Rescales the gallery 4800 4801 @param {number} width The target width 4802 @param {number} height The target height 4803 @param {Function} complete The callback to be called when the scaling is complete 4804 4805 @returns Instance 4806 */ 4807 4808 rescale : function( width, height, complete ) { 4809 4810 var self = this; 4811 4812 // allow rescale(fn) 4813 if ( typeof width === 'function' ) { 4814 complete = width; 4815 width = undef; 4816 } 4817 4818 var scale = function() { 4819 4820 // set stagewidth 4821 self._stageWidth = width || self.$( 'stage' ).width(); 4822 self._stageHeight = height || self.$( 'stage' ).height(); 4823 4824 if ( self._options.swipe ) { 4825 $.each( self._controls.slides, function(i, img) { 4826 self._scaleImage( img ); 4827 $( img.container ).css('left', self._stageWidth * i); 4828 }); 4829 self.$('images').css('width', self._stageWidth * self.getDataLength()); 4830 } else { 4831 // scale the active image 4832 self._scaleImage(); 4833 } 4834 4835 if ( self._options.carousel ) { 4836 self.updateCarousel(); 4837 } 4838 4839 var frame = self._controls.frames[ self._controls.active ]; 4840 4841 if (frame) { 4842 self._controls.frames[ self._controls.active ].scale({ 4843 width: self._stageWidth, 4844 height: self._stageHeight, 4845 iframelimit: self._options.maxVideoSize 4846 }); 4847 } 4848 4849 self.trigger( Galleria.RESCALE ); 4850 4851 if ( typeof complete === 'function' ) { 4852 complete.call( self ); 4853 } 4854 }; 4855 4856 scale.call( self ); 4857 4858 return this; 4859 }, 4860 4861 /** 4862 Refreshes the gallery. 4863 Useful if you change image options at runtime and want to apply the changes to the active image. 4864 4865 @returns Instance 4866 */ 4867 4868 refreshImage : function() { 4869 this._scaleImage(); 4870 if ( this._options.imagePan ) { 4871 this.addPan(); 4872 } 4873 return this; 4874 }, 4875 4876 _preload: function() { 4877 if ( this._options.preload ) { 4878 var p, i, 4879 n = this.getNext(), 4880 ndata; 4881 try { 4882 for ( i = this._options.preload; i > 0; i-- ) { 4883 p = new Galleria.Picture(); 4884 ndata = this.getData( n ); 4885 p.preload( this.isFullscreen() && ndata.big ? ndata.big : ndata.image ); 4886 n = this.getNext( n ); 4887 } 4888 } catch(e) {} 4889 } 4890 }, 4891 4892 /** 4893 Shows an image by index 4894 4895 @param {number|boolean} index The index to show 4896 @param {Boolean} rewind A boolean that should be true if you want the transition to go back 4897 4898 @returns Instance 4899 */ 4900 4901 show : function( index, rewind, _history ) { 4902 4903 var swipe = this._options.swipe; 4904 4905 // do nothing queue is long || index is false || queue is false and transition is in progress 4906 if ( !swipe && 4907 ( this._queue.length > 3 || index === false || ( !this._options.queue && this._queue.stalled ) ) ) { 4908 return; 4909 } 4910 4911 index = M.max( 0, M.min( parseInt( index, 10 ), this.getDataLength() - 1 ) ); 4912 4913 rewind = typeof rewind !== 'undefined' ? !!rewind : index < this.getIndex(); 4914 4915 _history = _history || false; 4916 4917 // do the history thing and return 4918 if ( !_history && Galleria.History ) { 4919 Galleria.History.set( index.toString() ); 4920 return; 4921 } 4922 4923 if ( this.finger && index !== this._active ) { 4924 this.finger.to = -( index*this.finger.width ); 4925 this.finger.index = index; 4926 } 4927 this._active = index; 4928 4929 // we do things a bit simpler in swipe: 4930 if ( swipe ) { 4931 4932 var data = this.getData(index), 4933 self = this; 4934 if ( !data ) { 4935 return; 4936 } 4937 4938 var src = this.isFullscreen() && data.big ? data.big : ( data.image || data.iframe ), 4939 image = this._controls.slides[index], 4940 cached = image.isCached( src ), 4941 thumb = this._thumbnails[ index ]; 4942 4943 var evObj = { 4944 cached: cached, 4945 index: index, 4946 rewind: rewind, 4947 imageTarget: image.image, 4948 thumbTarget: thumb.image, 4949 galleriaData: data 4950 }; 4951 4952 this.trigger($.extend(evObj, { 4953 type: Galleria.LOADSTART 4954 })); 4955 4956 self.$('container').removeClass( 'videoplay' ); 4957 4958 var complete = function() { 4959 4960 self._layers[index].innerHTML = self.getData().layer || ''; 4961 4962 self.trigger($.extend(evObj, { 4963 type: Galleria.LOADFINISH 4964 })); 4965 self._playCheck(); 4966 }; 4967 4968 self._preload(); 4969 4970 window.setTimeout(function() { 4971 4972 // load if not ready 4973 if ( !image.ready || $(image.image).attr('src') != src ) { 4974 if ( data.iframe && !data.image ) { 4975 image.isIframe = true; 4976 } 4977 image.load(src, function(image) { 4978 evObj.imageTarget = image.image; 4979 self._scaleImage(image, complete).trigger($.extend(evObj, { 4980 type: Galleria.IMAGE 4981 })); 4982 complete(); 4983 }); 4984 } else { 4985 self.trigger($.extend(evObj, { 4986 type: Galleria.IMAGE 4987 })); 4988 complete(); 4989 } 4990 }, 100); 4991 4992 } else { 4993 protoArray.push.call( this._queue, { 4994 index : index, 4995 rewind : rewind 4996 }); 4997 if ( !this._queue.stalled ) { 4998 this._show(); 4999 } 5000 } 5001 5002 return this; 5003 }, 5004 5005 // the internal _show method does the actual showing 5006 _show : function() { 5007 5008 // shortcuts 5009 var self = this, 5010 queue = this._queue[ 0 ], 5011 data = this.getData( queue.index ); 5012 5013 if ( !data ) { 5014 return; 5015 } 5016 5017 var src = this.isFullscreen() && data.big ? data.big : ( data.image || data.iframe ), 5018 active = this._controls.getActive(), 5019 next = this._controls.getNext(), 5020 cached = next.isCached( src ), 5021 thumb = this._thumbnails[ queue.index ], 5022 mousetrigger = function() { 5023 $( next.image ).trigger( 'mouseup' ); 5024 }; 5025 5026 self.$('container').toggleClass('iframe', !!data.isIframe).removeClass( 'videoplay' ); 5027 5028 // to be fired when loading & transition is complete: 5029 var complete = (function( data, next, active, queue, thumb ) { 5030 5031 return function() { 5032 5033 var win; 5034 5035 _transitions.active = false; 5036 5037 // optimize quality 5038 Utils.toggleQuality( next.image, self._options.imageQuality ); 5039 5040 // remove old layer 5041 self._layers[ self._controls.active ].innerHTML = ''; 5042 5043 // swap 5044 $( active.container ).css({ 5045 zIndex: 0, 5046 opacity: 0 5047 }).show(); 5048 5049 $( active.container ).find( 'iframe, .galleria-videoicon' ).remove(); 5050 $( self._controls.frames[ self._controls.active ].container ).hide(); 5051 5052 $( next.container ).css({ 5053 zIndex: 1, 5054 left: 0, 5055 top: 0 5056 }).show(); 5057 5058 self._controls.swap(); 5059 5060 // add pan according to option 5061 if ( self._options.imagePan ) { 5062 self.addPan( next.image ); 5063 } 5064 5065 // make the image clickable 5066 // order of precedence: iframe, link, lightbox, clicknext 5067 if ( ( data.iframe && data.image ) || data.link || self._options.lightbox || self._options.clicknext ) { 5068 5069 $( next.image ).css({ 5070 cursor: 'pointer' 5071 }).on( 'mouseup', function( e ) { 5072 5073 // non-left click 5074 if ( typeof e.which == 'number' && e.which > 1 ) { 5075 return; 5076 } 5077 5078 // iframe / video 5079 if ( data.iframe ) { 5080 5081 if ( self.isPlaying() ) { 5082 self.pause(); 5083 } 5084 var frame = self._controls.frames[ self._controls.active ], 5085 w = self._stageWidth, 5086 h = self._stageHeight; 5087 5088 $( frame.container ).css({ 5089 width: w, 5090 height: h, 5091 opacity: 0 5092 }).show().animate({ 5093 opacity: 1 5094 }, 200); 5095 5096 window.setTimeout(function() { 5097 frame.load( data.iframe + ( data.video ? '&autoplay=1' : '' ), { 5098 width: w, 5099 height: h 5100 }, function( frame ) { 5101 self.$( 'container' ).addClass( 'videoplay' ); 5102 frame.scale({ 5103 width: self._stageWidth, 5104 height: self._stageHeight, 5105 iframelimit: data.video ? self._options.maxVideoSize : undef 5106 }); 5107 }); 5108 }, 100); 5109 5110 return; 5111 } 5112 5113 // clicknext 5114 if ( self._options.clicknext && !Galleria.TOUCH ) { 5115 if ( self._options.pauseOnInteraction ) { 5116 self.pause(); 5117 } 5118 self.next(); 5119 return; 5120 } 5121 5122 // popup link 5123 if ( data.link ) { 5124 if ( self._options.popupLinks ) { 5125 win = window.open( data.link, '_blank' ); 5126 } else { 5127 window.location.href = data.link; 5128 } 5129 return; 5130 } 5131 5132 if ( self._options.lightbox ) { 5133 self.openLightbox(); 5134 } 5135 5136 }); 5137 } 5138 5139 // check if we are playing 5140 self._playCheck(); 5141 5142 // trigger IMAGE event 5143 self.trigger({ 5144 type: Galleria.IMAGE, 5145 index: queue.index, 5146 imageTarget: next.image, 5147 thumbTarget: thumb.image, 5148 galleriaData: data 5149 }); 5150 5151 // remove the queued image 5152 protoArray.shift.call( self._queue ); 5153 5154 // remove stalled 5155 self._queue.stalled = false; 5156 5157 // if we still have images in the queue, show it 5158 if ( self._queue.length ) { 5159 self._show(); 5160 } 5161 5162 }; 5163 }( data, next, active, queue, thumb )); 5164 5165 // let the carousel follow 5166 if ( this._options.carousel && this._options.carouselFollow ) { 5167 this._carousel.follow( queue.index ); 5168 } 5169 5170 // preload images 5171 self._preload(); 5172 5173 // show the next image, just in case 5174 Utils.show( next.container ); 5175 5176 next.isIframe = data.iframe && !data.image; 5177 5178 // add active classes 5179 $( self._thumbnails[ queue.index ].container ) 5180 .addClass( 'active' ) 5181 .siblings( '.active' ) 5182 .removeClass( 'active' ); 5183 5184 // trigger the LOADSTART event 5185 self.trigger( { 5186 type: Galleria.LOADSTART, 5187 cached: cached, 5188 index: queue.index, 5189 rewind: queue.rewind, 5190 imageTarget: next.image, 5191 thumbTarget: thumb.image, 5192 galleriaData: data 5193 }); 5194 5195 // stall the queue 5196 self._queue.stalled = true; 5197 5198 // begin loading the next image 5199 next.load( src, function( next ) { 5200 5201 // add layer HTML 5202 var layer = $( self._layers[ 1-self._controls.active ] ).html( data.layer || '' ).hide(); 5203 5204 self._scaleImage( next, { 5205 5206 complete: function( next ) { 5207 5208 // toggle low quality for IE 5209 if ( 'image' in active ) { 5210 Utils.toggleQuality( active.image, false ); 5211 } 5212 Utils.toggleQuality( next.image, false ); 5213 5214 // remove the image panning, if applied 5215 // TODO: rethink if this is necessary 5216 self.removePan(); 5217 5218 // set the captions and counter 5219 self.setInfo( queue.index ); 5220 self.setCounter( queue.index ); 5221 5222 // show the layer now 5223 if ( data.layer ) { 5224 layer.show(); 5225 // inherit click events set on image 5226 if ( ( data.iframe && data.image ) || data.link || self._options.lightbox || self._options.clicknext ) { 5227 layer.css( 'cursor', 'pointer' ).off( 'mouseup' ).mouseup( mousetrigger ); 5228 } 5229 } 5230 5231 // add play icon 5232 if( data.video && data.image ) { 5233 _playIcon( next.container ); 5234 } 5235 5236 var transition = self._options.transition; 5237 5238 // can JavaScript loop through objects in order? yes. 5239 $.each({ 5240 initial: active.image === null, 5241 touch: Galleria.TOUCH, 5242 fullscreen: self.isFullscreen() 5243 }, function( type, arg ) { 5244 if ( arg && self._options[ type + 'Transition' ] !== undef ) { 5245 transition = self._options[ type + 'Transition' ]; 5246 return false; 5247 } 5248 }); 5249 5250 // validate the transition 5251 if ( transition in _transitions.effects === false ) { 5252 complete(); 5253 } else { 5254 var params = { 5255 prev: active.container, 5256 next: next.container, 5257 rewind: queue.rewind, 5258 speed: self._options.transitionSpeed || 400 5259 }; 5260 5261 _transitions.active = true; 5262 5263 // call the transition function and send some stuff 5264 _transitions.init.call( self, transition, params, complete ); 5265 5266 } 5267 5268 // trigger the LOADFINISH event 5269 self.trigger({ 5270 type: Galleria.LOADFINISH, 5271 cached: cached, 5272 index: queue.index, 5273 rewind: queue.rewind, 5274 imageTarget: next.image, 5275 thumbTarget: self._thumbnails[ queue.index ].image, 5276 galleriaData: self.getData( queue.index ) 5277 }); 5278 } 5279 }); 5280 }); 5281 }, 5282 5283 /** 5284 Gets the next index 5285 5286 @param {number} [base] Optional starting point 5287 5288 @returns {number} the next index, or the first if you are at the first (looping) 5289 */ 5290 5291 getNext : function( base ) { 5292 base = typeof base === 'number' ? base : this.getIndex(); 5293 return base === this.getDataLength() - 1 ? 0 : base + 1; 5294 }, 5295 5296 /** 5297 Gets the previous index 5298 5299 @param {number} [base] Optional starting point 5300 5301 @returns {number} the previous index, or the last if you are at the first (looping) 5302 */ 5303 5304 getPrev : function( base ) { 5305 base = typeof base === 'number' ? base : this.getIndex(); 5306 return base === 0 ? this.getDataLength() - 1 : base - 1; 5307 }, 5308 5309 /** 5310 Shows the next image in line 5311 5312 @returns Instance 5313 */ 5314 5315 next : function() { 5316 if ( this.getDataLength() > 1 ) { 5317 this.show( this.getNext(), false ); 5318 } 5319 return this; 5320 }, 5321 5322 /** 5323 Shows the previous image in line 5324 5325 @returns Instance 5326 */ 5327 5328 prev : function() { 5329 if ( this.getDataLength() > 1 ) { 5330 this.show( this.getPrev(), true ); 5331 } 5332 return this; 5333 }, 5334 5335 /** 5336 Retrieve a DOM element by element ID 5337 5338 @param {string} elemId The delement ID to fetch 5339 5340 @returns {HTMLElement} The elements DOM node or null if not found. 5341 */ 5342 5343 get : function( elemId ) { 5344 return elemId in this._dom ? this._dom[ elemId ] : null; 5345 }, 5346 5347 /** 5348 Retrieve a data object 5349 5350 @param {number} index The data index to retrieve. 5351 If no index specified it will take the currently active image 5352 5353 @returns {Object} The data object 5354 */ 5355 5356 getData : function( index ) { 5357 return index in this._data ? 5358 this._data[ index ] : this._data[ this._active ]; 5359 }, 5360 5361 /** 5362 Retrieve the number of data items 5363 5364 @returns {number} The data length 5365 */ 5366 getDataLength : function() { 5367 return this._data.length; 5368 }, 5369 5370 /** 5371 Retrieve the currently active index 5372 5373 @returns {number|boolean} The active index or false if none found 5374 */ 5375 5376 getIndex : function() { 5377 return typeof this._active === 'number' ? this._active : false; 5378 }, 5379 5380 /** 5381 Retrieve the stage height 5382 5383 @returns {number} The stage height 5384 */ 5385 5386 getStageHeight : function() { 5387 return this._stageHeight; 5388 }, 5389 5390 /** 5391 Retrieve the stage width 5392 5393 @returns {number} The stage width 5394 */ 5395 5396 getStageWidth : function() { 5397 return this._stageWidth; 5398 }, 5399 5400 /** 5401 Retrieve the option 5402 5403 @param {string} key The option key to retrieve. If no key specified it will return all options in an object. 5404 5405 @returns option or options 5406 */ 5407 5408 getOptions : function( key ) { 5409 return typeof key === 'undefined' ? this._options : this._options[ key ]; 5410 }, 5411 5412 /** 5413 Set options to the instance. 5414 You can set options using a key & value argument or a single object argument (see examples) 5415 5416 @param {string} key The option key 5417 @param {string} value the the options value 5418 5419 @example setOptions( 'autoplay', true ) 5420 @example setOptions({ autoplay: true }); 5421 5422 @returns Instance 5423 */ 5424 5425 setOptions : function( key, value ) { 5426 if ( typeof key === 'object' ) { 5427 $.extend( this._options, key ); 5428 } else { 5429 this._options[ key ] = value; 5430 } 5431 return this; 5432 }, 5433 5434 /** 5435 Starts playing the slideshow 5436 5437 @param {number} delay Sets the slideshow interval in milliseconds. 5438 If you set it once, you can just call play() and get the same interval the next time. 5439 5440 @returns Instance 5441 */ 5442 5443 play : function( delay ) { 5444 5445 this._playing = true; 5446 5447 this._playtime = delay || this._playtime; 5448 5449 this._playCheck(); 5450 5451 this.trigger( Galleria.PLAY ); 5452 5453 return this; 5454 }, 5455 5456 /** 5457 Stops the slideshow if currently playing 5458 5459 @returns Instance 5460 */ 5461 5462 pause : function() { 5463 5464 this._playing = false; 5465 5466 this.trigger( Galleria.PAUSE ); 5467 5468 return this; 5469 }, 5470 5471 /** 5472 Toggle between play and pause events. 5473 5474 @param {number} delay Sets the slideshow interval in milliseconds. 5475 5476 @returns Instance 5477 */ 5478 5479 playToggle : function( delay ) { 5480 return ( this._playing ) ? this.pause() : this.play( delay ); 5481 }, 5482 5483 /** 5484 Checks if the gallery is currently playing 5485 5486 @returns {Boolean} 5487 */ 5488 5489 isPlaying : function() { 5490 return this._playing; 5491 }, 5492 5493 /** 5494 Checks if the gallery is currently in fullscreen mode 5495 5496 @returns {Boolean} 5497 */ 5498 5499 isFullscreen : function() { 5500 return this._fullscreen.active; 5501 }, 5502 5503 _playCheck : function() { 5504 var self = this, 5505 played = 0, 5506 interval = 20, 5507 now = Utils.timestamp(), 5508 timer_id = 'play' + this._id; 5509 5510 if ( this._playing ) { 5511 5512 this.clearTimer( timer_id ); 5513 5514 var fn = function() { 5515 5516 played = Utils.timestamp() - now; 5517 if ( played >= self._playtime && self._playing ) { 5518 self.clearTimer( timer_id ); 5519 self.next(); 5520 return; 5521 } 5522 if ( self._playing ) { 5523 5524 // trigger the PROGRESS event 5525 self.trigger({ 5526 type: Galleria.PROGRESS, 5527 percent: M.ceil( played / self._playtime * 100 ), 5528 seconds: M.floor( played / 1000 ), 5529 milliseconds: played 5530 }); 5531 5532 self.addTimer( timer_id, fn, interval ); 5533 } 5534 }; 5535 self.addTimer( timer_id, fn, interval ); 5536 } 5537 }, 5538 5539 /** 5540 Modify the slideshow delay 5541 5542 @param {number} delay the number of milliseconds between slides, 5543 5544 @returns Instance 5545 */ 5546 5547 setPlaytime: function( delay ) { 5548 this._playtime = delay; 5549 return this; 5550 }, 5551 5552 setIndex: function( val ) { 5553 this._active = val; 5554 return this; 5555 }, 5556 5557 /** 5558 Manually modify the counter 5559 5560 @param {number} [index] Optional data index to fectch, 5561 if no index found it assumes the currently active index 5562 5563 @returns Instance 5564 */ 5565 5566 setCounter: function( index ) { 5567 5568 if ( typeof index === 'number' ) { 5569 index++; 5570 } else if ( typeof index === 'undefined' ) { 5571 index = this.getIndex()+1; 5572 } 5573 5574 this.get( 'current' ).innerHTML = index; 5575 5576 if ( IE ) { // weird IE bug 5577 5578 var count = this.$( 'counter' ), 5579 opacity = count.css( 'opacity' ); 5580 5581 if ( parseInt( opacity, 10 ) === 1) { 5582 Utils.removeAlpha( count[0] ); 5583 } else { 5584 this.$( 'counter' ).css( 'opacity', opacity ); 5585 } 5586 5587 } 5588 5589 return this; 5590 }, 5591 5592 /** 5593 Manually set captions 5594 5595 @param {number} [index] Optional data index to fectch and apply as caption, 5596 if no index found it assumes the currently active index 5597 5598 @returns Instance 5599 */ 5600 5601 setInfo : function( index ) { 5602 5603 var self = this, 5604 data = this.getData( index ); 5605 5606 $.each( ['title','description'], function( i, type ) { 5607 5608 var elem = self.$( 'info-' + type ); 5609 5610 if ( !!data[type] ) { 5611 elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] ); 5612 } else { 5613 elem.empty().hide(); 5614 } 5615 }); 5616 5617 return this; 5618 }, 5619 5620 /** 5621 Checks if the data contains any captions 5622 5623 @param {number} [index] Optional data index to fectch, 5624 if no index found it assumes the currently active index. 5625 5626 @returns {boolean} 5627 */ 5628 5629 hasInfo : function( index ) { 5630 5631 var check = 'title description'.split(' '), 5632 i; 5633 5634 for ( i = 0; check[i]; i++ ) { 5635 if ( !!this.getData( index )[ check[i] ] ) { 5636 return true; 5637 } 5638 } 5639 return false; 5640 5641 }, 5642 5643 jQuery : function( str ) { 5644 5645 var self = this, 5646 ret = []; 5647 5648 $.each( str.split(','), function( i, elemId ) { 5649 elemId = $.trim( elemId ); 5650 5651 if ( self.get( elemId ) ) { 5652 ret.push( elemId ); 5653 } 5654 }); 5655 5656 var jQ = $( self.get( ret.shift() ) ); 5657 5658 $.each( ret, function( i, elemId ) { 5659 jQ = jQ.add( self.get( elemId ) ); 5660 }); 5661 5662 return jQ; 5663 5664 }, 5665 5666 /** 5667 Converts element IDs into a jQuery collection 5668 You can call for multiple IDs separated with commas. 5669 5670 @param {string} str One or more element IDs (comma-separated) 5671 5672 @returns jQuery 5673 5674 @example this.$('info,container').hide(); 5675 */ 5676 5677 $ : function( str ) { 5678 return this.jQuery.apply( this, Utils.array( arguments ) ); 5679 } 5680 5681}; 5682 5683// End of Galleria prototype 5684 5685// Add events as static variables 5686$.each( _events, function( i, ev ) { 5687 5688 // legacy events 5689 var type = /_/.test( ev ) ? ev.replace( /_/g, '' ) : ev; 5690 5691 Galleria[ ev.toUpperCase() ] = 'galleria.'+type; 5692 5693} ); 5694 5695$.extend( Galleria, { 5696 5697 // Browser helpers 5698 IE9: IE === 9, 5699 IE8: IE === 8, 5700 IE7: IE === 7, 5701 IE6: IE === 6, 5702 IE: IE, 5703 WEBKIT: /webkit/.test( NAV ), 5704 CHROME: /chrome/.test( NAV ), 5705 SAFARI: /safari/.test( NAV ) && !(/chrome/.test( NAV )), 5706 QUIRK: ( IE && doc.compatMode && doc.compatMode === "BackCompat" ), 5707 MAC: /mac/.test( navigator.platform.toLowerCase() ), 5708 OPERA: !!window.opera, 5709 IPHONE: /iphone/.test( NAV ), 5710 IPAD: /ipad/.test( NAV ), 5711 ANDROID: /android/.test( NAV ), 5712 TOUCH: ( 'ontouchstart' in doc ) && MOBILE // rule out false positives on Win10 5713 5714}); 5715 5716// Galleria static methods 5717 5718/** 5719 Adds a theme that you can use for your Gallery 5720 5721 @param {Object} theme Object that should contain all your theme settings. 5722 <ul> 5723 <li>name - name of the theme</li> 5724 <li>author - name of the author</li> 5725 <li>css - css file name (not path)</li> 5726 <li>defaults - default options to apply, including theme-specific options</li> 5727 <li>init - the init function</li> 5728 </ul> 5729 5730 @returns {Object} theme 5731*/ 5732 5733Galleria.addTheme = function( theme ) { 5734 5735 // make sure we have a name 5736 if ( !theme.name ) { 5737 Galleria.raise('No theme name specified'); 5738 } 5739 5740 // make sure it's compatible 5741 if ( !theme.version || parseInt(Galleria.version*10) > parseInt(theme.version*10) ) { 5742 Galleria.raise('This version of Galleria requires '+theme.name+' theme version '+parseInt(Galleria.version*10)/10+' or later', true); 5743 } 5744 5745 if ( typeof theme.defaults !== 'object' ) { 5746 theme.defaults = {}; 5747 } else { 5748 theme.defaults = _legacyOptions( theme.defaults ); 5749 } 5750 5751 var css = false, 5752 reg, reg2; 5753 5754 if ( typeof theme.css === 'string' ) { 5755 5756 // look for manually added CSS 5757 $('link').each(function( i, link ) { 5758 reg = new RegExp( theme.css ); 5759 if ( reg.test( link.href ) ) { 5760 5761 // we found the css 5762 css = true; 5763 5764 // the themeload trigger 5765 _themeLoad( theme ); 5766 5767 return false; 5768 } 5769 }); 5770 5771 // else look for the absolute path and load the CSS dynamic 5772 if ( !css ) { 5773 5774 5775 $(function() { 5776 // Try to determine the css-path from the theme script. 5777 // In IE8/9, the script-dom-element seems to be not present 5778 // at once, if galleria itself is inserted into the dom 5779 // dynamically. We therefore try multiple times before raising 5780 // an error. 5781 var retryCount = 0; 5782 var tryLoadCss = function() { 5783 $('script').each(function (i, script) { 5784 // look for the theme script 5785 reg = new RegExp('galleria\\.' + theme.name.toLowerCase() + '\\.'); 5786 reg2 = new RegExp('galleria\\.io\\/theme\\/' + theme.name.toLowerCase() + '\\/(\\d*\\.*)?(\\d*\\.*)?(\\d*\\/)?js'); 5787 if (reg.test(script.src) || reg2.test(script.src)) { 5788 // we have a match 5789 css = script.src.replace(/[^\/]*$/, '') + theme.css; 5790 5791 window.setTimeout(function () { 5792 Utils.loadCSS(css, 'galleria-theme-'+theme.name, function () { 5793 5794 // run galleries with this theme 5795 _themeLoad(theme); 5796 5797 }); 5798 }, 1); 5799 } 5800 }); 5801 if (!css) { 5802 if (retryCount++ > 5) { 5803 Galleria.raise('No theme CSS loaded'); 5804 } else { 5805 window.setTimeout(tryLoadCss, 500); 5806 } 5807 } 5808 }; 5809 tryLoadCss(); 5810 }); 5811 } 5812 5813 } else { 5814 5815 // pass 5816 _themeLoad( theme ); 5817 } 5818 return theme; 5819}; 5820 5821/** 5822 loadTheme loads a theme js file and attaches a load event to Galleria 5823 5824 @param {string} src The relative path to the theme source file 5825 5826 @param {Object} [options] Optional options you want to apply 5827 5828 @returns Galleria 5829*/ 5830 5831Galleria.loadTheme = function( src, options ) { 5832 5833 // Don't load if theme is already loaded 5834 if( $('script').filter(function() { return $(this).attr('src') == src; }).length ) { 5835 return; 5836 } 5837 5838 var loaded = false, 5839 err; 5840 5841 // start listening for the timeout onload 5842 $( window ).on('load', function() { 5843 if ( !loaded ) { 5844 // give it another 20 seconds 5845 err = window.setTimeout(function() { 5846 if ( !loaded ) { 5847 Galleria.raise( "Galleria had problems loading theme at " + src + ". Please check theme path or load manually.", true ); 5848 } 5849 }, 20000); 5850 } 5851 }); 5852 5853 // load the theme 5854 Utils.loadScript( src, function() { 5855 loaded = true; 5856 window.clearTimeout( err ); 5857 }); 5858 5859 return Galleria; 5860}; 5861 5862/** 5863 Retrieves a Galleria instance. 5864 5865 @param {number} [index] Optional index to retrieve. 5866 If no index is supplied, the method will return all instances in an array. 5867 5868 @returns Instance or Array of instances 5869*/ 5870 5871Galleria.get = function( index ) { 5872 if ( !!_instances[ index ] ) { 5873 return _instances[ index ]; 5874 } else if ( typeof index !== 'number' ) { 5875 return _instances; 5876 } else { 5877 Galleria.raise('Gallery index ' + index + ' not found'); 5878 } 5879}; 5880 5881/** 5882 5883 Configure Galleria options via a static function. 5884 The options will be applied to all instances 5885 5886 @param {string|object} key The options to apply or a key 5887 5888 @param [value] If key is a string, this is the value 5889 5890 @returns Galleria 5891 5892*/ 5893 5894Galleria.configure = function( key, value ) { 5895 5896 var opts = {}; 5897 5898 if( typeof key == 'string' && value ) { 5899 opts[key] = value; 5900 key = opts; 5901 } else { 5902 $.extend( opts, key ); 5903 } 5904 5905 Galleria.configure.options = opts; 5906 5907 $.each( Galleria.get(), function(i, instance) { 5908 instance.setOptions( opts ); 5909 }); 5910 5911 return Galleria; 5912}; 5913 5914Galleria.configure.options = {}; 5915 5916/** 5917 5918 Bind a Galleria event to the gallery 5919 5920 @param {string} type A string representing the galleria event 5921 5922 @param {function} callback The function that should run when the event is triggered 5923 5924 @returns Galleria 5925 5926*/ 5927 5928Galleria.on = function( type, callback ) { 5929 if ( !type ) { 5930 return; 5931 } 5932 5933 callback = callback || F; 5934 5935 // hash the bind 5936 var hash = type + callback.toString().replace(/\s/g,'') + Utils.timestamp(); 5937 5938 // for existing instances 5939 $.each( Galleria.get(), function(i, instance) { 5940 instance._binds.push( hash ); 5941 instance.bind( type, callback ); 5942 }); 5943 5944 // for future instances 5945 Galleria.on.binds.push({ 5946 type: type, 5947 callback: callback, 5948 hash: hash 5949 }); 5950 5951 return Galleria; 5952}; 5953 5954Galleria.on.binds = []; 5955 5956/** 5957 5958 Run Galleria 5959 Alias for $(selector).galleria(options) 5960 5961 @param {string} selector A selector of element(s) to intialize galleria to 5962 5963 @param {object} options The options to apply 5964 5965 @returns Galleria 5966 5967*/ 5968 5969Galleria.run = function( selector, options ) { 5970 if ( $.isFunction( options ) ) { 5971 options = { extend: options }; 5972 } 5973 $( selector || '#galleria' ).galleria( options ); 5974 return Galleria; 5975}; 5976 5977/** 5978 Creates a transition to be used in your gallery 5979 5980 @param {string} name The name of the transition that you will use as an option 5981 5982 @param {Function} fn The function to be executed in the transition. 5983 The function contains two arguments, params and complete. 5984 Use the params Object to integrate the transition, and then call complete when you are done. 5985 5986 @returns Galleria 5987 5988*/ 5989 5990Galleria.addTransition = function( name, fn ) { 5991 _transitions.effects[name] = fn; 5992 return Galleria; 5993}; 5994 5995/** 5996 The Galleria utilites 5997*/ 5998 5999Galleria.utils = Utils; 6000 6001/** 6002 A helper metod for cross-browser logging. 6003 It uses the console log if available otherwise it falls back to alert 6004 6005 @example Galleria.log("hello", document.body, [1,2,3]); 6006*/ 6007 6008Galleria.log = function() { 6009 var args = Utils.array( arguments ); 6010 if( 'console' in window && 'log' in window.console ) { 6011 try { 6012 return window.console.log.apply( window.console, args ); 6013 } catch( e ) { 6014 $.each( args, function() { 6015 window.console.log(this); 6016 }); 6017 } 6018 } else { 6019 return window.alert( args.join('<br>') ); 6020 } 6021}; 6022 6023/** 6024 A ready method for adding callbacks when a gallery is ready 6025 Each method is call before the extend option for every instance 6026 6027 @param {function} callback The function to call 6028 6029 @returns Galleria 6030*/ 6031 6032Galleria.ready = function( fn ) { 6033 if ( typeof fn != 'function' ) { 6034 return Galleria; 6035 } 6036 $.each( _galleries, function( i, gallery ) { 6037 fn.call( gallery, gallery._options ); 6038 }); 6039 Galleria.ready.callbacks.push( fn ); 6040 return Galleria; 6041}; 6042 6043Galleria.ready.callbacks = []; 6044 6045/** 6046 Method for raising errors 6047 6048 @param {string} msg The message to throw 6049 6050 @param {boolean} [fatal] Set this to true to override debug settings and display a fatal error 6051*/ 6052 6053Galleria.raise = function( msg, fatal ) { 6054 6055 var type = fatal ? 'Fatal error' : 'Error', 6056 6057 css = { 6058 color: '#fff', 6059 position: 'absolute', 6060 top: 0, 6061 left: 0, 6062 zIndex: 100000 6063 }, 6064 6065 echo = function( msg ) { 6066 6067 var html = '<div style="padding:4px;margin:0 0 2px;background:#' + 6068 ( fatal ? '811' : '222' ) + ';">' + 6069 ( fatal ? '<strong>' + type + ': </strong>' : '' ) + 6070 msg + '</div>'; 6071 6072 $.each( _instances, function() { 6073 6074 var cont = this.$( 'errors' ), 6075 target = this.$( 'target' ); 6076 6077 if ( !cont.length ) { 6078 6079 target.css( 'position', 'relative' ); 6080 6081 cont = this.addElement( 'errors' ).appendChild( 'target', 'errors' ).$( 'errors' ).css(css); 6082 } 6083 cont.append( html ); 6084 6085 }); 6086 6087 if ( !_instances.length ) { 6088 $('<div>').css( $.extend( css, { position: 'fixed' } ) ).append( html ).appendTo( DOM().body ); 6089 } 6090 }; 6091 6092 // if debug is on, display errors and throw exception if fatal 6093 if ( DEBUG ) { 6094 echo( msg ); 6095 if ( fatal ) { 6096 throw new Error(type + ': ' + msg); 6097 } 6098 6099 // else just echo a silent generic error if fatal 6100 } else if ( fatal ) { 6101 if ( _hasError ) { 6102 return; 6103 } 6104 _hasError = true; 6105 fatal = false; 6106 echo( 'Gallery could not load.' ); 6107 } 6108}; 6109 6110// Add the version 6111Galleria.version = VERSION; 6112 6113Galleria.getLoadedThemes = function() { 6114 return $.map(_loadedThemes, function(theme) { 6115 return theme.name; 6116 }); 6117}; 6118 6119/** 6120 A method for checking what version of Galleria the user has installed and throws a readable error if the user needs to upgrade. 6121 Useful when building plugins that requires a certain version to function. 6122 6123 @param {number} version The minimum version required 6124 6125 @param {string} [msg] Optional message to display. If not specified, Galleria will throw a generic error. 6126 6127 @returns Galleria 6128*/ 6129 6130Galleria.requires = function( version, msg ) { 6131 msg = msg || 'You need to upgrade Galleria to version ' + version + ' to use one or more components.'; 6132 if ( Galleria.version < version ) { 6133 Galleria.raise(msg, true); 6134 } 6135 return Galleria; 6136}; 6137 6138/** 6139 Adds preload, cache, scale and crop functionality 6140 6141 @constructor 6142 6143 @requires jQuery 6144 6145 @param {number} [id] Optional id to keep track of instances 6146*/ 6147 6148Galleria.Picture = function( id ) { 6149 6150 // save the id 6151 this.id = id || null; 6152 6153 // the image should be null until loaded 6154 this.image = null; 6155 6156 // Create a new container 6157 this.container = Utils.create('galleria-image'); 6158 6159 // add container styles 6160 $( this.container ).css({ 6161 overflow: 'hidden', 6162 position: 'relative' // for IE Standards mode 6163 }); 6164 6165 // saves the original measurements 6166 this.original = { 6167 width: 0, 6168 height: 0 6169 }; 6170 6171 // flag when the image is ready 6172 this.ready = false; 6173 6174 // flag for iframe Picture 6175 this.isIframe = false; 6176 6177}; 6178 6179Galleria.Picture.prototype = { 6180 6181 // the inherited cache object 6182 cache: {}, 6183 6184 // show the image on stage 6185 show: function() { 6186 Utils.show( this.image ); 6187 }, 6188 6189 // hide the image 6190 hide: function() { 6191 Utils.moveOut( this.image ); 6192 }, 6193 6194 clear: function() { 6195 this.image = null; 6196 }, 6197 6198 /** 6199 Checks if an image is in cache 6200 6201 @param {string} src The image source path, ex '/path/to/img.jpg' 6202 6203 @returns {boolean} 6204 */ 6205 6206 isCached: function( src ) { 6207 return !!this.cache[src]; 6208 }, 6209 6210 /** 6211 Preloads an image into the cache 6212 6213 @param {string} src The image source path, ex '/path/to/img.jpg' 6214 6215 @returns Galleria.Picture 6216 */ 6217 6218 preload: function( src ) { 6219 var $image = $( new Image() ).on( 'load', (function(src, cache) { 6220 return function() { 6221 cache[ src ] = src; 6222 }; 6223 }( src, this.cache ))); 6224 6225 // due to a bug in safari, need to set srcset first 6226 if (src.srcset) { 6227 $image.attr( 'srcset', src.srcset ); 6228 } 6229 6230 if (src.sizes) { 6231 $image.attr( 'sizes', src.sizes ); 6232 } 6233 6234 $image.attr( 'src', src ); 6235 }, 6236 6237 /** 6238 Loads an image and call the callback when ready. 6239 Will also add the image to cache. 6240 6241 @param {string} src The image source path, ex '/path/to/img.jpg' 6242 @param {Object} [size] The forced size of the image, defined as an object { width: xx, height:xx } 6243 @param {Function} callback The function to be executed when the image is loaded & scaled 6244 6245 @returns The image container (jQuery object) 6246 */ 6247 6248 load: function(src, size, callback) { 6249 6250 if ( typeof size == 'function' ) { 6251 callback = size; 6252 size = null; 6253 } 6254 6255 if( this.isIframe ) { 6256 var id = 'if'+new Date().getTime(); 6257 6258 var iframe = this.image = $('<iframe>', { 6259 src: src, 6260 frameborder: 0, 6261 id: id, 6262 allowfullscreen: true, 6263 css: { visibility: 'hidden' } 6264 })[0]; 6265 6266 if ( size ) { 6267 $( iframe ).css( size ); 6268 } 6269 6270 $( this.container ).find( 'iframe,img' ).remove(); 6271 6272 this.container.appendChild( this.image ); 6273 6274 $('#'+id).on( 'load', (function( self, callback ) { 6275 return function() { 6276 window.setTimeout(function() { 6277 $( self.image ).css( 'visibility', 'visible' ); 6278 if( typeof callback == 'function' ) { 6279 callback.call( self, self ); 6280 } 6281 }, 10); 6282 }; 6283 }( this, callback ))); 6284 6285 return this.container; 6286 } 6287 6288 this.image = new Image(); 6289 6290 // IE8 opacity inherit bug 6291 if ( Galleria.IE8 ) { 6292 $( this.image ).css( 'filter', 'inherit' ); 6293 } 6294 6295 // FF shaking images bug: 6296 // http://support.galleria.io/discussions/problems/12245-shaking-photos 6297 if ( !Galleria.IE && !Galleria.CHROME && !Galleria.SAFARI ) { 6298 $( this.image ).css( 'image-rendering', 'optimizequality' ); 6299 } 6300 6301 var reload = false, 6302 resort = false, 6303 6304 // some jquery cache 6305 $container = $( this.container ), 6306 $image = $( this.image ), 6307 6308 onerror = function() { 6309 if ( !reload ) { 6310 reload = true; 6311 // reload the image with a timestamp 6312 window.setTimeout((function(image, src) { 6313 return function() { 6314 image.attr('src', src + (src.indexOf('?') > -1 ? '&' : '?') + Utils.timestamp() ); 6315 }; 6316 }( $(this), src )), 50); 6317 } else { 6318 // apply the dummy image if it exists 6319 if ( DUMMY ) { 6320 $( this ).attr( 'src', DUMMY ); 6321 } else { 6322 Galleria.raise('Image not found: ' + src); 6323 } 6324 } 6325 }, 6326 6327 // the onload method 6328 onload = (function( self, callback, src ) { 6329 6330 return function() { 6331 6332 var complete = function() { 6333 6334 $( this ).off( 'load' ); 6335 6336 // save the original size 6337 self.original = size || { 6338 height: this.height, 6339 width: this.width 6340 }; 6341 6342 // translate3d if needed 6343 if ( Galleria.HAS3D ) { 6344 this.style.MozTransform = this.style.webkitTransform = 'translate3d(0,0,0)'; 6345 } 6346 6347 $container.append( this ); 6348 6349 self.cache[ src ] = src; // will override old cache 6350 6351 if (typeof callback == 'function' ) { 6352 window.setTimeout(function() { 6353 callback.call( self, self ); 6354 },1); 6355 } 6356 }; 6357 6358 // Delay the callback to "fix" the Adblock Bug 6359 // http://code.google.com/p/adblockforchrome/issues/detail?id=3701 6360 if ( ( !this.width || !this.height ) ) { 6361 (function( img ) { 6362 Utils.wait({ 6363 until: function() { 6364 return img.width && img.height; 6365 }, 6366 success: function() { 6367 complete.call( img ); 6368 }, 6369 error: function() { 6370 if ( !resort ) { 6371 $(new Image()).on( 'load', onload ).attr( 'src', img.src ); 6372 resort = true; 6373 } else { 6374 Galleria.raise('Could not extract width/height from image: ' + img.src + 6375 '. Traced measures: width:' + img.width + 'px, height: ' + img.height + 'px.'); 6376 } 6377 }, 6378 timeout: 100 6379 }); 6380 }( this )); 6381 } else { 6382 complete.call( this ); 6383 } 6384 }; 6385 }( this, callback, src )); 6386 6387 // remove any previous images 6388 $container.find( 'iframe,img' ).remove(); 6389 6390 // append the image 6391 $image.css( 'display', 'block'); 6392 6393 // hide it for now 6394 Utils.hide( this.image ); 6395 6396 // remove any max/min scaling 6397 $.each('minWidth minHeight maxWidth maxHeight'.split(' '), function(i, prop) { 6398 $image.css(prop, (/min/.test(prop) ? '0' : 'none')); 6399 }); 6400 6401 // begin load and insert in cache when done 6402 $image.on( 'load', onload ).on( 'error', onerror ); 6403 if (src.srcset) { 6404 $image.attr( 'srcset', src.srcset ); 6405 } 6406 if (src.sizes) { 6407 $image.attr( 'sizes', src.sizes ); 6408 } 6409 $image.attr( 'src', src ); 6410 6411 // return the container 6412 return this.container; 6413 }, 6414 6415 /** 6416 Scales and crops the image 6417 6418 @param {Object} options The method takes an object with a number of options: 6419 6420 <ul> 6421 <li>width - width of the container</li> 6422 <li>height - height of the container</li> 6423 <li>min - minimum scale ratio</li> 6424 <li>max - maximum scale ratio</li> 6425 <li>margin - distance in pixels from the image border to the container</li> 6426 <li>complete - a callback that fires when scaling is complete</li> 6427 <li>position - positions the image, works like the css background-image property.</li> 6428 <li>crop - defines how to crop. Can be true, false, 'width' or 'height'</li> 6429 <li>canvas - set to true to try a canvas-based rescale</li> 6430 </ul> 6431 6432 @returns The image container object (jQuery) 6433 */ 6434 6435 scale: function( options ) { 6436 6437 var self = this; 6438 6439 // extend some defaults 6440 options = $.extend({ 6441 width: 0, 6442 height: 0, 6443 min: undef, 6444 max: undef, 6445 margin: 0, 6446 complete: F, 6447 position: 'center', 6448 crop: false, 6449 canvas: false, 6450 iframelimit: undef 6451 }, options); 6452 6453 if( this.isIframe ) { 6454 6455 var cw = options.width, 6456 ch = options.height, 6457 nw, nh; 6458 if ( options.iframelimit ) { 6459 var r = M.min( options.iframelimit/cw, options.iframelimit/ch ); 6460 if ( r < 1 ) { 6461 nw = cw * r; 6462 nh = ch * r; 6463 6464 $( this.image ).css({ 6465 top: ch/2-nh/2, 6466 left: cw/2-nw/2, 6467 position: 'absolute' 6468 }); 6469 } else { 6470 $( this.image ).css({ 6471 top: 0, 6472 left: 0 6473 }); 6474 } 6475 } 6476 $( this.image ).width( nw || cw ).height( nh || ch ).removeAttr( 'width' ).removeAttr( 'height' ); 6477 $( this.container ).width( cw ).height( ch ); 6478 options.complete.call(self, self); 6479 try { 6480 if( this.image.contentWindow ) { 6481 $( this.image.contentWindow ).trigger('resize'); 6482 } 6483 } catch(e) {} 6484 return this.container; 6485 6486 } 6487 6488 // return the element if no image found 6489 if (!this.image) { 6490 return this.container; 6491 } 6492 6493 // store locale variables 6494 var width, 6495 height, 6496 $container = $( self.container ), 6497 data; 6498 6499 // wait for the width/height 6500 Utils.wait({ 6501 until: function() { 6502 width = options.width || 6503 $container.width() || 6504 Utils.parseValue( $container.css('width') ); 6505 6506 height = options.height || 6507 $container.height() || 6508 Utils.parseValue( $container.css('height') ); 6509 6510 return width && height; 6511 }, 6512 success: function() { 6513 6514 // calculate some cropping 6515 var newWidth = ( width - options.margin * 2 ) / self.original.width, 6516 newHeight = ( height - options.margin * 2 ) / self.original.height, 6517 min = M.min( newWidth, newHeight ), 6518 max = M.max( newWidth, newHeight ), 6519 cropMap = { 6520 'true' : max, 6521 'width' : newWidth, 6522 'height': newHeight, 6523 'false' : min, 6524 'landscape': self.original.width > self.original.height ? max : min, 6525 'portrait': self.original.width < self.original.height ? max : min 6526 }, 6527 ratio = cropMap[ options.crop.toString() ], 6528 canvasKey = ''; 6529 6530 // allow maxScaleRatio 6531 if ( options.max ) { 6532 ratio = M.min( options.max, ratio ); 6533 } 6534 6535 // allow minScaleRatio 6536 if ( options.min ) { 6537 ratio = M.max( options.min, ratio ); 6538 } 6539 6540 $.each( ['width','height'], function( i, m ) { 6541 $( self.image )[ m ]( self[ m ] = self.image[ m ] = M.round( self.original[ m ] * ratio ) ); 6542 }); 6543 6544 $( self.container ).width( width ).height( height ); 6545 6546 if ( options.canvas && _canvas ) { 6547 6548 _canvas.elem.width = self.width; 6549 _canvas.elem.height = self.height; 6550 6551 canvasKey = self.image.src + ':' + self.width + 'x' + self.height; 6552 6553 self.image.src = _canvas.cache[ canvasKey ] || (function( key ) { 6554 6555 _canvas.context.drawImage(self.image, 0, 0, self.original.width*ratio, self.original.height*ratio); 6556 6557 try { 6558 6559 data = _canvas.elem.toDataURL(); 6560 _canvas.length += data.length; 6561 _canvas.cache[ key ] = data; 6562 return data; 6563 6564 } catch( e ) { 6565 return self.image.src; 6566 } 6567 6568 }( canvasKey ) ); 6569 6570 } 6571 6572 // calculate image_position 6573 var pos = {}, 6574 mix = {}, 6575 getPosition = function(value, measure, margin) { 6576 var result = 0; 6577 if (/\%/.test(value)) { 6578 var flt = parseInt( value, 10 ) / 100, 6579 m = self.image[ measure ] || $( self.image )[ measure ](); 6580 6581 result = M.ceil( m * -1 * flt + margin * flt ); 6582 } else { 6583 result = Utils.parseValue( value ); 6584 } 6585 return result; 6586 }, 6587 positionMap = { 6588 'top': { top: 0 }, 6589 'left': { left: 0 }, 6590 'right': { left: '100%' }, 6591 'bottom': { top: '100%' } 6592 }; 6593 6594 $.each( options.position.toLowerCase().split(' '), function( i, value ) { 6595 if ( value === 'center' ) { 6596 value = '50%'; 6597 } 6598 pos[i ? 'top' : 'left'] = value; 6599 }); 6600 6601 $.each( pos, function( i, value ) { 6602 if ( positionMap.hasOwnProperty( value ) ) { 6603 $.extend( mix, positionMap[ value ] ); 6604 } 6605 }); 6606 6607 pos = pos.top ? $.extend( pos, mix ) : mix; 6608 6609 pos = $.extend({ 6610 top: '50%', 6611 left: '50%' 6612 }, pos); 6613 6614 // apply position 6615 $( self.image ).css({ 6616 position : 'absolute', 6617 top : getPosition(pos.top, 'height', height), 6618 left : getPosition(pos.left, 'width', width) 6619 }); 6620 6621 // show the image 6622 self.show(); 6623 6624 // flag ready and call the callback 6625 self.ready = true; 6626 options.complete.call( self, self ); 6627 6628 }, 6629 error: function() { 6630
6630// Edited by Photocrati: silently log this instead of raising an error 6631 if (window.console && console.trace) { 6632 // Galleria.raise('Could not scale image: '+self.image.src); 6633 console.trace('Could not scale image: ' + self.image.src); 6634 }
vendor: 10,234 bytes, lines 6634-6991
6634 6635 }, 6636 timeout: 1000 6637 }); 6638 return this; 6639 } 6640}; 6641 6642// our own easings 6643$.extend( $.easing, { 6644 6645 galleria: function (_, t, b, c, d) { 6646 if ((t/=d/2) < 1) { 6647 return c/2*t*t*t + b; 6648 } 6649 return c/2*((t-=2)*t*t + 2) + b; 6650 }, 6651 6652 galleriaIn: function (_, t, b, c, d) { 6653 return c*(t/=d)*t + b; 6654 }, 6655 6656 galleriaOut: function (_, t, b, c, d) { 6657 return -c *(t/=d)*(t-2) + b; 6658 } 6659 6660}); 6661 6662 6663// Forked version of Ainos Finger.js for native-style touch 6664 6665Galleria.Finger = (function() { 6666 6667 var abs = M.abs; 6668 6669 // test for translate3d support 6670 var has3d = Galleria.HAS3D = (function() { 6671 6672 var el = doc.createElement('p'), 6673 has3d, 6674 t = ['webkit','O','ms','Moz',''], 6675 s, 6676 i=0, 6677 a = 'transform'; 6678 6679 DOM().html.insertBefore(el, null); 6680 6681 for (; t[i]; i++) { 6682 s = t[i] ? t[i]+'Transform' : a; 6683 if (el.style[s] !== undefined) { 6684 el.style[s] = "translate3d(1px,1px,1px)"; 6685 has3d = $(el).css(t[i] ? '-'+t[i].toLowerCase()+'-'+a : a); 6686 } 6687 } 6688 6689 DOM().html.removeChild(el); 6690 return (has3d !== undefined && has3d.length > 0 && has3d !== "none"); 6691 }()); 6692 6693 // request animation shim 6694 var requestFrame = (function(){ 6695 var r = 'RequestAnimationFrame'; 6696 return window.requestAnimationFrame || 6697 window['webkit'+r] || 6698 window['moz'+r] || 6699 window['o'+r] || 6700 window['ms'+r] || 6701 function( callback ) { 6702 window.setTimeout(callback, 1000 / 60); 6703 }; 6704 }()); 6705 6706 var Finger = function(elem, options) { 6707 6708 // default options 6709 this.config = { 6710 start: 0, 6711 duration: 500, 6712 onchange: function() {}, 6713 oncomplete: function() {}, 6714 easing: function(x,t,b,c,d) { 6715 return -c * ((t=t/d-1)*t*t*t - 1) + b; // easeOutQuart 6716 } 6717 }; 6718 6719 this.easeout = function (x, t, b, c, d) { 6720 return c*((t=t/d-1)*t*t*t*t + 1) + b; 6721 }; 6722 6723 if ( !elem.children.length ) { 6724 return; 6725 } 6726 6727 var self = this; 6728 6729 // extend options 6730 $.extend(this.config, options); 6731 6732 this.elem = elem; 6733 this.child = elem.children[0]; 6734 this.to = this.pos = 0; 6735 this.touching = false; 6736 this.start = {}; 6737 this.index = this.config.start; 6738 this.anim = 0; 6739 this.easing = this.config.easing; 6740 6741 if ( !has3d ) { 6742 this.child.style.position = 'absolute'; 6743 this.elem.style.position = 'relative'; 6744 } 6745 6746 // Bind event handlers to context 6747 $.each(['ontouchstart','ontouchmove','ontouchend','setup'], function(i, fn) { 6748 self[fn] = (function(caller) { 6749 return function() { 6750 caller.apply( self, arguments ); 6751 }; 6752 }(self[fn])); 6753 }); 6754 6755 // the physical animator 6756 this.setX = function() { 6757 6758 var style = self.child.style; 6759 6760 if (!has3d) { 6761 // this is actually faster than CSS3 translate 6762 style.left = self.pos+'px'; 6763 return; 6764 } 6765 style.MozTransform = style.webkitTransform = style.transform = 'translate3d(' + self.pos + 'px,0,0)'; 6766 return; 6767 }; 6768 6769 // bind events 6770 $(elem).on('touchstart', this.ontouchstart); 6771 $(window).on('resize', this.setup); 6772 $(window).on('orientationchange', this.setup); 6773 6774 // set up width 6775 this.setup(); 6776 6777 // start the animations 6778 (function animloop(){ 6779 requestFrame(animloop); 6780 self.loop.call( self ); 6781 }()); 6782 6783 }; 6784 6785 Finger.prototype = { 6786 6787 constructor: Finger, 6788 6789 setup: function() { 6790 this.width = $( this.elem ).width(); 6791 this.length = M.ceil( $(this.child).width() / this.width ); 6792 if ( this.index !== 0 ) { 6793 this.index = M.max(0, M.min( this.index, this.length-1 ) ); 6794 this.pos = this.to = -this.width*this.index; 6795 } 6796 }, 6797 6798 setPosition: function(pos) { 6799 this.pos = pos; 6800 this.to = pos; 6801 }, 6802 6803 ontouchstart: function(e) { 6804 6805 var touch = e.originalEvent.touches; 6806 6807 this.start = { 6808 pageX: touch[0].pageX, 6809 pageY: touch[0].pageY, 6810 time: +new Date() 6811 }; 6812 6813 this.isScrolling = null; 6814 this.touching = true; 6815 this.deltaX = 0; 6816 6817 $doc.on('touchmove', this.ontouchmove); 6818 $doc.on('touchend', this.ontouchend); 6819 }, 6820 6821 ontouchmove: function(e) { 6822 6823 var touch = e.originalEvent.touches; 6824 6825 // ensure swiping with one touch and not pinching 6826 if( touch && touch.length > 1 || e.scale && e.scale !== 1 ) { 6827 return; 6828 } 6829 6830 this.deltaX = touch[0].pageX - this.start.pageX; 6831 6832 // determine if scrolling test has run - one time test 6833 if ( this.isScrolling === null ) { 6834 this.isScrolling = !!( 6835 this.isScrolling || 6836 M.abs(this.deltaX) < M.abs(touch[0].pageY - this.start.pageY) 6837 ); 6838 } 6839 6840 // if user is not trying to scroll vertically 6841 if (!this.isScrolling) { 6842 6843 // prevent native scrolling 6844 e.preventDefault(); 6845 6846 // increase resistance if first or last slide 6847 this.deltaX /= ( (!this.index && this.deltaX > 0 || this.index == this.length - 1 && this.deltaX < 0 ) ? 6848 ( M.abs(this.deltaX) / this.width + 1.8 ) : 1 ); 6849 this.to = this.deltaX - this.index * this.width; 6850 } 6851 e.stopPropagation(); 6852 }, 6853 6854 ontouchend: function(e) { 6855 6856 this.touching = false; 6857 6858 // determine if slide attempt triggers next/prev slide 6859 var isValidSlide = +new Date() - this.start.time < 250 && 6860 M.abs(this.deltaX) > 40 || 6861 M.abs(this.deltaX) > this.width/2, 6862 6863 isPastBounds = !this.index && this.deltaX > 0 || 6864 this.index == this.length - 1 && this.deltaX < 0; 6865 6866 // if not scrolling vertically 6867 if ( !this.isScrolling ) { 6868 this.show( this.index + ( isValidSlide && !isPastBounds ? (this.deltaX < 0 ? 1 : -1) : 0 ) ); 6869 } 6870 6871 $doc.off('touchmove', this.ontouchmove); 6872 $doc.off('touchend', this.ontouchend); 6873 }, 6874 6875 show: function( index ) { 6876 if ( index != this.index ) { 6877 this.config.onchange.call(this, index); 6878 } else { 6879 this.to = -( index*this.width ); 6880 } 6881 }, 6882 6883 moveTo: function( index ) { 6884 if ( index != this.index ) { 6885 this.pos = this.to = -( index*this.width ); 6886 this.index = index; 6887 } 6888 }, 6889 6890 loop: function() { 6891 6892 var distance = this.to - this.pos, 6893 factor = 1; 6894 6895 if ( this.width && distance ) { 6896 factor = M.max(0.5, M.min(1.5, M.abs(distance / this.width) ) ); 6897 } 6898 6899 // if distance is short or the user is touching, do a 1-1 animation 6900 if ( this.touching || M.abs(distance) <= 1 ) { 6901 this.pos = this.to; 6902 distance = 0; 6903 if ( this.anim && !this.touching ) { 6904 this.config.oncomplete( this.index ); 6905 } 6906 this.anim = 0; 6907 this.easing = this.config.easing; 6908 } else { 6909 if ( !this.anim ) { 6910 // save animation parameters 6911 this.anim = { start: this.pos, time: +new Date(), distance: distance, factor: factor, destination: this.to }; 6912 } 6913 // check if to has changed or time has run out 6914 var elapsed = +new Date() - this.anim.time; 6915 var duration = this.config.duration*this.anim.factor; 6916 6917 if ( elapsed > duration || this.anim.destination != this.to ) { 6918 this.anim = 0; 6919 this.easing = this.easeout; 6920 return; 6921 } 6922 // apply easing 6923 this.pos = this.easing( 6924 null, 6925 elapsed, 6926 this.anim.start, 6927 this.anim.distance, 6928 duration 6929 ); 6930 } 6931 this.setX(); 6932 } 6933 }; 6934 6935 return Finger; 6936 6937}()); 6938 6939// the plugin initializer 6940$.fn.galleria = function( options ) { 6941 6942 var selector = this.selector; 6943 6944 // try domReady if element not found 6945 if ( !$(this).length ) { 6946 6947 $(function() { 6948 if ( $( selector ).length ) { 6949 6950 // if found on domReady, go ahead 6951 $( selector ).galleria( options ); 6952 6953 } else { 6954 6955 // if not, try fetching the element for 5 secs, then raise a warning. 6956 Galleria.utils.wait({ 6957 until: function() { 6958 return $( selector ).length; 6959 }, 6960 success: function() { 6961 $( selector ).galleria( options ); 6962 }, 6963 error: function() { 6964 Galleria.raise('Init failed: Galleria could not find the element "'+selector+'".'); 6965 }, 6966 timeout: 5000 6967 }); 6968 6969 } 6970 }); 6971 return this; 6972 } 6973 6974 return this.each(function() { 6975 6976 // destroy previous instance and prepare for new load 6977 if ( $.data(this, 'galleria') ) { 6978 $.data( this, 'galleria' ).destroy(); 6979 $( this ).find( '*' ).hide(); 6980 } 6981 6982 // load the new gallery 6983 $.data( this, 'galleria', new Galleria().init( this, options ) ); 6984 }); 6985 6986}; 6987 6988// phew 6989return Galleria; 6990 6991}));
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.