ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

jquery.backstretch.js (48070B)


      1 /*
      2  * Backstretch
      3  * http://srobbin.com/jquery-plugins/backstretch/
      4  *
      5  * Copyright (c) 2013 Scott Robbin
      6  * Licensed under the MIT license.
      7  */
      8 
      9 ;(function ($, window, undefined) {
     10   'use strict';
     11 
     12   /** @const */
     13   var YOUTUBE_REGEXP = /^.*(youtu\.be\/|youtube\.com\/v\/|youtube\.com\/embed\/|youtube\.com\/watch\?v=|youtube\.com\/watch\?.*\&v=)([^#\&\?]*).*/i;
     14   
     15   /* PLUGIN DEFINITION
     16    * ========================= */
     17 
     18   $.fn.backstretch = function (images, options) {
     19     var args = arguments;
     20 
     21     /*
     22      * Scroll the page one pixel to get the right window height on iOS
     23      * Pretty harmless for everyone else
     24     */
     25     if ($(window).scrollTop() === 0 ) {
     26       window.scrollTo(0, 0);
     27     }
     28 
     29     var returnValues;
     30     
     31     this.each(function (eachIndex) {
     32       var $this = $(this)
     33         , obj = $this.data('backstretch');
     34 
     35       // Do we already have an instance attached to this element?
     36       if (obj) {
     37 
     38         // Is this a method they're trying to execute?
     39         if (typeof args[0] === 'string' &&
     40             typeof obj[args[0]] === 'function') {
     41               
     42           // Call the method
     43           var returnValue = obj[args[0]].apply(obj, Array.prototype.slice.call(args, 1));
     44           if (returnValue === obj) { // If a method is chaining
     45             returnValue = undefined;
     46           }
     47           if (returnValue !== undefined) {
     48             returnValues = returnValues || [];
     49             returnValues[eachIndex] = returnValue;
     50           }
     51           
     52           return; // Nothing further to do
     53         }
     54 
     55         // Merge the old options with the new
     56         options = $.extend(obj.options, options);
     57 
     58         // Remove the old instance
     59         if ( obj.hasOwnProperty('destroy') ) {
     60           obj.destroy(true);
     61         }
     62       }
     63 
     64       // We need at least one image
     65       if (!images || (images && images.length === 0)) {
     66         var cssBackgroundImage = $this.css('background-image');
     67         if (cssBackgroundImage && cssBackgroundImage !== 'none') {
     68           images = [ { url: $this.css('backgroundImage').replace(/url\(|\)|"|'/g,"") } ];
     69         } else {
     70           $.error('No images were supplied for Backstretch, or element must have a CSS-defined background image.');
     71         }
     72       }
     73 
     74       obj = new Backstretch(this, images, options || {});
     75       $this.data('backstretch', obj);
     76     });
     77     
     78     return returnValues ? returnValues.length === 1 ? returnValues[0] : returnValues : this;
     79   };
     80 
     81   // If no element is supplied, we'll attach to body
     82   $.backstretch = function (images, options) {
     83     // Return the instance
     84     return $('body')
     85             .backstretch(images, options)
     86             .data('backstretch');
     87   };
     88 
     89   // Custom selector
     90   $.expr[':'].backstretch = function(elem) {
     91     return $(elem).data('backstretch') !== undefined;
     92   };
     93 
     94   /* DEFAULTS
     95    * ========================= */
     96 
     97   $.fn.backstretch.defaults = {
     98     duration: 5000                // Amount of time in between slides (if slideshow)
     99     , transition: 'fade'          // Type of transition between slides
    100     , transitionDuration: 0       // Duration of transition between slides
    101     , animateFirst: true          // Animate the transition of first image of slideshow in?
    102     , alignX: 0.5                 // The x-alignment for the image, can be 'left'|'center'|'right' or any number between 0.0 and 1.0
    103     , alignY: 0.5                 // The y-alignment for the image, can be 'top'|'center'|'bottom' or any number between 0.0 and 1.0
    104     , paused: false               // Whether the images should slide after given duration
    105     , start: 0                    // Index of the first image to show
    106     , preload: 2                  // How many images preload at a time?
    107     , preloadSize: 1              // How many images can we preload in parallel?
    108     , resolutionRefreshRate: 2500 // How long to wait before switching resolution?
    109     , resolutionChangeRatioThreshold: 0.1 // How much a change should it be before switching resolution?
    110   };
    111 
    112   /* STYLES
    113    *
    114    * Baked-in styles that we'll apply to our elements.
    115    * In an effort to keep the plugin simple, these are not exposed as options.
    116    * That said, anyone can override these in their own stylesheet.
    117    * ========================= */
    118   var styles = {
    119     wrap: {
    120       left: 0
    121       , top: 0
    122       , overflow: 'hidden'
    123       , margin: 0
    124       , padding: 0
    125       , height: '100%'
    126       , width: '100%'
    127       , zIndex: -999999
    128     }
    129     , itemWrapper: {
    130       position: 'absolute'
    131       , display: 'none'
    132       , margin: 0
    133       , padding: 0
    134       , border: 'none'
    135       , width: '100%'
    136       , height: '100%'
    137       , zIndex: -999999
    138     }
    139     , item: {
    140       position: 'absolute'
    141       , margin: 0
    142       , padding: 0
    143       , border: 'none'
    144       , width: '100%'
    145       , height: '100%'
    146       , maxWidth: 'none'
    147     }
    148   };
    149 
    150   /* Given an array of different options for an image,
    151    * choose the optimal image for the container size.
    152    *
    153    * Given an image template (a string with {{ width }} and/or
    154    * {{height}} inside) and a container object, returns the
    155    * image url with the exact values for the size of that
    156    * container.
    157    *
    158    * Returns an array of urls optimized for the specified resolution.
    159    *
    160    */
    161   var optimalSizeImages = (function () {
    162 
    163     /* Sorts the array of image sizes based on width */
    164     var widthInsertSort = function (arr) {
    165       for (var i = 1; i < arr.length; i++) {
    166         var tmp = arr[i],
    167             j = i;
    168         while (arr[j - 1] && parseInt(arr[j - 1].width, 10) > parseInt(tmp.width, 10)) {
    169           arr[j] = arr[j - 1];
    170           --j;
    171         }
    172         arr[j] = tmp;
    173       }
    174 
    175       return arr;
    176     };
    177 
    178     /* Given an array of various sizes of the same image and a container width,
    179      * return the best image.
    180      */
    181     var selectBest = function (containerWidth, containerHeight, imageSizes) {
    182 
    183       var devicePixelRatio = window.devicePixelRatio || 1;
    184       var deviceOrientation = getDeviceOrientation();
    185       var windowOrientation = getWindowOrientation();
    186       var wrapperOrientation = (containerHeight > containerWidth) ?
    187         'portrait' :
    188         (containerWidth > containerHeight ? 'landscape' : 'square');
    189 
    190       var lastAllowedImage = 0;
    191       var testWidth;
    192 
    193       for (var j = 0, image; j < imageSizes.length; j++) {
    194 
    195           image = imageSizes[j];
    196 
    197           // In case a new image was pushed in, process it:
    198           if (typeof image === 'string') {
    199               image = imageSizes[j] = { url: image };
    200           }
    201 
    202           if (image.pixelRatio && image.pixelRatio !== 'auto' && parseFloat(image.pixelRatio) !== devicePixelRatio) {
    203               // We disallowed choosing this image for current device pixel ratio,
    204               // So skip this one.
    205               continue;
    206           }
    207 
    208           if (image.deviceOrientation && image.deviceOrientation !== deviceOrientation) {
    209               // We disallowed choosing this image for current device orientation,
    210               // So skip this one.
    211               continue;
    212           }
    213 
    214           if (image.windowOrientation && image.windowOrientation !== deviceOrientation) {
    215               // We disallowed choosing this image for current window orientation,
    216               // So skip this one.
    217               continue;
    218           }
    219 
    220           if (image.orientation && image.orientation !== wrapperOrientation) {
    221               // We disallowed choosing this image for current element's orientation,
    222               // So skip this one.
    223               continue;
    224           }
    225 
    226           // Mark this one as the last one we investigated
    227           // which does not violate device pixel ratio rules.
    228           // We may choose this one later if there's no match.
    229           lastAllowedImage = j;
    230 
    231           // For most images, we match the specified width against element width,
    232           // And enforcing a limit depending on the "pixelRatio" property if specified.
    233           // But if a pixelRatio="auto", then we consider the width as the physical width of the image,
    234           // And match it while considering the device's pixel ratio.
    235           testWidth = containerWidth;
    236           if (image.pixelRatio === 'auto') {
    237               containerWidth *= devicePixelRatio;
    238           }
    239 
    240           // Stop when the width of the image is larger or equal to the container width
    241           if (image.width >= testWidth) {
    242               break;
    243           }
    244       }
    245 
    246       // Use the image located at where we stopped
    247       return imageSizes[Math.min(j, lastAllowedImage)];
    248     };
    249     
    250     var replaceTagsInUrl = function (url, templateReplacer) {
    251         
    252         if (typeof url === 'string') {
    253             url = url.replace(/{{(width|height)}}/g, templateReplacer);
    254         } else if (url instanceof Array) {
    255             for (var i = 0; i < url.length; i++) {
    256                 if (url[i].src) {
    257                     url[i].src = replaceTagsInUrl(url[i].src, templateReplacer);
    258                 } else {
    259                     url[i] = replaceTagsInUrl(url[i], templateReplacer);
    260                 }
    261             }
    262         }
    263         
    264         return url;
    265     };
    266 
    267     return function ($container, images) {
    268       var containerWidth = $container.width(),
    269           containerHeight = $container.height();
    270 
    271       var chosenImages = [];
    272 
    273       var templateReplacer = function (match, key) {
    274         if (key === 'width') {
    275           return containerWidth;
    276         }
    277         if (key === 'height') {
    278           return containerHeight;
    279         }
    280         return match;
    281       };
    282 
    283       for (var i = 0; i < images.length; i++) {
    284         if ($.isArray(images[i])) {
    285           images[i] = widthInsertSort(images[i]);
    286           var chosen = selectBest(containerWidth, containerHeight, images[i]);
    287           chosenImages.push(chosen);
    288         } else {
    289           // In case a new image was pushed in, process it:
    290           if (typeof images[i] === 'string') {
    291               images[i] = { url: images[i] };
    292           }
    293 
    294           var item = $.extend({}, images[i]);
    295           item.url = replaceTagsInUrl(item.url, templateReplacer);
    296           chosenImages.push(item);
    297         }
    298       }
    299       return chosenImages;
    300     };
    301 
    302   })();
    303   
    304   var isVideoSource = function (source) {
    305     return YOUTUBE_REGEXP.test(source.url) || source.isVideo;
    306   };
    307 
    308   /* Preload images */
    309   var preload = (function (sources, startAt, count, batchSize, callback) {
    310     // Plugin cache
    311     var cache = [];
    312 
    313     // Wrapper for cache
    314     var caching = function(image){
    315       for (var i = 0; i < cache.length; i++) {
    316         if (cache[i].src === image.src) {
    317           return cache[i];
    318         }
    319       }
    320       cache.push(image);
    321       return image;
    322     };
    323 
    324     // Execute callback
    325     var exec = function(sources, callback, last){
    326       if (typeof callback === 'function') {
    327         callback.call(sources, last);
    328       }
    329     };
    330 
    331     // Closure to hide cache
    332     return function preload (sources, startAt, count, batchSize, callback){
    333       // Check input data
    334       if (typeof sources === 'undefined') {
    335         return;
    336       }
    337       if (!$.isArray(sources)) {
    338         sources = [sources];
    339       }
    340 
    341       if (arguments.length < 5 && typeof arguments[arguments.length - 1] === 'function') {
    342         callback = arguments[arguments.length - 1];
    343       }
    344 
    345       startAt = (typeof startAt === 'function' || !startAt) ? 0 : startAt;
    346       count = (typeof count === 'function' || !count || count < 0) ? sources.length : Math.min(count, sources.length);
    347       batchSize = (typeof batchSize === 'function' || !batchSize) ? 1 : batchSize;
    348 
    349       if (startAt >= sources.length) {
    350           startAt = 0;
    351           count = 0;
    352       }
    353       if (batchSize < 0) {
    354           batchSize = count;
    355       }
    356       batchSize = Math.min(batchSize, count);
    357 
    358       var next = sources.slice(startAt + batchSize, count - batchSize);
    359       sources = sources.slice(startAt, batchSize);
    360       count = sources.length;
    361 
    362       // If sources array is empty
    363       if (!count) {
    364         exec(sources, callback, true);
    365         return;
    366       }
    367 
    368       // Image loading callback
    369       var countLoaded = 0;
    370 
    371       var loaded = function() {
    372         countLoaded++;
    373         if (countLoaded !== count) {
    374           return;
    375         }
    376 
    377         exec(sources, callback, !next);
    378         preload(next, 0, 0, batchSize, callback);
    379       };
    380 
    381       // Loop sources to preload
    382       var image;
    383 
    384       for (var i = 0; i < sources.length; i++) {
    385         
    386         if (isVideoSource(sources[i])) {
    387           
    388           // Do not preload videos. There are issues with that.
    389           // First - we need to keep an instance of the preloaded and use that exactly, not a copy.
    390           // Second - there are memory issues.
    391           // If there will be a requirement from users - I'll try to implement this.
    392 
    393           continue;
    394             
    395         } else {
    396       
    397           image = new Image();
    398           image.src = sources[i].url;
    399 
    400           image = caching(image);
    401 
    402           if (image.complete) {
    403             loaded();
    404           } else {
    405             $(image).on('load error', loaded);
    406           }
    407             
    408         }
    409         
    410       }
    411     };
    412   })();
    413 
    414   /* Process images array */
    415   var processImagesArray = function (images) {
    416     var processed = [];
    417     for (var i = 0; i < images.length; i++) {
    418       if (typeof images[i] === 'string') {
    419         processed.push({ url: images[i] });
    420       }
    421       else if ($.isArray(images[i])) {
    422         processed.push(processImagesArray(images[i]));
    423       }
    424       else {
    425         processed.push(processOptions(images[i]));
    426       }
    427     }
    428     return processed;
    429   };
    430 
    431   /* Process options */
    432   var processOptions = function (options, required) {
    433 
    434     // Convert old options
    435 
    436     // centeredX/centeredY are deprecated
    437     if (options.centeredX || options.centeredY) {
    438       if (window.console && window.console.log) {
    439         window.console.log('jquery.backstretch: `centeredX`/`centeredY` is deprecated, please use `alignX`/`alignY`');
    440       }
    441       if (options.centeredX) {
    442         options.alignX = 0.5;
    443       }
    444       if (options.centeredY) {
    445         options.alignY = 0.5;
    446       }
    447     }
    448 
    449     // Deprecated spec
    450     if (options.speed !== undefined) {
    451 
    452       if (window.console && window.console.log) {
    453         window.console.log('jquery.backstretch: `speed` is deprecated, please use `transitionDuration`');
    454       }
    455 
    456       options.transitionDuration = options.speed;
    457       options.transition = 'fade';
    458     }
    459 
    460     // Typo
    461     if (options.resolutionChangeRatioTreshold !== undefined) {
    462       window.console.log('jquery.backstretch: `treshold` is a typo!');
    463       options.resolutionChangeRatioThreshold = options.resolutionChangeRatioTreshold;
    464     }
    465 
    466     // Current spec that needs processing
    467 
    468     if (options.fadeFirst !== undefined) {
    469       options.animateFirst = options.fadeFirst;
    470     }
    471 
    472     if (options.fade !== undefined) {
    473       options.transitionDuration = options.fade;
    474       options.transition = 'fade';
    475     }
    476 
    477     return processAlignOptions(options);
    478   };
    479 
    480   /* Process align options */
    481   var processAlignOptions = function (options, required) {
    482     if (options.alignX === 'left') {
    483       options.alignX = 0.0;
    484     }
    485     else if (options.alignX === 'center') {
    486       options.alignX = 0.5;
    487     }
    488     else if (options.alignX === 'right') {
    489       options.alignX = 1.0;
    490     }
    491     else {
    492       if (options.alignX !== undefined || required) {
    493         options.alignX = parseFloat(options.alignX);
    494         if (isNaN(options.alignX)) {
    495           options.alignX = 0.5;
    496         }
    497       }
    498     }
    499 
    500     if (options.alignY === 'top') {
    501       options.alignY = 0.0;
    502     }
    503     else if (options.alignY === 'center') {
    504       options.alignY = 0.5;
    505     }
    506     else if (options.alignY === 'bottom') {
    507       options.alignY = 1.0;
    508     }
    509     else {
    510       if (options.alignX !== undefined || required) {
    511         options.alignY = parseFloat(options.alignY);
    512         if (isNaN(options.alignY)) {
    513           options.alignY = 0.5;
    514         }
    515       }
    516     }
    517 
    518     return options;
    519   };
    520 
    521   /* CLASS DEFINITION
    522    * ========================= */
    523   var Backstretch = function (container, images, options) {
    524     this.options = $.extend({}, $.fn.backstretch.defaults, options || {});
    525 
    526     this.firstShow = true;
    527 
    528     // Process options
    529     processOptions(this.options, true);
    530 
    531     /* In its simplest form, we allow Backstretch to be called on an image path.
    532      * e.g. $.backstretch('/path/to/image.jpg')
    533      * So, we need to turn this back into an array.
    534      */
    535     this.images = processImagesArray($.isArray(images) ? images : [images]);
    536 
    537     /**
    538      * Paused-Option
    539      */
    540     if (this.options.paused) {
    541         this.paused = true;
    542     }
    543 
    544     /**
    545      * Start-Option (Index)
    546      */
    547     if (this.options.start >= this.images.length)
    548     {
    549         this.options.start = this.images.length - 1;
    550     }
    551     if (this.options.start < 0)
    552     {
    553         this.options.start = 0;
    554     }
    555 
    556     // Convenience reference to know if the container is body.
    557     this.isBody = container === document.body;
    558 
    559     /* We're keeping track of a few different elements
    560      *
    561      * Container: the element that Backstretch was called on.
    562      * Wrap: a DIV that we place the image into, so we can hide the overflow.
    563      * Root: Convenience reference to help calculate the correct height.
    564      */
    565     var $window = $(window);
    566     this.$container = $(container);
    567     this.$root = this.isBody ? supportsFixedPosition ? $window : $(document) : this.$container;
    568 
    569     this.originalImages = this.images;
    570     this.images = optimalSizeImages(
    571         this.options.alwaysTestWindowResolution ? $window : this.$root, 
    572         this.originalImages);
    573 
    574     /**
    575      * Pre-Loading.
    576      * This is the first image, so we will preload a minimum of 1 images.
    577      */
    578     preload(this.images, this.options.start || 0, this.options.preload || 1);
    579 
    580     // Don't create a new wrap if one already exists (from a previous instance of Backstretch)
    581     var $existing = this.$container.children(".backstretch").first();
    582     this.$wrap = $existing.length ? $existing : 
    583         $('<div class="backstretch"></div>')
    584         .css(this.options.bypassCss ? {} : styles.wrap)
    585         .appendTo(this.$container);
    586 
    587     if (!this.options.bypassCss) {
    588         
    589         // Non-body elements need some style adjustments
    590         if (!this.isBody) {
    591           // If the container is statically positioned, we need to make it relative,
    592           // and if no zIndex is defined, we should set it to zero.
    593           var position = this.$container.css('position')
    594             , zIndex = this.$container.css('zIndex');
    595 
    596           this.$container.css({
    597               position: position === 'static' ? 'relative' : position
    598             , zIndex: zIndex === 'auto' ? 0 : zIndex
    599           });
    600 
    601           // Needs a higher z-index
    602           this.$wrap.css({zIndex: -999998});
    603         }
    604 
    605         // Fixed or absolute positioning?
    606         this.$wrap.css({
    607             position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute'
    608         });
    609     
    610     }
    611 
    612     // Set the first image
    613     this.index = this.options.start;
    614     this.show(this.index);
    615 
    616     // Listen for resize
    617     $window.on('resize.backstretch', $.proxy(this.resize, this))
    618            .on('orientationchange.backstretch', $.proxy(function () {
    619               // Need to do this in order to get the right window height
    620               if (this.isBody && window.pageYOffset === 0) {
    621                 window.scrollTo(0, 1);
    622                 this.resize();
    623               }
    624            }, this));
    625   };
    626 
    627   var performTransition = function (options) {
    628 
    629     var transition = options.transition || 'fade';
    630 
    631     // Look for multiple options
    632     if (typeof transition === 'string' && transition.indexOf('|') > -1) {
    633       transition = transition.split('|');
    634     }
    635 
    636     if (transition instanceof Array) {
    637       transition = transition[Math.round(Math.random() * (transition.length - 1))];
    638     }
    639     
    640     var $new = options['new'];
    641     var $old = options['old'] ? options['old'] : $([]);
    642 
    643     switch (transition.toString().toLowerCase()) {
    644 
    645       default:
    646       case 'fade':
    647         $new.fadeIn({
    648           duration: options.duration,
    649           complete: options.complete,
    650           easing: options.easing || undefined
    651         });
    652         break;
    653         
    654       case 'fadeinout':
    655       case 'fade_in_out':
    656             
    657         var fadeInNew = function () {
    658             $new.fadeIn({
    659               duration: options.duration / 2,
    660               complete: options.complete,
    661               easing: options.easing || undefined
    662             });
    663         };
    664         
    665         if ($old.length) {
    666             $old.fadeOut({
    667               duration: options.duration / 2,
    668               complete: fadeInNew,
    669               easing: options.easing || undefined
    670             });
    671         } else {
    672             fadeInNew();
    673         }
    674         
    675         break;
    676 
    677       case 'pushleft':
    678       case 'push_left':
    679       case 'pushright':
    680       case 'push_right':
    681       case 'pushup':
    682       case 'push_up':
    683       case 'pushdown':
    684       case 'push_down':
    685       case 'coverleft':
    686       case 'cover_left':
    687       case 'coverright':
    688       case 'cover_right':
    689       case 'coverup':
    690       case 'cover_up':
    691       case 'coverdown':
    692       case 'cover_down':
    693 
    694         var transitionParts = transition.match(/^(cover|push)_?(.*)$/);
    695 
    696         var animProp = transitionParts[2] === 'left' ? 'right' :
    697             transitionParts[2] === 'right' ? 'left' :
    698                 transitionParts[2] === 'down' ? 'top' :
    699                     transitionParts[2] === 'up' ? 'bottom' :
    700                         'right';
    701 
    702         var newCssStart = {
    703           'display': ''
    704         }, newCssAnim = {};
    705         newCssStart[animProp] = '-100%';
    706         newCssAnim[animProp] = 0;
    707 
    708         $new
    709             .css(newCssStart)
    710             .animate(newCssAnim, {
    711               duration: options.duration,
    712               complete: function () {
    713                 $new.css(animProp, '');
    714                 options.complete.apply(this, arguments);
    715               },
    716               easing: options.easing || undefined
    717             });
    718 
    719         if (transitionParts[1] === 'push' && $old.length) {
    720             var oldCssAnim = {};
    721             oldCssAnim[animProp] = '100%';
    722 
    723             $old
    724                 .animate(oldCssAnim, {
    725                   duration: options.duration,
    726                   complete: function () {
    727                     $old.css('display', 'none');
    728                   },
    729                   easing: options.easing || undefined
    730                 });
    731         }
    732 
    733         break;
    734     }
    735 
    736   };
    737 
    738   /* PUBLIC METHODS
    739    * ========================= */
    740   Backstretch.prototype = {
    741 
    742       resize: function () {
    743         try {
    744 
    745           // Check for a better suited image after the resize
    746           var $resTest = this.options.alwaysTestWindowResolution ? $(window) : this.$root;
    747           var newContainerWidth = $resTest.width();
    748           var newContainerHeight = $resTest.height();
    749           var changeRatioW = newContainerWidth / (this._lastResizeContainerWidth || 0);
    750           var changeRatioH = newContainerHeight / (this._lastResizeContainerHeight || 0);
    751           var resolutionChangeRatioThreshold = this.options.resolutionChangeRatioThreshold || 0.0;
    752 
    753           // check for big changes in container size
    754           if ((newContainerWidth !== this._lastResizeContainerWidth ||
    755                newContainerHeight !== this._lastResizeContainerHeight) &&
    756               ((Math.abs(changeRatioW - 1) >= resolutionChangeRatioThreshold || isNaN(changeRatioW)) ||
    757               (Math.abs(changeRatioH - 1) >= resolutionChangeRatioThreshold || isNaN(changeRatioH)))) {
    758 
    759             this._lastResizeContainerWidth = newContainerWidth;
    760             this._lastResizeContainerHeight = newContainerHeight;
    761 
    762             // Big change: rebuild the entire images array
    763             this.images = optimalSizeImages($resTest, this.originalImages);
    764 
    765             // Preload them (they will be automatically inserted on the next cycle)
    766             if (this.options.preload) {
    767               preload(this.images, (this.index + 1) % this.images.length, this.options.preload);
    768             }
    769 
    770             // In case there is no cycle and the new source is different than the current
    771             if (this.images.length === 1 &&
    772                 this._currentImage.url !== this.images[0].url) {
    773 
    774               // Wait a little an update the image being showed
    775               var that = this;
    776               clearTimeout(that._selectAnotherResolutionTimeout);
    777               that._selectAnotherResolutionTimeout = setTimeout(function () {
    778                 that.show(0);
    779               }, this.options.resolutionRefreshRate);
    780             }
    781           }
    782 
    783           var bgCSS = {left: 0, top: 0, right: 'auto', bottom: 'auto'}
    784             , rootWidth = this.isBody ? this.$root.width() : this.$root.innerWidth()
    785             , rootHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight()
    786             , bgWidth = rootWidth
    787             , bgHeight = bgWidth / this.$itemWrapper.data('ratio')
    788             , evt = $.Event('backstretch.resize', {
    789               relatedTarget: this.$container[0]
    790             })
    791             , bgOffset
    792             , alignX = this._currentImage.alignX === undefined ? this.options.alignX : this._currentImage.alignX
    793             , alignY = this._currentImage.alignY === undefined ? this.options.alignY : this._currentImage.alignY;
    794 
    795             // Make adjustments based on image ratio
    796             if (bgHeight >= rootHeight) {
    797                 bgCSS.top = -(bgHeight - rootHeight) * alignY;
    798             } else {
    799                 bgHeight = rootHeight;
    800                 bgWidth = bgHeight * this.$itemWrapper.data('ratio');
    801                 bgOffset = (bgWidth - rootWidth) / 2;
    802                 bgCSS.left = -(bgWidth - rootWidth) * alignX;
    803             }
    804 
    805             if (!this.options.bypassCss) {
    806 
    807                 this.$wrap
    808                     .css({width: rootWidth, height: rootHeight})
    809                     .find('>.backstretch-item').not('.deleteable')
    810                     .each(function () {
    811                         var $wrapper = $(this);
    812                         $wrapper.find('img,video,iframe')
    813                                 .css({width: bgWidth, height: bgHeight})
    814                                 .css(bgCSS);
    815                     });
    816             }
    817 
    818             this.$container.trigger(evt, this);
    819         } catch(err) {
    820             // IE7 seems to trigger resize before the image is loaded.
    821             // This try/catch block is a hack to let it fail gracefully.
    822         }
    823 
    824         return this;
    825       }
    826 
    827       // Show the slide at a certain position
    828     , show: function (newIndex, overrideOptions) {
    829 
    830         // Validate index
    831         if (Math.abs(newIndex) > this.images.length - 1) {
    832           return;
    833         }
    834 
    835         // Vars
    836         var that = this
    837           , $oldItemWrapper = that.$wrap.find('>.backstretch-item').addClass('deleteable')
    838           , oldVideoWrapper = that.videoWrapper
    839           , evtOptions = { relatedTarget: that.$container[0] };
    840 
    841         // Trigger the "before" event
    842         that.$container.trigger($.Event('backstretch.before', evtOptions), [that, newIndex]);
    843 
    844         // Set the new frame index
    845         this.index = newIndex;
    846         var selectedImage = that.images[newIndex];
    847 
    848         // Pause the slideshow
    849         clearTimeout(that._cycleTimeout);
    850 
    851         // New image
    852 
    853         delete that.videoWrapper; // Current item may not be a video
    854 
    855         var isVideo = isVideoSource(selectedImage);
    856         if (isVideo) {
    857           that.videoWrapper = new VideoWrapper(selectedImage);
    858           that.$item = that.videoWrapper.$video.css('pointer-events', 'none');
    859         } else {
    860           that.$item = $('<img />');
    861         }
    862 
    863         that.$itemWrapper = $('<div class="backstretch-item">')
    864             .append(that.$item);
    865 
    866         if (this.options.bypassCss) {
    867             that.$itemWrapper.css({
    868               'display': 'none'
    869             });
    870         } else {
    871           that.$itemWrapper.css(styles.itemWrapper);
    872           that.$item.css(styles.item);
    873         }
    874 
    875         that.$item.bind(isVideo ? 'canplay' : 'load', function (e) {
    876             var $this = $(this)
    877               , $wrapper = $this.parent()
    878               , options = $wrapper.data('options');
    879               
    880             if (overrideOptions) {
    881               options = $.extend({}, options, overrideOptions);
    882             }
    883 
    884             var imgWidth = this.naturalWidth || this.videoWidth || this.width
    885               , imgHeight = this.naturalHeight || this.videoHeight || this.height;
    886 
    887             // Save the ratio
    888             $wrapper.data('ratio', imgWidth / imgHeight);
    889 
    890             var getOption = function (opt) {
    891               return options[opt] !== undefined ?
    892                 options[opt] :
    893                 that.options[opt];
    894             };
    895 
    896             var transition = getOption('transition');
    897             var transitionEasing = getOption('transitionEasing');
    898             var transitionDuration = getOption('transitionDuration');
    899 
    900             // Show the image, then delete the old one
    901             var bringInNextImage = function () {
    902               
    903               if (oldVideoWrapper) {
    904                 oldVideoWrapper.stop();
    905                 oldVideoWrapper.destroy();
    906               }
    907               
    908               $oldItemWrapper.remove();
    909 
    910               // Resume the slideshow
    911               if (!that.paused && that.images.length > 1) {
    912                 that.cycle();
    913               }
    914 
    915               // Now we can clear the background on the element, to spare memory
    916               if (!that.options.bypassCss && !that.isBody) {
    917                 that.$container.css('background-image', 'none');
    918               }
    919 
    920               // Trigger the "after" and "show" events
    921               // "show" is being deprecated
    922               $(['after', 'show']).each(function () {
    923                 that.$container.trigger($.Event('backstretch.' + this, evtOptions), [that, newIndex]);
    924               });
    925               
    926               if (isVideo) {
    927                 that.videoWrapper.play();
    928               }
    929             };
    930 
    931             if ((that.firstShow && !that.options.animateFirst) || !transitionDuration || !transition) {
    932                 // Avoid transition on first show or if there's no transitionDuration value
    933                 $wrapper.show();
    934                 bringInNextImage();
    935             } else {
    936 
    937                 performTransition({
    938                     'new': $wrapper,
    939                     old: $oldItemWrapper,
    940                     transition: transition,
    941                     duration: transitionDuration,
    942                     easing: transitionEasing,
    943                     complete: bringInNextImage
    944                 });
    945 
    946             }
    947 
    948             that.firstShow = false;
    949 
    950             // Resize
    951             that.resize();
    952         });
    953 
    954         that.$itemWrapper.appendTo(that.$wrap);
    955 
    956         that.$item.attr('alt', selectedImage.alt || '');
    957         that.$itemWrapper.data('options', selectedImage);
    958 
    959         if (!isVideo) {
    960           that.$item.attr('src', selectedImage.url);
    961         }
    962         
    963         that._currentImage = selectedImage;
    964 
    965         return that;
    966       }
    967 
    968     , current: function () {
    969         return this.index;
    970       }
    971 
    972     , next: function () {
    973         var args = Array.prototype.slice.call(arguments, 0);
    974         args.unshift(this.index < this.images.length - 1 ? this.index + 1 : 0);
    975         return this.show.apply(this, args);
    976       }
    977 
    978     , prev: function () {
    979         var args = Array.prototype.slice.call(arguments, 0);
    980         args.unshift(this.index === 0 ? this.images.length - 1 : this.index - 1);
    981         return this.show.apply(this, args);
    982       }
    983 
    984     , pause: function () {
    985         // Pause the slideshow
    986         this.paused = true;
    987         
    988         if (this.videoWrapper) {
    989           this.videoWrapper.pause();
    990         }
    991         
    992         return this;
    993       }
    994 
    995     , resume: function () {
    996         // Resume the slideshow
    997         this.paused = false;
    998         
    999         if (this.videoWrapper) {
   1000           this.videoWrapper.play();
   1001         }
   1002         
   1003         this.cycle();
   1004         return this;
   1005       }
   1006 
   1007     , cycle: function () {
   1008         // Start/resume the slideshow
   1009         if(this.images.length > 1) {
   1010           // Clear the timeout, just in case
   1011           clearTimeout(this._cycleTimeout);
   1012 
   1013           var duration = (this._currentImage && this._currentImage.duration) || this.options.duration;
   1014           var isVideo = isVideoSource(this._currentImage);
   1015           
   1016           var callNext = function () {
   1017             this.$item.off('.cycle');
   1018             
   1019             // Check for paused slideshow
   1020             if (!this.paused) {
   1021               this.next();
   1022             }
   1023           };
   1024 
   1025           // Special video handling
   1026           if (isVideo) {
   1027 
   1028             // Leave video at last frame
   1029             if (!this._currentImage.loop) {
   1030               var lastFrameTimeout = 0;
   1031 
   1032               this.$item
   1033                 .on('playing.cycle', function () {
   1034                   var player = $(this).data('player');
   1035 
   1036                   clearTimeout(lastFrameTimeout);
   1037                   lastFrameTimeout = setTimeout(function () {
   1038                     player.pause();
   1039                     player.$video.trigger('ended');
   1040                   }, (player.getDuration() - player.getCurrentTime()) * 1000);
   1041                 })
   1042                 .on('ended.cycle', function () {
   1043                   clearTimeout(lastFrameTimeout);
   1044                 });
   1045             }
   1046 
   1047             // On error go to next
   1048             this.$item.on('error.cycle initerror.cycle', $.proxy(callNext, this));
   1049           }
   1050 
   1051           if (isVideo && !this._currentImage.duration) {
   1052             // It's a video - playing until end
   1053             this.$item.on('ended.cycle', $.proxy(callNext, this));
   1054             
   1055           } else {
   1056             // Cycling according to specified duration
   1057             this._cycleTimeout = setTimeout($.proxy(callNext, this), duration);
   1058           }
   1059           
   1060         }
   1061         return this;
   1062       }
   1063 
   1064     , destroy: function (preserveBackground) {
   1065         // Stop the resize events
   1066         $(window).off('resize.backstretch orientationchange.backstretch');
   1067 
   1068         // Stop any videos
   1069         if (this.videoWrapper) {
   1070           this.videoWrapper.destroy();
   1071         }
   1072         
   1073         // Clear the timeout
   1074         clearTimeout(this._cycleTimeout);
   1075 
   1076         // Remove Backstretch
   1077         if(!preserveBackground) {
   1078           this.$wrap.remove();
   1079         }
   1080         this.$container.removeData('backstretch');
   1081       }
   1082   };
   1083     
   1084  /**
   1085   * Video Abstraction Layer
   1086   *
   1087   * Static methods:
   1088   * > VideoWrapper.loadYoutubeAPI() -> Call in order to load the Youtube API. 
   1089   *                                   An 'youtube_api_load' event will be triggered on $(window) when the API is loaded.
   1090   *
   1091   * Generic:
   1092   * > player.type -> type of the video
   1093   * > player.video / player.$video -> contains the element holding the video
   1094   * > player.play() -> plays the video
   1095   * > player.pause() -> pauses the video
   1096   * > player.setCurrentTime(position) -> seeks to a position by seconds
   1097   * 
   1098   * Youtube:
   1099   * > player.ytId will contain the youtube ID if the source is a youtube url
   1100   * > player.ytReady is a flag telling whether the youtube source is ready for playback
   1101   * */
   1102 
   1103   var VideoWrapper = function () { this.init.apply(this, arguments); };
   1104 
   1105   /**
   1106    * @param {Object} options
   1107    * @param {String|Array<String>|Array<{{src: String, type: String?}}>} options.url
   1108    * @param {Boolean} options.loop=false
   1109    * @param {Boolean?} options.mute=true
   1110    * @param {String?} options.poster
   1111    * loop, mute, poster
   1112    */
   1113   VideoWrapper.prototype.init = function (options) {
   1114 
   1115     var that = this;
   1116     
   1117     var $video;
   1118 
   1119     var setVideoElement = function () {
   1120       that.$video = $video;
   1121       that.video = $video[0];
   1122     };
   1123     
   1124     // Determine video type
   1125     
   1126     var videoType = 'video';
   1127     
   1128     if (!(options.url instanceof Array) &&
   1129       YOUTUBE_REGEXP.test(options.url)) {
   1130       videoType = 'youtube';
   1131     }
   1132     
   1133     that.type = videoType;
   1134 
   1135     if (videoType === 'youtube') {
   1136 
   1137       // Try to load the API in the meantime
   1138       VideoWrapper.loadYoutubeAPI();
   1139 
   1140       that.ytId = options.url.match(YOUTUBE_REGEXP)[2];
   1141       var src = 'https://www.youtube.com/embed/' + that.ytId +
   1142         '?rel=0&autoplay=0&showinfo=0&controls=0&modestbranding=1' +
   1143         '&cc_load_policy=0&disablekb=1&iv_load_policy=3&loop=0' +
   1144         '&enablejsapi=1&origin=' + encodeURIComponent(window.location.origin);
   1145 
   1146       that.__ytStartMuted = !!options.mute || options.mute === undefined;
   1147 
   1148       $video = $('<iframe />')
   1149         .attr({ 'src_to_load': src })
   1150         .css({ 'border': 0, 'margin': 0, 'padding': 0 })
   1151         .data('player', that);
   1152         
   1153       if (options.loop) {
   1154         $video.on('ended.loop', function () {
   1155           if (!that.__manuallyStopped) {
   1156            that.play();
   1157           }
   1158         });
   1159       }
   1160 
   1161       that.ytReady = false;
   1162 
   1163       setVideoElement();
   1164 
   1165       if (window['YT']) {
   1166         that._initYoutube();
   1167         $video.trigger('initsuccess');
   1168       } else {
   1169         $(window).one('youtube_api_load', function () {
   1170           that._initYoutube();
   1171           $video.trigger('initsuccess');
   1172         });
   1173       }
   1174       
   1175     }
   1176     else {
   1177       // Traditional <video> tag with multiple sources
   1178       
   1179       $video = $('<video>')
   1180         .prop('autoplay', false)
   1181         .prop('controls', false)
   1182         .prop('loop', !!options.loop)
   1183         .prop('muted', !!options.mute || options.mute === undefined)
   1184         
   1185         // Let the first frames be available before playback, as we do transitions
   1186         .prop('preload', 'auto')
   1187         .prop('poster', options.poster || '');
   1188         
   1189       var sources = (options.url instanceof Array) ? options.url : [options.url];
   1190 
   1191       for (var i = 0; i < sources.length; i++) {
   1192         var sourceItem = sources[i];
   1193         if (typeof(sourceItem) === 'string') {
   1194           sourceItem = { src: sourceItem };
   1195         }
   1196         $('<source>')
   1197           .attr('src', sourceItem.src)
   1198           // Make sure to not specify type if unknown - 
   1199           //   so the browser will try to autodetect.
   1200           .attr('type', sourceItem.type || null)
   1201           .appendTo($video);
   1202       }
   1203       
   1204       if (!$video[0].canPlayType || !sources.length) {
   1205         $video.trigger('initerror');
   1206       } else {
   1207         $video.trigger('initsuccess');
   1208       }
   1209 
   1210       setVideoElement();
   1211     }
   1212 
   1213   };
   1214 
   1215   VideoWrapper.prototype._initYoutube = function () {
   1216     var that = this;
   1217     
   1218     var YT = window['YT'];
   1219 
   1220     that.$video
   1221       .attr('src', that.$video.attr('src_to_load'))
   1222       .removeAttr('src_to_load');
   1223 
   1224     // It won't init if it's not in the DOM, so we emulate that
   1225     var hasParent = !!that.$video[0].parentNode;
   1226     if (!hasParent) {
   1227       var $tmpParent = $('<div>').css('display', 'none !important').appendTo(document.body);
   1228       that.$video.appendTo($tmpParent);
   1229     }
   1230 
   1231     var player = new YT.Player(that.video, {
   1232       events: {
   1233         'onReady': function () {
   1234 
   1235           if (that.__ytStartMuted) {
   1236             player.mute();
   1237           }
   1238 
   1239           if (!hasParent) {
   1240             // Restore parent to old state - without interrupting any changes
   1241             if (that.$video[0].parentNode === $tmpParent[0]) {
   1242               that.$video.detach();
   1243             }
   1244             $tmpParent.remove();
   1245           }
   1246 
   1247           that.ytReady = true;
   1248           that._updateYoutubeSize();
   1249           that.$video.trigger('canplay');
   1250         },
   1251         'onStateChange': function (event) {
   1252           switch (event.data) {
   1253             case YT.PlayerState.PLAYING:
   1254               that.$video.trigger('playing');
   1255               break;
   1256             case YT.PlayerState.ENDED:
   1257               that.$video.trigger('ended');
   1258               break;
   1259             case YT.PlayerState.PAUSED:
   1260               that.$video.trigger('pause');
   1261               break;
   1262             case YT.PlayerState.BUFFERING:
   1263               that.$video.trigger('waiting');
   1264               break;
   1265             case YT.PlayerState.CUED:
   1266               that.$video.trigger('canplay');
   1267               break;
   1268           }
   1269         },
   1270         'onPlaybackQualityChange': function () {
   1271           that._updateYoutubeSize();
   1272           that.$video.trigger('resize');
   1273         },
   1274         'onError': function (err) {
   1275           that.hasError = true;
   1276           that.$video.trigger({ 'type': 'error', 'error': err });
   1277         }
   1278       }
   1279     });
   1280 
   1281     that.ytPlayer = player;
   1282 
   1283     return that;
   1284   };    
   1285     
   1286   VideoWrapper.prototype._updateYoutubeSize = function () {
   1287     var that = this;
   1288 
   1289     switch (that.ytPlayer.getPlaybackQuality() || 'medium') {
   1290       case 'small':
   1291         that.video.videoWidth = 426;
   1292         that.video.videoHeight = 240;
   1293         break;
   1294       case 'medium':
   1295         that.video.videoWidth = 640;
   1296         that.video.videoHeight = 360;
   1297         break;
   1298       default:
   1299       case 'large':
   1300         that.video.videoWidth = 854;
   1301         that.video.videoHeight = 480;
   1302         break;
   1303       case 'hd720':
   1304         that.video.videoWidth = 1280;
   1305         that.video.videoHeight = 720;
   1306         break;
   1307       case 'hd1080':
   1308         that.video.videoWidth = 1920;
   1309         that.video.videoHeight = 1080;
   1310         break;
   1311       case 'highres':
   1312         that.video.videoWidth = 2560;
   1313         that.video.videoHeight = 1440;
   1314         break;
   1315     }
   1316 
   1317     return that;
   1318   };
   1319 
   1320   VideoWrapper.prototype.play = function () {
   1321     var that = this;
   1322 
   1323     that.__manuallyStopped = false;
   1324     
   1325     if (that.type === 'youtube') {
   1326       if (that.ytReady) {
   1327         that.$video.trigger('play');
   1328         that.ytPlayer.playVideo();
   1329       }
   1330     } else {
   1331       that.video.play();
   1332     }
   1333 
   1334     return that;
   1335   };
   1336 
   1337   VideoWrapper.prototype.pause = function () {
   1338     var that = this;
   1339 
   1340     that.__manuallyStopped = false;
   1341     
   1342     if (that.type === 'youtube') {
   1343       if (that.ytReady) {
   1344         that.ytPlayer.pauseVideo();
   1345       }
   1346     } else {
   1347       that.video.pause();
   1348     }
   1349 
   1350     return that;
   1351   };
   1352 
   1353   VideoWrapper.prototype.stop = function () {
   1354     var that = this;
   1355 
   1356     that.__manuallyStopped = true;
   1357     
   1358     if (that.type === 'youtube') {
   1359       if (that.ytReady) {
   1360         that.ytPlayer.pauseVideo();
   1361         that.ytPlayer.seekTo(0);
   1362       }
   1363     } else {
   1364       that.video.pause();
   1365       that.video.currentTime = 0;
   1366     }
   1367 
   1368     return that;
   1369   };
   1370 
   1371   VideoWrapper.prototype.destroy = function () {
   1372     var that = this;
   1373 
   1374     if (that.ytPlayer) {
   1375       that.ytPlayer.destroy();
   1376     }
   1377 
   1378     that.$video.remove();
   1379 
   1380     return that;
   1381   };
   1382 
   1383   VideoWrapper.prototype.getCurrentTime = function (seconds) {
   1384     var that = this;
   1385 
   1386     if (that.type === 'youtube') {
   1387       if (that.ytReady) {
   1388         return that.ytPlayer.getCurrentTime();
   1389       }
   1390     } else {
   1391       return that.video.currentTime;
   1392     }
   1393 
   1394     return 0;
   1395   };
   1396 
   1397   VideoWrapper.prototype.setCurrentTime = function (seconds) {
   1398     var that = this;
   1399 
   1400     if (that.type === 'youtube') {
   1401       if (that.ytReady) {
   1402         that.ytPlayer.seekTo(seconds, true);
   1403       }
   1404     } else {
   1405       that.video.currentTime = seconds;
   1406     }
   1407 
   1408     return that;
   1409   };
   1410 
   1411   VideoWrapper.prototype.getDuration = function () {
   1412     var that = this;
   1413 
   1414     if (that.type === 'youtube') {
   1415       if (that.ytReady) {
   1416         return that.ytPlayer.getDuration();
   1417       }
   1418     } else {
   1419       return that.video.duration;
   1420     }
   1421 
   1422     return 0;
   1423   };
   1424 
   1425   /**
   1426    * This will load the youtube API (if not loaded yet)
   1427    * Use $(window).one('youtube_api_load', ...) to listen for API loaded event
   1428    */
   1429   VideoWrapper.loadYoutubeAPI = function () {
   1430     if (window['YT']) {
   1431       return;
   1432     }
   1433     if (!$('script[src*=www\\.youtube\\.com\\/iframe_api]').length) {
   1434       $('<script type="text/javascript" src="https://www.youtube.com/iframe_api">').appendTo('body');
   1435     }
   1436     var ytAPILoadInt = setInterval(function () {
   1437       if (window['YT'] && window['YT'].loaded) {
   1438         $(window).trigger('youtube_api_load');
   1439         clearTimeout(ytAPILoadInt);
   1440       }
   1441     }, 50);
   1442   };
   1443 
   1444   var getDeviceOrientation = function () {
   1445 
   1446     if ('matchMedia' in window) {
   1447       if (window.matchMedia("(orientation: portrait)").matches) {
   1448          return 'portrait';
   1449       } else if (window.matchMedia("(orientation: landscape)").matches) {
   1450          return 'landscape';
   1451       }
   1452     }
   1453 
   1454     if (screen.height > screen.width) {
   1455       return 'portrait';
   1456     }
   1457 
   1458     // Even square devices have orientation,
   1459     //   but a desktop browser may be too old for `matchMedia`.
   1460     // Defaulting to `landscape` for the VERY rare case of a square desktop screen is good enough.
   1461     return 'landscape';
   1462   };
   1463 
   1464   var getWindowOrientation = function () {
   1465     if (window.innerHeight > window.innerWidth) {
   1466       return 'portrait';
   1467     }
   1468     if (window.innerWidth > window.innerHeight) {
   1469       return 'landscape';
   1470     }
   1471 
   1472     return 'square';
   1473   };
   1474 
   1475   /* SUPPORTS FIXED POSITION?
   1476    *
   1477    * Based on code from jQuery Mobile 1.1.0
   1478    * http://jquerymobile.com/
   1479    *
   1480    * In a nutshell, we need to figure out if fixed positioning is supported.
   1481    * Unfortunately, this is very difficult to do on iOS, and usually involves
   1482    * injecting content, scrolling the page, etc.. It's ugly.
   1483    * jQuery Mobile uses this workaround. It's not ideal, but works.
   1484    *
   1485    * Modified to detect IE6
   1486    * ========================= */
   1487 
   1488   var supportsFixedPosition = (function () {
   1489     var ua = navigator.userAgent
   1490       , platform = navigator.platform
   1491         // Rendering engine is Webkit, and capture major version
   1492       , wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ )
   1493       , wkversion = !!wkmatch && wkmatch[ 1 ]
   1494       , ffmatch = ua.match( /Fennec\/([0-9]+)/ )
   1495       , ffversion = !!ffmatch && ffmatch[ 1 ]
   1496       , operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ )
   1497       , omversion = !!operammobilematch && operammobilematch[ 1 ]
   1498       , iematch = ua.match( /MSIE ([0-9]+)/ )
   1499       , ieversion = !!iematch && iematch[ 1 ];
   1500 
   1501     return !(
   1502       // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
   1503       ((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1  || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) ||
   1504 
   1505       // Opera Mini
   1506       (window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") ||
   1507       (operammobilematch && omversion < 7458) ||
   1508 
   1509       //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
   1510       (ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) ||
   1511 
   1512       // Firefox Mobile before 6.0 -
   1513       (ffversion && ffversion < 6) ||
   1514 
   1515       // WebOS less than 3
   1516       ("palmGetResource" in window && wkversion && wkversion < 534) ||
   1517 
   1518       // MeeGo
   1519       (ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) ||
   1520 
   1521       // IE6
   1522       (ieversion && ieversion <= 6)
   1523     );
   1524   }());
   1525 
   1526 }(jQuery, window));