1/*! 2* - v1.5.0 3* Homepage: http://bqworks.com/slider-pro/ 4* Author: bqworks 5* Author URL: http://bqworks.com/ 6*/ 7;(function( window, $ ) { 8 9 "use strict"; 10 11 // Static methods for Slider Pro 12 $.SliderPro = { 13 14 // List of added modules 15 modules: [], 16 17 // Add a module by extending the core prototype 18 addModule: function( name, module ) { 19 this.modules.push( name ); 20 $.extend( SliderPro.prototype, module ); 21 } 22 }; 23 24 // namespace 25 var NS = $.SliderPro.namespace = 'SliderPro'; 26 27 var SliderPro = function( instance, options ) { 28 29 // Reference to the slider instance 30 this.instance = instance; 31 32 // Reference to the slider jQuery element 33 this.$slider = $( this.instance ); 34 35 // Reference to the slides (sp-slides) jQuery element 36 this.$slides = null; 37 38 // Reference to the mask (sp-mask) jQuery element 39 this.$slidesMask = null; 40 41 // Reference to the slides (sp-slides-container) jQuery element 42 this.$slidesContainer = null; 43 44 // Array of SliderProSlide objects, ordered by their DOM index 45 this.slides = []; 46 47 // Array of SliderProSlide objects, ordered by their left/top position in the slider. 48 // This will be updated continuously if the slider is loopable. 49 this.slidesOrder = []; 50 51 // Holds the options passed to the slider when it was instantiated 52 this.options = options; 53 54 // Holds the final settings of the slider after merging the specified 55 // ones with the default ones. 56 this.settings = {}; 57 58 // Another reference to the settings which will not be altered by breakpoints or by other means 59 this.originalSettings = {}; 60 61 // Reference to the original 'gotoSlide' method 62 this.originalGotoSlide = null; 63 64 // The index of the currently selected slide (starts with 0) 65 this.selectedSlideIndex = 0; 66 67 // The index of the previously selected slide 68 this.previousSlideIndex = 0; 69 70 // Indicates the position of the slide considered to be in the middle. 71 // If there are 5 slides (0, 1, 2, 3, 4) the middle position will be 2. 72 // If there are 6 slides (0, 1, 2, 3, 4, 5) the middle position will be approximated to 2. 73 this.middleSlidePosition = 0; 74 75 // Indicates the type of supported transition (CSS3 2D, CSS3 3D or JavaScript) 76 this.supportedAnimation = null; 77 78 // Indicates the required vendor prefix for CSS (i.e., -webkit, -moz, etc.) 79 this.vendorPrefix = null; 80 81 // Indicates the name of the CSS transition's complete event (i.e., transitionend, webkitTransitionEnd, etc.) 82 this.transitionEvent = null; 83 84 // Indicates the 'left' or 'top' position, depending on the orientation of the slides 85 this.positionProperty = null; 86 87 // Indicates the 'width' or 'height', depending on the orientation of the slides 88 this.sizeProperty = null; 89 90 // Indicates if the current browser is IE 91 this.isIE = null; 92 93 // The position of the slides container 94 this.slidesPosition = 0; 95 96 // The total width/height of the slides 97 this.slidesSize = 0; 98 99 // The average width/height of a slide 100 this.averageSlideSize = 0; 101 102 // The width of the individual slide 103 this.slideWidth = 0; 104 105 // The height of the individual slide 106 this.slideHeight = 0; 107 108 // Reference to the old slide width, used to check if the width has changed 109 this.previousSlideWidth = 0; 110 111 // Reference to the old slide height, used to check if the height has changed 112 this.previousSlideHeight = 0; 113 114 // Reference to the old window width, used to check if the window width has changed 115 this.previousWindowWidth = 0; 116 117 // Reference to the old window height, used to check if the window height has changed 118 this.previousWindowHeight = 0; 119 120 // Property used for deferring the resizing of the slider 121 this.allowResize = true; 122 123 // Unique ID to be used for event listening 124 this.uniqueId = new Date().valueOf(); 125 126 // Stores size breakpoints 127 this.breakpoints = []; 128 129 // Indicates the current size breakpoint 130 this.currentBreakpoint = -1; 131 132 // An array of shuffled indexes, based on which the slides will be shuffled 133 this.shuffledIndexes = []; 134 135 // Initialize the slider 136 this._init(); 137 }; 138 139 SliderPro.prototype = { 140 141 // The starting place for the slider 142 _init: function() { 143 var that = this; 144 145 this.supportedAnimation = SliderProUtils.getSupportedAnimation(); 146 this.vendorPrefix = SliderProUtils.getVendorPrefix(); 147 this.transitionEvent = SliderProUtils.getTransitionEvent(); 148 this.isIE = SliderProUtils.checkIE(); 149 150 // Remove the 'sp-no-js' when the slider's JavaScript code starts running 151 this.$slider.removeClass( 'sp-no-js' ); 152 153 // Add the 'ios' class if it's an iOS device 154 if ( window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ) { 155 this.$slider.addClass( 'ios' ); 156 } 157 158 // Check if IE (older than 11) is used and add the version number as a class to the slider since 159 // older IE versions might need CSS tweaks. 160 var rmsie = /(msie) ([\w.]+)/, 161 ieVersion = rmsie.exec( window.navigator.userAgent.toLowerCase() ); 162 163 if ( this.isIE ) { 164 this.$slider.addClass( 'ie' ); 165 } 166 167 if ( ieVersion !== null ) { 168 this.$slider.addClass( 'ie' + parseInt( ieVersion[2], 10 ) ); 169 } 170 171 // Set up the slides containers 172 // slider-pro > sp-slides-container > sp-mask > sp-slides > sp-slide 173 this.$slidesContainer = $( '<div class="sp-slides-container"></div>' ).appendTo( this.$slider ); 174 this.$slidesMask = $( '<div class="sp-mask"></div>' ).appendTo( this.$slidesContainer ); 175 this.$slides = this.$slider.find( '.sp-slides' ).appendTo( this.$slidesMask ); 176 this.$slider.find( '.sp-slide' ).appendTo( this.$slides ); 177 178 var modules = $.SliderPro.modules; 179 180 // Merge the modules' default settings with the core's default settings 181 if ( typeof modules !== 'undefined' ) { 182 for ( var i = 0; i < modules.length; i++ ) { 183 var defaults = modules[ i ].substring( 0, 1 ).toLowerCase() + modules[ i ].substring( 1 ) + 'Defaults'; 184 185 if ( typeof this[ defaults ] !== 'undefined' ) { 186 $.extend( this.defaults, this[ defaults ] ); 187 } 188 } 189 } 190 191 // Merge the specified setting with the default ones 192 this.settings = $.extend( {}, this.defaults, this.options ); 193 194 // Initialize the modules 195 if ( typeof modules !== 'undefined' ) { 196 for ( var j = 0; j < modules.length; j++ ) { 197 if ( typeof this[ 'init' + modules[ j ] ] !== 'undefined' ) { 198 this[ 'init' + modules[ j ] ](); 199 } 200 } 201 } 202 203 // Keep a reference of the original settings and use it 204 // to restore the settings when the breakpoints are used. 205 this.originalSettings = $.extend( {}, this.settings ); 206
207 // Get the reference to the 'gotoSlide' method 208 this.originalGotoSlide = this.gotoSlide; 209 210 // Parse the breakpoints object and store the values into an array, 211 // sorting them in ascending order based on the specified size. 212 if ( this.settings.breakpoints !== null ) { 213 for ( var sizes in this.settings.breakpoints ) { 214 this.breakpoints.push({ size: parseInt( sizes, 10 ), properties:this.settings.breakpoints[ sizes ] }); 215 } 216 217 this.breakpoints = this.breakpoints.sort(function( a, b ) { 218 return a.size >= b.size ? 1: -1; 219 }); 220 } 221 222 // Set which slide should be selected initially 223 this.selectedSlideIndex = this.settings.startSlide; 224 225 // Shuffle/randomize the slides 226 if ( this.settings.shuffle === true ) { 227 var slides = this.$slides.find( '.sp-slide' ), 228 shuffledSlides = []; 229 230 // Populate the 'shuffledIndexes' with index numbers 231 slides.each(function( index ) { 232 that.shuffledIndexes.push( index ); 233 }); 234 235 for ( var k = this.shuffledIndexes.length - 1; k > 0; k-- ) { 236 var l = Math.floor( Math.random() * ( k + 1 ) ), 237 temp = this.shuffledIndexes[ k ]; 238 239 this.shuffledIndexes[ k ] = this.shuffledIndexes[ l ]; 240 this.shuffledIndexes[ l ] = temp; 241 } 242 243 // Reposition the slides based on the order of the indexes in the 244 // 'shuffledIndexes' array 245 $.each( this.shuffledIndexes, function( index, element ) { 246 shuffledSlides.push( slides[ element ] ); 247 }); 248 249 // Append the sorted slides to the slider 250 this.$slides.empty().append( shuffledSlides ) ; 251 } 252 253 // Resize the slider when the browser window resizes. 254 // Also, deffer the resizing in order to not allow multiple 255 // resizes in a 200 milliseconds interval. 256 $( window ).on( 'resize.' + this.uniqueId + '.' + NS, function() { 257 258 // Get the current width and height of the window 259 var newWindowWidth = $( window ).width(), 260 newWindowHeight = $( window ).height(); 261 262 // If the resize is not allowed yet or if the window size hasn't changed (this needs to be verified 263 // because in IE8 and lower the resize event is triggered whenever an element from the page changes 264 // its size) return early. 265 if ( that.allowResize === false || 266 ( that.previousWindowWidth === newWindowWidth && that.previousWindowHeight === newWindowHeight ) ) { 267 return; 268 } 269 270 // Assign the new values for the window width and height 271 that.previousWindowWidth = newWindowWidth; 272 that.previousWindowHeight = newWindowHeight; 273 274 that.allowResize = false; 275 276 setTimeout(function() { 277 that.resize(); 278 that.allowResize = true; 279 }, 200 ); 280 }); 281 282 // Resize the slider when the 'update' method is called. 283 this.on( 'update.' + NS, function() { 284 285 // Reset the previous slide width 286 that.previousSlideWidth = 0; 287 288 // Some updates might require a resize 289 that.resize(); 290 }); 291 292 this.update(); 293 294 // add the 'sp-selected' class to the initially selected slide 295 this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected' ); 296 297 // Fire the 'init' event 298 this.trigger({ type: 'init' }); 299 if ( $.isFunction( this.settings.init ) ) { 300 this.settings.init.call( this, { type: 'init' }); 301 } 302 }, 303 304 // Update the slider by checking for setting changes and for slides 305 // that weren't initialized yet. 306 update: function() { 307 var that = this; 308 309 // Check the current slider orientation and reset CSS that might have been 310 // added for a different orientation, since the orientation can be changed 311 // at runtime. 312 if ( this.settings.orientation === 'horizontal' ) { 313 this.$slider.removeClass( 'sp-vertical' ).addClass( 'sp-horizontal' ); 314 this.$slider.css({ 'height': '', 'max-height': '' }); 315 this.$slides.find( '.sp-slide' ).css( 'top', '' ); 316 } else if ( this.settings.orientation === 'vertical' ) { 317 this.$slider.removeClass( 'sp-horizontal' ).addClass( 'sp-vertical' ); 318 this.$slides.find( '.sp-slide' ).css( 'left', '' ); 319 } 320 321 if ( this.settings.rightToLeft === true ) { 322 this.$slider.addClass( 'sp-rtl' ); 323 } else { 324 this.$slider.removeClass( 'sp-rtl' ); 325 } 326 327 this.positionProperty = this.settings.orientation === 'horizontal' ? 'left' : 'top'; 328 this.sizeProperty = this.settings.orientation === 'horizontal' ? 'width' : 'height';
329 330 // Reset the 'gotoSlide' method 331 this.gotoSlide = this.originalGotoSlide; 332 333 // Loop through the array of SliderProSlide objects and if a stored slide is found 334 // which is not in the DOM anymore, destroy that slide. 335 for ( var i = this.slides.length - 1; i >= 0; i-- ) { 336 if ( this.$slider.find( '.sp-slide[data-index="' + i + '"]' ).length === 0 ) { 337 var slide = this.slides[ i ]; 338 339 slide.off( 'imagesLoaded.' + NS ); 340 slide.destroy(); 341 this.slides.splice( i, 1 ); 342 } 343 } 344 345 this.slidesOrder.length = 0; 346 347 // Loop through the list of slides and initialize newly added slides if any, 348 // and reset the index of each slide. 349 this.$slider.find( '.sp-slide' ).each(function( index ) { 350 var $slide = $( this ); 351 352 if ( typeof $slide.attr( 'data-init' ) === 'undefined' ) { 353 that._createSlide( index, $slide ); 354 } else { 355 that.slides[ index ].setIndex( index ); 356 } 357 358 that.slidesOrder.push( index ); 359 }); 360 361 // Calculate the position/index of the middle slide 362 this.middleSlidePosition = parseInt( ( that.slidesOrder.length - 1 ) / 2, 10 ); 363 364 // Arrange the slides in a loop 365 if ( this.settings.loop === true ) { 366 this._updateSlidesOrder(); 367 } 368 369 // Fire the 'update' event 370 this.trigger({ type: 'update' }); 371 if ( $.isFunction( this.settings.update ) ) { 372 this.settings.update.call( this, { type: 'update' } ); 373 } 374 }, 375 376 // Create a SliderProSlide instance for the slide passed as a jQuery element 377 _createSlide: function( index, element ) { 378 var that = this, 379 slide = new SliderProSlide( $( element ), index, this.settings ); 380 381 this.slides.splice( index, 0, slide ); 382 383 slide.on( 'imagesLoaded.' + NS, function( event ) { 384 if ( that.settings.autoSlideSize === true ) { 385 if ( that.$slides.hasClass( 'sp-animated' ) === false ) { 386 that._resetSlidesPosition(); 387 } 388 389 that._calculateSlidesSize(); 390 } 391 392 if ( that.settings.autoHeight === true && event.index === that.selectedSlideIndex ) { 393 that._resizeHeightTo( slide.getSize().height); 394 } 395 }); 396 }, 397 398 // Arrange the slide elements in a loop inside the 'slidesOrder' array 399 _updateSlidesOrder: function() { 400 var slicedItems, 401 i, 402 403 // Calculate the distance between the selected element and the middle position 404 distance = $.inArray( this.selectedSlideIndex, this.slidesOrder ) - this.middleSlidePosition; 405 406 // If the distance is negative it means that the selected slider is before the middle position, so 407 // slides from the end of the array will be added at the beginning, in order to shift the selected slide 408 // forward. 409 // 410 // If the distance is positive, slides from the beginning of the array will be added at the end. 411 if ( distance < 0 ) { 412 slicedItems = this.slidesOrder.splice( distance, Math.abs( distance ) ); 413 414 for ( i = slicedItems.length - 1; i >= 0; i-- ) { 415 this.slidesOrder.unshift( slicedItems[ i ] ); 416 } 417 } else if ( distance > 0 ) { 418 slicedItems = this.slidesOrder.splice( 0, distance ); 419 420 for ( i = 0; i <= slicedItems.length - 1; i++ ) { 421 this.slidesOrder.push( slicedItems[ i ] ); 422 } 423 } 424 }, 425 426 // Set the left/top position of the slides based on their position in the 'slidesOrder' array 427 _updateSlidesPosition: function() { 428 var selectedSlidePixelPosition = parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ), 429 slide, 430 $slideElement, 431 slideIndex, 432 previousPosition = selectedSlidePixelPosition, 433 directionMultiplier, 434 slideSize; 435 436 if ( this.settings.autoSlideSize === true ) { 437 if ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) { 438 for ( slideIndex = this.middleSlidePosition; slideIndex >= 0; slideIndex-- ) { 439 slide = this.getSlideAt( this.slidesOrder[ slideIndex ] ); 440 $slideElement = slide.$slide; 441 $slideElement.css( this.positionProperty, previousPosition ); 442 previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ) + slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance; 443 } 444 445 previousPosition = selectedSlidePixelPosition; 446 447 for ( slideIndex = this.middleSlidePosition + 1; slideIndex < this.slidesOrder.length; slideIndex++ ) {
448 slide = this.getSlideAt( this.slidesOrder[ slideIndex ] ); 449 $slideElement = slide.$slide; 450 $slideElement.css( this.positionProperty, previousPosition - ( slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance ) ); 451 previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ); 452 } 453 } else { 454 for ( slideIndex = this.middleSlidePosition - 1; slideIndex >= 0; slideIndex-- ) { 455 slide = this.getSlideAt( this.slidesOrder[ slideIndex ] ); 456 $slideElement = slide.$slide; 457 $slideElement.css( this.positionProperty, previousPosition - ( slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance ) ); 458 previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ); 459 } 460 461 previousPosition = selectedSlidePixelPosition; 462 463 for ( slideIndex = this.middleSlidePosition; slideIndex < this.slidesOrder.length; slideIndex++ ) { 464 slide = this.getSlideAt( this.slidesOrder[ slideIndex ] ); 465 $slideElement = slide.$slide; 466 $slideElement.css( this.positionProperty, previousPosition ); 467 previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ) + slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance; 468 } 469 } 470 } else { 471 directionMultiplier = ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) ? -1 : 1; 472 slideSize = ( this.settings.orientation === 'horizontal' ) ? this.slideWidth : this.slideHeight; 473 474 for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) { 475 $slideElement = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ slideIndex ] ); 476 $slideElement.css( this.positionProperty, selectedSlidePixelPosition + directionMultiplier * ( slideIndex - this.middleSlidePosition ) * ( slideSize + this.settings.slideDistance ) ); 477 } 478 } 479 }, 480 481 // Set the left/top position of the slides based on their position in the 'slidesOrder' array, 482 // and also set the position of the slides container. 483 _resetSlidesPosition: function() { 484 var previousPosition = 0, 485 slide, 486 $slideElement, 487 slideIndex, 488 selectedSlideSize, 489 directionMultiplier, 490 slideSize; 491 492 if ( this.settings.autoSlideSize === true ) { 493 if ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) { 494 for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) { 495 slide = this.getSlideAt( this.slidesOrder[ slideIndex ] ); 496 $slideElement = slide.$slide; 497 $slideElement.css( this.positionProperty, previousPosition - ( slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance ) ); 498 previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ); 499 } 500 } else { 501 for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) { 502 slide = this.getSlideAt( this.slidesOrder[ slideIndex ] ); 503 $slideElement = slide.$slide; 504 $slideElement.css( this.positionProperty, previousPosition ); 505 previousPosition = parseInt( $slideElement.css( this.positionProperty ), 10 ) + slide.getSize()[ this.sizeProperty ] + this.settings.slideDistance; 506 } 507 } 508 509 selectedSlideSize = this.getSlideAt( this.selectedSlideIndex ).getSize()[ this.sizeProperty ]; 510 } else { 511 directionMultiplier = ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ) === true ? -1 : 1; 512 slideSize = ( this.settings.orientation === 'horizontal' ) ? this.slideWidth : this.slideHeight; 513 514 for ( slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) { 515 $slideElement = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ slideIndex ] ); 516 $slideElement.css( this.positionProperty, directionMultiplier * slideIndex * ( slideSize + this.settings.slideDistance ) ); 517 } 518 519 selectedSlideSize = slideSize; 520 } 521 522 var selectedSlideOffset = this.settings.centerSelectedSlide === true && this.settings.visibleSize !== 'auto' ? Math.round( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10 ) - selectedSlideSize ) / 2 ) : 0, 523 newSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + selectedSlideOffset; 524 525 this._moveTo( newSlidesPosition, true ); 526 }, 527 528 // Calculate the total size of the slides and the average size of a single slide 529 _calculateSlidesSize: function() { 530 if ( this.settings.autoSlideSize === true ) { 531 var firstSlide = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ 0 ] ), 532 firstSlidePosition = parseInt( firstSlide.css( this.positionProperty ), 10 ), 533 lastSlide = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ this.slidesOrder.length - 1 ] ), 534 lastSlidePosition = parseInt( lastSlide.css( this.positionProperty ), 10 ) + ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ? -1 : 1 ) * parseInt( lastSlide.css( this.sizeProperty ), 10 ); 535 536 this.slidesSize = Math.abs( lastSlidePosition - firstSlidePosition ); 537 this.averageSlideSize = Math.round( this.slidesSize / this.slides.length ); 538 } else { 539 this.slidesSize = ( ( this.settings.orientation === 'horizontal' ? this.slideWidth : this.slideHeight ) + this.settings.slideDistance ) * this.slides.length - this.settings.slideDistance; 540 this.averageSlideSize = this.settings.orientation === 'horizontal' ? this.slideWidth : this.slideHeight; 541 } 542 }, 543 544 // Called when the slider needs to resize 545 resize: function() { 546 var that = this; 547 548 // Check if the current window width is bigger than the biggest breakpoint 549 // and if necessary reset the properties to the original settings. 550 // 551 // If the window width is smaller than a certain breakpoint, apply the settings specified 552 // for that breakpoint but only after merging them with the original settings 553 // in order to make sure that only the specified settings for the breakpoint are applied 554 if ( this.settings.breakpoints !== null && this.breakpoints.length > 0 ) { 555 if ( $( window ).width() > this.breakpoints[ this.breakpoints.length - 1 ].size && this.currentBreakpoint !== -1 ) { 556 this.currentBreakpoint = -1; 557 this._setProperties( this.originalSettings, false ); 558 } else { 559 for ( var i = 0, n = this.breakpoints.length; i < n; i++ ) { 560 if ( $( window ).width() <= this.breakpoints[ i ].size ) { 561 if ( this.currentBreakpoint !== this.breakpoints[ i ].size ) { 562 var eventObject = { type: 'breakpointReach', size: this.breakpoints[ i ].size, settings: this.breakpoints[ i ].properties }; 563 this.trigger( eventObject ); 564 if ( $.isFunction( this.settings.breakpointReach ) ) 565 this.settings.breakpointReach.call( this, eventObject ); 566 567 this.currentBreakpoint = this.breakpoints[ i ].size; 568 var settings = $.extend( {}, this.originalSettings, this.breakpoints[ i ].properties ); 569 this._setProperties( settings, false ); 570 571 return; 572 } 573 574 break; 575 } 576 } 577 } 578 } 579 580 // Set the width of the main slider container based on whether or not the slider is responsive, 581 // full width or full size 582 if ( this.settings.responsive === true ) { 583 if ( ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) && 584 ( this.settings.visibleSize === 'auto' || this.settings.visibleSize !== 'auto' && this.settings.orientation === 'vertical' ) 585 ) { 586 this.$slider.css( 'margin', 0 ); 587 this.$slider.css({ 'width': $( window ).width(), 'max-width': '', 'marginLeft': - this.$slider.offset().left }); 588 } else { 589 this.$slider.css({ 'width': '100%', 'max-width': this.settings.width, 'marginLeft': '' }); 590 } 591 } else { 592 this.$slider.css({ 'width': this.settings.width }); 593 } 594 595 // Calculate the aspect ratio of the slider 596 if ( this.settings.aspectRatio === -1 ) { 597 this.settings.aspectRatio = this.settings.width / this.settings.height; 598 } 599 600 // Initially set the slide width to the size of the slider. 601 // Later, this will be set to less if there are multiple visible slides. 602 this.slideWidth = this.$slider.width(); 603 604 // Set the height to the same size as the browser window if the slider is set to be 'fullWindow', 605 // or calculate the height based on the width and the aspect ratio. 606 if ( this.settings.forceSize === 'fullWindow' ) { 607 this.slideHeight = $( window ).height(); 608 } else { 609 this.slideHeight = isNaN( this.settings.aspectRatio ) ? this.settings.height : this.slideWidth / this.settings.aspectRatio; 610 } 611 612 // Resize the slider only if the size of the slider has changed 613 // If it hasn't, return. 614 if ( this.previousSlideWidth !== this.slideWidth || 615 this.previousSlideHeight !== this.slideHeight || 616 this.settings.visibleSize !== 'auto' || 617 this.$slider.outerWidth() > this.$slider.parent().width() || 618 this.$slider.width() !== this.$slidesMask.width() 619 ) { 620 this.previousSlideWidth = this.slideWidth; 621 this.previousSlideHeight = this.slideHeight; 622 } else { 623 return; 624 } 625 626 this._resizeSlides(); 627 628 // Set the initial size of the mask container to the size of an individual slide 629 this.$slidesMask.css({ 'width': this.slideWidth, 'height': this.slideHeight }); 630 631 // Adjust the height if it's set to 'auto' 632 if ( this.settings.autoHeight === true ) { 633 634 // Delay the resizing of the height to allow for other resize handlers 635 // to execute first before calculating the final height of the slide 636 setTimeout( function() { 637 that._resizeHeight(); 638 }, 1 ); 639 } else { 640 this.$slidesMask.css( this.vendorPrefix + 'transition', '' ); 641 } 642 643 // The 'visibleSize' option can be set to fixed or percentage size to make more slides 644 // visible at a time. 645 // By default it's set to 'auto'. 646 if ( this.settings.visibleSize !== 'auto' ) { 647 if ( this.settings.orientation === 'horizontal' ) { 648 649 // If the size is forced to full width or full window, the 'visibleSize' option will be 650 // ignored and the slider will become as wide as the browser window. 651 if ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) { 652 this.$slider.css( 'margin', 0 ); 653 this.$slider.css({ 'width': $( window ).width(), 'max-width': '', 'marginLeft': - this.$slider.offset().left }); 654 } else { 655 this.$slider.css({ 'width': this.settings.visibleSize, 'max-width': '100%', 'marginLeft': 0 }); 656 } 657 658 this.$slidesMask.css( 'width', this.$slider.width() ); 659 } else { 660 661 // If the size is forced to full window, the 'visibleSize' option will be 662 // ignored and the slider will become as high as the browser window. 663 if ( this.settings.forceSize === 'fullWindow' ) { 664 this.$slider.css({ 'height': $( window ).height(), 'max-height': '' }); 665 } else { 666 this.$slider.css({ 'height': this.settings.visibleSize, 'max-height': '100%' }); 667 } 668 669 this.$slidesMask.css( 'height', this.$slider.height() ); 670 } 671 } 672 673 this._resetSlidesPosition(); 674 this._calculateSlidesSize(); 675 676 // Fire the 'sliderResize' event 677 this.trigger({ type: 'sliderResize' }); 678 if ( $.isFunction( this.settings.sliderResize ) ) { 679 this.settings.sliderResize.call( this, { type: 'sliderResize' }); 680 } 681 }, 682 683 // Resize each individual slide 684 _resizeSlides: function() { 685 var slideWidth = this.slideWidth, 686 slideHeight = this.slideHeight; 687 688 if ( this.settings.autoSlideSize === true ) { 689 if ( this.settings.orientation === 'horizontal' ) { 690 slideWidth = 'auto'; 691 } else if ( this.settings.orientation === 'vertical' ) { 692 slideHeight = 'auto'; 693 } 694 } else if ( this.settings.autoHeight === true ) { 695 slideHeight = 'auto'; 696 } 697
698 // Loop through the existing slides and reset their size. 699 $.each( this.slides, function( index, element ) { 700 element.setSize( slideWidth, slideHeight ); 701 }); 702 }, 703 704 // Resize the height of the slider to the height of the selected slide. 705 // It's used when the 'autoHeight' option is set to 'true'. 706 _resizeHeight: function() { 707 var that = this, 708 selectedSlide = this.getSlideAt( this.selectedSlideIndex ); 709 710 this._resizeHeightTo( selectedSlide.getSize().height ); 711 }, 712 713 // Open the slide at the specified index 714 gotoSlide: function( index ) { 715 if ( index === this.selectedSlideIndex || typeof this.slides[ index ] === 'undefined' ) { 716 return; 717 } 718 719 var that = this; 720 721 this.previousSlideIndex = this.selectedSlideIndex; 722 this.selectedSlideIndex = index; 723 724 // Re-assign the 'sp-selected' class to the currently selected slide 725 this.$slides.find( '.sp-selected' ).removeClass( 'sp-selected' ); 726 this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected' ); 727 728 // If the slider is loopable reorder the slides to have the selected slide in the middle 729 // and update the slides' position. 730 if ( this.settings.loop === true ) { 731 this._updateSlidesOrder(); 732 this._updateSlidesPosition(); 733 } 734 735 // Adjust the height of the slider 736 if ( this.settings.autoHeight === true ) { 737 this._resizeHeight(); 738 } 739 740 var selectedSlideOffset = this.settings.centerSelectedSlide === true && this.settings.visibleSize !== 'auto' ? Math.round( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10 ) - this.getSlideAt( this.selectedSlideIndex ).getSize()[ this.sizeProperty ] ) / 2 ) : 0, 741 newSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + selectedSlideOffset; 742 743 // Move the slides container to the new position 744 this._moveTo( newSlidesPosition, false, function() { 745 that._resetSlidesPosition(); 746 747 // Fire the 'gotoSlideComplete' event 748 that.trigger({ type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex }); 749 if ( $.isFunction( that.settings.gotoSlideComplete ) ) { 750 that.settings.gotoSlideComplete.call( that, { type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex } ); 751 } 752 }); 753 754 // Fire the 'gotoSlide' event 755 this.trigger({ type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex }); 756 if ( $.isFunction( this.settings.gotoSlide ) ) { 757 this.settings.gotoSlide.call( this, { type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex } ); 758 } 759 }, 760 761 // Open the next slide 762 nextSlide: function() { 763 var index = ( this.selectedSlideIndex >= this.getTotalSlides() - 1 ) ? 0 : ( this.selectedSlideIndex + 1 ); 764 this.gotoSlide( index ); 765 }, 766 767 // Open the previous slide 768 previousSlide: function() { 769 var index = this.selectedSlideIndex <= 0 ? ( this.getTotalSlides() - 1 ) : ( this.selectedSlideIndex - 1 ); 770 this.gotoSlide( index ); 771 }, 772 773 // Move the slides container to the specified position. 774 // The movement can be instant or animated. 775 _moveTo: function( position, instant, callback ) { 776 var that = this, 777 css = {}; 778 779 if ( position === this.slidesPosition ) { 780 return; 781 } 782 783 this.slidesPosition = position; 784 785 if ( ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) && this.isIE === false ) { 786 var transition, 787 left = this.settings.orientation === 'horizontal' ? position : 0, 788 top = this.settings.orientation === 'horizontal' ? 0 : position; 789 790 if ( this.supportedAnimation === 'css-3d' ) { 791 css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; 792 } else { 793 css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)'; 794 } 795 796 if ( typeof instant !== 'undefined' && instant === true ) { 797 transition = ''; 798 } else { 799 this.$slides.addClass( 'sp-animated' ); 800 transition = this.vendorPrefix + 'transform ' + this.settings.slideAnimationDuration / 1000 + 's'; 801 802 this.$slides.on( this.transitionEvent, function( event ) { 803 if ( event.target !== event.currentTarget ) { 804 return; 805 } 806 807 that.$slides.off( that.transitionEvent ); 808 that.$slides.removeClass( 'sp-animated' ); 809 810 if ( typeof callback === 'function' ) { 811 callback(); 812 } 813 }); 814 } 815 816 css[ this.vendorPrefix + 'transition' ] = transition; 817 818 this.$slides.css( css ); 819 } else { 820 css[ 'margin-' + this.positionProperty ] = position; 821 822 if ( typeof instant !== 'undefined' && instant === true ) { 823 this.$slides.css( css ); 824 } else { 825 this.$slides.addClass( 'sp-animated' ); 826 this.$slides.animate( css, this.settings.slideAnimationDuration, function() { 827 that.$slides.removeClass( 'sp-animated' ); 828 829 if ( typeof callback === 'function' ) { 830 callback(); 831 } 832 }); 833 } 834 } 835 }, 836 837 // Stop the movement of the slides 838 _stopMovement: function() { 839 var css = {}; 840 841 if ( ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) && this.isIE === false) { 842 843 // Get the current position of the slides by parsing the 'transform' property 844 var matrixString = this.$slides.css( this.vendorPrefix + 'transform' ), 845 matrixType = matrixString.indexOf( 'matrix3d' ) !== -1 ? 'matrix3d' : 'matrix', 846 matrixArray = matrixString.replace( matrixType, '' ).match( /-?[0-9\.]+/g ), 847 left = matrixType === 'matrix3d' ? parseInt( matrixArray[ 12 ], 10 ) : parseInt( matrixArray[ 4 ], 10 ), 848 top = matrixType === 'matrix3d' ? parseInt( matrixArray[ 13 ], 10 ) : parseInt( matrixArray[ 5 ], 10 ); 849 850 // Set the transform property to the value that the transform had when the function was called 851 if ( this.supportedAnimation === 'css-3d' ) { 852 css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; 853 } else { 854 css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)'; 855 } 856 857 css[ this.vendorPrefix + 'transition' ] = ''; 858 859 this.$slides.css( css ); 860 this.$slides.off( this.transitionEvent ); 861 this.slidesPosition = this.settings.orientation === 'horizontal' ? left : top; 862 } else { 863 this.$slides.stop(); 864 this.slidesPosition = parseInt( this.$slides.css( 'margin-' + this.positionProperty ), 10 ); 865 } 866 867 this.$slides.removeClass( 'sp-animated' ); 868 }, 869 870 // Resize the height of the slider to the specified value 871 _resizeHeightTo: function( height ) { 872 var that = this, 873 css = { 'height': height }; 874 875 if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) { 876 css[ this.vendorPrefix + 'transition' ] = 'height ' + this.settings.heightAnimationDuration / 1000 + 's'; 877 878 this.$slidesMask.off( this.transitionEvent ); 879 this.$slidesMask.on( this.transitionEvent, function( event ) { 880 if ( event.target !== event.currentTarget ) { 881 return; 882 } 883 884 that.$slidesMask.off( that.transitionEvent ); 885 886 // Fire the 'resizeHeightComplete' event 887 that.trigger({ type: 'resizeHeightComplete' }); 888 if ( $.isFunction( that.settings.resizeHeightComplete ) ) { 889 that.settings.resizeHeightComplete.call( that, { type: 'resizeHeightComplete' } ); 890 } 891 }); 892 893 this.$slidesMask.css( css ); 894 } else { 895 this.$slidesMask.stop().animate( css, this.settings.heightAnimationDuration, function( event ) { 896 // Fire the 'resizeHeightComplete' event 897 that.trigger({ type: 'resizeHeightComplete' }); 898 if ( $.isFunction( that.settings.resizeHeightComplete ) ) { 899 that.settings.resizeHeightComplete.call( that, { type: 'resizeHeightComplete' } ); 900 } 901 }); 902 } 903 }, 904 905 // Destroy the slider instance 906 destroy: function() { 907 // Remove the stored reference to this instance 908 this.$slider.removeData( 'sliderPro' ); 909 910 // Clean the CSS 911 this.$slider.removeAttr( 'style' ); 912 this.$slides.removeAttr( 'style' ); 913 914 // Remove event listeners 915 this.off( 'update.' + NS ); 916 $( window ).off( 'resize.' + this.uniqueId + '.' + NS ); 917 918 // Destroy modules 919 var modules = $.SliderPro.modules; 920 921 if ( typeof modules !== 'undefined' ) { 922 for ( var i = 0; i < modules.length; i++ ) { 923 if ( typeof this[ 'destroy' + modules[ i ] ] !== 'undefined' ) { 924 this[ 'destroy' + modules[ i ] ](); 925 } 926 } 927 } 928 929 // Destroy all slides 930 $.each( this.slides, function( index, element ) { 931 element.destroy(); 932 }); 933 934 this.slides.length = 0; 935 936 // Move the slides to their initial position in the DOM and 937 // remove the container elements created dynamically. 938 this.$slides.prependTo( this.$slider ); 939 this.$slidesContainer.remove(); 940 }, 941 942 // Set properties on runtime 943 _setProperties: function( properties, store ) { 944 // Parse the properties passed as an object 945 for ( var prop in properties ) { 946 this.settings[ prop ] = properties[ prop ]; 947 948 // Alter the original settings as well unless 'false' is passed to the 'store' parameter 949 if ( store !== false ) { 950 this.originalSettings[ prop ] = properties[ prop ]; 951 } 952 } 953 954 this.update(); 955 }, 956 957 // Attach an event handler to the slider 958 on: function( type, callback ) { 959 return this.$slider.on( type, callback ); 960 }, 961 962 // Detach an event handler 963 off: function( type ) { 964 return this.$slider.off( type ); 965 }, 966 967 // Trigger an event on the slider 968 trigger: function( data ) { 969 return this.$slider.triggerHandler( data ); 970 }, 971 972 // Return the slide at the specified index 973 getSlideAt: function( index ) { 974 return this.slides[ index ]; 975 }, 976 977 // Return the index of the currently opened slide 978 getSelectedSlide: function() { 979 return this.selectedSlideIndex; 980 }, 981 982 // Return the total amount of slides 983 getTotalSlides: function() { 984 return this.slides.length; 985 }, 986 987 // The default options of the slider 988 defaults: { 989 // Width of the slide 990 width: 500, 991 992 // Height of the slide 993 height: 300, 994 995 // Indicates if the slider is responsive 996 responsive: true, 997 998 // The aspect ratio of the slider (width/height) 999 aspectRatio: -1, 1000 1001 // The scale mode for images (cover, contain, exact and none) 1002 imageScaleMode: 'cover', 1003 1004 // Indicates if the image will be centered 1005 centerImage: true, 1006 1007 // Indicates if the image can be scaled up more than its original size 1008 allowScaleUp: true, 1009 1010 // Indicates if height of the slider will be adjusted to the
1011 // height of the selected slide 1012 autoHeight: false, 1013 1014 // Will maintain all the slides at the same height, but will allow the width 1015 // of the slides to be variable if the orientation of the slides is horizontal 1016 // and vice-versa if the orientation is vertical 1017 autoSlideSize: false, 1018 1019 // Indicates the initially selected slide 1020 startSlide: 0, 1021 1022 // Indicates if the slides will be shuffled 1023 shuffle: false, 1024 1025 // Indicates whether the slides will be arranged horizontally 1026 // or vertically. Can be set to 'horizontal' or 'vertical'. 1027 orientation: 'horizontal', 1028 1029 // Indicates if the size of the slider will be forced to 'fullWidth' or 'fullWindow' 1030 forceSize: 'none', 1031 1032 // Indicates if the slider will be loopable 1033 loop: true, 1034 1035 // The distance between slides 1036 slideDistance: 10, 1037 1038 // The duration of the slide animation 1039 slideAnimationDuration: 700, 1040 1041 // The duration of the height animation 1042 heightAnimationDuration: 700, 1043 1044 // Sets the size of the visible area, allowing the increase of it in order 1045 // to make more slides visible. 1046 // By default, only the selected slide will be visible. 1047 visibleSize: 'auto', 1048 1049 // Indicates whether the selected slide will be in the center of the slider, when there 1050 // are more slides visible at a time. If set to false, the selected slide will be in the 1051 // left side of the slider. 1052 centerSelectedSlide: true, 1053 1054 // Indicates if the direction of the slider will be from right to left, 1055 // instead of the default left to right 1056 rightToLeft: false, 1057 1058 // Breakpoints for allowing the slider's options to be changed 1059 // based on the size of the window. 1060 breakpoints: null, 1061 1062 // Called when the slider is initialized 1063 init: function() {}, 1064 1065 // Called when the slider is updates 1066 update: function() {}, 1067 1068 // Called when the slider is resized 1069 sliderResize: function() {}, 1070 1071 // Called when a new slide is selected 1072 gotoSlide: function() {}, 1073 1074 // Called when the navigation to the newly selected slide is complete 1075 gotoSlideComplete: function() {}, 1076 1077 // Called when the height animation of the slider is complete 1078 resizeHeightComplete: function() {}, 1079 1080 // Called when a breakpoint is reached 1081 breakpointReach: function() {} 1082 } 1083 }; 1084 1085 var SliderProSlide = function( slide, index, settings ) { 1086 1087 // Reference to the slide jQuery element 1088 this.$slide = slide; 1089 1090 // Reference to the main slide image 1091 this.$mainImage = null; 1092 1093 // Reference to the container that will hold the main image 1094 this.$imageContainer = null; 1095 1096 // Indicates whether the slide has a main image 1097 this.hasMainImage = false; 1098 1099 // Indicates whether the main image is loaded 1100 this.isMainImageLoaded = false; 1101 1102 // Indicates whether the main image is in the process of being loaded 1103 this.isMainImageLoading = false; 1104 1105 // Indicates whether the slide has any image. There could be other images (i.e., in layers) 1106 // besides the main slide image. 1107 this.hasImages = false; 1108 1109 // Indicates if all the images in the slide are loaded 1110 this.areImagesLoaded = false; 1111 1112 // Indicates if the images inside the slide are in the process of being loaded 1113 this.areImagesLoading = false; 1114 1115 // The width and height of the slide 1116 this.width = 0; 1117 this.height = 0; 1118 1119 // Reference to the global settings of the slider 1120 this.settings = settings; 1121 1122 // Set the index of the slide 1123 this.setIndex( index ); 1124 1125 // Initialize the slide 1126 this._init(); 1127 }; 1128 1129 SliderProSlide.prototype = { 1130 1131 // The starting point for the slide 1132 _init: function() { 1133 var that = this; 1134 1135 // Mark the slide as initialized 1136 this.$slide.attr( 'data-init', true ); 1137 1138 // Get the main slide image if there is one 1139 this.$mainImage = this.$slide.find( '.sp-image' ).length !== 0 ? this.$slide.find( '.sp-image' ) : null; 1140 1141 // If there is a main slide image, create a container for it and add the image to this container. 1142 // The container will allow the isolation of the image from the rest of the slide's content. This is 1143 // helpful when you want to show some content below the image and not cover it. 1144 if ( this.$mainImage !== null ) { 1145 this.hasMainImage = true; 1146 1147 this.$imageContainer = $( '<div class="sp-image-container"></div>' ).prependTo( this.$slide ); 1148 1149 if ( this.$mainImage.parent( 'a' ).length !== 0 ) { 1150 this.$mainImage.parent( 'a' ).appendTo( this.$imageContainer ); 1151 } else { 1152 this.$mainImage.appendTo( this.$imageContainer ); 1153 } 1154 } 1155
1156 this.hasImages = this.$slide.find( 'img' ).length !== 0 ? true : false; 1157 }, 1158 1159 // Set the size of the slide 1160 setSize: function( width, height ) { 1161 var that = this; 1162 1163 this.width = width; 1164 this.height = height; 1165 1166 this.$slide.css({ 1167 'width': this.width, 1168 'height': this.height 1169 }); 1170 1171 if ( this.hasMainImage === true ) { 1172 1173 // Initially set the width and height of the container to the width and height 1174 // specified in the settings. This will prevent content overflowing if the width or height 1175 // are 'auto'. The 'auto' value will be passed only after the image is loaded. 1176 this.$imageContainer.css({ 1177 'width': this.settings.width, 1178 'height': this.settings.height 1179 }); 1180 1181 // Resize the main image if it's loaded. If the 'data-src' attribute is present it means 1182 // that the image will be lazy-loaded 1183 if ( typeof this.$mainImage.attr( 'data-src' ) === 'undefined' ) { 1184 this.resizeMainImage(); 1185 } 1186 } 1187 }, 1188 1189 // Get the size (width and height) of the slide 1190 getSize: function() { 1191 var that = this, 1192 size; 1193 1194 // Check if all images have loaded, and if they have, return the size, else, return 1195 // the original width and height of the slide 1196 if ( this.hasImages === true && this.areImagesLoaded === false && this.areImagesLoading === false ) { 1197 this.areImagesLoading = true; 1198 1199 var status = SliderProUtils.checkImagesStatus( this.$slide ); 1200 1201 if ( status !== 'complete' ) { 1202 SliderProUtils.checkImagesComplete( this.$slide, function() { 1203 that.areImagesLoaded = true; 1204 that.areImagesLoading = false; 1205 that.trigger({ type: 'imagesLoaded.' + NS, index: that.index }); 1206 }); 1207 1208 // if the image is not loaded yet, return the original width and height of the slider 1209 return { 1210 'width': this.settings.width, 1211 'height': this.settings.height 1212 }; 1213 } 1214 } 1215 1216 size = this.calculateSize(); 1217 1218 return { 1219 'width': size.width, 1220 'height': size.height 1221 }; 1222 }, 1223 1224 // Calculate the width and height of the slide by going 1225 // through all the child elements and measuring their 'bottom' 1226 // and 'right' properties. The element with the biggest 1227 // 'right'/'bottom' property will determine the slide's 1228 // width/height. 1229 calculateSize: function() { 1230 var width = this.$slide.width(), 1231 height = this.$slide.height(); 1232 1233 this.$slide.children().each(function( index, element ) { 1234 var child = $( element ); 1235 1236 if ( child.is( ':hidden' ) === true ) { 1237 return; 1238 } 1239 1240 var rect = element.getBoundingClientRect(), 1241 bottom = child.position().top + ( rect.bottom - rect.top ), 1242 right = child.position().left + ( rect.right - rect.left ); 1243 1244 if ( bottom > height ) { 1245 height = bottom; 1246 } 1247 1248 if ( right > width ) { 1249 width = right; 1250 } 1251 }); 1252 1253 return { 1254 width: width, 1255 height: height 1256 }; 1257 }, 1258 1259 // Resize the main image. 1260 // 1261 // Call this when the slide resizes or when the main image has changed to a different image. 1262 resizeMainImage: function( isNewImage ) { 1263 var that = this; 1264 1265 // If the main image has changed, reset the 'flags' 1266 if ( isNewImage === true ) { 1267 this.isMainImageLoaded = false; 1268 this.isMainImageLoading = false; 1269 } 1270 1271 // If the image was not loaded yet and it's not in the process of being loaded, load it 1272 if ( this.isMainImageLoaded === false && this.isMainImageLoading === false ) { 1273 this.isMainImageLoading = true; 1274 1275 SliderProUtils.checkImagesComplete( this.$mainImage, function() { 1276 that.isMainImageLoaded = true; 1277 that.isMainImageLoading = false; 1278 that.resizeMainImage(); 1279 that.trigger({ type: 'imagesLoaded.' + NS, index: that.index }); 1280 }); 1281 1282 return; 1283 } 1284 1285 // Set the size of the image container element to the proper 'width' and 'height' 1286 // values, as they were calculated. Previous values were the 'width' and 'height' 1287 // from the settings. 1288 this.$imageContainer.css({ 1289 'width': this.width, 1290 'height': this.height 1291 }); 1292 1293 if ( this.settings.allowScaleUp === false ) { 1294 // reset the image to its natural size 1295 this.$mainImage.css({ 'width': '', 'height': '', 'maxWidth': '', 'maxHeight': '' }); 1296 1297 // set the boundaries 1298 this.$mainImage.css({ 'maxWidth': this.$mainImage.width(), 'maxHeight': this.$mainImage.height() }); 1299 } 1300 1301 // After the main image has loaded, resize it 1302 if ( this.settings.autoSlideSize === true ) { 1303 if ( this.settings.orientation === 'horizontal' ) { 1304 this.$mainImage.css({ width: 'auto', height: '100%' }); 1305 1306 // resize the slide's width to a fixed value instead of 'auto', to 1307 // prevent incorrect sizing caused by links added to the main image 1308 this.$slide.css( 'width', this.$mainImage.width() ); 1309 } else if ( this.settings.orientation === 'vertical' ) { 1310 this.$mainImage.css({ width: '100%', height: 'auto' }); 1311 1312 // resize the slide's height to a fixed value instead of 'auto', to 1313 // prevent incorrect sizing caused by links added to the main image 1314 this.$slide.css( 'height', this.$mainImage.height() ); 1315 } 1316 } else if ( this.settings.autoHeight === true ) { 1317 this.$mainImage.css({ width: '100%', height: 'auto' }); 1318 } else { 1319 if ( this.settings.imageScaleMode === 'cover' ) { 1320 if ( this.$mainImage.width() / this.$mainImage.height() <= this.$slide.width() / this.$slide.height() ) { 1321 this.$mainImage.css({ width: '100%', height: 'auto' }); 1322 } else { 1323 this.$mainImage.css({ width: 'auto', height: '100%' }); 1324 } 1325 } else if ( this.settings.imageScaleMode === 'contain' ) { 1326 if ( this.$mainImage.width() / this.$mainImage.height() >= this.$slide.width() / this.$slide.height() ) { 1327 this.$mainImage.css({ width: '100%', height: 'auto' }); 1328 } else { 1329 this.$mainImage.css({ width: 'auto', height: '100%' }); 1330 } 1331 } else if ( this.settings.imageScaleMode === 'exact' ) { 1332 this.$mainImage.css({ width: '100%', height: '100%' }); 1333 } 1334 1335 if ( this.settings.centerImage === true ) { 1336 this.$mainImage.css({ 'marginLeft': ( this.$imageContainer.width() - this.$mainImage.width() ) * 0.5, 'marginTop': ( this.$imageContainer.height() - this.$mainImage.height() ) * 0.5 }); 1337 } 1338 } 1339 }, 1340 1341 // Destroy the slide 1342 destroy: function() { 1343 // Clean the slide element from attached styles and data 1344 this.$slide.removeAttr( 'style' ); 1345 this.$slide.removeAttr( 'data-init' ); 1346 this.$slide.removeAttr( 'data-index' ); 1347 this.$slide.removeAttr( 'data-loaded' ); 1348 1349 // If there is a main image, remove its container 1350 if ( this.hasMainImage === true ) { 1351 this.$slide.find( '.sp-image' ) 1352 .removeAttr( 'style' ) 1353 .appendTo( this.$slide ); 1354 1355 this.$slide.find( '.sp-image-container' ).remove(); 1356 } 1357 }, 1358 1359 // Return the index of the slide 1360 getIndex: function() { 1361 return this.index; 1362 }, 1363 1364 // Set the index of the slide 1365 setIndex: function( index ) { 1366 this.index = index; 1367 this.$slide.attr( 'data-index', this.index ); 1368 }, 1369 1370 // Attach an event handler to the slide 1371 on: function( type, callback ) { 1372 return this.$slide.on( type, callback ); 1373 }, 1374 1375 // Detach an event handler to the slide 1376 off: function( type ) { 1377 return this.$slide.off( type ); 1378 }, 1379 1380 // Trigger an event on the slide 1381 trigger: function( data ) { 1382 return this.$slide.triggerHandler( data ); 1383 } 1384 }; 1385 1386 window.SliderPro = SliderPro; 1387 window.SliderProSlide = SliderProSlide;
1388 1389 $.fn.sliderPro = function( options ) { 1390 var args = Array.prototype.slice.call( arguments, 1 ); 1391 1392 return this.each(function() { 1393 // Instantiate the slider or alter it 1394 if ( typeof $( this ).data( 'sliderPro' ) === 'undefined' ) { 1395 var newInstance = new SliderPro( this, options ); 1396 1397 // Store a reference to the instance created 1398 $( this ).data( 'sliderPro', newInstance ); 1399 } else if ( typeof options !== 'undefined' ) { 1400 var currentInstance = $( this ).data( 'sliderPro' ); 1401 1402 // Check the type of argument passed 1403 if ( typeof currentInstance[ options ] === 'function' ) { 1404 currentInstance[ options ].apply( currentInstance, args ); 1405 } else if ( typeof currentInstance.settings[ options ] !== 'undefined' ) { 1406 var obj = {}; 1407 obj[ options ] = args[ 0 ]; 1408 currentInstance._setProperties( obj ); 1409 } else if ( typeof options === 'object' ) { 1410 currentInstance._setProperties( options ); 1411 } else { 1412 $.error( options + ' does not exist in sliderPro.' ); 1413 } 1414 } 1415 }); 1416 }; 1417 1418 // Contains useful utility functions 1419 var SliderProUtils = { 1420 1421 // Indicates what type of animations are supported in the current browser 1422 // Can be CSS 3D, CSS 2D or JavaScript 1423 supportedAnimation: null, 1424 1425 // Indicates the required vendor prefix for the current browser 1426 vendorPrefix: null, 1427 1428 // Indicates the name of the transition's complete event for the current browser 1429 transitionEvent: null, 1430 1431 // Indicates if the current browser is Internet Explorer (any version) 1432 isIE: null, 1433 1434 // Check whether CSS3 3D or 2D transforms are supported. If they aren't, use JavaScript animations 1435 getSupportedAnimation: function() { 1436 if ( this.supportedAnimation !== null ) { 1437 return this.supportedAnimation; 1438 } 1439 1440 var element = document.body || document.documentElement, 1441 elementStyle = element.style, 1442 isCSSTransitions = typeof elementStyle.transition !== 'undefined' || 1443 typeof elementStyle.WebkitTransition !== 'undefined' || 1444 typeof elementStyle.MozTransition !== 'undefined' || 1445 typeof elementStyle.OTransition !== 'undefined'; 1446 1447 if ( isCSSTransitions === true ) { 1448 var div = document.createElement( 'div' ); 1449 1450 // Check if 3D transforms are supported 1451 if ( typeof div.style.WebkitPerspective !== 'undefined' || typeof div.style.perspective !== 'undefined' ) { 1452 this.supportedAnimation = 'css-3d'; 1453 } 1454 1455 // Additional checks for Webkit 1456 if ( this.supportedAnimation === 'css-3d' && typeof div.styleWebkitPerspective !== 'undefined' ) { 1457 var style = document.createElement( 'style' ); 1458 style.textContent = '@media (transform-3d),(-webkit-transform-3d){#test-3d{left:9px;position:absolute;height:5px;margin:0;padding:0;border:0;}}'; 1459 document.getElementsByTagName( 'head' )[0].appendChild( style ); 1460 1461 div.id = 'test-3d'; 1462 document.body.appendChild( div ); 1463 1464 if ( ! ( div.offsetLeft === 9 && div.offsetHeight === 5 ) ) { 1465 this.supportedAnimation = null; 1466 } 1467 1468 style.parentNode.removeChild( style ); 1469 div.parentNode.removeChild( div ); 1470 } 1471 1472 // If CSS 3D transforms are not supported, check if 2D transforms are supported 1473 if ( this.supportedAnimation === null && ( typeof div.style['-webkit-transform'] !== 'undefined' || typeof div.style.transform !== 'undefined' ) ) { 1474 this.supportedAnimation = 'css-2d'; 1475 } 1476 } else { 1477 this.supportedAnimation = 'javascript'; 1478 } 1479 1480 return this.supportedAnimation; 1481 }, 1482 1483 // Check what vendor prefix should be used in the current browser 1484 getVendorPrefix: function() { 1485 if ( this.vendorPrefix !== null ) { 1486 return this.vendorPrefix; 1487 } 1488 1489 var div = document.createElement( 'div' ), 1490 prefixes = [ 'Webkit', 'Moz', 'ms', 'O' ]; 1491 1492 if ( 'transform' in div.style ) { 1493 this.vendorPrefix = ''; 1494 return this.vendorPrefix; 1495 } 1496 1497 for ( var i = 0; i < prefixes.length; i++ ) { 1498 if ( ( prefixes[ i ] + 'Transform' ) in div.style ) { 1499 this.vendorPrefix = '-' + prefixes[ i ].toLowerCase() + '-'; 1500 break; 1501 } 1502 } 1503 1504 return this.vendorPrefix; 1505 }, 1506 1507 // Check the name of the transition's complete event in the current browser 1508 getTransitionEvent: function() { 1509 if ( this.transitionEvent !== null ) { 1510 return this.transitionEvent; 1511 } 1512 1513 var div = document.createElement( 'div' ), 1514 transitions = { 1515 'transition': 'transitionend', 1516 'WebkitTransition': 'webkitTransitionEnd', 1517 'MozTransition': 'transitionend', 1518 'OTransition': 'oTransitionEnd' 1519 }; 1520 1521 for ( var transition in transitions ) { 1522 if ( transition in div.style ) { 1523 this.transitionEvent = transitions[ transition ]; 1524 break; 1525 } 1526 } 1527 1528 return this.transitionEvent; 1529 }, 1530 1531 // If a single image is passed, check if it's loaded. 1532 // If a different element is passed, check if there are images 1533 // inside it, and check if these images are loaded. 1534 checkImagesComplete: function( target, callback ) { 1535 var that = this, 1536 1537 // Check the initial status of the image(s) 1538 status = this.checkImagesStatus( target ); 1539 1540 // If there are loading images, wait for them to load. 1541 // If the images are loaded, call the callback function directly. 1542 if ( status === 'loading' ) { 1543 var checkImages = setInterval(function() { 1544 status = that.checkImagesStatus( target ); 1545 1546 if ( status === 'complete' ) { 1547 clearInterval( checkImages ); 1548 1549 if ( typeof callback === 'function' ) { 1550 callback(); 1551 } 1552 } 1553 }, 100 ); 1554 } else if ( typeof callback === 'function' ) { 1555 callback(); 1556 } 1557 1558 return status; 1559 }, 1560 1561 checkImagesStatus: function( target ) { 1562 var status = 'complete'; 1563 1564 if ( target.is( 'img' ) && target[0].complete === false ) { 1565 status = 'loading'; 1566 } else {
1567 target.find( 'img' ).each(function( index ) { 1568 var image = $( this )[0]; 1569 1570 if ( image.complete === false ) { 1571 status = 'loading'; 1572 } 1573 }); 1574 } 1575 1576 return status; 1577 }, 1578 1579 checkIE: function() { 1580 if ( this.isIE !== null ) { 1581 return this.isIE; 1582 } 1583 1584 var userAgent = window.navigator.userAgent, 1585 msie = userAgent.indexOf( 'MSIE' ); 1586 1587 if ( userAgent.indexOf( 'MSIE' ) !== -1 || userAgent.match( /Trident.*rv\:11\./ ) ) { 1588 this.isIE = true; 1589 } else { 1590 this.isIE = false; 1591 } 1592 1593 return this.isIE; 1594 } 1595 }; 1596 1597 window.SliderProUtils = SliderProUtils; 1598 1599})( window, jQuery ); 1600 1601// Thumbnails module for Slider Pro. 1602// 1603// Adds the possibility to create a thumbnail scroller, each thumbnail 1604// corresponding to a slide. 1605;(function( window, $ ) { 1606 1607 "use strict"; 1608 1609 var NS = 'Thumbnails.' + $.SliderPro.namespace; 1610 1611 var Thumbnails = { 1612 1613 // Reference to the thumbnail scroller 1614 $thumbnails: null, 1615 1616 // Reference to the container of the thumbnail scroller 1617 $thumbnailsContainer: null, 1618 1619 // List of Thumbnail objects 1620 thumbnails: null, 1621 1622 // Index of the selected thumbnail 1623 selectedThumbnailIndex: 0, 1624 1625 // Total size (width or height, depending on the orientation) of the thumbnails 1626 thumbnailsSize: 0, 1627 1628 // Size of the thumbnail's container 1629 thumbnailsContainerSize: 0, 1630 1631 // The position of the thumbnail scroller inside its container 1632 thumbnailsPosition: 0, 1633 1634 // Orientation of the thumbnails 1635 thumbnailsOrientation: null, 1636 1637 // Indicates the 'left' or 'top' position based on the orientation of the thumbnails 1638 thumbnailsPositionProperty: null, 1639 1640 // Indicates if there are thumbnails in the slider 1641 isThumbnailScroller: false, 1642 1643 initThumbnails: function() { 1644 var that = this; 1645 1646 this.thumbnails = []; 1647 1648 this.on( 'update.' + NS, $.proxy( this._thumbnailsOnUpdate, this ) ); 1649 this.on( 'sliderResize.' + NS, $.proxy( this._thumbnailsOnResize, this ) );
1650 this.on( 'gotoSlide.' + NS, function( event ) { 1651 that._gotoThumbnail( event.index ); 1652 }); 1653 }, 1654 1655 // Called when the slider is updated 1656 _thumbnailsOnUpdate: function() { 1657 var that = this; 1658 1659 if ( this.$slider.find( '.sp-thumbnail' ).length === 0 && this.thumbnails.length === 0 ) { 1660 this.isThumbnailScroller = false; 1661 return; 1662 } 1663 1664 this.isThumbnailScroller = true; 1665 1666 // Create the container of the thumbnail scroller, if it wasn't created yet 1667 if ( this.$thumbnailsContainer === null ) { 1668 this.$thumbnailsContainer = $( '<div class="sp-thumbnails-container"></div>' ).insertAfter( this.$slidesContainer ); 1669 } 1670 1671 // If the thumbnails' main container doesn't exist, create it, and get a reference to it 1672 if ( this.$thumbnails === null ) { 1673 if ( this.$slider.find( '.sp-thumbnails' ).length !== 0 ) { 1674 this.$thumbnails = this.$slider.find( '.sp-thumbnails' ).appendTo( this.$thumbnailsContainer ); 1675 1676 // Shuffle/randomize the thumbnails 1677 if ( this.settings.shuffle === true ) { 1678 var thumbnails = this.$thumbnails.find( '.sp-thumbnail' ), 1679 shuffledThumbnails = []; 1680 1681 // Reposition the thumbnails based on the order of the indexes in the 1682 // 'shuffledIndexes' array 1683 $.each( this.shuffledIndexes, function( index, element ) { 1684 var $thumbnail = $( thumbnails[ element ] ); 1685 1686 if ( $thumbnail.parent( 'a' ).length !== 0 ) { 1687 $thumbnail = $thumbnail.parent( 'a' ); 1688 } 1689 1690 shuffledThumbnails.push( $thumbnail ); 1691 }); 1692 1693 // Append the sorted thumbnails to the thumbnail scroller 1694 this.$thumbnails.empty().append( shuffledThumbnails ) ; 1695 } 1696 } else { 1697 this.$thumbnails = $( '<div class="sp-thumbnails"></div>' ).appendTo( this.$thumbnailsContainer ); 1698 } 1699 } 1700 1701 // Check if there are thumbnails inside the slides and move them in the thumbnails container 1702 this.$slides.find( '.sp-thumbnail' ).each( function( index ) { 1703 var $thumbnail = $( this ), 1704 thumbnailIndex = $thumbnail.parents( '.sp-slide' ).index(), 1705 lastThumbnailIndex = that.$thumbnails.find( '.sp-thumbnail' ).length - 1; 1706 1707 if ( $thumbnail.parent( 'a' ).length !== 0 ) { 1708 $thumbnail = $thumbnail.parent( 'a' ); 1709 } 1710 1711 // If the index of the slide that contains the thumbnail is greater than the total number 1712 // of thumbnails from the thumbnails container, position the thumbnail at the end. 1713 // Otherwise, add the thumbnails at the corresponding position. 1714 if ( thumbnailIndex > lastThumbnailIndex ) { 1715 $thumbnail.appendTo( that.$thumbnails ); 1716 } else { 1717 $thumbnail.insertBefore( that.$thumbnails.find( '.sp-thumbnail' ).eq( thumbnailIndex ) ); 1718 } 1719 }); 1720 1721 // Loop through the Thumbnail objects and if a corresponding element is not found in the DOM, 1722 // it means that the thumbnail might have been removed. In this case, destroy that Thumbnail instance. 1723 for ( var i = this.thumbnails.length - 1; i >= 0; i-- ) { 1724 if ( this.$thumbnails.find( '.sp-thumbnail[data-index="' + i + '"]' ).length === 0 ) { 1725 var thumbnail = this.thumbnails[ i ]; 1726 1727 thumbnail.destroy(); 1728 this.thumbnails.splice( i, 1 ); 1729 } 1730 } 1731 1732 // Loop through the thumbnails and if there is any uninitialized thumbnail, 1733 // initialize it, else update the thumbnail's index. 1734 this.$thumbnails.find( '.sp-thumbnail' ).each(function( index ) { 1735 var $thumbnail = $( this ); 1736 1737 if ( typeof $thumbnail.attr( 'data-init' ) === 'undefined' ) { 1738 that._createThumbnail( $thumbnail, index ); 1739 } else { 1740 that.thumbnails[ index ].setIndex( index ); 1741 } 1742 }); 1743 1744 // Remove the previous class that corresponds to the position of the thumbnail scroller 1745 this.$thumbnailsContainer.removeClass( 'sp-top-thumbnails sp-bottom-thumbnails sp-left-thumbnails sp-right-thumbnails' ); 1746 1747 // Check the position of the thumbnail scroller and assign it the appropriate class and styling 1748 if ( this.settings.thumbnailsPosition === 'top' ) { 1749 this.$thumbnailsContainer.addClass( 'sp-top-thumbnails' ); 1750 this.thumbnailsOrientation = 'horizontal'; 1751 } else if ( this.settings.thumbnailsPosition === 'bottom' ) { 1752 this.$thumbnailsContainer.addClass( 'sp-bottom-thumbnails' ); 1753 this.thumbnailsOrientation = 'horizontal'; 1754 } else if ( this.settings.thumbnailsPosition === 'left' ) { 1755 this.$thumbnailsContainer.addClass( 'sp-left-thumbnails' ); 1756 this.thumbnailsOrientation = 'vertical'; 1757 } else if ( this.settings.thumbnailsPosition === 'right' ) { 1758 this.$thumbnailsContainer.addClass( 'sp-right-thumbnails' ); 1759 this.thumbnailsOrientation = 'vertical'; 1760 } 1761 1762 // Check if the pointer needs to be created 1763 if ( this.settings.thumbnailPointer === true ) { 1764 this.$thumbnailsContainer.addClass( 'sp-has-pointer' ); 1765 } else { 1766 this.$thumbnailsContainer.removeClass( 'sp-has-pointer' ); 1767 } 1768
1769 // Mark the thumbnail that corresponds to the selected slide 1770 this.selectedThumbnailIndex = this.selectedSlideIndex; 1771 this.$thumbnails.find( '.sp-thumbnail-container' ).eq( this.selectedThumbnailIndex ).addClass( 'sp-selected-thumbnail' ); 1772 1773 // Calculate the total size of the thumbnails 1774 this.thumbnailsSize = 0; 1775 1776 $.each( this.thumbnails, function( index, thumbnail ) { 1777 thumbnail.setSize( that.settings.thumbnailWidth, that.settings.thumbnailHeight ); 1778 that.thumbnailsSize += that.thumbnailsOrientation === 'horizontal' ? thumbnail.getSize().width : thumbnail.getSize().height; 1779 }); 1780 1781 // Set the size of the thumbnails 1782 if ( this.thumbnailsOrientation === 'horizontal' ) { 1783 this.$thumbnails.css({ 'width': this.thumbnailsSize, 'height': this.settings.thumbnailHeight }); 1784 this.$thumbnailsContainer.css( 'height', '' ); 1785 this.thumbnailsPositionProperty = 'left'; 1786 } else { 1787 this.$thumbnails.css({ 'width': this.settings.thumbnailWidth, 'height': this.thumbnailsSize }); 1788 this.$thumbnailsContainer.css( 'width', '' ); 1789 this.thumbnailsPositionProperty = 'top'; 1790 } 1791 1792 // Fire the 'thumbnailsUpdate' event 1793 this.trigger({ type: 'thumbnailsUpdate' }); 1794 if ( $.isFunction( this.settings.thumbnailsUpdate ) ) { 1795 this.settings.thumbnailsUpdate.call( this, { type: 'thumbnailsUpdate' } ); 1796 } 1797 }, 1798 1799 // Create an individual thumbnail 1800 _createThumbnail: function( element, index ) { 1801 var that = this, 1802 thumbnail = new Thumbnail( element, this.$thumbnails, index ); 1803 1804 // When the thumbnail is clicked, navigate to the corresponding slide 1805 thumbnail.on( 'thumbnailClick.' + NS, function( event ) { 1806 that.gotoSlide( event.index ); 1807 }); 1808 1809 // Add the thumbnail at the specified index 1810 this.thumbnails.splice( index, 0, thumbnail ); 1811 }, 1812 1813 // Called when the slider is resized. 1814 // Resets the size and position of the thumbnail scroller container. 1815 _thumbnailsOnResize: function() { 1816 if ( this.isThumbnailScroller === false ) { 1817 return; 1818 } 1819 1820 var that = this, 1821 newThumbnailsPosition; 1822 1823 if ( this.thumbnailsOrientation === 'horizontal' ) { 1824 this.thumbnailsContainerSize = Math.min( this.$slidesMask.width(), this.thumbnailsSize ); 1825 this.$thumbnailsContainer.css( 'width', this.thumbnailsContainerSize ); 1826 1827 // Reduce the slide mask's height, to make room for the thumbnails 1828 if ( this.settings.forceSize === 'fullWindow' ) { 1829 this.$slidesMask.css( 'height', this.$slidesMask.height() - this.$thumbnailsContainer.outerHeight( true ) ); 1830 1831 // Resize the slides 1832 this.slideHeight = this.$slidesMask.height(); 1833 this._resizeSlides(); 1834 1835 // Re-arrange the slides 1836 this._resetSlidesPosition(); 1837 } 1838 } else if ( this.thumbnailsOrientation === 'vertical' ) { 1839 1840 // Check if the width of the slide mask plus the width of the thumbnail scroller is greater than 1841 // the width of the slider's container and if that's the case, reduce the slides container width 1842 // in order to make the entire slider fit inside the slider's container. 1843 if ( this.$slidesMask.width() + this.$thumbnailsContainer.outerWidth( true ) > this.$slider.parent().width() ) { 1844 // Reduce the slider's width, to make room for the thumbnails 1845 if ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) { 1846 this.$slider.css( 'max-width', $( window ).width() - this.$thumbnailsContainer.outerWidth( true ) ); 1847 } else { 1848 this.$slider.css( 'max-width', this.$slider.parent().width() - this.$thumbnailsContainer.outerWidth( true ) ); 1849 } 1850 1851 this.$slidesMask.css( 'width', this.$slider.width() ); 1852 1853 // If the slides are vertically oriented, update the width and height (to maintain the aspect ratio) 1854 // of the slides. 1855 if ( this.settings.orientation === 'vertical' ) { 1856 this.slideWidth = this.$slider.width(); 1857 1858 this._resizeSlides(); 1859 } 1860 1861 // Re-arrange the slides 1862 this._resetSlidesPosition(); 1863 } 1864 1865 this.thumbnailsContainerSize = Math.min( this.$slidesMask.height(), this.thumbnailsSize ); 1866 this.$thumbnailsContainer.css( 'height', this.thumbnailsContainerSize ); 1867 } 1868 1869 // If the total size of the thumbnails is smaller than the thumbnail scroller' container (which has 1870 // the same size as the slides container), it means that all the thumbnails will be visible, so set 1871 // the position of the thumbnail scroller to 0. 1872 // 1873 // If that's not the case, the thumbnail scroller will be positioned based on which thumbnail is selected. 1874 if ( this.thumbnailsSize <= this.thumbnailsContainerSize || this.$thumbnails.find( '.sp-selected-thumbnail' ).length === 0 ) { 1875 newThumbnailsPosition = 0; 1876 } else { 1877 newThumbnailsPosition = Math.max( - this.thumbnails[ this.selectedThumbnailIndex ].getPosition()[ this.thumbnailsPositionProperty ], this.thumbnailsContainerSize - this.thumbnailsSize ); 1878 } 1879 1880 // Add a padding to the slider, based on the thumbnail scroller's orientation, to make room 1881 // for the thumbnails. 1882 if ( this.settings.thumbnailsPosition === 'top' ) { 1883 this.$slider.css({ 'paddingTop': this.$thumbnailsContainer.outerHeight( true ), 'paddingLeft': '', 'paddingRight': '' }); 1884 } else if ( this.settings.thumbnailsPosition === 'bottom' ) { 1885 this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': '' }); 1886 } else if ( this.settings.thumbnailsPosition === 'left' ) { 1887 this.$slider.css({ 'paddingTop': '', 'paddingLeft': this.$thumbnailsContainer.outerWidth( true ), 'paddingRight': '' }); 1888 } else if ( this.settings.thumbnailsPosition === 'right' ) { 1889 this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': this.$thumbnailsContainer.outerWidth( true ) }); 1890 } 1891
1892 this._moveThumbnailsTo( newThumbnailsPosition, true ); 1893 }, 1894 1895 // Selects the thumbnail at the indicated index and moves the thumbnail scroller 1896 // accordingly. 1897 _gotoThumbnail: function( index ) { 1898 if ( this.isThumbnailScroller === false || typeof this.thumbnails[ index ] === 'undefined' ) { 1899 return; 1900 } 1901 1902 var previousIndex = this.selectedThumbnailIndex, 1903 newThumbnailsPosition = this.thumbnailsPosition; 1904 1905 this.selectedThumbnailIndex = index; 1906 1907 // Set the 'selected' class to the appropriate thumbnail 1908 this.$thumbnails.find( '.sp-selected-thumbnail' ).removeClass( 'sp-selected-thumbnail' ); 1909 this.$thumbnails.find( '.sp-thumbnail-container' ).eq( this.selectedThumbnailIndex ).addClass( 'sp-selected-thumbnail' ); 1910 1911 // Calculate the new position that the thumbnail scroller needs to go to. 1912 // 1913 // If the selected thumbnail has a higher index than the previous one, make sure that the thumbnail 1914 // that comes after the selected thumbnail will be visible, if the selected thumbnail is not the 1915 // last thumbnail in the list. 1916 // 1917 // If the selected thumbnail has a lower index than the previous one, make sure that the thumbnail 1918 // that's before the selected thumbnail will be visible, if the selected thumbnail is not the 1919 // first thumbnail in the list. 1920 if ( this.settings.rightToLeft === true && this.thumbnailsOrientation === 'horizontal' ) { 1921 if ( this.selectedThumbnailIndex >= previousIndex ) { 1922 var rtlNextThumbnailIndex = this.selectedThumbnailIndex === this.thumbnails.length - 1 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex + 1, 1923 rtlNextThumbnail = this.thumbnails[ rtlNextThumbnailIndex ]; 1924 1925 if ( rtlNextThumbnail.getPosition().left < - this.thumbnailsPosition ) { 1926 newThumbnailsPosition = - rtlNextThumbnail.getPosition().left; 1927 } 1928 } else if ( this.selectedThumbnailIndex < previousIndex ) { 1929 var rtlPreviousThumbnailIndex = this.selectedThumbnailIndex === 0 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex - 1, 1930 rtlPreviousThumbnail = this.thumbnails[ rtlPreviousThumbnailIndex ], 1931 rtlThumbnailsRightPosition = - this.thumbnailsPosition + this.thumbnailsContainerSize; 1932 1933 if ( rtlPreviousThumbnail.getPosition().right > rtlThumbnailsRightPosition ) { 1934 newThumbnailsPosition = this.thumbnailsPosition - ( rtlPreviousThumbnail.getPosition().right - rtlThumbnailsRightPosition ); 1935 } 1936 } 1937 } else { 1938 if ( this.selectedThumbnailIndex >= previousIndex ) { 1939 var nextThumbnailIndex = this.selectedThumbnailIndex === this.thumbnails.length - 1 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex + 1, 1940 nextThumbnail = this.thumbnails[ nextThumbnailIndex ], 1941 nextThumbnailPosition = this.thumbnailsOrientation === 'horizontal' ? nextThumbnail.getPosition().right : nextThumbnail.getPosition().bottom, 1942 thumbnailsRightPosition = - this.thumbnailsPosition + this.thumbnailsContainerSize; 1943 1944 if ( nextThumbnailPosition > thumbnailsRightPosition ) { 1945 newThumbnailsPosition = this.thumbnailsPosition - ( nextThumbnailPosition - thumbnailsRightPosition ); 1946 } 1947 } else if ( this.selectedThumbnailIndex < previousIndex ) { 1948 var previousThumbnailIndex = this.selectedThumbnailIndex === 0 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex - 1, 1949 previousThumbnail = this.thumbnails[ previousThumbnailIndex ], 1950 previousThumbnailPosition = this.thumbnailsOrientation === 'horizontal' ? previousThumbnail.getPosition().left : previousThumbnail.getPosition().top; 1951 1952 if ( previousThumbnailPosition < - this.thumbnailsPosition ) { 1953 newThumbnailsPosition = - previousThumbnailPosition; 1954 } 1955 } 1956 } 1957 1958 // Move the thumbnail scroller to the calculated position 1959 this._moveThumbnailsTo( newThumbnailsPosition ); 1960 1961 // Fire the 'gotoThumbnail' event 1962 this.trigger({ type: 'gotoThumbnail' }); 1963 if ( $.isFunction( this.settings.gotoThumbnail ) ) { 1964 this.settings.gotoThumbnail.call( this, { type: 'gotoThumbnail' }); 1965 } 1966 }, 1967 1968 // Move the thumbnail scroller to the indicated position
1969 _moveThumbnailsTo: function( position, instant, callback ) { 1970 var that = this, 1971 css = {}; 1972 1973 // Return if the position hasn't changed 1974 if ( position === this.thumbnailsPosition ) { 1975 return; 1976 } 1977 1978 this.thumbnailsPosition = position; 1979 1980 // Use CSS transitions if they are supported. If not, use JavaScript animation 1981 if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) { 1982 var transition, 1983 left = this.thumbnailsOrientation === 'horizontal' ? position : 0, 1984 top = this.thumbnailsOrientation === 'horizontal' ? 0 : position; 1985 1986 if ( this.supportedAnimation === 'css-3d' ) { 1987 css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; 1988 } else { 1989 css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)'; 1990 } 1991 1992 if ( typeof instant !== 'undefined' && instant === true ) { 1993 transition = ''; 1994 } else { 1995 this.$thumbnails.addClass( 'sp-animated' ); 1996 transition = this.vendorPrefix + 'transform ' + 700 / 1000 + 's'; 1997 1998 this.$thumbnails.on( this.transitionEvent, function( event ) { 1999 if ( event.target !== event.currentTarget ) { 2000 return; 2001 } 2002 2003 that.$thumbnails.off( that.transitionEvent ); 2004 that.$thumbnails.removeClass( 'sp-animated' ); 2005 2006 if ( typeof callback === 'function' ) { 2007 callback(); 2008 } 2009 2010 // Fire the 'thumbnailsMoveComplete' event 2011 that.trigger({ type: 'thumbnailsMoveComplete' }); 2012 if ( $.isFunction( that.settings.thumbnailsMoveComplete ) ) { 2013 that.settings.thumbnailsMoveComplete.call( that, { type: 'thumbnailsMoveComplete' }); 2014 } 2015 }); 2016 } 2017 2018 css[ this.vendorPrefix + 'transition' ] = transition; 2019 2020 this.$thumbnails.css( css ); 2021 } else { 2022 css[ 'margin-' + this.thumbnailsPositionProperty ] = position; 2023 2024 if ( typeof instant !== 'undefined' && instant === true ) { 2025 this.$thumbnails.css( css ); 2026 } else { 2027 this.$thumbnails 2028 .addClass( 'sp-animated' ) 2029 .animate( css, 700, function() { 2030 that.$thumbnails.removeClass( 'sp-animated' ); 2031 2032 if ( typeof callback === 'function' ) { 2033 callback(); 2034 } 2035 2036 // Fire the 'thumbnailsMoveComplete' event 2037 that.trigger({ type: 'thumbnailsMoveComplete' }); 2038 if ( $.isFunction( that.settings.thumbnailsMoveComplete ) ) { 2039 that.settings.thumbnailsMoveComplete.call( that, { type: 'thumbnailsMoveComplete' }); 2040 } 2041 }); 2042 } 2043 } 2044 }, 2045 2046 // Stop the movement of the thumbnail scroller 2047 _stopThumbnailsMovement: function() { 2048 var css = {}; 2049 2050 if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) { 2051 var matrixString = this.$thumbnails.css( this.vendorPrefix + 'transform' ), 2052 matrixType = matrixString.indexOf( 'matrix3d' ) !== -1 ? 'matrix3d' : 'matrix', 2053 matrixArray = matrixString.replace( matrixType, '' ).match( /-?[0-9\.]+/g ), 2054 left = matrixType === 'matrix3d' ? parseInt( matrixArray[ 12 ], 10 ) : parseInt( matrixArray[ 4 ], 10 ), 2055 top = matrixType === 'matrix3d' ? parseInt( matrixArray[ 13 ], 10 ) : parseInt( matrixArray[ 5 ], 10 ); 2056 2057 if ( this.supportedAnimation === 'css-3d' ) { 2058 css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; 2059 } else { 2060 css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)'; 2061 } 2062 2063 css[ this.vendorPrefix + 'transition' ] = ''; 2064 2065 this.$thumbnails.css( css ); 2066 this.$thumbnails.off( this.transitionEvent ); 2067 this.thumbnailsPosition = this.thumbnailsOrientation === 'horizontal' ? parseInt( matrixArray[ 4 ] , 10 ) : parseInt( matrixArray[ 5 ] , 10 ); 2068 } else { 2069 this.$thumbnails.stop(); 2070 this.thumbnailsPosition = parseInt( this.$thumbnails.css( 'margin-' + this.thumbnailsPositionProperty ), 10 ); 2071 } 2072 2073 this.$thumbnails.removeClass( 'sp-animated' ); 2074 }, 2075 2076 // Destroy the module 2077 destroyThumbnails: function() { 2078 var that = this; 2079 2080 // Remove event listeners 2081 this.off( 'update.' + NS ); 2082 2083 if ( this.isThumbnailScroller === false ) { 2084 return; 2085 } 2086 2087 this.off( 'sliderResize.' + NS );
2088 this.off( 'gotoSlide.' + NS ); 2089 $( window ).off( 'resize.' + this.uniqueId + '.' + NS ); 2090 2091 // Destroy the individual thumbnails 2092 this.$thumbnails.find( '.sp-thumbnail' ).each( function() { 2093 var $thumbnail = $( this ), 2094 index = parseInt( $thumbnail.attr( 'data-index' ), 10 ), 2095 thumbnail = that.thumbnails[ index ]; 2096 2097 thumbnail.off( 'thumbnailClick.' + NS ); 2098 thumbnail.destroy(); 2099 }); 2100 2101 this.thumbnails.length = 0; 2102 2103 // Add the thumbnail scroller directly in the slider and 2104 // remove the thumbnail scroller container 2105 this.$thumbnails.appendTo( this.$slider ); 2106 this.$thumbnailsContainer.remove(); 2107 2108 // Remove any created padding 2109 this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': '' }); 2110 }, 2111 2112 thumbnailsDefaults: { 2113 2114 // Sets the width of the thumbnail 2115 thumbnailWidth: 100, 2116 2117 // Sets the height of the thumbnail 2118 thumbnailHeight: 80, 2119 2120 // Sets the position of the thumbnail scroller (top, bottom, right, left) 2121 thumbnailsPosition: 'bottom', 2122 2123 // Indicates if a pointer will be displayed for the selected thumbnail 2124 thumbnailPointer: false, 2125 2126 // Called when the thumbnails are updated 2127 thumbnailsUpdate: function() {}, 2128 2129 // Called when a new thumbnail is selected 2130 gotoThumbnail: function() {}, 2131 2132 // Called when the thumbnail scroller has moved 2133 thumbnailsMoveComplete: function() {} 2134 } 2135 }; 2136 2137 var Thumbnail = function( thumbnail, thumbnails, index ) { 2138 2139 // Reference to the thumbnail jQuery element 2140 this.$thumbnail = thumbnail; 2141 2142 // Reference to the thumbnail scroller 2143 this.$thumbnails = thumbnails; 2144 2145 // Reference to the thumbnail's container, which will be 2146 // created dynamically. 2147 this.$thumbnailContainer = null; 2148 2149 // The width and height of the thumbnail 2150 this.width = 0; 2151 this.height = 0; 2152 2153 // Indicates whether the thumbnail's image is loaded 2154 this.isImageLoaded = false; 2155 2156 // Set the index of the slide 2157 this.setIndex( index ); 2158 2159 // Initialize the thumbnail 2160 this._init(); 2161 }; 2162 2163 Thumbnail.prototype = { 2164 2165 _init: function() { 2166 var that = this; 2167 2168 // Mark the thumbnail as initialized 2169 this.$thumbnail.attr( 'data-init', true ); 2170 2171 // Create a container for the thumbnail and add the original thumbnail to this container. 2172 // Having a container will help crop the thumbnail image if it's too large. 2173 this.$thumbnailContainer = $( '<div class="sp-thumbnail-container"></div>' ).appendTo( this.$thumbnails ); 2174 2175 if ( this.$thumbnail.parent( 'a' ).length !== 0 ) { 2176 this.$thumbnail.parent( 'a' ).appendTo( this.$thumbnailContainer ); 2177 } else { 2178 this.$thumbnail.appendTo( this.$thumbnailContainer ); 2179 } 2180 2181 // When the thumbnail container is clicked, fire an event 2182 this.$thumbnailContainer.on( 'click.' + NS, function() { 2183 that.trigger({ type: 'thumbnailClick.' + NS, index: that.index }); 2184 }); 2185 }, 2186 2187 // Set the width and height of the thumbnail 2188 setSize: function( width, height ) { 2189 this.width = width; 2190 this.height = height; 2191 2192 // Apply the width and height to the thumbnail's container 2193 this.$thumbnailContainer.css({ 'width': this.width, 'height': this.height }); 2194 2195 // If there is an image, resize it to fit the thumbnail container 2196 if ( this.$thumbnail.is( 'img' ) && typeof this.$thumbnail.attr( 'data-src' ) === 'undefined' ) { 2197 this.resizeImage(); 2198 } 2199 }, 2200 2201 // Return the width and height of the thumbnail 2202 getSize: function() { 2203 return { 2204 width: this.$thumbnailContainer.outerWidth( true ), 2205 height: this.$thumbnailContainer.outerHeight( true ) 2206 }; 2207 }, 2208 2209 // Return the top, bottom, left and right position of the thumbnail 2210 getPosition: function() { 2211 return { 2212 left: this.$thumbnailContainer.position().left + parseInt( this.$thumbnailContainer.css( 'marginLeft' ) , 10 ), 2213 right: this.$thumbnailContainer.position().left + parseInt( this.$thumbnailContainer.css( 'marginLeft' ) , 10 ) + this.$thumbnailContainer.outerWidth(), 2214 top: this.$thumbnailContainer.position().top + parseInt( this.$thumbnailContainer.css( 'marginTop' ) , 10 ), 2215 bottom: this.$thumbnailContainer.position().top + parseInt( this.$thumbnailContainer.css( 'marginTop' ) , 10 ) + this.$thumbnailContainer.outerHeight() 2216 }; 2217 }, 2218 2219 // Set the index of the thumbnail 2220 setIndex: function( index ) { 2221 this.index = index; 2222 this.$thumbnail.attr( 'data-index', this.index ); 2223 }, 2224 2225 // Resize the thumbnail's image 2226 resizeImage: function() { 2227 var that = this; 2228 2229 // If the image is not loaded yet, load it 2230 if ( this.isImageLoaded === false ) { 2231 SliderProUtils.checkImagesComplete( this.$thumbnailContainer , function() { 2232 that.isImageLoaded = true; 2233 that.resizeImage(); 2234 }); 2235 2236 return; 2237 } 2238 2239 // Get the reference to the thumbnail image again because it was replaced by 2240 // another img element during the loading process
2241 this.$thumbnail = this.$thumbnailContainer.find( '.sp-thumbnail' ); 2242 2243 // Calculate whether the image should stretch horizontally or vertically 2244 var imageWidth = this.$thumbnail.width(), 2245 imageHeight = this.$thumbnail.height(); 2246 2247 if ( imageWidth / imageHeight <= this.width / this.height ) { 2248 this.$thumbnail.css({ width: '100%', height: 'auto' }); 2249 } else { 2250 this.$thumbnail.css({ width: 'auto', height: '100%' }); 2251 } 2252 2253 this.$thumbnail.css({ 'marginLeft': ( this.$thumbnailContainer.width() - this.$thumbnail.width() ) * 0.5, 'marginTop': ( this.$thumbnailContainer.height() - this.$thumbnail.height() ) * 0.5 }); 2254 }, 2255 2256 // Destroy the thumbnail 2257 destroy: function() { 2258 this.$thumbnailContainer.off( 'click.' + NS ); 2259 2260 // Remove added attributes 2261 this.$thumbnail.removeAttr( 'data-init' ); 2262 this.$thumbnail.removeAttr( 'data-index' ); 2263 2264 // Remove the thumbnail's container and add the thumbnail 2265 // back to the thumbnail scroller container 2266 if ( this.$thumbnail.parent( 'a' ).length !== 0 ) { 2267 this.$thumbnail.parent( 'a' ).insertBefore( this.$thumbnailContainer ); 2268 } else { 2269 this.$thumbnail.insertBefore( this.$thumbnailContainer ); 2270 } 2271 2272 this.$thumbnailContainer.remove(); 2273 }, 2274 2275 // Attach an event handler to the slide 2276 on: function( type, callback ) { 2277 return this.$thumbnailContainer.on( type, callback ); 2278 }, 2279 2280 // Detach an event handler to the slide 2281 off: function( type ) { 2282 return this.$thumbnailContainer.off( type ); 2283 }, 2284 2285 // Trigger an event on the slide 2286 trigger: function( data ) { 2287 return this.$thumbnailContainer.triggerHandler( data ); 2288 } 2289 }; 2290 2291 $.SliderPro.addModule( 'Thumbnails', Thumbnails ); 2292 2293})( window, jQuery ); 2294 2295// ConditionalImages module for Slider Pro. 2296// 2297// Adds the possibility to specify multiple sources for each image and 2298// load the image that's the most appropriate for the size of the slider. 2299// For example, instead of loading a large image even if the slider will be small 2300// you can specify a smaller image that will be loaded instead. 2301;(function( window, $ ) { 2302 2303 "use strict"; 2304 2305 var NS = 'ConditionalImages.' + $.SliderPro.namespace; 2306 2307 var ConditionalImages = { 2308 2309 // Reference to the previous size 2310 previousImageSize: null, 2311 2312 // Reference to the current size 2313 currentImageSize: null, 2314 2315 // Indicates if the current display supports high PPI 2316 isRetinaScreen: false, 2317 2318 initConditionalImages: function() { 2319 this.currentImageSize = this.previousImageSize = 'default'; 2320 this.isRetinaScreen = ( typeof this._isRetina !== 'undefined' ) && ( this._isRetina() === true ); 2321 2322 this.on( 'update.' + NS, $.proxy( this._conditionalImagesOnUpdate, this ) ); 2323 this.on( 'sliderResize.' + NS, $.proxy( this._conditionalImagesOnResize, this ) ); 2324 }, 2325 2326 // Loop through all the existing images and specify the original path of the image 2327 // inside the 'data-default' attribute. 2328 _conditionalImagesOnUpdate: function() { 2329 $.each( this.slides, function( index, element ) { 2330 var $slide = element.$slide; 2331 2332 $slide.find( 'img:not([ data-default ])' ).each(function() { 2333 var $image = $( this ); 2334 2335 if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) { 2336 $image.attr( 'data-default', $image.attr( 'data-src' ) ); 2337 } else { 2338 $image.attr( 'data-default', $image.attr( 'src' ) ); 2339 } 2340 }); 2341 }); 2342 }, 2343 2344 // When the window resizes, identify the applyable image size based on the current size of the slider 2345 // and apply it to all images that have a version of the image specified for this size. 2346 _conditionalImagesOnResize: function() { 2347 if ( this.slideWidth <= this.settings.smallSize ) { 2348 this.currentImageSize = 'small'; 2349 } else if ( this.slideWidth <= this.settings.mediumSize ) { 2350 this.currentImageSize = 'medium'; 2351 } else if ( this.slideWidth <= this.settings.largeSize ) { 2352 this.currentImageSize = 'large'; 2353 } else { 2354 this.currentImageSize = 'default'; 2355 } 2356 2357 if ( this.previousImageSize !== this.currentImageSize ) { 2358 var that = this; 2359 2360 $.each( this.slides, function( index, element ) { 2361 var $slide = element.$slide; 2362
2363 $slide.find( 'img' ).each(function() { 2364 var $image = $( this ), 2365 imageSource = ''; 2366 2367 // Check if the current display supports high PPI and if a retina version of the current size was specified 2368 if ( that.isRetinaScreen === true && typeof $image.attr( 'data-retina' + that.currentImageSize ) !== 'undefined' ) { 2369 imageSource = $image.attr( 'data-retina' + that.currentImageSize ); 2370 2371 // If the retina image was not loaded yet, replace the default image source with the one 2372 // that corresponds to the current slider size 2373 if ( typeof $image.attr( 'data-retina' ) !== 'undefined' && $image.attr( 'data-retina' ) !== imageSource ) { 2374 $image.attr( 'data-retina', imageSource ); 2375 } 2376 } else if ( ( that.isRetinaScreen === false || that.isRetinaScreen === true && typeof $image.attr( 'data-retina' ) === 'undefined' ) && typeof $image.attr( 'data-' + that.currentImageSize ) !== 'undefined' ) { 2377 imageSource = $image.attr( 'data-' + that.currentImageSize ); 2378 2379 // If the image is set to lazy load, replace the image source with the one 2380 // that corresponds to the current slider size 2381 if ( typeof $image.attr( 'data-src' ) !== 'undefined' && $image.attr( 'data-src' ) !== imageSource ) { 2382 $image.attr( 'data-src', imageSource ); 2383 } 2384 } 2385 2386 // If a new image was found 2387 if ( imageSource !== '' ) { 2388 2389 // The existence of the 'data-src' attribute indicates that the image 2390 // will be lazy loaded, so don't load the new image yet 2391 if ( typeof $image.attr( 'data-src' ) === 'undefined' && $image.attr( 'src' ) !== imageSource ) { 2392 that._loadConditionalImage( $image, imageSource, function( newImage ) { 2393 if ( newImage.hasClass( 'sp-image' ) ) { 2394 element.$mainImage = newImage; 2395 element.resizeMainImage( true ); 2396 } 2397 }); 2398 } 2399 } 2400 }); 2401 }); 2402 2403 this.previousImageSize = this.currentImageSize; 2404 } 2405 }, 2406 2407 // Replace the target image with a new image 2408 _loadConditionalImage: function( image, source, callback ) { 2409 2410 // Create a new image element 2411 var newImage = $( new Image() ); 2412 2413 // Copy the class(es) and inline style 2414 newImage.attr( 'class', image.attr( 'class' ) ); 2415 newImage.attr( 'style', image.attr( 'style' ) ); 2416 2417 // Copy the data attributes 2418 $.each( image.data(), function( name, value ) { 2419 newImage.attr( 'data-' + name, value ); 2420 }); 2421 2422 // Copy the width and height attributes if they exist 2423 if ( typeof image.attr( 'width' ) !== 'undefined') { 2424 newImage.attr( 'width', image.attr( 'width' ) ); 2425 } 2426 2427 if ( typeof image.attr( 'height' ) !== 'undefined') { 2428 newImage.attr( 'height', image.attr( 'height' ) ); 2429 } 2430 2431 if ( typeof image.attr( 'alt' ) !== 'undefined' ) { 2432 newImage.attr( 'alt', image.attr( 'alt' ) ); 2433 } 2434 2435 if ( typeof image.attr( 'title' ) !== 'undefined' ) { 2436 newImage.attr( 'title', image.attr( 'title' ) ); 2437 } 2438 2439 newImage.attr( 'src', source ); 2440 2441 // Add the new image in the same container and remove the older image 2442 newImage.insertAfter( image ); 2443 image.remove(); 2444 image = null; 2445 2446 if ( typeof callback === 'function' ) { 2447 callback( newImage ); 2448 } 2449 }, 2450 2451 // Destroy the module 2452 destroyConditionalImages: function() { 2453 this.off( 'update.' + NS ); 2454 this.off( 'sliderResize.' + NS ); 2455 }, 2456 2457 conditionalImagesDefaults: { 2458 2459 // If the slider size is below this size, the small version of the images will be used 2460 smallSize: 480, 2461 2462 // If the slider size is below this size, the small version of the images will be used 2463 mediumSize: 768, 2464 2465 // If the slider size is below this size, the small version of the images will be used 2466 largeSize: 1024 2467 } 2468 }; 2469 2470 $.SliderPro.addModule( 'ConditionalImages', ConditionalImages ); 2471 2472})( window, jQuery ); 2473 2474// Retina module for Slider Pro. 2475// 2476// Adds the possibility to load a different image when the slider is 2477// viewed on a retina screen. 2478;(function( window, $ ) { 2479 2480 "use strict"; 2481 2482 var NS = 'Retina.' + $.SliderPro.namespace; 2483 2484 var Retina = { 2485 2486 initRetina: function() { 2487 var that = this; 2488 2489 // Return if it's not a retina screen 2490 if ( this._isRetina() === false ) { 2491 return; 2492 } 2493 2494 this.on( 'sliderResize.' + NS, $.proxy( this._checkRetinaImages, this ) ); 2495
2496 if ( this.$slider.find( '.sp-thumbnail' ).length !== 0 ) { 2497 this.on( 'update.Thumbnails.' + NS, $.proxy( this._checkRetinaThumbnailImages, this ) ); 2498 } 2499 }, 2500 2501 // Checks if the current display supports high PPI 2502 _isRetina: function() { 2503 if ( window.devicePixelRatio >= 2 ) { 2504 return true; 2505 } 2506 2507 if ( window.matchMedia && ( window.matchMedia( "(-webkit-min-device-pixel-ratio: 2),(min-resolution: 2dppx)" ).matches ) ) { 2508 return true; 2509 } 2510 2511 return false; 2512 }, 2513 2514 // Loop through the slides and replace the images with their retina version 2515 _checkRetinaImages: function() { 2516 var that = this; 2517 2518 $.each( this.slides, function( index, element ) { 2519 var $slide = element.$slide; 2520 2521 if ( typeof $slide.attr( 'data-retina-loaded' ) === 'undefined' ) { 2522 $slide.attr( 'data-retina-loaded', true ); 2523 2524 $slide.find( 'img[data-retina]' ).each(function() { 2525 var $image = $( this ); 2526 2527 if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) { 2528 $image.attr( 'data-src', $image.attr( 'data-retina' ) ); 2529 } else { 2530 that._loadRetinaImage( $image, function( newImage ) { 2531 if ( newImage.hasClass( 'sp-image' ) ) { 2532 element.$mainImage = newImage; 2533 element.resizeMainImage( true ); 2534 } 2535 }); 2536 } 2537 }); 2538 } 2539 }); 2540 }, 2541 2542 // Loop through the thumbnails and replace the images with their retina version 2543 _checkRetinaThumbnailImages: function() { 2544 var that = this; 2545 2546 $.each( this.thumbnails, function( index, element ) { 2547 var $thumbnail = element.$thumbnailContainer; 2548 2549 if ( typeof $thumbnail.attr( 'data-retina-loaded' ) === 'undefined' ) { 2550 $thumbnail.attr( 'data-retina-loaded', true ); 2551 2552 $thumbnail.find( 'img[data-retina]' ).each(function() { 2553 var $image = $( this ); 2554 2555 if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) { 2556 $image.attr( 'data-src', $image.attr( 'data-retina' ) ); 2557 } else { 2558 that._loadRetinaImage( $image, function( newImage ) { 2559 if ( newImage.hasClass( 'sp-thumbnail' ) ) { 2560 element.resizeImage(); 2561 } 2562 }); 2563 } 2564 }); 2565 } 2566 }); 2567 }, 2568 2569 // Load the retina image 2570 _loadRetinaImage: function( image, callback ) { 2571 var retinaFound = false, 2572 newImagePath = ''; 2573 2574 // Check if there is a retina image specified 2575 if ( typeof image.attr( 'data-retina' ) !== 'undefined' ) { 2576 retinaFound = true; 2577 2578 newImagePath = image.attr( 'data-retina' ); 2579 } 2580 2581 // Check if there is a lazy loaded, non-retina, image specified 2582 if ( typeof image.attr( 'data-src' ) !== 'undefined' ) { 2583 if ( retinaFound === false ) { 2584 newImagePath = image.attr( 'data-src') ; 2585 } 2586 2587 image.removeAttr('data-src'); 2588 } 2589 2590 // Return if there isn't a retina or lazy loaded image 2591 if ( newImagePath === '' ) { 2592 return; 2593 } 2594 2595 // Create a new image element 2596 var newImage = $( new Image() ); 2597 2598 // Copy the class(es) and inline style 2599 newImage.attr( 'class', image.attr('class') ); 2600 newImage.attr( 'style', image.attr('style') ); 2601 2602 // Copy the data attributes 2603 $.each( image.data(), function( name, value ) { 2604 newImage.attr( 'data-' + name, value ); 2605 }); 2606 2607 // Copy the width and height attributes if they exist 2608 if ( typeof image.attr( 'width' ) !== 'undefined' ) { 2609 newImage.attr( 'width', image.attr( 'width' ) ); 2610 } 2611 2612 if ( typeof image.attr( 'height' ) !== 'undefined' ) { 2613 newImage.attr( 'height', image.attr( 'height' ) ); 2614 } 2615 2616 if ( typeof image.attr( 'alt' ) !== 'undefined' ) { 2617 newImage.attr( 'alt', image.attr( 'alt' ) ); 2618 } 2619 2620 if ( typeof image.attr( 'title' ) !== 'undefined' ) { 2621 newImage.attr( 'title', image.attr( 'title' ) ); 2622 } 2623 2624 // Add the new image in the same container and remove the older image 2625 newImage.insertAfter( image ); 2626 image.remove(); 2627 image = null; 2628 2629 // Assign the source of the image 2630 newImage.attr( 'src', newImagePath ); 2631 2632 if ( typeof callback === 'function' ) { 2633 callback( newImage ); 2634 } 2635 }, 2636 2637 // Destroy the module 2638 destroyRetina: function() { 2639 this.off( 'update.' + NS ); 2640 this.off( 'update.Thumbnails.' + NS ); 2641 } 2642 }; 2643 2644 $.SliderPro.addModule( 'Retina', Retina ); 2645 2646})( window, jQuery ); 2647 2648// Lazy Loading module for Slider Pro. 2649// 2650// Adds the possibility to delay the loading of the images until the slides/thumbnails 2651// that contain them become visible. This technique improves the initial loading 2652// performance. 2653;(function( window, $ ) { 2654 2655 "use strict"; 2656 2657 var NS = 'LazyLoading.' + $.SliderPro.namespace; 2658 2659 var LazyLoading = { 2660 2661 allowLazyLoadingCheck: true, 2662 2663 initLazyLoading: function() { 2664 var that = this; 2665 2666 // The 'resize' event is fired after every update, so it's possible to use it for checking 2667 // if the update made new slides become visible 2668 // 2669 // Also, resizing the slider might make new slides or thumbnails visible 2670 this.on( 'sliderResize.' + NS, $.proxy( this._lazyLoadingOnResize, this ) ); 2671 2672 // Check visible images when a new slide is selected
2673 this.on( 'gotoSlide.' + NS, $.proxy( this._checkAndLoadVisibleImages, this ) ); 2674 2675 // Check visible thumbnail images when the thumbnails are updated because new thumbnail 2676 // might have been added or the settings might have been changed so that more thumbnail 2677 // images become visible 2678 // 2679 // Also, check visible thumbnail images after the thumbnails have moved because new thumbnails might 2680 // have become visible 2681 this.on( 'thumbnailsUpdate.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, $.proxy( this._checkAndLoadVisibleThumbnailImages, this ) ); 2682 }, 2683 2684 _lazyLoadingOnResize: function() { 2685 var that = this; 2686 2687 if ( this.allowLazyLoadingCheck === false ) { 2688 return; 2689 } 2690 2691 this.allowLazyLoadingCheck = false; 2692 2693 this._checkAndLoadVisibleImages(); 2694 2695 if ( this.$slider.find( '.sp-thumbnail' ).length !== 0 ) { 2696 this._checkAndLoadVisibleThumbnailImages(); 2697 } 2698 2699 // Use a timer to deffer the loading of images in order to prevent too many 2700 // checking attempts 2701 setTimeout(function() { 2702 that.allowLazyLoadingCheck = true; 2703 }, 500 ); 2704 }, 2705 2706 // Check visible slides and load their images 2707 _checkAndLoadVisibleImages: function() { 2708 if ( this.$slider.find( '.sp-slide:not([ data-loaded ])' ).length === 0 ) { 2709 return; 2710 } 2711 2712 var that = this, 2713 2714 // Use either the middle position or the index of the selected slide as a reference, depending on 2715 // whether the slider is loopable 2716 referencePosition = this.settings.loop === true ? this.middleSlidePosition : this.selectedSlideIndex, 2717 2718 // Calculate how many slides are visible at the sides of the selected slide 2719 visibleOnSides = Math.ceil( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10) - this.averageSlideSize ) / 2 / this.averageSlideSize ), 2720 2721 // Calculate the indexes of the first and last slide that will be checked 2722 from = this.settings.centerSelectedSlide === true ? Math.max( referencePosition - visibleOnSides - 1, 0 ) : Math.max( referencePosition - 1, 0 ), 2723 to = this.settings.centerSelectedSlide === true ? Math.min( referencePosition + visibleOnSides + 1, this.getTotalSlides() - 1 ) : Math.min( referencePosition + visibleOnSides * 2 + 1, this.getTotalSlides() - 1 ), 2724 2725 // Get all the slides that need to be checked 2726 slidesToCheck = this.slidesOrder.slice( from, to + 1 ); 2727 2728 // Loop through the selected slides and if the slide is not marked as having 2729 // been loaded yet, loop through its images and load them. 2730 $.each( slidesToCheck, function( index, element ) { 2731 var slide = that.slides[ element ], 2732 $slide = slide.$slide; 2733 2734 if ( typeof $slide.attr( 'data-loaded' ) === 'undefined' ) { 2735 $slide.attr( 'data-loaded', true ); 2736 2737 $slide.find( 'img[ data-src ]' ).each(function() { 2738 var image = $( this ); 2739 that._loadImage( image, function( newImage ) { 2740 if ( newImage.hasClass( 'sp-image' ) ) { 2741 slide.$mainImage = newImage; 2742 slide.resizeMainImage( true ); 2743 } 2744 }); 2745 }); 2746 } 2747 }); 2748 }, 2749 2750 // Check visible thumbnails and load their images 2751 _checkAndLoadVisibleThumbnailImages: function() { 2752 if ( this.$slider.find( '.sp-thumbnail-container:not([ data-loaded ])' ).length === 0 ) { 2753 return; 2754 } 2755 2756 var that = this, 2757 thumbnailSize = this.thumbnailsSize / this.thumbnails.length, 2758 2759 // Calculate the indexes of the first and last thumbnail that will be checked 2760 from = Math.floor( Math.abs( this.thumbnailsPosition / thumbnailSize ) ), 2761 to = Math.floor( ( - this.thumbnailsPosition + this.thumbnailsContainerSize ) / thumbnailSize ), 2762 2763 // Get all the thumbnails that need to be checked 2764 thumbnailsToCheck = this.thumbnails.slice( from, to + 1 ); 2765 2766 // Loop through the selected thumbnails and if the thumbnail is not marked as having 2767 // been loaded yet, load its image. 2768 $.each( thumbnailsToCheck, function( index, element ) { 2769 var $thumbnailContainer = element.$thumbnailContainer; 2770 2771 if ( typeof $thumbnailContainer.attr( 'data-loaded' ) === 'undefined' ) { 2772 $thumbnailContainer.attr( 'data-loaded', true ); 2773
2774 $thumbnailContainer.find( 'img[ data-src ]' ).each(function() { 2775 var image = $( this ); 2776 2777 that._loadImage( image, function() { 2778 element.resizeImage(); 2779 }); 2780 }); 2781 } 2782 }); 2783 }, 2784 2785 // Load an image 2786 _loadImage: function( image, callback ) { 2787 // Create a new image element 2788 var newImage = $( new Image() ); 2789 2790 // Copy the class(es) and inline style 2791 newImage.attr( 'class', image.attr( 'class' ) ); 2792 newImage.attr( 'style', image.attr( 'style' ) ); 2793 2794 // Copy the data attributes 2795 $.each( image.data(), function( name, value ) { 2796 newImage.attr( 'data-' + name, value ); 2797 }); 2798 2799 // Copy the width and height attributes if they exist 2800 if ( typeof image.attr( 'width' ) !== 'undefined') { 2801 newImage.attr( 'width', image.attr( 'width' ) ); 2802 } 2803 2804 if ( typeof image.attr( 'height' ) !== 'undefined') { 2805 newImage.attr( 'height', image.attr( 'height' ) ); 2806 } 2807 2808 if ( typeof image.attr( 'alt' ) !== 'undefined' ) { 2809 newImage.attr( 'alt', image.attr( 'alt' ) ); 2810 } 2811 2812 if ( typeof image.attr( 'title' ) !== 'undefined' ) { 2813 newImage.attr( 'title', image.attr( 'title' ) ); 2814 } 2815 2816 // Assign the source of the image 2817 newImage.attr( 'src', image.attr( 'data-src' ) ); 2818 newImage.removeAttr( 'data-src' ); 2819 2820 // Add the new image in the same container and remove the older image 2821 newImage.insertAfter( image ); 2822 image.remove(); 2823 image = null; 2824 2825 if ( typeof callback === 'function' ) { 2826 callback( newImage ); 2827 } 2828 }, 2829 2830 // Destroy the module 2831 destroyLazyLoading: function() { 2832 this.off( 'update.' + NS ); 2833 this.off( 'gotoSlide.' + NS ); 2834 this.off( 'sliderResize.' + NS ); 2835 this.off( 'thumbnailsUpdate.' + NS ); 2836 this.off( 'thumbnailsMoveComplete.' + NS ); 2837 } 2838 }; 2839 2840 $.SliderPro.addModule( 'LazyLoading', LazyLoading ); 2841 2842})( window, jQuery ); 2843 2844// Layers module for Slider Pro. 2845// 2846// Adds support for animated and static layers. The layers can contain any content, 2847// from simple text for video elements. 2848;(function( window, $ ) { 2849 2850 "use strict"; 2851 2852 var NS = 'Layers.' + $.SliderPro.namespace; 2853 2854 var Layers = { 2855 2856 // Reference to the original 'gotoSlide' method 2857 layersGotoSlideReference: null, 2858 2859 // Reference to the timer that will delay the overriding 2860 // of the 'gotoSlide' method 2861 waitForLayersTimer: null, 2862 2863 initLayers: function() { 2864 this.on( 'update.' + NS, $.proxy( this._layersOnUpdate, this ) ); 2865 this.on( 'sliderResize.' + NS, $.proxy( this._layersOnResize, this ) );
2866 this.on( 'gotoSlide.' + NS, $.proxy( this._layersOnGotoSlide, this ) ); 2867 }, 2868 2869 // Loop through the slides and initialize all layers 2870 _layersOnUpdate: function( event ) { 2871 var that = this; 2872 2873 $.each( this.slides, function( index, element ) { 2874 var $slide = element.$slide; 2875 2876 // Initialize the layers 2877 this.$slide.find( '.sp-layer:not([ data-layer-init ])' ).each(function() { 2878 var layer = new Layer( $( this ) ); 2879 2880 // Add the 'layers' array to the slide objects (instance of SliderProSlide) 2881 if ( typeof element.layers === 'undefined' ) { 2882 element.layers = []; 2883 } 2884 2885 element.layers.push( layer ); 2886 2887 if ( $( this ).hasClass( 'sp-static' ) === false ) { 2888 2889 // Add the 'animatedLayers' array to the slide objects (instance of SliderProSlide) 2890 if ( typeof element.animatedLayers === 'undefined' ) { 2891 element.animatedLayers = []; 2892 } 2893 2894 element.animatedLayers.push( layer ); 2895 } 2896 }); 2897 }); 2898 2899 // If the 'waitForLayers' option is enabled, the slider will not move to another slide 2900 // until all the layers from the previous slide will be hidden. To achieve this, 2901 // replace the current 'gotoSlide' function with another function that will include the 2902 // required functionality. 2903 // 2904 // Since the 'gotoSlide' method might be overridden by other modules as well, delay this 2905 // override to make sure it's the last override. 2906 if ( this.settings.waitForLayers === true ) { 2907 clearTimeout( this.waitForLayersTimer ); 2908 2909 this.waitForLayersTimer = setTimeout(function() { 2910 that.layersGotoSlideReference = that.gotoSlide; 2911 that.gotoSlide = that._layersGotoSlide; 2912 }, 1 ); 2913 } 2914 2915 // Show the layers for the initial slide 2916 // Delay the call in order to make sure the layers 2917 // are scaled properly before displaying them 2918 setTimeout(function() { 2919 that.showLayers( that.selectedSlideIndex ); 2920 }, 1); 2921 }, 2922 2923 // When the slider resizes, try to scale down the layers proportionally. The automatic scaling 2924 // will make use of an option, 'autoScaleReference', by comparing the current width of the slider 2925 // with the reference width. So, if the reference width is 1000 pixels and the current width is 2926 // 500 pixels, it means that the layers will be scaled down to 50% of their size. 2927 _layersOnResize: function() { 2928 var that = this, 2929 autoScaleReference, 2930 useAutoScale = this.settings.autoScaleLayers, 2931 scaleRatio; 2932 2933 if ( this.settings.autoScaleLayers === false ) { 2934 return; 2935 } 2936 2937 // If there isn't a reference for how the layers should scale down automatically, use the 'width' 2938 // option as a reference, unless the width was set to a percentage. If there isn't a set reference and 2939 // the width was set to a percentage, auto scaling will not be used because it's not possible to 2940 // calculate how much should the layers scale. 2941 if ( this.settings.autoScaleReference === -1 ) { 2942 if ( typeof this.settings.width === 'string' && this.settings.width.indexOf( '%' ) !== -1 ) { 2943 useAutoScale = false; 2944 } else { 2945 autoScaleReference = parseInt( this.settings.width, 10 ); 2946 } 2947 } else { 2948 autoScaleReference = this.settings.autoScaleReference; 2949 } 2950 2951 if ( useAutoScale === true && this.slideWidth < autoScaleReference ) { 2952 scaleRatio = that.slideWidth / autoScaleReference; 2953 } else { 2954 scaleRatio = 1; 2955 } 2956 2957 $.each( this.slides, function( index, slide ) { 2958 if ( typeof slide.layers !== 'undefined' ) { 2959 $.each( slide.layers, function( index, layer ) { 2960 layer.scale( scaleRatio ); 2961 }); 2962 } 2963 }); 2964 }, 2965 2966 // Replace the 'gotoSlide' method with this one, which makes it possible to 2967 // change the slide only after the layers from the previous slide are hidden. 2968 _layersGotoSlide: function( index ) { 2969 var that = this, 2970 animatedLayers = this.slides[ this.selectedSlideIndex ].animatedLayers; 2971 2972 // If the slider is dragged, don't wait for the layer to hide 2973 if ( this.$slider.hasClass( 'sp-swiping' ) || typeof animatedLayers === 'undefined' || animatedLayers.length === 0 ) { 2974 this.layersGotoSlideReference( index ); 2975 } else { 2976 this.on( 'hideLayersComplete.' + NS, function() { 2977 that.off( 'hideLayersComplete.' + NS ); 2978 that.layersGotoSlideReference( index ); 2979 }); 2980 2981 this.hideLayers( this.selectedSlideIndex ); 2982 } 2983 }, 2984 2985 // When a new slide is selected, hide the layers from the previous slide 2986 // and show the layers from the current slide. 2987 _layersOnGotoSlide: function( event ) { 2988 if ( this.previousSlideIndex !== this.selectedSlideIndex ) { 2989 this.hideLayers( this.previousSlideIndex ); 2990 } 2991 2992 this.showLayers( this.selectedSlideIndex ); 2993 }, 2994 2995 // Show the animated layers from the slide at the specified index, 2996 // and fire an event when all the layers from the slide become visible. 2997 showLayers: function( index ) { 2998 var that = this, 2999 animatedLayers = this.slides[ index ].animatedLayers, 3000 layerCounter = 0; 3001 3002 if ( typeof animatedLayers === 'undefined' ) { 3003 return; 3004 } 3005 3006 $.each( animatedLayers, function( index, element ) { 3007 3008 // If the layer is already visible, increment the counter directly, else wait 3009 // for the layer's showing animation to complete. 3010 if ( element.isVisible() === true ) { 3011 layerCounter++; 3012 3013 if ( layerCounter === animatedLayers.length ) { 3014 that.trigger({ type: 'showLayersComplete', index: index }); 3015 if ( $.isFunction( that.settings.showLayersComplete ) ) { 3016 that.settings.showLayersComplete.call( that, { type: 'showLayersComplete', index: index }); 3017 } 3018 } 3019 } else { 3020 element.show(function() { 3021 layerCounter++; 3022 3023 if ( layerCounter === animatedLayers.length ) { 3024 that.trigger({ type: 'showLayersComplete', index: index }); 3025 if ( $.isFunction( that.settings.showLayersComplete ) ) { 3026 that.settings.showLayersComplete.call( that, { type: 'showLayersComplete', index: index }); 3027 } 3028 } 3029 }); 3030 } 3031 }); 3032 }, 3033 3034 // Hide the animated layers from the slide at the specified index, 3035 // and fire an event when all the layers from the slide become invisible. 3036 hideLayers: function( index ) { 3037 var that = this, 3038 animatedLayers = this.slides[ index ].animatedLayers, 3039 layerCounter = 0; 3040 3041 if ( typeof animatedLayers === 'undefined' ) { 3042 return; 3043 } 3044 3045 $.each( animatedLayers, function( index, element ) { 3046 3047 // If the layer is already invisible, increment the counter directly, else wait 3048 // for the layer's hiding animation to complete. 3049 if ( element.isVisible() === false ) { 3050 layerCounter++; 3051 3052 if ( layerCounter === animatedLayers.length ) { 3053 that.trigger({ type: 'hideLayersComplete', index: index }); 3054 if ( $.isFunction( that.settings.hideLayersComplete ) ) { 3055 that.settings.hideLayersComplete.call( that, { type: 'hideLayersComplete', index: index }); 3056 } 3057 } 3058 } else { 3059 element.hide(function() { 3060 layerCounter++; 3061 3062 if ( layerCounter === animatedLayers.length ) { 3063 that.trigger({ type: 'hideLayersComplete', index: index }); 3064 if ( $.isFunction( that.settings.hideLayersComplete ) ) { 3065 that.settings.hideLayersComplete.call( that, { type: 'hideLayersComplete', index: index }); 3066 } 3067 } 3068 }); 3069 } 3070 }); 3071 }, 3072 3073 // Destroy the module 3074 destroyLayers: function() { 3075 this.off( 'update.' + NS ); 3076 this.off( 'sliderResize.' + NS );
3077 this.off( 'gotoSlide.' + NS ); 3078 this.off( 'hideLayersComplete.' + NS ); 3079 }, 3080 3081 layersDefaults: { 3082 3083 // Indicates whether the slider will wait for the layers to disappear before 3084 // going to a new slide 3085 waitForLayers: false, 3086 3087 // Indicates whether the layers will be scaled automatically 3088 autoScaleLayers: true, 3089 3090 // Sets a reference width which will be compared to the current slider width 3091 // in order to determine how much the layers need to scale down. By default, 3092 // the reference width will be equal to the slide width. However, if the slide width 3093 // is set to a percentage value, then it's necessary to set a specific value for 'autoScaleReference'. 3094 autoScaleReference: -1, 3095 3096 // Called when all animated layers become visible 3097 showLayersComplete: function() {}, 3098 3099 // Called when all animated layers become invisible 3100 hideLayersComplete: function() {} 3101 } 3102 }; 3103 3104 // Override the slide's 'destroy' method in order to destroy the 3105 // layers that where added to the slide as well. 3106 var slideDestroy = window.SliderProSlide.prototype.destroy; 3107 3108 window.SliderProSlide.prototype.destroy = function() { 3109 if ( typeof this.layers !== 'undefined' ) { 3110 $.each( this.layers, function( index, element ) { 3111 element.destroy(); 3112 }); 3113 3114 this.layers.length = 0; 3115 } 3116 3117 if ( typeof this.animatedLayers !== 'undefined' ) { 3118 this.animatedLayers.length = 0; 3119 } 3120 3121 slideDestroy.apply( this ); 3122 }; 3123 3124 var Layer = function( layer ) { 3125 3126 // Reference to the layer jQuery element 3127 this.$layer = layer; 3128 3129 // Indicates whether a layer is currently visible or hidden 3130 this.visible = false; 3131 3132 // Indicates whether the layer was styled 3133 this.styled = false; 3134 3135 // Holds the data attributes added to the layer 3136 this.data = null; 3137 3138 // Indicates the layer's reference point (topLeft, bottomLeft, topRight or bottomRight) 3139 this.position = null; 3140 3141 // Indicates which CSS property (left or right) will be used for positioning the layer 3142 this.horizontalProperty = null; 3143 3144 // Indicates which CSS property (top or bottom) will be used for positioning the layer 3145 this.verticalProperty = null; 3146 3147 // Indicates the value of the horizontal position 3148 this.horizontalPosition = null; 3149 3150 // Indicates the value of the vertical position 3151 this.verticalPosition = null; 3152 3153 // Indicates how much the layers needs to be scaled 3154 this.scaleRatio = 1; 3155 3156 // Indicates the type of supported transition (CSS3 2D, CSS3 3D or JavaScript) 3157 this.supportedAnimation = SliderProUtils.getSupportedAnimation(); 3158 3159 // Indicates the required vendor prefix for CSS (i.e., -webkit, -moz, etc.) 3160 this.vendorPrefix = SliderProUtils.getVendorPrefix(); 3161 3162 // Indicates the name of the CSS transition's complete event (i.e., transitionend, webkitTransitionEnd, etc.) 3163 this.transitionEvent = SliderProUtils.getTransitionEvent(); 3164 3165 // Reference to the timer that will be used to hide/show the layers 3166 this.delayTimer = null; 3167 3168 // Reference to the timer that will be used to hide the layers automatically after a given time interval 3169 this.stayTimer = null; 3170 3171 this._init(); 3172 }; 3173 3174 Layer.prototype = { 3175 3176 // Initialize the layers 3177 _init: function() { 3178 this.$layer.attr( 'data-layer-init', true ); 3179 3180 if ( this.$layer.hasClass( 'sp-static' ) ) { 3181 this._setStyle(); 3182 } else { 3183 this.$layer.css({ 'visibility': 'hidden' }); 3184 } 3185 }, 3186 3187 // Set the size and position of the layer 3188 _setStyle: function() { 3189 this.styled = true; 3190 3191 // Get the data attributes specified in HTML 3192 this.data = this.$layer.data(); 3193 3194 if ( typeof this.data.width !== 'undefined' ) { 3195 this.$layer.css( 'width', this.data.width ); 3196 } 3197 3198 if ( typeof this.data.height !== 'undefined' ) { 3199 this.$layer.css( 'height', this.data.height ); 3200 } 3201 3202 if ( typeof this.data.depth !== 'undefined' ) { 3203 this.$layer.css( 'z-index', this.data.depth ); 3204 } 3205 3206 this.position = this.data.position ? ( this.data.position ).toLowerCase() : 'topleft'; 3207 3208 if ( this.position.indexOf( 'right' ) !== -1 ) { 3209 this.horizontalProperty = 'right'; 3210 } else if ( this.position.indexOf( 'left' ) !== -1 ) { 3211 this.horizontalProperty = 'left'; 3212 } else { 3213 this.horizontalProperty = 'center'; 3214 } 3215 3216 if ( this.position.indexOf( 'bottom' ) !== -1 ) { 3217 this.verticalProperty = 'bottom'; 3218 } else if ( this.position.indexOf( 'top' ) !== -1 ) { 3219 this.verticalProperty = 'top'; 3220 } else { 3221 this.verticalProperty = 'center'; 3222 } 3223 3224 this._setPosition(); 3225 3226 this.scale( this.scaleRatio ); 3227 }, 3228 3229 // Set the position of the layer 3230 _setPosition: function() { 3231 var inlineStyle = this.$layer.attr( 'style' ); 3232
3233 this.horizontalPosition = typeof this.data.horizontal !== 'undefined' ? this.data.horizontal : 0; 3234 this.verticalPosition = typeof this.data.vertical !== 'undefined' ? this.data.vertical : 0; 3235 3236 // Set the horizontal position of the layer based on the data set 3237 if ( this.horizontalProperty === 'center' ) { 3238 3239 // prevent content wrapping while setting the width 3240 if ( this.$layer.is( 'img' ) === false && ( typeof inlineStyle === 'undefined' || ( typeof inlineStyle !== 'undefined' && inlineStyle.indexOf( 'width' ) === -1 ) ) ) { 3241 this.$layer.css( 'white-space', 'nowrap' ); 3242 this.$layer.css( 'width', this.$layer.outerWidth( true ) ); 3243 } 3244 3245 this.$layer.css({ 'marginLeft': 'auto', 'marginRight': 'auto', 'left': this.horizontalPosition, 'right': 0 }); 3246 } else { 3247 this.$layer.css( this.horizontalProperty, this.horizontalPosition ); 3248 } 3249 3250 // Set the vertical position of the layer based on the data set 3251 if ( this.verticalProperty === 'center' ) { 3252 3253 // prevent content wrapping while setting the height 3254 if ( this.$layer.is( 'img' ) === false && ( typeof inlineStyle === 'undefined' || ( typeof inlineStyle !== 'undefined' && inlineStyle.indexOf( 'height' ) === -1 ) ) ) { 3255 this.$layer.css( 'white-space', 'nowrap' ); 3256 this.$layer.css( 'height', this.$layer.outerHeight( true ) ); 3257 } 3258 3259 this.$layer.css({ 'marginTop': 'auto', 'marginBottom': 'auto', 'top': this.verticalPosition, 'bottom': 0 }); 3260 } else { 3261 this.$layer.css( this.verticalProperty, this.verticalPosition ); 3262 } 3263 }, 3264 3265 // Scale the layer 3266 scale: function( ratio ) { 3267 3268 // Return if the layer is set to be unscalable 3269 if ( this.$layer.hasClass( 'sp-no-scale' ) ) { 3270 return; 3271 } 3272 3273 // Store the ratio (even if the layer is not ready to be scaled yet) 3274 this.scaleRatio = ratio; 3275 3276 // Return if the layer is not styled yet 3277 if ( this.styled === false ) { 3278 return; 3279 } 3280 3281 var horizontalProperty = this.horizontalProperty === 'center' ? 'left' : this.horizontalProperty, 3282 verticalProperty = this.verticalProperty === 'center' ? 'top' : this.verticalProperty, 3283 css = {}; 3284 3285 // Apply the scaling 3286 css[ this.vendorPrefix + 'transform-origin' ] = this.horizontalProperty + ' ' + this.verticalProperty; 3287 css[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')'; 3288 3289 // If the position is not set to a percentage value, apply the scaling to the position 3290 if ( typeof this.horizontalPosition !== 'string' ) { 3291 css[ horizontalProperty ] = this.horizontalPosition * this.scaleRatio; 3292 } 3293 3294 // If the position is not set to a percentage value, apply the scaling to the position 3295 if ( typeof this.verticalPosition !== 'string' ) { 3296 css[ verticalProperty ] = this.verticalPosition * this.scaleRatio; 3297 } 3298 3299 // If the width or height is set to a percentage value, increase the percentage in order to 3300 // maintain the same layer to slide proportions. This is necessary because otherwise the scaling 3301 // transform would minimize the layers more than intended. 3302 if ( typeof this.data.width === 'string' && this.data.width.indexOf( '%' ) !== -1 ) { 3303 css.width = ( parseInt( this.data.width, 10 ) / this.scaleRatio ).toString() + '%'; 3304 } 3305 3306 if ( typeof this.data.height === 'string' && this.data.height.indexOf( '%' ) !== -1 ) { 3307 css.height = ( parseInt( this.data.height, 10 ) / this.scaleRatio ).toString() + '%'; 3308 } 3309 3310 this.$layer.css( css ); 3311 }, 3312 3313 // Show the layer 3314 show: function( callback ) { 3315 if ( this.visible === true ) { 3316 return; 3317 } 3318 3319 this.visible = true; 3320 3321 // First, style the layer if it's not already styled 3322 if ( this.styled === false ) { 3323 this._setStyle(); 3324 } 3325 3326 var that = this, 3327 offset = typeof this.data.showOffset !== 'undefined' ? this.data.showOffset : 50, 3328 duration = typeof this.data.showDuration !== 'undefined' ? this.data.showDuration / 1000 : 0.4, 3329 delay = typeof this.data.showDelay !== 'undefined' ? this.data.showDelay : 10, 3330 stayDuration = typeof that.data.stayDuration !== 'undefined' ? parseInt( that.data.stayDuration, 10 ) : -1; 3331 3332 // Animate the layers with CSS3 or with JavaScript 3333 if ( this.supportedAnimation === 'javascript' ) { 3334 this.$layer 3335 .stop() 3336 .delay( delay ) 3337 .css({ 'opacity': 0, 'visibility': 'visible' }) 3338 .animate( { 'opacity': 1 }, duration * 1000, function() { 3339 3340 // Hide the layer after a given time interval 3341 if ( stayDuration !== -1 ) { 3342 that.stayTimer = setTimeout(function() { 3343 that.hide(); 3344 that.stayTimer = null; 3345 }, stayDuration ); 3346 } 3347 3348 if ( typeof callback !== 'undefined' ) { 3349 callback(); 3350 } 3351 }); 3352 } else { 3353 var start = { 'opacity': 0, 'visibility': 'visible' }, 3354 target = { 'opacity': 1 }, 3355 transformValues = ''; 3356 3357 start[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')'; 3358 target[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')'; 3359 target[ this.vendorPrefix + 'transition' ] = 'opacity ' + duration + 's'; 3360 3361 if ( typeof this.data.showTransition !== 'undefined' ) { 3362 if ( this.data.showTransition === 'left' ) { 3363 transformValues = offset + 'px, 0'; 3364 } else if ( this.data.showTransition === 'right' ) { 3365 transformValues = '-' + offset + 'px, 0'; 3366 } else if ( this.data.showTransition === 'up' ) { 3367 transformValues = '0, ' + offset + 'px'; 3368 } else if ( this.data.showTransition === 'down') { 3369 transformValues = '0, -' + offset + 'px'; 3370 } 3371 3372 start[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(' + transformValues + ', 0)' : ' translate(' + transformValues + ')'; 3373 target[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(0, 0, 0)' : ' translate(0, 0)'; 3374 target[ this.vendorPrefix + 'transition' ] += ', ' + this.vendorPrefix + 'transform ' + duration + 's'; 3375 } 3376 3377 // Listen when the layer animation is complete 3378 this.$layer.on( this.transitionEvent, function( event ) { 3379 if ( event.target !== event.currentTarget ) { 3380 return; 3381 } 3382 3383 that.$layer 3384 .off( that.transitionEvent ) 3385 .css( that.vendorPrefix + 'transition', '' ); 3386 3387 // Hide the layer after a given time interval 3388 if ( stayDuration !== -1 ) { 3389 that.stayTimer = setTimeout(function() { 3390 that.hide(); 3391 that.stayTimer = null; 3392 }, stayDuration ); 3393 } 3394 3395 if ( typeof callback !== 'undefined' ) { 3396 callback(); 3397 } 3398 }); 3399 3400 this.$layer.css( start ); 3401 3402 this.delayTimer = setTimeout( function() { 3403 that.$layer.css( target ); 3404 }, delay ); 3405 } 3406 }, 3407 3408 // Hide the layer 3409 hide: function( callback ) { 3410 if ( this.visible === false ) { 3411 return; 3412 } 3413 3414 var that = this, 3415 offset = typeof this.data.hideOffset !== 'undefined' ? this.data.hideOffset : 50, 3416 duration = typeof this.data.hideDuration !== 'undefined' ? this.data.hideDuration / 1000 : 0.4,
3417 delay = typeof this.data.hideDelay !== 'undefined' ? this.data.hideDelay : 10; 3418 3419 this.visible = false; 3420 3421 // If the layer is hidden before it hides automatically, clear the timer 3422 if ( this.stayTimer !== null ) { 3423 clearTimeout( this.stayTimer ); 3424 } 3425 3426 // Animate the layers with CSS3 or with JavaScript 3427 if ( this.supportedAnimation === 'javascript' ) { 3428 this.$layer 3429 .stop() 3430 .delay( delay ) 3431 .animate({ 'opacity': 0 }, duration * 1000, function() { 3432 $( this ).css( 'visibility', 'hidden' ); 3433 3434 if ( typeof callback !== 'undefined' ) { 3435 callback(); 3436 } 3437 }); 3438 } else { 3439 var transformValues = '', 3440 target = { 'opacity': 0 }; 3441 3442 target[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')'; 3443 target[ this.vendorPrefix + 'transition' ] = 'opacity ' + duration + 's'; 3444 3445 if ( typeof this.data.hideTransition !== 'undefined' ) { 3446 if ( this.data.hideTransition === 'left' ) { 3447 transformValues = '-' + offset + 'px, 0'; 3448 } else if ( this.data.hideTransition === 'right' ) { 3449 transformValues = offset + 'px, 0'; 3450 } else if ( this.data.hideTransition === 'up' ) { 3451 transformValues = '0, -' + offset + 'px'; 3452 } else if ( this.data.hideTransition === 'down' ) { 3453 transformValues = '0, ' + offset + 'px'; 3454 } 3455 3456 target[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(' + transformValues + ', 0)' : ' translate(' + transformValues + ')'; 3457 target[ this.vendorPrefix + 'transition' ] += ', ' + this.vendorPrefix + 'transform ' + duration + 's'; 3458 } 3459 3460 // Listen when the layer animation is complete 3461 this.$layer.on( this.transitionEvent, function( event ) { 3462 if ( event.target !== event.currentTarget ) { 3463 return; 3464 } 3465 3466 that.$layer 3467 .off( that.transitionEvent ) 3468 .css( that.vendorPrefix + 'transition', '' ); 3469 3470 // Hide the layer after transition 3471 if ( that.visible === false ) { 3472 that.$layer.css( 'visibility', 'hidden' ); 3473 } 3474 3475 if ( typeof callback !== 'undefined' ) { 3476 callback(); 3477 } 3478 }); 3479 3480 this.delayTimer = setTimeout( function() { 3481 that.$layer.css( target ); 3482 }, delay ); 3483 } 3484 }, 3485 3486 isVisible: function() { 3487 if ( this.visible === false || this.$layer.is( ':hidden' ) ) { 3488 return false; 3489 } 3490 3491 return true; 3492 }, 3493 3494 // Destroy the layer 3495 destroy: function() { 3496 this.$layer.removeAttr( 'style' ); 3497 this.$layer.removeAttr( 'data-layer-init' ); 3498 clearTimeout( this.delayTimer ); 3499 clearTimeout( this.stayTimer ); 3500 this.delayTimer = null; 3501 this.stayTimer = null; 3502 } 3503 }; 3504 3505 $.SliderPro.addModule( 'Layers', Layers ); 3506 3507})( window, jQuery ); 3508 3509// Fade module for Slider Pro. 3510// 3511// Adds the possibility to navigate through slides using a cross-fade effect. 3512;(function( window, $ ) { 3513 3514 "use strict"; 3515 3516 var NS = 'Fade.' + $.SliderPro.namespace; 3517 3518 var Fade = { 3519 3520 // Reference to the original 'gotoSlide' method 3521 fadeGotoSlideReference: null, 3522 3523 initFade: function() { 3524 this.on( 'update.' + NS, $.proxy( this._fadeOnUpdate, this ) ); 3525 }, 3526 3527 // If fade is enabled, store a reference to the original 'gotoSlide' method 3528 // and then assign a new function to 'gotoSlide'. 3529 _fadeOnUpdate: function() { 3530 if ( this.settings.fade === true ) { 3531 this.fadeGotoSlideReference = this.gotoSlide; 3532 this.gotoSlide = this._fadeGotoSlide; 3533 } 3534 }, 3535 3536 // Will replace the original 'gotoSlide' function by adding a cross-fade effect 3537 // between the previous and the next slide. 3538 _fadeGotoSlide: function( index ) { 3539 if ( index === this.selectedSlideIndex ) { 3540 return; 3541 } 3542 3543 // If the slides are being swiped/dragged, don't use fade, but call the original method instead. 3544 // If not, which means that a new slide was selected through a button, arrows or direct call, then 3545 // use fade. 3546 if ( this.$slider.hasClass( 'sp-swiping' ) ) { 3547 this.fadeGotoSlideReference( index ); 3548 } else { 3549 var that = this, 3550 $nextSlide, 3551 $previousSlide, 3552 newIndex = index; 3553 3554 // Loop through all the slides and overlap the previous and next slide, 3555 // and hide the other slides. 3556 $.each( this.slides, function( index, element ) { 3557 var slideIndex = element.getIndex(), 3558 $slide = element.$slide; 3559 3560 if ( slideIndex === newIndex ) { 3561 $slide.css({ 'opacity': 0, 'left': 0, 'top': 0, 'z-index': 20, visibility: 'visible' }); 3562 $nextSlide = $slide; 3563 }
3563 else if ( slideIndex === that.selectedSlideIndex ) { 3564 $slide.css({ 'opacity': 1, 'left': 0, 'top': 0, 'z-index': 10, visibility: 'visible' }); 3565 $previousSlide = $slide; 3566 } else { 3567 $slide.css({ 'opacity': 1, visibility: 'hidden', 'z-index': '' }); 3568 } 3569 }); 3570 3571 // Set the new indexes for the previous and selected slides 3572 this.previousSlideIndex = this.selectedSlideIndex; 3573 this.selectedSlideIndex = index; 3574 3575 // Re-assign the 'sp-selected' class to the currently selected slide 3576 this.$slides.find( '.sp-selected' ).removeClass( 'sp-selected' ); 3577 this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected' ); 3578 3579 // Rearrange the slides if the slider is loop-able 3580 if ( that.settings.loop === true ) { 3581 that._updateSlidesOrder(); 3582 } 3583 3584 // Move the slides container so that the cross-fading slides (which now have the top and left 3585 // position set to 0) become visible. 3586 this._moveTo( 0, true ); 3587 3588 // Fade in the selected slide 3589 this._fadeSlideTo( $nextSlide, 1, function() { 3590 3591 // This flag will indicate if all the fade transitions are complete, 3592 // in case there are multiple running at the same time, which happens 3593 // when the slides are navigated very quickly 3594 var allTransitionsComplete = true; 3595 3596 // Go through all the slides and check if there is at least one slide 3597 // that is still transitioning. 3598 $.each( that.slides, function( index, element ) { 3599 if ( typeof element.$slide.attr( 'data-transitioning' ) !== 'undefined' ) { 3600 allTransitionsComplete = false; 3601 } 3602 }); 3603 3604 if ( allTransitionsComplete === true ) { 3605 3606 // After all the transitions are complete, make all the slides visible again 3607 $.each( that.slides, function( index, element ) { 3608 var $slide = element.$slide; 3609 $slide.css({ 'visibility': '', 'opacity': '', 'z-index': '' }); 3610 }); 3611 3612 // Reset the position of the slides and slides container 3613 that._resetSlidesPosition(); 3614 } 3615 3616 // Fire the 'gotoSlideComplete' event 3617 that.trigger({ type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex }); 3618 if ( $.isFunction( that.settings.gotoSlideComplete ) ) { 3619 that.settings.gotoSlideComplete.call( that, { type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex } ); 3620 } 3621 }); 3622 3623 // Fade out the previous slide, if indicated, in addition to fading in the next slide 3624 if ( this.settings.fadeOutPreviousSlide === true ) { 3625 this._fadeSlideTo( $previousSlide, 0 ); 3626 } 3627 3628 if ( this.settings.autoHeight === true ) { 3629 this._resizeHeight(); 3630 } 3631 3632 // Fire the 'gotoSlide' event 3633 this.trigger({ type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex }); 3634 if ( $.isFunction( this.settings.gotoSlide ) ) { 3635 this.settings.gotoSlide.call( this, { type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex }); 3636 } 3637 } 3638 }, 3639 3640 // Fade the target slide to the specified opacity (0 or 1) 3641 _fadeSlideTo: function( target, opacity, callback ) { 3642 var that = this; 3643 3644 // apply the attribute only to slides that fade in 3645 if ( opacity === 1 ) { 3646 target.attr( 'data-transitioning', true ); 3647 } 3648 3649 // Use CSS transitions if they are supported. If not, use JavaScript animation. 3650 if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) { 3651 3652 // There needs to be a delay between the moment the opacity is set 3653 // and the moment the transitions starts. 3654 setTimeout(function(){ 3655 var css = { 'opacity': opacity }; 3656 css[ that.vendorPrefix + 'transition' ] = 'opacity ' + that.settings.fadeDuration / 1000 + 's'; 3657 target.css( css ); 3658 }, 100 ); 3659 3660 target.on( this.transitionEvent, function( event ) { 3661 if ( event.target !== event.currentTarget ) { 3662 return; 3663 } 3664 3665 target.off( that.transitionEvent ); 3666 target.css( that.vendorPrefix + 'transition', '' ); 3667 target.removeAttr( 'data-transitioning'); 3668 3669 if ( typeof callback === 'function' ) { 3670 callback(); 3671 } 3672 }); 3673 } else { 3674 target.stop().animate({ 'opacity': opacity }, this.settings.fadeDuration, function() { 3675 target.removeAttr( 'data-transitioning' ); 3676 3677 if ( typeof callback === 'function' ) { 3678 callback(); 3679 } 3680 }); 3681 } 3682 }, 3683 3684 // Destroy the module 3685 destroyFade: function() { 3686 this.off( 'update.' + NS ); 3687
3688 if ( this.fadeGotoSlideReference !== null ) { 3689 this.gotoSlide = this.fadeGotoSlideReference; 3690 } 3691 }, 3692 3693 fadeDefaults: { 3694 3695 // Indicates if fade will be used 3696 fade: false, 3697 3698 // Indicates if the previous slide will be faded out (in addition to the next slide being faded in) 3699 fadeOutPreviousSlide: true, 3700 3701 // Sets the duration of the fade effect 3702 fadeDuration: 500 3703 } 3704 }; 3705 3706 $.SliderPro.addModule( 'Fade', Fade ); 3707 3708})( window, jQuery ); 3709 3710// Touch Swipe module for Slider Pro. 3711// 3712// Adds touch-swipe functionality for slides. 3713;(function( window, $ ) { 3714 3715 "use strict"; 3716 3717 var NS = 'TouchSwipe.' + $.SliderPro.namespace; 3718 3719 var TouchSwipe = { 3720 3721 // The x and y coordinates of the pointer/finger's starting position 3722 touchStartPoint: {x: 0, y: 0}, 3723 3724 // The x and y coordinates of the pointer/finger's end position 3725 touchEndPoint: {x: 0, y: 0}, 3726 3727 // The distance from the starting to the end position on the x and y axis 3728 touchDistance: {x: 0, y: 0}, 3729 3730 // The position of the slides when the touch swipe starts 3731 touchStartPosition: 0, 3732 3733 // Indicates if the slides are being swiped 3734 isTouchMoving: false, 3735 3736 // Stores the names of the events 3737 touchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' }, 3738 3739 // Indicates if scrolling (the page) in the opposite direction of the 3740 // slides' layout is allowed. This is used to block vertical (or horizontal) 3741 // scrolling when the user is scrolling through the slides. 3742 allowOppositeScrolling: true, 3743 3744 // Indicates whether the previous 'start' event was a 'touchstart' or 'mousedown' 3745 previousStartEvent: '', 3746 3747 initTouchSwipe: function() { 3748 var that = this; 3749 3750 // check if touch swipe is enabled 3751 if ( this.settings.touchSwipe === false ) { 3752 return; 3753 } 3754 3755 this.touchSwipeEvents.startEvent = 'touchstart' + '.' + NS + ' mousedown' + '.' + NS; 3756 this.touchSwipeEvents.moveEvent = 'touchmove' + '.' + NS + ' mousemove' + '.' + NS; 3757 this.touchSwipeEvents.endEvent = 'touchend' + '.' + this.uniqueId + '.' + NS + ' mouseup' + '.' + this.uniqueId + '.' + NS; 3758 3759 // Listen for touch swipe/mouse move events 3760 this.$slidesMask.on( this.touchSwipeEvents.startEvent, $.proxy( this._onTouchStart, this ) ); 3761 this.$slidesMask.on( 'dragstart.' + NS, function( event ) { 3762 event.preventDefault(); 3763 }); 3764 3765 // Prevent 'click' events unless there is intention for a 'click' 3766 this.$slidesMask.find( 'a' ).on( 'click.' + NS, function( event ) { 3767 if ( that.$slider.hasClass( 'sp-swiping' ) ) { 3768 event.preventDefault(); 3769 } 3770 }); 3771 3772 // Add the grabbing icon 3773 this.$slidesMask.addClass( 'sp-grab' ); 3774 }, 3775 3776 // Called when the slides starts being dragged 3777 _onTouchStart: function( event ) { 3778 3779 // Return if a 'mousedown' event follows a 'touchstart' event 3780 if ( event.type === 'mousedown' && this.previousStartEvent === 'touchstart' ) { 3781 this.previousStartEvent = event.type; 3782 return; 3783 } 3784 3785 // Assign the new 'start' event 3786 this.previousStartEvent = event.type; 3787 3788 // Disable dragging if the element is set to allow selections 3789 if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) { 3790 return; 3791 } 3792 3793 var that = this, 3794 eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent; 3795 3796 // Get the initial position of the mouse pointer and the initial position 3797 // of the slides' container 3798 this.touchStartPoint.x = eventObject.pageX || eventObject.clientX; 3799 this.touchStartPoint.y = eventObject.pageY || eventObject.clientY; 3800 this.touchStartPosition = this.slidesPosition; 3801 3802 // Clear the previous distance values 3803 this.touchDistance.x = this.touchDistance.y = 0; 3804 3805 // If the slides are being grabbed while they're still animating, stop the 3806 // current movement 3807 if ( this.$slides.hasClass( 'sp-animated' ) ) { 3808 this.isTouchMoving = true; 3809 this._stopMovement(); 3810 this.touchStartPosition = this.slidesPosition; 3811 } 3812 3813 // Listen for move and end events 3814 this.$slidesMask.on( this.touchSwipeEvents.moveEvent, $.proxy( this._onTouchMove, this ) ); 3815 $( document ).on( this.touchSwipeEvents.endEvent, $.proxy( this._onTouchEnd, this ) ); 3816 3817 // Swap grabbing icons 3818 this.$slidesMask.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' ); 3819 }, 3820 3821 // Called during the slides' dragging 3822 _onTouchMove: function( event ) { 3823 var eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent; 3824 3825 // Indicate that the move event is being fired 3826 this.isTouchMoving = true; 3827 3828 // Add 'sp-swiping' class to indicate that the slides are being swiped 3829 if ( this.$slider.hasClass( 'sp-swiping' ) === false ) { 3830 this.$slider.addClass( 'sp-swiping' ); 3831 } 3832 3833 // Get the current position of the mouse pointer 3834 this.touchEndPoint.x = eventObject.pageX || eventObject.clientX; 3835 this.touchEndPoint.y = eventObject.pageY || eventObject.clientY; 3836 3837 // Calculate the distance of the movement on both axis 3838 this.touchDistance.x = this.touchEndPoint.x - this.touchStartPoint.x; 3839 this.touchDistance.y = this.touchEndPoint.y - this.touchStartPoint.y; 3840 3841 // Calculate the distance of the swipe that takes place in the same direction as the orientation of the slides 3842 // and calculate the distance from the opposite direction. 3843 // 3844 // For a swipe to be valid there should more distance in the same direction as the orientation of the slides. 3845 var distance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y, 3846 oppositeDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.y : this.touchDistance.x; 3847 3848 // If the movement is in the same direction as the orientation of the slides, the swipe is valid 3849 // and opposite scrolling will not be allowed. 3850 if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) { 3851 this.allowOppositeScrolling = false;
3852 } 3853 3854 // If opposite scrolling is still allowed, the swipe wasn't valid, so return. 3855 if ( this.allowOppositeScrolling === true ) { 3856 return; 3857 } 3858 3859 // Don't allow opposite scrolling 3860 event.preventDefault(); 3861 3862 if ( this.settings.loop === false ) { 3863 // Make the slides move slower if they're dragged outside its bounds 3864 if ( ( this.slidesPosition > this.touchStartPosition && this.selectedSlideIndex === 0 ) || 3865 ( this.slidesPosition < this.touchStartPosition && this.selectedSlideIndex === this.getTotalSlides() - 1 ) 3866 ) { 3867 distance = distance * 0.2; 3868 } 3869 } 3870 3871 this._moveTo( this.touchStartPosition + distance, true ); 3872 }, 3873 3874 // Called when the slides are released 3875 _onTouchEnd: function( event ) { 3876 var that = this, 3877 touchDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y; 3878 3879 // Remove the 'move' and 'end' listeners 3880 this.$slidesMask.off( this.touchSwipeEvents.moveEvent ); 3881 $( document ).off( this.touchSwipeEvents.endEvent ); 3882 3883 this.allowOppositeScrolling = true; 3884 3885 // Swap grabbing icons 3886 this.$slidesMask.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' ); 3887 3888 // Remove the 'sp-swiping' class with a delay, to allow 3889 // other event listeners (i.e. click) to check the existance 3890 // of the swipe event. 3891 if ( this.$slider.hasClass( 'sp-swiping' ) ) { 3892 setTimeout(function() { 3893 that.$slider.removeClass( 'sp-swiping' ); 3894 }, 100 ); 3895 } 3896 3897 // Return if the slides didn't move 3898 if ( this.isTouchMoving === false ) { 3899 return; 3900 } 3901 3902 this.isTouchMoving = false; 3903 3904 // Calculate the old position of the slides in order to return to it if the swipe 3905 // is below the threshold 3906 var selectedSlideOffset = this.settings.centerSelectedSlide === true && this.settings.visibleSize !== 'auto' ? Math.round( ( parseInt( this.$slidesMask.css( this.sizeProperty ), 10 ) - this.getSlideAt( this.selectedSlideIndex ).getSize()[ this.sizeProperty ] ) / 2 ) : 0, 3907 oldSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + selectedSlideOffset; 3908 3909 if ( Math.abs( touchDistance ) < this.settings.touchSwipeThreshold ) { 3910 this._moveTo( oldSlidesPosition ); 3911 } else { 3912 3913 // Calculate by how many slides the slides container has moved 3914 var slideArrayDistance = ( this.settings.rightToLeft === true && this.settings.orientation === 'horizontal' ? -1 : 1 ) * touchDistance / ( this.averageSlideSize + this.settings.slideDistance ); 3915 3916 // Floor the obtained value and add or subtract 1, depending on the direction of the swipe 3917 slideArrayDistance = parseInt( slideArrayDistance, 10 ) + ( slideArrayDistance > 0 ? 1 : - 1 ); 3918 3919 // Get the index of the currently selected slide and subtract the position index in order to obtain 3920 // the new index of the selected slide. 3921 var nextSlideIndex = this.slidesOrder[ $.inArray( this.selectedSlideIndex, this.slidesOrder ) - slideArrayDistance ]; 3922 3923 if ( this.settings.loop === true ) { 3924 this.gotoSlide( nextSlideIndex ); 3925 } else { 3926 if ( typeof nextSlideIndex !== 'undefined' ) { 3927 this.gotoSlide( nextSlideIndex ); 3928 } else { 3929 this._moveTo( oldSlidesPosition ); 3930 } 3931 } 3932 } 3933 }, 3934 3935 // Destroy the module 3936 destroyTouchSwipe: function() { 3937 this.$slidesMask.off( 'dragstart.' + NS ); 3938 this.$slidesMask.find( 'a' ).off( 'click.' + NS ); 3939 3940 this.$slidesMask.off( this.touchSwipeEvents.startEvent ); 3941 this.$slidesMask.off( this.touchSwipeEvents.moveEvent ); 3942 $( document ).off( this.touchSwipeEvents.endEvent ); 3943 3944 this.$slidesMask.removeClass( 'sp-grab' ); 3945 }, 3946 3947 touchSwipeDefaults: { 3948 3949 // Indicates whether the touch swipe will be enabled 3950 touchSwipe: true, 3951 3952 // Sets the minimum amount that the slides should move 3953 touchSwipeThreshold: 50 3954 } 3955 }; 3956 3957 $.SliderPro.addModule( 'TouchSwipe', TouchSwipe ); 3958 3959})( window, jQuery ); 3960 3961// Caption module for Slider Pro. 3962// 3963// Adds a corresponding caption for each slide. The caption 3964// will appear and disappear with the slide. 3965;(function( window, $ ) { 3966 3967 "use strict"; 3968 3969 var NS = 'Caption.' + $.SliderPro.namespace; 3970 3971 var Caption = { 3972 3973 // Reference to the container element that will hold the caption 3974 $captionContainer: null, 3975 3976 // The caption content/text 3977 captionContent: '', 3978 3979 initCaption: function() { 3980 this.on( 'update.' + NS, $.proxy( this._captionOnUpdate, this ) );
3981 this.on( 'gotoSlide.' + NS, $.proxy( this._updateCaptionContent, this ) ); 3982 }, 3983 3984 // Create the caption container and hide the captions inside the slides 3985 _captionOnUpdate: function() { 3986 this.$captionContainer = this.$slider.find( '.sp-caption-container' ); 3987 3988 if ( this.$slider.find( '.sp-caption' ).length && this.$captionContainer.length === 0 ) { 3989 this.$captionContainer = $( '<div class="sp-caption-container"></div>' ).appendTo( this.$slider ); 3990 3991 // Show the caption for the selected slide 3992 this._updateCaptionContent(); 3993 } 3994 3995 // Hide the captions inside the slides 3996 this.$slides.find( '.sp-caption' ).each(function() { 3997 $( this ).css( 'display', 'none' ); 3998 }); 3999 }, 4000 4001 // Show the caption content for the selected slide 4002 _updateCaptionContent: function() { 4003 var that = this, 4004 newCaptionField = this.$slider.find( '.sp-slide' ).eq( this.selectedSlideIndex ).find( '.sp-caption' ), 4005 newCaptionContent = newCaptionField.length !== 0 ? newCaptionField.html() : ''; 4006 4007 // Either use a fade effect for swapping the captions or use an instant change 4008 if ( this.settings.fadeCaption === true ) { 4009 4010 // If the previous slide had a caption, fade out that caption first and when the animation is over 4011 // fade in the current caption. 4012 // If the previous slide didn't have a caption, fade in the current caption directly. 4013 if ( this.captionContent !== '' ) { 4014 4015 // If the caption container has 0 opacity when the fade out transition starts, set it 4016 // to 1 because the transition wouldn't work if the initial and final values are the same, 4017 // and the callback functions wouldn't fire in this case. 4018 if ( parseFloat( this.$captionContainer.css( 'opacity' ), 10 ) === 0 ) { 4019 this.$captionContainer.css( this.vendorPrefix + 'transition', '' ); 4020 this.$captionContainer.css( 'opacity', 1 ); 4021 } 4022 4023 this._fadeCaptionTo( 0, function() { 4024 that.captionContent = newCaptionContent; 4025 4026 if ( newCaptionContent !== '' ) { 4027 that.$captionContainer.html( that.captionContent ); 4028 that._fadeCaptionTo( 1 ); 4029 } else { 4030 that.$captionContainer.empty(); 4031 } 4032 }); 4033 } else { 4034 this.captionContent = newCaptionContent; 4035 this.$captionContainer.html( this.captionContent ); 4036 this.$captionContainer.css( 'opacity', 0 ); 4037 this._fadeCaptionTo( 1 ); 4038 } 4039 } else { 4040 this.captionContent = newCaptionContent; 4041 this.$captionContainer.html( this.captionContent ); 4042 } 4043 }, 4044 4045 // Fade the caption container to the specified opacity 4046 _fadeCaptionTo: function( opacity, callback ) { 4047 var that = this; 4048 4049 // Use CSS transitions if they are supported. If not, use JavaScript animation. 4050 if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) { 4051 4052 // There needs to be a delay between the moment the opacity is set 4053 // and the moment the transitions starts. 4054 setTimeout(function(){ 4055 var css = { 'opacity': opacity }; 4056 css[ that.vendorPrefix + 'transition' ] = 'opacity ' + that.settings.captionFadeDuration / 1000 + 's'; 4057 that.$captionContainer.css( css ); 4058 }, 1 ); 4059 4060 this.$captionContainer.on( this.transitionEvent, function( event ) { 4061 if ( event.target !== event.currentTarget ) { 4062 return; 4063 } 4064 4065 that.$captionContainer.off( that.transitionEvent ); 4066 that.$captionContainer.css( that.vendorPrefix + 'transition', '' ); 4067 4068 if ( typeof callback === 'function' ) { 4069 callback(); 4070 } 4071 }); 4072 } else { 4073 this.$captionContainer.stop().animate({ 'opacity': opacity }, this.settings.captionFadeDuration, function() { 4074 if ( typeof callback === 'function' ) { 4075 callback(); 4076 } 4077 }); 4078 } 4079 }, 4080 4081 // Destroy the module 4082 destroyCaption: function() { 4083 this.off( 'update.' + NS ); 4084 this.off( 'gotoSlide.' + NS ); 4085 4086 this.$captionContainer.remove(); 4087 4088 this.$slider.find( '.sp-caption' ).each(function() { 4089 $( this ).css( 'display', '' ); 4090 }); 4091 }, 4092 4093 captionDefaults: { 4094 4095 // Indicates whether or not the captions will be faded 4096 fadeCaption: true, 4097 4098 // Sets the duration of the fade animation 4099 captionFadeDuration: 500 4100 } 4101 }; 4102 4103 $.SliderPro.addModule( 'Caption', Caption ); 4104 4105})( window, jQuery ); 4106 4107// Deep Linking module for Slider Pro. 4108// 4109// Updates the hash of the URL as the user navigates through the slides. 4110// Also, allows navigating to a specific slide by indicating it in the hash. 4111;(function( window, $ ) { 4112 4113 "use strict"; 4114 4115 var NS = 'DeepLinking.' + $.SliderPro.namespace; 4116 4117 var DeepLinking = { 4118 4119 initDeepLinking: function() { 4120 var that = this; 4121 4122 // Parse the initial hash 4123 this.on( 'init.' + NS, function() { 4124 that._gotoHash( window.location.hash ); 4125 }); 4126 4127 // Update the hash when a new slide is selected
4128 this.on( 'gotoSlide.' + NS, function( event ) { 4129 if ( that.settings.updateHash === true ) { 4130 4131 // get the 'id' attribute of the slide 4132 var slideId = that.$slider.find( '.sp-slide' ).eq( event.index ).attr( 'id' ); 4133 4134 // if the slide doesn't have an 'id' attribute, use the slide index 4135 if ( typeof slideId === 'undefined' ) { 4136 slideId = event.index; 4137 } 4138 4139 window.location.hash = that.$slider.attr( 'id' ) + '/' + slideId; 4140 } 4141 }); 4142 4143 // Check when the hash changes and navigate to the indicated slide 4144 $( window ).on( 'hashchange.' + this.uniqueId + '.' + NS, function() { 4145 that._gotoHash( window.location.hash ); 4146 }); 4147 }, 4148 4149 // Parse the hash and return the slider id and the slide id 4150 _parseHash: function( hash ) { 4151 if ( hash !== '' ) { 4152 // Eliminate the # symbol 4153 hash = hash.substring(1); 4154 4155 // Get the specified slider id and slide id 4156 var values = hash.split( '/' ), 4157 slideId = values.pop(), 4158 sliderId = hash.slice( 0, - slideId.toString().length - 1 ); 4159 4160 if ( this.$slider.attr( 'id' ) === sliderId ) { 4161 return { 'sliderID': sliderId, 'slideId': slideId }; 4162 } 4163 } 4164 4165 return false; 4166 }, 4167 4168 // Navigate to the appropriate slide, based on the specified hash 4169 _gotoHash: function( hash ) { 4170 var result = this._parseHash( hash ); 4171 4172 if ( result === false ) { 4173 return; 4174 } 4175 4176 var slideId = result.slideId, 4177 slideIdNumber = parseInt( slideId, 10 ); 4178 4179 // check if the specified slide id is a number or string 4180 if ( isNaN( slideIdNumber ) ) { 4181 // get the index of the slide based on the specified id 4182 var slideIndex = this.$slider.find( '.sp-slide#' + slideId ).index(); 4183 4184 if ( slideIndex !== -1 && slideIndex !== this.selectedSlideIndex ) { 4185 this.gotoSlide( slideIndex ); 4186 } 4187 } else if ( slideIdNumber !== this.selectedSlideIndex ) { 4188 this.gotoSlide( slideIdNumber ); 4189 } 4190 }, 4191 4192 // Destroy the module 4193 destroyDeepLinking: function() { 4194 this.off( 'init.' + NS ); 4195 this.off( 'gotoSlide.' + NS ); 4196 $( window ).off( 'hashchange.' + this.uniqueId + '.' + NS ); 4197 }, 4198 4199 deepLinkingDefaults: { 4200 4201 // Indicates whether the hash will be updated when a new slide is selected 4202 updateHash: false 4203 } 4204 }; 4205 4206 $.SliderPro.addModule( 'DeepLinking', DeepLinking ); 4207 4208})( window, jQuery ); 4209 4210// Autoplay module for Slider Pro. 4211// 4212// Adds automatic navigation through the slides by calling the 4213// 'nextSlide' or 'previousSlide' methods at certain time intervals. 4214;(function( window, $ ) { 4215 4216 "use strict"; 4217 4218 var NS = 'Autoplay.' + $.SliderPro.namespace; 4219 4220 var Autoplay = { 4221 4222 autoplayTimer: null, 4223 4224 isTimerRunning: false, 4225 4226 isTimerPaused: false, 4227 4228 initAutoplay: function() { 4229 this.on( 'update.' + NS, $.proxy( this._autoplayOnUpdate, this ) ); 4230 }, 4231 4232 // Start the autoplay if it's enabled, or stop it if it's disabled but running 4233 _autoplayOnUpdate: function( event ) { 4234 if ( this.settings.autoplay === true ) {
4235 this.on( 'gotoSlide.' + NS, $.proxy( this._autoplayOnGotoSlide, this ) ); 4236 this.on( 'mouseenter.' + NS, $.proxy( this._autoplayOnMouseEnter, this ) ); 4237 this.on( 'mouseleave.' + NS, $.proxy( this._autoplayOnMouseLeave, this ) ); 4238 4239 this.startAutoplay(); 4240 } else { 4241 this.off( 'gotoSlide.' + NS ); 4242 this.off( 'mouseenter.' + NS ); 4243 this.off( 'mouseleave.' + NS ); 4244 4245 this.stopAutoplay(); 4246 } 4247 }, 4248 4249 // Restart the autoplay timer when a new slide is selected 4250 _autoplayOnGotoSlide: function( event ) { 4251 // stop previous timers before starting a new one 4252 if ( this.isTimerRunning === true ) { 4253 this.stopAutoplay(); 4254 } 4255 4256 if ( this.isTimerPaused === false ) { 4257 this.startAutoplay(); 4258 } 4259 }, 4260 4261 // Pause the autoplay when the slider is hovered 4262 _autoplayOnMouseEnter: function( event ) { 4263 if ( this.isTimerRunning && ( this.settings.autoplayOnHover === 'pause' || this.settings.autoplayOnHover === 'stop' ) ) { 4264 this.stopAutoplay(); 4265 this.isTimerPaused = true; 4266 } 4267 }, 4268 4269 // Start the autoplay when the mouse moves away from the slider 4270 _autoplayOnMouseLeave: function( event ) { 4271 if ( this.settings.autoplay === true && this.isTimerRunning === false && this.settings.autoplayOnHover !== 'stop' ) { 4272 this.startAutoplay(); 4273 this.isTimerPaused = false; 4274 } 4275 }, 4276 4277 // Starts the autoplay 4278 startAutoplay: function() { 4279 var that = this; 4280 4281 this.isTimerRunning = true; 4282 4283 this.autoplayTimer = setTimeout(function() { 4284 if ( that.settings.autoplayDirection === 'normal' ) { 4285 that.nextSlide(); 4286 } else if ( that.settings.autoplayDirection === 'backwards' ) { 4287 that.previousSlide(); 4288 } 4289 }, this.settings.autoplayDelay ); 4290 }, 4291 4292 // Stops the autoplay 4293 stopAutoplay: function() { 4294 this.isTimerRunning = false; 4295 this.isTimerPaused = false; 4296 4297 clearTimeout( this.autoplayTimer ); 4298 }, 4299 4300 // Destroy the module 4301 destroyAutoplay: function() { 4302 clearTimeout( this.autoplayTimer ); 4303 4304 this.off( 'update.' + NS ); 4305 this.off( 'gotoSlide.' + NS ); 4306 this.off( 'mouseenter.' + NS ); 4307 this.off( 'mouseleave.' + NS ); 4308 }, 4309 4310 autoplayDefaults: { 4311 // Indicates whether or not autoplay will be enabled 4312 autoplay: true, 4313 4314 // Sets the delay/interval at which the autoplay will run 4315 autoplayDelay: 5000, 4316 4317 // Indicates whether autoplay will navigate to the next slide or previous slide 4318 autoplayDirection: 'normal', 4319 4320 // Indicates if the autoplay will be paused or stopped when the slider is hovered. 4321 // Possible values are 'pause', 'stop' or 'none'. 4322 autoplayOnHover: 'pause' 4323 } 4324 }; 4325 4326 $.SliderPro.addModule( 'Autoplay', Autoplay ); 4327 4328})(window, jQuery); 4329 4330// Keyboard module for Slider Pro. 4331// 4332// Adds the possibility to navigate through slides using the keyboard arrow keys, or 4333// open the link attached to the main slide image by using the Enter key. 4334;(function( window, $ ) { 4335 4336 "use strict"; 4337 4338 var NS = 'Keyboard.' + $.SliderPro.namespace; 4339 4340 var Keyboard = { 4341 4342 initKeyboard: function() { 4343 var that = this, 4344 hasFocus = false; 4345 4346 if ( this.settings.keyboard === false ) { 4347 return; 4348 } 4349 4350 // Detect when the slide is in focus and when it's not, and, optionally, make it 4351 // responsive to keyboard input only when it's in focus 4352 this.$slider.on( 'focus.' + NS, function() { 4353 hasFocus = true; 4354 }); 4355 4356 this.$slider.on( 'blur.' + NS, function() { 4357 hasFocus = false; 4358 }); 4359 4360 $( document ).on( 'keydown.' + this.uniqueId + '.' + NS, function( event ) { 4361 if ( that.settings.keyboardOnlyOnFocus === true && hasFocus === false ) { 4362 return; 4363 } 4364 4365 // If the left arrow key is pressed, go to the previous slide. 4366 // If the right arrow key is pressed, go to the next slide. 4367 // If the Enter key is pressed, open the link attached to the main slide image. 4368 if ( event.which === 37 ) { 4369 that.previousSlide(); 4370 } else if ( event.which === 39 ) { 4371 that.nextSlide(); 4372 } else if ( event.which === 13 ) { 4373 var link = that.$slider.find( '.sp-slide' ).eq( that.selectedSlideIndex ).find( '.sp-image-container a' ); 4374 4375 if ( link.length !== 0 ) { 4376 link[0].click(); 4377 } 4378 } 4379 }); 4380 }, 4381 4382 // Destroy the module 4383 destroyKeyboard: function() { 4384 this.$slider.off( 'focus.' + NS ); 4385 this.$slider.off( 'blur.' + NS ); 4386 $( document ).off( 'keydown.' + this.uniqueId + '.' + NS ); 4387 }, 4388 4389 keyboardDefaults: { 4390 4391 // Indicates whether keyboard navigation will be enabled 4392 keyboard: true, 4393 4394 // Indicates whether the slider will respond to keyboard input only when
4395 // the slider is in focus. 4396 keyboardOnlyOnFocus: false 4397 } 4398 }; 4399 4400 $.SliderPro.addModule( 'Keyboard', Keyboard ); 4401 4402})( window, jQuery ); 4403 4404// Full Screen module for Slider Pro. 4405// 4406// Adds the possibility to open the slider full-screen, using the HTML5 FullScreen API. 4407;(function( window, $ ) { 4408 4409 "use strict"; 4410 4411 var NS = 'FullScreen.' + $.SliderPro.namespace; 4412 4413 var FullScreen = { 4414 4415 // Indicates whether the slider is currently in full-screen mode 4416 isFullScreen: false, 4417 4418 // Reference to the full-screen button 4419 $fullScreenButton: null, 4420 4421 // Reference to a set of settings that influence the slider's size 4422 // before it goes full-screen 4423 sizeBeforeFullScreen: {}, 4424 4425 initFullScreen: function() { 4426 if ( ! ( document.fullscreenEnabled || 4427 document.webkitFullscreenEnabled || 4428 document.mozFullScreenEnabled || 4429 document.msFullscreenEnabled ) ) { 4430 return; 4431 } 4432 4433 this.on( 'update.' + NS, $.proxy( this._fullScreenOnUpdate, this ) ); 4434 }, 4435 4436 // Create or remove the full-screen button depending on the value of the 'fullScreen' option 4437 _fullScreenOnUpdate: function() { 4438 if ( this.settings.fullScreen === true && this.$fullScreenButton === null ) { 4439 this._addFullScreen(); 4440 } else if ( this.settings.fullScreen === false && this.$fullScreenButton !== null ) { 4441 this._removeFullScreen(); 4442 } 4443 4444 if ( this.settings.fullScreen === true ) { 4445 if ( this.settings.fadeFullScreen === true ) { 4446 this.$fullScreenButton.addClass( 'sp-fade-full-screen' ); 4447 } else if ( this.settings.fadeFullScreen === false ) { 4448 this.$fullScreenButton.removeClass( 'sp-fade-full-screen' ); 4449 } 4450 } 4451 }, 4452 4453 // Create the full-screen button 4454 _addFullScreen: function() { 4455 this.$fullScreenButton = $('<div class="sp-full-screen-button"></div>').appendTo( this.$slider ); 4456 this.$fullScreenButton.on( 'click.' + NS, $.proxy( this._onFullScreenButtonClick, this ) ); 4457 4458 document.addEventListener( 'fullscreenchange', $.proxy( this._onFullScreenChange, this ) ); 4459 document.addEventListener( 'mozfullscreenchange', $.proxy( this._onFullScreenChange, this ) ); 4460 document.addEventListener( 'webkitfullscreenchange', $.proxy( this._onFullScreenChange, this ) ); 4461 document.addEventListener( 'MSFullscreenChange', $.proxy( this._onFullScreenChange, this ) ); 4462 }, 4463 4464 // Remove the full-screen button 4465 _removeFullScreen: function() { 4466 if ( this.$fullScreenButton !== null ) { 4467 this.$fullScreenButton.off( 'click.' + NS ); 4468 this.$fullScreenButton.remove(); 4469 this.$fullScreenButton = null; 4470 document.removeEventListener( 'fullscreenchange', this._onFullScreenChange ); 4471 document.removeEventListener( 'mozfullscreenchange', this._onFullScreenChange ); 4472 document.removeEventListener( 'webkitfullscreenchange', this._onFullScreenChange ); 4473 document.removeEventListener( 'MSFullscreenChange', this._onFullScreenChange ); 4474 } 4475 }, 4476 4477 // When the full-screen button is clicked, put the slider into full-screen mode, and 4478 // take it out of the full-screen mode when it's clicked again. 4479 _onFullScreenButtonClick: function() { 4480 if ( this.isFullScreen === false ) { 4481 if ( this.instance.requestFullScreen ) { 4482 this.instance.requestFullScreen(); 4483 } else if ( this.instance.mozRequestFullScreen ) { 4484 this.instance.mozRequestFullScreen(); 4485 } else if ( this.instance.webkitRequestFullScreen ) { 4486 this.instance.webkitRequestFullScreen(); 4487 } else if ( this.instance.msRequestFullscreen ) { 4488 this.instance.msRequestFullscreen(); 4489 } 4490 } else { 4491 if ( document.exitFullScreen ) { 4492 document.exitFullScreen(); 4493 } else if ( document.mozCancelFullScreen ) { 4494 document.mozCancelFullScreen(); 4495 } else if ( document.webkitCancelFullScreen ) { 4496 document.webkitCancelFullScreen(); 4497 } else if ( document.msExitFullscreen ) { 4498 document.msExitFullscreen(); 4499 } 4500 } 4501 }, 4502 4503 // This will be called whenever the full-screen mode changes. 4504 // If the slider is in full-screen mode, set it to 'full window', and if it's 4505 // not in full-screen mode anymore, set it back to the original size. 4506 _onFullScreenChange: function() { 4507 this.isFullScreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement ? true : false;
4508 4509 if ( this.isFullScreen === true ) { 4510 this.sizeBeforeFullScreen = { forceSize: this.settings.forceSize, autoHeight: this.settings.autoHeight }; 4511 this.$slider.addClass( 'sp-full-screen' ); 4512 this.settings.forceSize = 'fullWindow'; 4513 this.settings.autoHeight = false; 4514 } else { 4515 this.$slider.css( 'margin', '' ); 4516 this.$slider.removeClass( 'sp-full-screen' ); 4517 this.settings.forceSize = this.sizeBeforeFullScreen.forceSize; 4518 this.settings.autoHeight = this.sizeBeforeFullScreen.autoHeight; 4519 } 4520 4521 this.resize(); 4522 }, 4523 4524 // Destroy the module 4525 destroyFullScreen: function() { 4526 this.off( 'update.' + NS ); 4527 this._removeFullScreen(); 4528 }, 4529 4530 fullScreenDefaults: { 4531 4532 // Indicates whether the full-screen button is enabled 4533 fullScreen: false, 4534 4535 // Indicates whether the button will fade in only on hover 4536 fadeFullScreen: true 4537 } 4538 }; 4539 4540 $.SliderPro.addModule( 'FullScreen', FullScreen ); 4541 4542})( window, jQuery ); 4543 4544// Buttons module for Slider Pro. 4545// 4546// Adds navigation buttons at the bottom of the slider. 4547;(function( window, $ ) { 4548 4549 "use strict"; 4550 4551 var NS = 'Buttons.' + $.SliderPro.namespace; 4552 4553 var Buttons = { 4554 4555 // Reference to the buttons container 4556 $buttons: null, 4557 4558 initButtons: function() { 4559 this.on( 'update.' + NS, $.proxy( this._buttonsOnUpdate, this ) ); 4560 }, 4561 4562 _buttonsOnUpdate: function() { 4563 this.$buttons = this.$slider.find('.sp-buttons'); 4564 4565 // If there is more that one slide but the buttons weren't created yet, create the buttons. 4566 // If the buttons were created but their number differs from the total number of slides, re-create the buttons. 4567 // If the buttons were created but there are less than one slide, remove the buttons.s 4568 if ( this.settings.buttons === true && this.getTotalSlides() > 1 && this.$buttons.length === 0 ) { 4569 this._createButtons(); 4570 } else if ( this.settings.buttons === true && this.getTotalSlides() !== this.$buttons.find( '.sp-button' ).length && this.$buttons.length !== 0 ) { 4571 this._adjustButtons(); 4572 } else if ( this.settings.buttons === false || ( this.getTotalSlides() <= 1 && this.$buttons.length !== 0 ) ) { 4573 this._removeButtons(); 4574 } 4575 }, 4576 4577 // Create the buttons 4578 _createButtons: function() { 4579 var that = this; 4580 4581 // Create the buttons' container 4582 this.$buttons = $( '<div class="sp-buttons"></div>' ).appendTo( this.$slider ); 4583 4584 // Create the buttons 4585 for ( var i = 0; i < this.getTotalSlides(); i++ ) { 4586 $( '<div class="sp-button"></div>' ).appendTo( this.$buttons ); 4587 } 4588 4589 // Listen for button clicks 4590 this.$buttons.on( 'click.' + NS, '.sp-button', function() { 4591 that.gotoSlide( $( this ).index() ); 4592 }); 4593 4594 // Set the initially selected button 4595 this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' ); 4596 4597 // Select the corresponding button when the slide changes
4598 this.on( 'gotoSlide.' + NS, function( event ) { 4599 that.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' ); 4600 that.$buttons.find( '.sp-button' ).eq( event.index ).addClass( 'sp-selected-button' ); 4601 }); 4602 4603 // Indicate that the slider has buttons 4604 this.$slider.addClass( 'sp-has-buttons' ); 4605 }, 4606 4607 // Re-create the buttons. This is calles when the number of slides changes. 4608 _adjustButtons: function() { 4609 this.$buttons.empty(); 4610 4611 // Create the buttons 4612 for ( var i = 0; i < this.getTotalSlides(); i++ ) { 4613 $( '<div class="sp-button"></div>' ).appendTo( this.$buttons ); 4614 } 4615 4616 // Change the selected the buttons 4617 this.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' ); 4618 this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' ); 4619 }, 4620 4621 // Remove the buttons 4622 _removeButtons: function() { 4623 this.$buttons.off( 'click.' + NS, '.sp-button' ); 4624 this.off( 'gotoSlide.' + NS ); 4625 this.$buttons.remove(); 4626 this.$slider.removeClass( 'sp-has-buttons' ); 4627 }, 4628 4629 destroyButtons: function() { 4630 this._removeButtons(); 4631 this.off( 'update.' + NS ); 4632 }, 4633 4634 buttonsDefaults: { 4635 4636 // Indicates whether the buttons will be created 4637 buttons: true 4638 } 4639 }; 4640 4641 $.SliderPro.addModule( 'Buttons', Buttons ); 4642 4643})( window, jQuery ); 4644 4645// Arrows module for Slider Pro. 4646// 4647// Adds arrows for navigating to the next or previous slide. 4648;(function( window, $ ) { 4649 4650 "use strict"; 4651 4652 var NS = 'Arrows.' + $.SliderPro.namespace; 4653 4654 var Arrows = { 4655 4656 // Reference to the arrows container 4657 $arrows: null, 4658 4659 // Reference to the previous arrow 4660 $previousArrow: null, 4661 4662 // Reference to the next arrow 4663 $nextArrow: null, 4664 4665 initArrows: function() { 4666 this.on( 'update.' + NS, $.proxy( this._arrowsOnUpdate, this ) );
4667 this.on( 'gotoSlide.' + NS, $.proxy( this._checkArrowsVisibility, this ) ); 4668 }, 4669 4670 _arrowsOnUpdate: function() { 4671 var that = this; 4672 4673 // Create the arrows if the 'arrows' option is set to true 4674 if ( this.settings.arrows === true && this.$arrows === null ) { 4675 this.$arrows = $( '<div class="sp-arrows"></div>' ).appendTo( this.$slidesContainer ); 4676 4677 this.$previousArrow = $( '<div class="sp-arrow sp-previous-arrow"></div>' ).appendTo( this.$arrows ); 4678 this.$nextArrow = $( '<div class="sp-arrow sp-next-arrow"></div>' ).appendTo( this.$arrows ); 4679 4680 this.$previousArrow.on( 'click.' + NS, function() { 4681 that.previousSlide(); 4682 }); 4683 4684 this.$nextArrow.on( 'click.' + NS, function() { 4685 that.nextSlide(); 4686 }); 4687 4688 this._checkArrowsVisibility(); 4689 } else if ( this.settings.arrows === false && this.$arrows !== null ) { 4690 this._removeArrows(); 4691 } 4692 4693 if ( this.settings.arrows === true ) { 4694 if ( this.settings.fadeArrows === true ) { 4695 this.$arrows.addClass( 'sp-fade-arrows' ); 4696 } else if ( this.settings.fadeArrows === false ) { 4697 this.$arrows.removeClass( 'sp-fade-arrows' ); 4698 } 4699 } 4700 }, 4701 4702 // Show or hide the arrows depending on the position of the selected slide 4703 _checkArrowsVisibility: function() { 4704 if ( this.settings.arrows === false || this.settings.loop === true ) { 4705 return; 4706 } 4707 4708 if ( this.selectedSlideIndex === 0 ) { 4709 this.$previousArrow.css( 'display', 'none' ); 4710 } else { 4711 this.$previousArrow.css( 'display', 'block' ); 4712 } 4713 4714 if ( this.selectedSlideIndex === this.getTotalSlides() - 1 ) { 4715 this.$nextArrow.css( 'display', 'none' ); 4716 } else { 4717 this.$nextArrow.css( 'display', 'block' ); 4718 } 4719 }, 4720 4721 _removeArrows: function() { 4722 if ( this.$arrows !== null ) { 4723 this.$previousArrow.off( 'click.' + NS ); 4724 this.$nextArrow.off( 'click.' + NS ); 4725 this.$arrows.remove(); 4726 this.$arrows = null; 4727 } 4728 }, 4729 4730 destroyArrows: function() { 4731 this._removeArrows(); 4732 this.off( 'update.' + NS ); 4733 this.off( 'gotoSlide.' + NS ); 4734 }, 4735 4736 arrowsDefaults: { 4737 4738 // Indicates whether the arrow buttons will be created 4739 arrows: false, 4740 4741 // Indicates whether the arrows will fade in only on hover 4742 fadeArrows: true 4743 } 4744 }; 4745 4746 $.SliderPro.addModule( 'Arrows', Arrows ); 4747 4748})( window, jQuery ); 4749 4750// Thumbnail Touch Swipe module for Slider Pro. 4751// 4752// Adds touch-swipe functionality for thumbnails. 4753;(function( window, $ ) { 4754 4755 "use strict"; 4756 4757 var NS = 'ThumbnailTouchSwipe.' + $.SliderPro.namespace; 4758 4759 var ThumbnailTouchSwipe = { 4760 4761 // The x and y coordinates of the pointer/finger's starting position 4762 thumbnailTouchStartPoint: { x: 0, y: 0 }, 4763 4764 // The x and y coordinates of the pointer/finger's end position 4765 thumbnailTouchEndPoint: { x: 0, y: 0 }, 4766 4767 // The distance from the starting to the end position on the x and y axis 4768 thumbnailTouchDistance: { x: 0, y: 0 }, 4769 4770 // The position of the thumbnail scroller when the touch swipe starts 4771 thumbnailTouchStartPosition: 0, 4772 4773 // Indicates if the thumbnail scroller is being swiped 4774 isThumbnailTouchMoving: false, 4775 4776 // Indicates if the touch swipe was initialized 4777 isThumbnailTouchSwipe: false, 4778 4779 // Stores the names of the events 4780 thumbnailTouchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' }, 4781 4782 // Indicates whether the previous 'start' event was a 'touchstart' or 'mousedown' 4783 thumbnailPreviousStartEvent: '', 4784 4785 initThumbnailTouchSwipe: function() { 4786 this.on( 'update.' + NS, $.proxy( this._thumbnailTouchSwipeOnUpdate, this ) ); 4787 }, 4788 4789 _thumbnailTouchSwipeOnUpdate: function() { 4790 4791 // Return if there are no thumbnails 4792 if ( this.isThumbnailScroller === false ) { 4793 return; 4794 } 4795 4796 // Initialize the touch swipe functionality if it wasn't initialized yet 4797 if ( this.settings.thumbnailTouchSwipe === true && this.isThumbnailTouchSwipe === false ) { 4798 this.isThumbnailTouchSwipe = true; 4799 4800 this.thumbnailTouchSwipeEvents.startEvent = 'touchstart' + '.' + NS + ' mousedown' + '.' + NS; 4801 this.thumbnailTouchSwipeEvents.moveEvent = 'touchmove' + '.' + NS + ' mousemove' + '.' + NS; 4802 this.thumbnailTouchSwipeEvents.endEvent = 'touchend' + '.' + this.uniqueId + '.' + NS + ' mouseup' + '.' + this.uniqueId + '.' + NS; 4803 4804 // Listen for touch swipe/mouse move events 4805 this.$thumbnails.on( this.thumbnailTouchSwipeEvents.startEvent, $.proxy( this._onThumbnailTouchStart, this ) ); 4806 this.$thumbnails.on( 'dragstart.' + NS, function( event ) { 4807 event.preventDefault(); 4808 }); 4809 4810 // Add the grabbing icon 4811 this.$thumbnails.addClass( 'sp-grab' ); 4812 } 4813 4814 // Remove the default thumbnailClick 4815 $.each( this.thumbnails, function( index, thumbnail ) { 4816 thumbnail.off( 'thumbnailClick' ); 4817 }); 4818 }, 4819 4820 // Called when the thumbnail scroller starts being dragged 4821 _onThumbnailTouchStart: function( event ) { 4822 4823 // Return if a 'mousedown' event follows a 'touchstart' event 4824 if ( event.type === 'mousedown' && this.thumbnailPreviousStartEvent === 'touchstart' ) { 4825 this.thumbnailPreviousStartEvent = event.type; 4826 return; 4827 } 4828 4829 // Assign the new 'start' event 4830 this.thumbnailPreviousStartEvent = event.type; 4831 4832 // Disable dragging if the element is set to allow selections 4833 if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) { 4834 return; 4835 } 4836 4837 var that = this, 4838 eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent; 4839 4840 // Prevent default behavior for mouse events 4841 if ( typeof event.originalEvent.touches === 'undefined' ) { 4842 event.preventDefault(); 4843 } 4844 4845 // Disable click events on links
4846 $( event.target ).parents( '.sp-thumbnail-container' ).find( 'a' ).one( 'click.' + NS, function( event ) { 4847 event.preventDefault(); 4848 }); 4849 4850 // Get the initial position of the mouse pointer and the initial position 4851 // of the thumbnail scroller 4852 this.thumbnailTouchStartPoint.x = eventObject.pageX || eventObject.clientX; 4853 this.thumbnailTouchStartPoint.y = eventObject.pageY || eventObject.clientY; 4854 this.thumbnailTouchStartPosition = this.thumbnailsPosition; 4855 4856 // Clear the previous distance values 4857 this.thumbnailTouchDistance.x = this.thumbnailTouchDistance.y = 0; 4858 4859 // If the thumbnail scroller is being grabbed while it's still animating, stop the 4860 // current movement 4861 if ( this.$thumbnails.hasClass( 'sp-animated' ) ) { 4862 this.isThumbnailTouchMoving = true; 4863 this._stopThumbnailsMovement(); 4864 this.thumbnailTouchStartPosition = this.thumbnailsPosition; 4865 } 4866 4867 // Listen for move and end events 4868 this.$thumbnails.on( this.thumbnailTouchSwipeEvents.moveEvent, $.proxy( this._onThumbnailTouchMove, this ) ); 4869 $( document ).on( this.thumbnailTouchSwipeEvents.endEvent, $.proxy( this._onThumbnailTouchEnd, this ) ); 4870 4871 // Swap grabbing icons 4872 this.$thumbnails.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' ); 4873 4874 // Add 'sp-swiping' class to indicate that the thumbnail scroller is being swiped 4875 this.$thumbnailsContainer.addClass( 'sp-swiping' ); 4876 }, 4877 4878 // Called during the thumbnail scroller's dragging 4879 _onThumbnailTouchMove: function( event ) { 4880 var eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent; 4881 4882 // Indicate that the move event is being fired 4883 this.isThumbnailTouchMoving = true; 4884 4885 // Get the current position of the mouse pointer 4886 this.thumbnailTouchEndPoint.x = eventObject.pageX || eventObject.clientX; 4887 this.thumbnailTouchEndPoint.y = eventObject.pageY || eventObject.clientY; 4888 4889 // Calculate the distance of the movement on both axis 4890 this.thumbnailTouchDistance.x = this.thumbnailTouchEndPoint.x - this.thumbnailTouchStartPoint.x; 4891 this.thumbnailTouchDistance.y = this.thumbnailTouchEndPoint.y - this.thumbnailTouchStartPoint.y; 4892 4893 // Calculate the distance of the swipe that takes place in the same direction as the orientation of the thumbnails 4894 // and calculate the distance from the opposite direction. 4895 // 4896 // For a swipe to be valid there should more distance in the same direction as the orientation of the thumbnails. 4897 var distance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y, 4898 oppositeDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.y : this.thumbnailTouchDistance.x; 4899 4900 // If the movement is in the same direction as the orientation of the thumbnails, the swipe is valid 4901 if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) { 4902 event.preventDefault(); 4903 } else { 4904 return; 4905 } 4906 4907 // Make the thumbnail scroller move slower if it's dragged outside its bounds 4908 if ( this.thumbnailsPosition >= 0 ) { 4909 var infOffset = - this.thumbnailTouchStartPosition; 4910 distance = infOffset + ( distance - infOffset ) * 0.2; 4911 } else if ( this.thumbnailsPosition <= - this.thumbnailsSize + this.thumbnailsContainerSize ) { 4912 var supOffset = this.thumbnailsSize - this.thumbnailsContainerSize + this.thumbnailTouchStartPosition; 4913 distance = - supOffset + ( distance + supOffset ) * 0.2; 4914 } 4915 4916 this._moveThumbnailsTo( this.thumbnailTouchStartPosition + distance, true ); 4917 }, 4918 4919 // Called when the thumbnail scroller is released 4920 _onThumbnailTouchEnd: function( event ) { 4921 var that = this, 4922 thumbnailTouchDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y; 4923 4924 // Remove the move and end listeners 4925 this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent ); 4926 $( document ).off( this.thumbnailTouchSwipeEvents.endEvent ); 4927 4928 // Swap grabbing icons 4929 this.$thumbnails.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' ); 4930
4931 // Check if there is intention for a tap/click 4932 if ( this.isThumbnailTouchMoving === false || 4933 this.isThumbnailTouchMoving === true && 4934 Math.abs( this.thumbnailTouchDistance.x ) < 10 && 4935 Math.abs( this.thumbnailTouchDistance.y ) < 10 4936 ) { 4937 var targetThumbnail = $( event.target ).hasClass( 'sp-thumbnail-container' ) ? $( event.target ) : $( event.target ).parents( '.sp-thumbnail-container' ), 4938 index = targetThumbnail.index(); 4939 4940 // If a link is cliked, navigate to that link, else navigate to the slide that corresponds to the thumbnail 4941 if ( $( event.target ).parents( 'a' ).length !== 0 ) { 4942 $( event.target ).parents( 'a' ).off( 'click.' + NS ); 4943 this.$thumbnailsContainer.removeClass( 'sp-swiping' ); 4944 } else if ( index !== this.selectedThumbnailIndex && index !== -1 ) { 4945 this.gotoSlide( index ); 4946 } 4947 4948 return; 4949 } 4950 4951 this.isThumbnailTouchMoving = false; 4952 4953 $( event.target ).parents( '.sp-thumbnail' ).one( 'click', function( event ) { 4954 event.preventDefault(); 4955 }); 4956 4957 // Remove the 'sp-swiping' class but with a delay 4958 // because there might be other event listeners that check 4959 // the existence of this class, and this class should still be 4960 // applied for those listeners, since there was a swipe event 4961 setTimeout(function() { 4962 that.$thumbnailsContainer.removeClass( 'sp-swiping' ); 4963 }, 1 ); 4964 4965 // Keep the thumbnail scroller inside the bounds 4966 if ( this.thumbnailsPosition > 0 ) { 4967 this._moveThumbnailsTo( 0 ); 4968 } else if ( this.thumbnailsPosition < this.thumbnailsContainerSize - this.thumbnailsSize ) { 4969 this._moveThumbnailsTo( this.thumbnailsContainerSize - this.thumbnailsSize ); 4970 } 4971 4972 // Fire the 'thumbnailsMoveComplete' event 4973 this.trigger({ type: 'thumbnailsMoveComplete' }); 4974 if ( $.isFunction( this.settings.thumbnailsMoveComplete ) ) { 4975 this.settings.thumbnailsMoveComplete.call( this, { type: 'thumbnailsMoveComplete' }); 4976 } 4977 }, 4978 4979 // Destroy the module 4980 destroyThumbnailTouchSwipe: function() { 4981 this.off( 'update.' + NS ); 4982 4983 if ( this.isThumbnailScroller === false ) { 4984 return; 4985 } 4986 4987 this.$thumbnails.off( this.thumbnailTouchSwipeEvents.startEvent ); 4988 this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent ); 4989 this.$thumbnails.off( 'dragstart.' + NS ); 4990 $( document ).off( this.thumbnailTouchSwipeEvents.endEvent ); 4991 this.$thumbnails.removeClass( 'sp-grab' ); 4992 }, 4993 4994 thumbnailTouchSwipeDefaults: { 4995 4996 // Indicates whether the touch swipe will be enabled for thumbnails 4997 thumbnailTouchSwipe: true 4998 } 4999 }; 5000 5001 $.SliderPro.addModule( 'ThumbnailTouchSwipe', ThumbnailTouchSwipe ); 5002 5003})( window, jQuery ); 5004 5005// Thumbnail Arrows module for Slider Pro. 5006// 5007// Adds thumbnail arrows for moving the thumbnail scroller. 5008;(function( window, $ ) { 5009 5010 "use strict"; 5011 5012 var NS = 'ThumbnailArrows.' + $.SliderPro.namespace; 5013 5014 var ThumbnailArrows = { 5015 5016 // Reference to the arrows container 5017 $thumbnailArrows: null, 5018 5019 // Reference to the 'previous' thumbnail arrow 5020 $previousThumbnailArrow: null, 5021 5022 // Reference to the 'next' thumbnail arrow 5023 $nextThumbnailArrow: null, 5024 5025 initThumbnailArrows: function() { 5026 var that = this; 5027 5028 this.on( 'update.' + NS, $.proxy( this._thumbnailArrowsOnUpdate, this ) ); 5029 5030 // Check if the arrows need to be visible or invisible when the thumbnail scroller 5031 // resizes and when the thumbnail scroller moves. 5032 this.on( 'sliderResize.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, function() { 5033 if ( that.isThumbnailScroller === true && that.settings.thumbnailArrows === true ) { 5034 that._checkThumbnailArrowsVisibility(); 5035 } 5036 }); 5037 }, 5038 5039 // Called when the slider is updated 5040 _thumbnailArrowsOnUpdate: function() { 5041 var that = this; 5042 5043 if ( this.isThumbnailScroller === false ) { 5044 return; 5045 } 5046 5047 // Create or remove the thumbnail scroller arrows 5048 if ( this.settings.thumbnailArrows === true && this.$thumbnailArrows === null ) { 5049 this.$thumbnailArrows = $( '<div class="sp-thumbnail-arrows"></div>' ).appendTo( this.$thumbnailsContainer ); 5050
5051 this.$previousThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-previous-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows ); 5052 this.$nextThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-next-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows ); 5053 5054 this.$previousThumbnailArrow.on( 'click.' + NS, function() { 5055 var previousPosition = Math.min( 0, that.thumbnailsPosition + that.thumbnailsContainerSize ); 5056 that._moveThumbnailsTo( previousPosition ); 5057 }); 5058 5059 this.$nextThumbnailArrow.on( 'click.' + NS, function() { 5060 var nextPosition = Math.max( that.thumbnailsContainerSize - that.thumbnailsSize, that.thumbnailsPosition - that.thumbnailsContainerSize ); 5061 that._moveThumbnailsTo( nextPosition ); 5062 }); 5063 } else if ( this.settings.thumbnailArrows === false && this.$thumbnailArrows !== null ) { 5064 this._removeThumbnailArrows(); 5065 } 5066 5067 // Add fading functionality and check if the arrows need to be visible or not 5068 if ( this.settings.thumbnailArrows === true ) { 5069 if ( this.settings.fadeThumbnailArrows === true ) { 5070 this.$thumbnailArrows.addClass( 'sp-fade-thumbnail-arrows' ); 5071 } else if ( this.settings.fadeThumbnailArrows === false ) { 5072 this.$thumbnailArrows.removeClass( 'sp-fade-thumbnail-arrows' ); 5073 } 5074 5075 this._checkThumbnailArrowsVisibility(); 5076 } 5077 }, 5078 5079 // Checks if the 'next' or 'previous' arrows need to be visible or hidden, 5080 // based on the position of the thumbnail scroller 5081 _checkThumbnailArrowsVisibility: function() { 5082 if ( this.thumbnailsPosition === 0 ) { 5083 this.$previousThumbnailArrow.css( 'display', 'none' ); 5084 } else { 5085 this.$previousThumbnailArrow.css( 'display', 'block' ); 5086 } 5087 5088 if ( this.thumbnailsPosition === this.thumbnailsContainerSize - this.thumbnailsSize ) { 5089 this.$nextThumbnailArrow.css( 'display', 'none' ); 5090 } else { 5091 this.$nextThumbnailArrow.css( 'display', 'block' ); 5092 } 5093 }, 5094 5095 // Remove the thumbnail arrows 5096 _removeThumbnailArrows: function() { 5097 if ( this.$thumbnailArrows !== null ) { 5098 this.$previousThumbnailArrow.off( 'click.' + NS ); 5099 this.$nextThumbnailArrow.off( 'click.' + NS ); 5100 this.$thumbnailArrows.remove(); 5101 this.$thumbnailArrows = null; 5102 } 5103 }, 5104 5105 // Destroy the module 5106 destroyThumbnailArrows: function() { 5107 this._removeThumbnailArrows(); 5108 this.off( 'update.' + NS ); 5109 this.off( 'sliderResize.' + NS ); 5110 this.off( 'thumbnailsMoveComplete.' + NS ); 5111 }, 5112 5113 thumbnailArrowsDefaults: { 5114 5115 // Indicates whether the thumbnail arrows will be enabled 5116 thumbnailArrows: false, 5117 5118 // Indicates whether the thumbnail arrows will be faded 5119 fadeThumbnailArrows: true 5120 } 5121 }; 5122 5123 $.SliderPro.addModule( 'ThumbnailArrows', ThumbnailArrows ); 5124 5125})( window, jQuery ); 5126 5127// Video module for Slider Pro 5128// 5129// Adds automatic control for several video players and providers 5130;(function( window, $ ) { 5131 5132 "use strict"; 5133 5134 var NS = 'Video.' + $.SliderPro.namespace; 5135 5136 var Video = { 5137 5138 firstInit: false, 5139 5140 initVideo: function() { 5141 this.on( 'update.' + NS, $.proxy( this._videoOnUpdate, this ) );
5142 this.on( 'gotoSlide.' + NS, $.proxy( this._videoOnGotoSlide, this ) ); 5143 this.on( 'gotoSlideComplete.' + NS, $.proxy( this._videoOnGotoSlideComplete, this ) ); 5144 }, 5145 5146 _videoOnUpdate: function() { 5147 var that = this; 5148 5149 // Find all the inline videos and initialize them 5150 this.$slider.find( '.sp-video' ).not( 'a, [data-video-init]' ).each(function() { 5151 var video = $( this ); 5152 that._initVideo( video ); 5153 }); 5154 5155 // Find all the lazy-loaded videos and preinitialize them. They will be initialized 5156 // only when their play button is clicked. 5157 this.$slider.find( 'a.sp-video' ).not( '[data-video-preinit]' ).each(function() { 5158 var video = $( this ); 5159 that._preinitVideo( video ); 5160 }); 5161 5162 // call the 'gotoSlideComplete' method in case the first slide contains a video that 5163 // needs to play automatically 5164 if ( this.firstInit === false ) { 5165 this.firstInit = true; 5166 this._videoOnGotoSlideComplete({ index: this.selectedSlideIndex, previousIndex: -1 }); 5167 } 5168 }, 5169 5170 // Initialize the target video 5171 _initVideo: function( video ) { 5172 var that = this; 5173 5174 video.attr( 'data-video-init', true ) 5175 .videoController(); 5176 5177 // When the video starts playing, pause the autoplay if it's running 5178 video.on( 'videoPlay.' + NS, function() { 5179 if ( that.settings.playVideoAction === 'stopAutoplay' && typeof that.stopAutoplay !== 'undefined' ) { 5180 that.stopAutoplay(); 5181 that.settings.autoplay = false; 5182 } 5183 5184 // Fire the 'videoPlay' event 5185 var eventObject = { type: 'videoPlay', video: video }; 5186 that.trigger( eventObject ); 5187 if ( $.isFunction( that.settings.videoPlay ) ) { 5188 that.settings.videoPlay.call( that, eventObject ); 5189 } 5190 }); 5191 5192 // When the video is paused, restart the autoplay 5193 video.on( 'videoPause.' + NS, function() { 5194 if ( that.settings.pauseVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) { 5195 that.stopAutoplay(); 5196 that.startAutoplay(); 5197 that.settings.autoplay = true; 5198 } 5199 5200 // Fire the 'videoPause' event 5201 var eventObject = { type: 'videoPause', video: video }; 5202 that.trigger( eventObject ); 5203 if ( $.isFunction( that.settings.videoPause ) ) { 5204 that.settings.videoPause.call( that, eventObject ); 5205 } 5206 }); 5207 5208 // When the video ends, restart the autoplay (which was paused during the playback), or 5209 // go to the next slide, or replay the video 5210 video.on( 'videoEnded.' + NS, function() { 5211 if ( that.settings.endVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) { 5212 that.stopAutoplay(); 5213 that.startAutoplay(); 5214 that.settings.autoplay = true; 5215 } else if ( that.settings.endVideoAction === 'nextSlide' ) { 5216 that.nextSlide(); 5217 } else if ( that.settings.endVideoAction === 'replayVideo' ) { 5218 video.videoController( 'replay' ); 5219 } 5220 5221 // Fire the 'videoEnd' event 5222 var eventObject = { type: 'videoEnd', video: video }; 5223 that.trigger( eventObject ); 5224 if ( $.isFunction(that.settings.videoEnd ) ) { 5225 that.settings.videoEnd.call( that, eventObject ); 5226 } 5227 }); 5228 }, 5229 5230 // Pre-initialize the video. This is for lazy loaded videos. 5231 _preinitVideo: function( video ) { 5232 var that = this; 5233 5234 video.attr( 'data-video-preinit', true ); 5235 5236 // When the video poster is clicked, remove the poster and create 5237 // the inline video 5238 video.on( 'click.' + NS, function( event ) { 5239 5240 // If the video is being dragged, don't start the video 5241 if ( that.$slider.hasClass( 'sp-swiping' ) ) { 5242 return; 5243 } 5244 5245 event.preventDefault(); 5246 5247 var href = video.attr( 'href' ), 5248 iframe, 5249 provider, 5250 regExp, 5251 match, 5252 id, 5253 src, 5254 videoAttributes, 5255 videoWidth = video.children( 'img' ).attr( 'width' ) || video.children( 'img' ).width(), 5256 videoHeight = video.children( 'img' ).attr( 'height') || video.children( 'img' ).height(); 5257 5258 // Check if it's a youtube or vimeo video 5259 if ( href.indexOf( 'youtube' ) !== -1 || href.indexOf( 'youtu.be' ) !== -1 ) { 5260 provider = 'youtube'; 5261 } else if ( href.indexOf( 'vimeo' ) !== -1 ) { 5262 provider = 'vimeo'; 5263 } 5264 5265 // Get the id of the video 5266 regExp = provider === 'youtube' ? /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ : /http:\/\/(www\.)?vimeo.com\/(\d+)/; 5267 match = href.match( regExp ); 5268 id = match[2]; 5269 5270 // Get the source of the iframe that will be created 5271 src = provider === 'youtube' ? '//www.youtube.com/embed/' + id + '?enablejsapi=1&wmode=opaque' : '//player.vimeo.com/video/'+ id; 5272 5273 // Get the attributes passed to the video link and then pass them to the iframe's src 5274 videoAttributes = href.split( '?' )[ 1 ]; 5275 5276 if ( typeof videoAttributes !== 'undefined' ) { 5277 videoAttributes = videoAttributes.split( '&' ); 5278 5279 $.each( videoAttributes, function( index, value ) { 5280 if ( value.indexOf( id ) === -1 ) { 5281 src += '&' + value; 5282 } 5283 }); 5284 } 5285 5286 // Create the iframe 5287 iframe = $( '<iframe></iframe>' ) 5288 .attr({ 5289 'src': src, 5290 'width': videoWidth, 5291 'height': videoHeight, 5292 'class': video.attr( 'class' ), 5293 'frameborder': 0, 5294 'allowfullscreen': 'allowfullscreen' 5295 }).insertBefore( video ); 5296 5297 // Initialize the video and play it 5298 that._initVideo( iframe ); 5299 iframe.videoController( 'play' ); 5300 5301 // Hide the video poster 5302 video.css( 'display', 'none' ); 5303 }); 5304 }, 5305 5306 // Called when a new slide is selected 5307 _videoOnGotoSlide: function( event ) { 5308 5309 // Get the video from the previous slide 5310 var previousVideo = this.$slides.find( '.sp-slide' ).eq( event.previousIndex ).find( '.sp-video[data-video-init]' ); 5311 5312 // Handle the video from the previous slide by stopping it, or pausing it, 5313 // or remove it, depending on the value of the 'leaveVideoAction' option. 5314 if ( event.previousIndex !== -1 && previousVideo.length !== 0 ) { 5315 if ( this.settings.leaveVideoAction === 'stopVideo' ) { 5316 previousVideo.videoController( 'stop' ); 5317 } else if ( this.settings.leaveVideoAction === 'pauseVideo' ) { 5318 previousVideo.videoController( 'pause' ); 5319 } else if ( this.settings.leaveVideoAction === 'removeVideo' ) { 5320 // If the video was lazy-loaded, remove it and show the poster again. If the video 5321 // was not lazy-loaded, but inline, stop the video. 5322 if ( previousVideo.siblings( 'a.sp-video' ).length !== 0 ) { 5323 previousVideo.siblings( 'a.sp-video' ).css( 'display', '' ); 5324 previousVideo.videoController( 'destroy' ); 5325 previousVideo.remove(); 5326 } else { 5327 previousVideo.videoController( 'stop' ); 5328 } 5329 } 5330 } 5331 }, 5332 5333 // Called when a new slide is selected, 5334 // after the transition animation is complete. 5335 _videoOnGotoSlideComplete: function( event ) { 5336 5337 // Handle the video from the selected slide 5338 if ( this.settings.reachVideoAction === 'playVideo' && event.index === this.selectedSlideIndex ) { 5339 var loadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-video-init]' ), 5340 unloadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-video-preinit]' ); 5341 5342 // If the video was already initialized, play it. If it's not initialized (because 5343 // it's lazy loaded) initialize it and play it. 5344 if ( loadedVideo.length !== 0 ) { 5345 loadedVideo.videoController( 'play' ); 5346 } else if ( unloadedVideo.length !== 0 ) { 5347 unloadedVideo.trigger( 'click.' + NS ); 5348 } 5349 5350 // Autoplay is stopped when the video starts playing 5351 // and the video's 'play' event is fired, but on slower connections, 5352 // the video's playing will be delayed and the 'play' event 5353 // will not fire in time to stop the autoplay, so we'll 5354 // stop it here as well. 5355 if ( ( loadedVideo.length !== 0 || unloadedVideo.length !== 0 ) && this.settings.playVideoAction === 'stopAutoplay' && typeof this.stopAutoplay !== 'undefined' ) { 5356 this.stopAutoplay(); 5357 this.settings.autoplay = false;
5358 } 5359 } 5360 }, 5361 5362 // Destroy the module 5363 destroyVideo: function() { 5364 this.$slider.find( '.sp-video[ data-video-preinit ]' ).each(function() { 5365 var video = $( this ); 5366 video.removeAttr( 'data-video-preinit' ); 5367 video.off( 'click.' + NS ); 5368 }); 5369 5370 // Loop through the all the videos and destroy them 5371 this.$slider.find( '.sp-video[ data-video-init ]' ).each(function() { 5372 var video = $( this ); 5373 video.removeAttr( 'data-video-init' ); 5374 video.off( 'Video' ); 5375 video.videoController( 'destroy' ); 5376 }); 5377 5378 this.off( 'update.' + NS ); 5379 this.off( 'gotoSlide.' + NS ); 5380 this.off( 'gotoSlideComplete.' + NS ); 5381 }, 5382 5383 videoDefaults: { 5384 5385 // Sets the action that the video will perform when its slide container is selected 5386 // ( 'playVideo' and 'none' ) 5387 reachVideoAction: 'none', 5388 5389 // Sets the action that the video will perform when another slide is selected 5390 // ( 'stopVideo', 'pauseVideo', 'removeVideo' and 'none' ) 5391 leaveVideoAction: 'pauseVideo', 5392 5393 // Sets the action that the slider will perform when the video starts playing 5394 // ( 'stopAutoplay' and 'none' ) 5395 playVideoAction: 'stopAutoplay', 5396 5397 // Sets the action that the slider will perform when the video is paused 5398 // ( 'startAutoplay' and 'none' ) 5399 pauseVideoAction: 'none', 5400 5401 // Sets the action that the slider will perform when the video ends 5402 // ( 'startAutoplay', 'nextSlide', 'replayVideo' and 'none' ) 5403 endVideoAction: 'none', 5404 5405 // Called when the video starts playing 5406 videoPlay: function() {}, 5407 5408 // Called when the video is paused 5409 videoPause: function() {}, 5410 5411 // Called when the video ends 5412 videoEnd: function() {} 5413 } 5414 }; 5415 5416 $.SliderPro.addModule( 'Video', Video ); 5417 5418})( window, jQuery ); 5419 5420// Video Controller jQuery plugin 5421// Creates a universal controller for multiple video types and providers 5422;(function( $ ) { 5423 5424 "use strict"; 5425 5426// Check if an iOS device is used. 5427// This information is important because a video can not be 5428// controlled programmatically unless the user has started the video manually. 5429var isIOS = window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ? true : false; 5430 5431var VideoController = function( instance, options ) { 5432 this.$video = $( instance ); 5433 this.options = options; 5434 this.settings = {}; 5435 this.player = null; 5436 5437 this._init(); 5438}; 5439 5440VideoController.prototype = { 5441 5442 _init: function() { 5443 this.settings = $.extend( {}, this.defaults, this.options ); 5444 5445 var that = this, 5446 players = $.VideoController.players, 5447 videoID = this.$video.attr( 'id' ); 5448 5449 // Loop through the available video players 5450 // and check if the targeted video element is supported by one of the players. 5451 // If a compatible type is found, store the video type. 5452 for ( var name in players ) { 5453 if ( typeof players[ name ] !== 'undefined' && players[ name ].isType( this.$video ) ) { 5454 this.player = new players[ name ]( this.$video ); 5455 break; 5456 } 5457 } 5458 5459 // Return if the player could not be instantiated 5460 if ( this.player === null ) { 5461 return; 5462 } 5463 5464 // Add event listeners 5465 var events = [ 'ready', 'start', 'play', 'pause', 'ended' ]; 5466 5467 $.each( events, function( index, element ) { 5468 var event = 'video' + element.charAt( 0 ).toUpperCase() + element.slice( 1 ); 5469 5470 that.player.on( element, function() { 5471 that.trigger({ type: event, video: videoID }); 5472 if ( $.isFunction( that.settings[ event ] ) ) { 5473 that.settings[ event ].call( that, { type: event, video: videoID } ); 5474 } 5475 }); 5476 }); 5477 }, 5478 5479 play: function() { 5480 if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'playing' ) { 5481 return; 5482 } 5483 5484 this.player.play(); 5485 }, 5486 5487 stop: function() { 5488 if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'stopped' ) { 5489 return; 5490 } 5491 5492 this.player.stop(); 5493 }, 5494 5495 pause: function() { 5496 if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'paused' ) { 5497 return; 5498 } 5499 5500 this.player.pause(); 5501 }, 5502 5503 replay: function() { 5504 if ( isIOS === true && this.player.isStarted() === false ) { 5505 return; 5506 } 5507 5508 this.player.replay(); 5509 }, 5510 5511 on: function( type, callback ) { 5512 return this.$video.on( type, callback ); 5513 }, 5514 5515 off: function( type ) { 5516 return this.$video.off( type ); 5517 }, 5518 5519 trigger: function( data ) { 5520 return this.$video.triggerHandler( data ); 5521 }, 5522 5523 destroy: function() { 5524 if ( this.player.isStarted() === true ) { 5525 this.stop(); 5526 } 5527 5528 this.player.off( 'ready' ); 5529 this.player.off( 'start' ); 5530 this.player.off( 'play' ); 5531 this.player.off( 'pause' ); 5532 this.player.off( 'ended' ); 5533 5534 this.$video.removeData( 'videoController' ); 5535 }, 5536 5537 defaults: { 5538 videoReady: function() {}, 5539 videoStart: function() {}, 5540 videoPlay: function() {}, 5541 videoPause: function() {}, 5542 videoEnded: function() {} 5543 } 5544}; 5545 5546$.VideoController = { 5547 players: {}, 5548 5549 addPlayer: function( name, player ) { 5550 this.players[ name ] = player; 5551 } 5552}; 5553 5554$.fn.videoController = function( options ) { 5555 var args = Array.prototype.slice.call( arguments, 1 ); 5556 5557 return this.each(function() { 5558 // Instantiate the video controller or call a function on the current instance 5559 if ( typeof $( this ).data( 'videoController' ) === 'undefined' ) { 5560 var newInstance = new VideoController( this, options ); 5561 5562 // Store a reference to the instance created 5563 $( this ).data( 'videoController', newInstance ); 5564 } else if ( typeof options !== 'undefined' ) { 5565 var currentInstance = $( this ).data( 'videoController' ); 5566 5567 // Check the type of argument passed 5568 if ( typeof currentInstance[ options ] === 'function' ) { 5569 currentInstance[ options ].apply( currentInstance, args ); 5570 } else { 5571 $.error( options + ' does not exist in videoController.' ); 5572 } 5573 } 5574 }); 5575}; 5576 5577// Base object for the video players 5578var Video = function( video ) { 5579 this.$video = video; 5580 this.player = null; 5581 this.ready = false;
5582 this.started = false; 5583 this.state = ''; 5584 this.events = $({}); 5585 5586 this._init(); 5587}; 5588 5589Video.prototype = { 5590 _init: function() {}, 5591 5592 play: function() {}, 5593 5594 pause: function() {}, 5595 5596 stop: function() {}, 5597 5598 replay: function() {}, 5599 5600 isType: function() {}, 5601 5602 isReady: function() { 5603 return this.ready; 5604 }, 5605 5606 isStarted: function() { 5607 return this.started; 5608 }, 5609 5610 getState: function() { 5611 return this.state; 5612 }, 5613 5614 on: function( type, callback ) { 5615 return this.events.on( type, callback ); 5616 }, 5617 5618 off: function( type ) { 5619 return this.events.off( type ); 5620 }, 5621 5622 trigger: function( data ) { 5623 return this.events.triggerHandler( data ); 5624 } 5625}; 5626 5627// YouTube video 5628var YoutubeVideoHelper = { 5629 youtubeAPIAdded: false, 5630 youtubeVideos: [] 5631}; 5632 5633var YoutubeVideo = function( video ) { 5634 this.init = false; 5635 var youtubeAPILoaded = window.YT && window.YT.Player; 5636 5637 if ( typeof youtubeAPILoaded !== 'undefined' ) { 5638 Video.call( this, video ); 5639 } else { 5640 YoutubeVideoHelper.youtubeVideos.push({ 'video': video, 'scope': this }); 5641 5642 if ( YoutubeVideoHelper.youtubeAPIAdded === false ) { 5643 YoutubeVideoHelper.youtubeAPIAdded = true; 5644 5645 var tag = document.createElement( 'script' ); 5646 tag.src = "//www.youtube.com/player_api"; 5647 var firstScriptTag = document.getElementsByTagName( 'script' )[0]; 5648 firstScriptTag.parentNode.insertBefore( tag, firstScriptTag ); 5649 5650 window.onYouTubePlayerAPIReady = function() { 5651 $.each( YoutubeVideoHelper.youtubeVideos, function( index, element ) { 5652 Video.call( element.scope, element.video ); 5653 }); 5654 }; 5655 } 5656 } 5657}; 5658 5659YoutubeVideo.prototype = new Video(); 5660YoutubeVideo.prototype.constructor = YoutubeVideo; 5661$.VideoController.addPlayer( 'YoutubeVideo', YoutubeVideo ); 5662 5663YoutubeVideo.isType = function( video ) { 5664 if ( video.is( 'iframe' ) ) { 5665 var src = video.attr( 'src' ); 5666 5667 if ( src.indexOf( 'youtube.com' ) !== -1 || src.indexOf( 'youtu.be' ) !== -1 ) { 5668 return true; 5669 } 5670 } 5671 5672 return false; 5673}; 5674 5675YoutubeVideo.prototype._init = function() { 5676 this.init = true; 5677 this._setup(); 5678}; 5679 5680YoutubeVideo.prototype._setup = function() { 5681 var that = this; 5682 5683 // Get a reference to the player 5684 this.player = new YT.Player( this.$video[0], { 5685 events: { 5686 'onReady': function() { 5687 that.trigger({ type: 'ready' }); 5688 that.ready = true; 5689 }, 5690 5691 'onStateChange': function( event ) { 5692 switch ( event.data ) { 5693 case YT.PlayerState.PLAYING: 5694 if (that.started === false) { 5695 that.started = true; 5696 that.trigger({ type: 'start' }); 5697 } 5698 5699 that.state = 'playing'; 5700 that.trigger({ type: 'play' }); 5701 break; 5702 5703 case YT.PlayerState.PAUSED: 5704 that.state = 'paused'; 5705 that.trigger({ type: 'pause' }); 5706 break; 5707 5708 case YT.PlayerState.ENDED: 5709 that.state = 'ended'; 5710 that.trigger({ type: 'ended' }); 5711 break; 5712 } 5713 } 5714 } 5715 }); 5716}; 5717 5718YoutubeVideo.prototype.play = function() { 5719 var that = this; 5720 5721 if ( this.ready === true ) { 5722 this.player.playVideo(); 5723 } else { 5724 var timer = setInterval(function() { 5725 if ( that.ready === true ) { 5726 clearInterval( timer ); 5727 that.player.playVideo(); 5728 } 5729 }, 100 ); 5730 } 5731}; 5732 5733YoutubeVideo.prototype.pause = function() { 5734 // On iOS, simply pausing the video can make other videos unresponsive 5735 // so we stop the video instead. 5736 if ( isIOS === true ) { 5737 this.stop(); 5738 } else { 5739 this.player.pauseVideo(); 5740 } 5741}; 5742 5743YoutubeVideo.prototype.stop = function() { 5744 this.player.seekTo( 1 ); 5745 this.player.stopVideo(); 5746 this.state = 'stopped'; 5747}; 5748 5749YoutubeVideo.prototype.replay = function() { 5750 this.player.seekTo( 1 ); 5751 this.player.playVideo(); 5752}; 5753 5754YoutubeVideo.prototype.on = function( type, callback ) { 5755 var that = this; 5756 5757 if ( this.init === true ) { 5758 Video.prototype.on.call( this, type, callback ); 5759 } else { 5760 var timer = setInterval(function() { 5761 if ( that.init === true ) { 5762 clearInterval( timer ); 5763 Video.prototype.on.call( that, type, callback ); 5764 } 5765 }, 100 ); 5766 } 5767}; 5768 5769// Vimeo video 5770var VimeoVideoHelper = { 5771 vimeoAPIAdded: false, 5772 vimeoVideos: [] 5773}; 5774 5775var VimeoVideo = function( video ) { 5776 this.init = false; 5777 5778 if ( typeof window.Vimeo !== 'undefined' ) { 5779 Video.call( this, video ); 5780 } else { 5781 VimeoVideoHelper.vimeoVideos.push({ 'video': video, 'scope': this }); 5782 5783 if ( VimeoVideoHelper.vimeoAPIAdded === false ) { 5784 VimeoVideoHelper.vimeoAPIAdded = true; 5785 5786 var tag = document.createElement('script'); 5787 tag.src = "//player.vimeo.com/api/player.js"; 5788 var firstScriptTag = document.getElementsByTagName( 'script' )[0]; 5789 firstScriptTag.parentNode.insertBefore( tag, firstScriptTag ); 5790 5791 var checkVimeoAPITimer = setInterval(function() { 5792 if ( typeof window.Vimeo !== 'undefined' ) { 5793 clearInterval( checkVimeoAPITimer ); 5794 5795 $.each( VimeoVideoHelper.vimeoVideos, function( index, element ) { 5796 Video.call( element.scope, element.video ); 5797 }); 5798 } 5799 }, 100 ); 5800 } 5801 } 5802}; 5803 5804VimeoVideo.prototype = new Video(); 5805VimeoVideo.prototype.constructor = VimeoVideo; 5806$.VideoController.addPlayer( 'VimeoVideo', VimeoVideo ); 5807 5808VimeoVideo.isType = function( video ) { 5809 if ( video.is( 'iframe' ) ) { 5810 var src = video.attr('src'); 5811 5812 if ( src.indexOf( 'vimeo.com' ) !== -1 ) { 5813 return true; 5814 } 5815 } 5816 5817 return false;
5818}; 5819 5820VimeoVideo.prototype._init = function() { 5821 this.init = true; 5822 this._setup(); 5823}; 5824 5825VimeoVideo.prototype._setup = function() { 5826 var that = this; 5827 5828 // Get a reference to the player 5829 this.player = new Vimeo.Player( this.$video[0] ); 5830 5831 that.ready = true; 5832 that.trigger({ type: 'ready' }); 5833 5834 that.player.on( 'play', function() { 5835 if ( that.started === false ) { 5836 that.started = true; 5837 that.trigger({ type: 'start' }); 5838 } 5839 5840 that.state = 'playing'; 5841 that.trigger({ type: 'play' }); 5842 }); 5843 5844 that.player.on( 'pause', function() { 5845 that.state = 'paused'; 5846 that.trigger({ type: 'pause' }); 5847 }); 5848 5849 that.player.on( 'ended', function() { 5850 that.state = 'ended'; 5851 that.trigger({ type: 'ended' }); 5852 }); 5853}; 5854 5855VimeoVideo.prototype.play = function() { 5856 var that = this; 5857 5858 if ( this.ready === true ) { 5859 this.player.play(); 5860 } else { 5861 var timer = setInterval(function() { 5862 if ( that.ready === true ) { 5863 clearInterval( timer ); 5864 that.player.play(); 5865 } 5866 }, 100 ); 5867 } 5868}; 5869 5870VimeoVideo.prototype.pause = function() { 5871 this.player.pause(); 5872}; 5873 5874VimeoVideo.prototype.stop = function() { 5875 var that = this; 5876 5877 this.player.setCurrentTime( 0 ).then( function() { 5878 that.player.pause(); 5879 that.state = 'stopped'; 5880 } ); 5881}; 5882 5883VimeoVideo.prototype.replay = function() { 5884 var that = this; 5885 5886 this.player.setCurrentTime( 0 ).then( function() { 5887 that.player.play(); 5888 } ); 5889}; 5890 5891VimeoVideo.prototype.on = function( type, callback ) { 5892 var that = this; 5893 5894 if ( this.init === true ) { 5895 Video.prototype.on.call( this, type, callback ); 5896 } else { 5897 var timer = setInterval(function() { 5898 if ( that.init === true ) { 5899 clearInterval( timer ); 5900 Video.prototype.on.call( that, type, callback ); 5901 } 5902 }, 100 ); 5903 } 5904}; 5905 5906// HTML5 video 5907var HTML5Video = function( video ) { 5908 Video.call( this, video ); 5909}; 5910 5911HTML5Video.prototype = new Video(); 5912HTML5Video.prototype.constructor = HTML5Video; 5913$.VideoController.addPlayer( 'HTML5Video', HTML5Video ); 5914 5915HTML5Video.isType = function( video ) { 5916 if ( video.is( 'video' ) && video.hasClass( 'video-js' ) === false && video.hasClass( 'sublime' ) === false ) { 5917 return true; 5918 } 5919 5920 return false; 5921}; 5922 5923HTML5Video.prototype._init = function() { 5924 var that = this; 5925 5926 // Get a reference to the player 5927 this.player = this.$video[0]; 5928 5929 var checkVideoReady = setInterval(function() { 5930 if ( that.player.readyState === 4 ) { 5931 clearInterval( checkVideoReady ); 5932 5933 that.ready = true; 5934 that.trigger({ type: 'ready' }); 5935 5936 that.player.addEventListener( 'play', function() { 5937 if ( that.started === false ) { 5938 that.started = true; 5939 that.trigger({ type: 'start' }); 5940 } 5941 5942 that.state = 'playing'; 5943 that.trigger({ type: 'play' }); 5944 }); 5945 5946 that.player.addEventListener( 'pause', function() { 5947 that.state = 'paused'; 5948 that.trigger({ type: 'pause' }); 5949 }); 5950 5951 that.player.addEventListener( 'ended', function() { 5952 that.state = 'ended'; 5953 that.trigger({ type: 'ended' }); 5954 }); 5955 } 5956 }, 100 ); 5957}; 5958 5959HTML5Video.prototype.play = function() { 5960 var that = this; 5961 5962 if ( this.ready === true ) { 5963 this.player.play(); 5964 } else { 5965 var timer = setInterval(function() { 5966 if ( that.ready === true ) { 5967 clearInterval( timer ); 5968 that.player.play(); 5969 } 5970 }, 100 ); 5971 } 5972}; 5973 5974HTML5Video.prototype.pause = function() { 5975 this.player.pause(); 5976}; 5977 5978HTML5Video.prototype.stop = function() { 5979 this.player.currentTime = 0; 5980 this.player.pause(); 5981 this.state = 'stopped'; 5982}; 5983 5984HTML5Video.prototype.replay = function() { 5985 this.player.currentTime = 0; 5986 this.player.play(); 5987}; 5988 5989// VideoJS video 5990var VideoJSVideo = function( video ) { 5991 Video.call( this, video ); 5992}; 5993 5994VideoJSVideo.prototype = new Video(); 5995VideoJSVideo.prototype.constructor = VideoJSVideo; 5996$.VideoController.addPlayer( 'VideoJSVideo', VideoJSVideo ); 5997 5998VideoJSVideo.isType = function( video ) { 5999 if ( ( typeof video.attr( 'data-videojs-id' ) !== 'undefined' || video.hasClass( 'video-js' ) ) && typeof videojs !== 'undefined' ) { 6000 return true; 6001 } 6002 6003 return false; 6004}; 6005 6006VideoJSVideo.prototype._init = function() { 6007 var that = this, 6008 videoID = this.$video.hasClass( 'video-js' ) ? this.$video.attr( 'id' ) : this.$video.attr( 'data-vi
6008deojs-id' ); 6009 6010 this.player = videojs( videoID ); 6011 6012 this.player.ready(function() { 6013 that.ready = true; 6014 that.trigger({ type: 'ready' }); 6015 6016 that.player.on( 'play', function() { 6017 if ( that.started === false ) { 6018 that.started = true; 6019 that.trigger({ type: 'start' }); 6020 } 6021 6022 that.state = 'playing'; 6023 that.trigger({ type: 'play' }); 6024 }); 6025 6026 that.player.on( 'pause', function() { 6027 that.state = 'paused'; 6028 that.trigger({ type: 'pause' }); 6029 }); 6030 6031 that.player.on( 'ended', function() { 6032 that.state = 'ended'; 6033 that.trigger({ type: 'ended' }); 6034 }); 6035 }); 6036}; 6037 6038VideoJSVideo.prototype.play = function() { 6039 this.player.play(); 6040}; 6041 6042VideoJSVideo.prototype.pause = function() { 6043 this.player.pause(); 6044}; 6045 6046VideoJSVideo.prototype.stop = function() { 6047 this.player.currentTime( 0 ); 6048 this.player.pause(); 6049 this.state = 'stopped'; 6050}; 6051 6052VideoJSVideo.prototype.replay = function() { 6053 this.player.currentTime( 0 ); 6054 this.player.play(); 6055}; 6056 6057// Sublime video 6058var SublimeVideo = function( video ) { 6059 Video.call( this, video ); 6060}; 6061 6062SublimeVideo.prototype = new Video(); 6063SublimeVideo.prototype.constructor = SublimeVideo; 6064$.VideoController.addPlayer( 'SublimeVideo', SublimeVideo ); 6065 6066SublimeVideo.isType = function( video ) { 6067 if ( video.hasClass( 'sublime' ) && typeof sublime !== 'undefined' ) { 6068 return true; 6069 } 6070 6071 return false; 6072}; 6073 6074SublimeVideo.prototype._init = function() { 6075 var that = this; 6076 6077 sublime.ready(function() { 6078 // Get a reference to the player 6079 that.player = sublime.player( that.$video.attr( 'id' ) ); 6080 6081 that.ready = true; 6082 that.trigger({ type: 'ready' }); 6083 6084 that.player.on( 'play', function() { 6085 if ( that.started === false ) { 6086 that.started = true; 6087 that.trigger({ type: 'start' }); 6088 } 6089 6090 that.state = 'playing'; 6091 that.trigger({ type: 'play' }); 6092 }); 6093 6094 that.player.on( 'pause', function() { 6095 that.state = 'paused'; 6096 that.trigger({ type: 'pause' }); 6097 }); 6098 6099 that.player.on( 'stop', function() { 6100 that.state = 'stopped'; 6101 that.trigger({ type: 'stop' }); 6102 }); 6103 6104 that.player.on( 'end', function() { 6105 that.state = 'ended'; 6106 that.trigger({ type: 'ended' }); 6107 }); 6108 }); 6109}; 6110 6111SublimeVideo.prototype.play = function() { 6112 this.player.play(); 6113}; 6114 6115SublimeVideo.prototype.pause = function() { 6116 this.player.pause(); 6117}; 6118 6119SublimeVideo.prototype.stop = function() { 6120 this.player.stop(); 6121}; 6122 6123SublimeVideo.prototype.replay = function() { 6124 this.player.stop(); 6125 this.player.play(); 6126}; 6127 6128// JWPlayer video 6129var JWPlayerVideo = function( video ) { 6130 Video.call( this, video ); 6131}; 6132 6133JWPlayerVideo.prototype = new Video(); 6134JWPlayerVideo.prototype.constructor = JWPlayerVideo; 6135$.VideoController.addPlayer( 'JWPlayerVideo', JWPlayerVideo ); 6136 6137JWPlayerVideo.isType = function( video ) { 6138 if ( ( typeof video.attr( 'data-jwplayer-id' ) !== 'undefined' || video.hasClass( 'jwplayer' ) || video.find( "object[data*='jwplayer']" ).length !== 0 ) && 6139 typeof jwplayer !== 'undefined') { 6140 return true; 6141 } 6142 6143 return false; 6144}; 6145 6146JWPlayerVideo.prototype._init = function() { 6147 var that = this, 6148 videoID; 6149 6150 if ( this.$video.hasClass( 'jwplayer' ) ) { 6151 videoID = this.$video.attr( 'id' ); 6152 } else if ( typeof this.$video.attr( 'data-jwplayer-id' ) !== 'undefined' ) { 6153 videoID = this.$video.attr( 'data-jwplayer-id'); 6154 } else if ( this.$video.find( "object[data*='jwplayer']" ).length !== 0 ) { 6155 videoID = this.$video.find( 'object' ).attr( 'id' ); 6156 } 6157 6158 // Get a reference to the player 6159 this.player = jwplayer( videoID ); 6160 6161 this.player.onReady(function() { 6162 that.ready = true; 6163 that.trigger({ type: 'ready' }); 6164 6165 that.player.onPlay(function() { 6166 if ( that.started === false ) { 6167 that.started = true; 6168 that.trigger({ type: 'start' }); 6169 } 6170 6171 that.state = 'playing'; 6172 that.trigger({ type: 'play' }); 6173 }); 6174 6175 that.player.onPause(function() { 6176 that.state = 'paused'; 6177 that.trigger({ type: 'pause' }); 6178 }); 6179 6180 that.player.onComplete(function() { 6181 that.state = 'ended'; 6182 that.trigger({ type: 'ended' }); 6183 }); 6184 }); 6185}; 6186 6187JWPlayerVideo.prototype.play = function() { 6188 this.player.play( true ); 6189}; 6190 6191JWPlayerVideo.prototype.pause = function() { 6192 this.player.pause( true ); 6193}; 6194 6195JWPlayerVideo.prototype.stop = function() { 6196 this.player.stop(); 6197 this.state = 'stopped'; 6198}; 6199 6200JWPlayerVideo.prototype.replay = function() { 6201 this.player.seek( 0 ); 6202 this.player.play( true ); 6203}; 6204 6205})( jQuery );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.