PageSourceSearch

https://guduk.co.kr/theme/theme_t34/js/jquery.backstretch.js

js guduk.co.kr collected 2026-10-08 11:01:16 UTC 47,922 bytes, 1,576 lines download raw bytes

vendor: 47,602 bytes, lines 1-1562
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    if (options.scale) {
478      options.scale = validScale(options.scale);
479    }
480    
481    return processAlignOptions(options);
482  };
483
484  /* Process align options */
485  var processAlignOptions = function (options, required) {
486    if (options.alignX === 'left') {
487      options.alignX = 0.0;
488    }
489    else if (options.alignX === 'center') {
490      options.alignX = 0.5;
491    }
492    else if (options.alignX === 'right') {
493      options.alignX = 1.0;
494    }
495    else {
496      if (options.alignX !== undefined || required) {
497        options.alignX = parseFloat(options.alignX);
498        if (isNaN(options.alignX)) {
499          options.alignX = 0.5;
500        }
501      }
502    }
503
504    if (options.alignY === 'top') {
505      options.alignY = 0.0;
506    }
507    else if (options.alignY === 'center') {
508      options.alignY = 0.5;
509    }
510    else if (options.alignY === 'bottom') {
511      options.alignY = 1.0;
512    }
513    else {
514      if (options.alignX !== undefined || required) {
515        options.alignY = parseFloat(options.alignY);
516        if (isNaN(options.alignY)) {
517          options.alignY = 0.5;
518        }
519      }
520    }
521
522    return options;
523  };
524
525  var SUPPORTED_SCALE_OPTIONS = {
526      'cover': 'cover',
527      'fit': 'fit',
528      'fit-smaller': 'fit-smaller',
529      'fill': 'fill'
530  };
531  
532  function validScale(scale) {
533    if (!SUPPORTED_SCALE_OPTIONS.hasOwnProperty(scale)) {
534      return 'cover';
535    }
536    return scale;
537  }
538  
539  /* CLASS DEFINITION
540   * ========================= */
541  var Backstretch = function (container, images, options) {
542    this.options = $.extend({}, $.fn.backstretch.defaults, options || {});
543
544    this.firstShow = true;
545
546    // Process options
547    processOptions(this.options, true);
548
549    /* In its simplest form, we allow Backstretch to be called on an image path.
550     * e.g. $.backstretch('/path/to/image.jpg')
551     * So, we need to turn this back into an array.
552     */
553    this.images = processImagesArray($.isArray(images) ? images : [images]);
554
555    /**
556     * Paused-Option
557     */
558    if (this.options.paused) {
559        this.paused = true;
560    }
561
562    /**
563     * Start-Option (Index)
564     */
565    if (this.options.start >= this.images.length)
566    {
567        this.options.start = this.images.length - 1;
568    }
569    if (this.options.start < 0)
570    {
571        this.options.start = 0;
572    }
573
574    // Convenience reference to know if the container is body.
575    this.isBody = container === document.body;
576
577    /* We're keeping track of a few different elements
578     *
579     * Container: the element that Backstretch was called on.
580     * Wrap: a DIV that we place the image into, so we can hide the overflow.
581     * Root: Convenience reference to help calculate the correct height.
582     */
583    var $window = $(window);
584    this.$container = $(container);
585    this.$root = this.isBody ? supportsFixedPosition ? $window : $(document) : this.$container;
586
587    this.originalImages = this.images;
588    this.images = optimalSizeImages(
589        this.options.alwaysTestWindowResolution ? $window : this.$root, 
590        this.originalImages);
591
592    /**
593     * Pre-Loading.
594     * This is the first image, so we will preload a minimum of 1 images.
595     */
596    preload(this.images, this.options.start || 0, this.options.preload || 1);
597
598    // Don't create a new wrap if one already exists (from a previous instance of Backstretch)
599    var $existing = this.$container.children(".backstretch").first();
600    this.$wrap = $existing.length ? $existing : 
601        $('<div class="backstretch"></div>')
602        .css(this.options.bypassCss ? {} : styles.wrap)
603        .appendTo(this.$container);
604
605    if (!this.options.bypassCss) {
606        
607        // Non-body elements need some style adjustments
608        if (!this.isBody) {
609          // If the container is statically positioned, we need to make it relative,
610          // and if no zIndex is defined, we should set it to zero.
611          var position = this.$container.css('position')
612            , zIndex = this.$container.css('zIndex');
613
614          this.$container.css({
615              position: position === 'static' ? 'relative' : position
616            , zIndex: zIndex === 'auto' ? 0 : zIndex
617          });
618
619          // Needs a higher z-index
620          this.$wrap.css({zIndex: -999998});
621        }
622
623        // Fixed or absolute positioning?
624        this.$wrap.css({
625            position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute'
626        });
627    
628    }
629
630    // Set the first image
631    this.index = this.options.start;
632    this.show(this.index);
633
634    // Listen for resize
635    $window.on('resize.backstretch', $.proxy(this.resize, this))
636           .on('orientationchange.backstretch', $.proxy(function () {
637              // Need to do this in order to get the right window height
638              if (this.isBody && window.pageYOffset === 0) {
639                window.scrollTo(0, 1);
640                this.resize();
641              }
642           }, this));
643  };
644
645  var performTransition = function (options) {
646
647    var transition = options.transition || 'fade';
648
649    // Look for multiple options
650    if (typeof transition === 'string' && transition.indexOf('|') > -1) {
651      transition = transition.split('|');
652    }
653
654    if (transition instanceof Array) {
655      transition = transition[Math.round(Math.random() * (transition.length - 1))];
656    }
657    
658    var $new = options['new'];
659    var $old = options['old'] ? options['old'] : $([]);
660
661    switch (transition.toString().toLowerCase()) {
662
663      default:
664      case 'fade':
665        $new.fadeIn({
666          duration: options.duration,
667          complete: options.complete,
668          easing: options.easing || undefined
669        });
670        break;
671        
672      case 'fadeinout':
673      case 'fade_in_out':
674            
675        var fadeInNew = function () {
676            $new.fadeIn({
677              duration: options.duration / 2,
678              complete: options.complete,
679              easing: options.easing || undefined
680            });
681        };
682        
683        if ($old.length) {
684            $old.fadeOut({
685              duration: options.duration / 2,
686              complete: fadeInNew,
687              easing: options.easing || undefined
688            });
689        } else {
690            fadeInNew();
691        }
692        
693        break;
694
695      case 'pushleft':
696      case 'push_left':
697      case 'pushright':
698      case 'push_right':
699      case 'pushup':
700      case 'push_up':
701      case 'pushdown':
702      case 'push_down':
703      case 'coverleft':
704      case 'cover_left':
705      case 'coverright':
706      case 'cover_right':
707      case 'coverup':
708      case 'cover_up':
709      case 'coverdown':
710      case 'cover_down':
711
712        var transitionParts = transition.match(/^(cover|push)_?(.*)$/);
713
714        var animProp = transitionParts[2] === 'left' ? 'right' :
715            transitionParts[2] === 'right' ? 'left' :
716                transitionParts[2] === 'down' ? 'top' :
717                    transitionParts[2] === 'up' ? 'bottom' :
718                        'right';
719
720        var newCssStart = {
721          'display': ''
722        }, newCssAnim = {};
723        newCssStart[animProp] = '-100%';
724        newCssAnim[animProp] = 0;
725
726        $new
727            .css(newCssStart)
728            .animate(newCssAnim, {
729              duration: options.duration,
730              complete: function () {
731                $new.css(animProp, '');
732                options.complete.apply(this, arguments);
733              },
734              easing: options.easing || undefined
735            });
736
737        if (transitionParts[1] === 'push' && $old.length) {
738            var oldCssAnim = {};
739            oldCssAnim[animProp] = '100%';
740
741            $old
742                .animate(oldCssAnim, {
743                  duration: options.duration,
744                  complete: function () {
745                    $old.css('display', 'none');
746                  },
747                  easing: options.easing || undefined
748                });
749        }
750
751        break;
752    }
753
754  };
755
756  /* PUBLIC METHODS
757   * ========================= */
758  Backstretch.prototype = {
759
760      resize: function () {
761        try {
762
763          // Check for a better suited image after the resize
764          var $resTest = this.options.alwaysTestWindowResolution ? $(window) : this.$root;
765          var newContainerWidth = $resTest.width();
766          var newContainerHeight = $resTest.height();
767          var changeRatioW = newContainerWidth / (this._lastResizeContainerWidth || 0);
768          var changeRatioH = newContainerHeight / (this._lastResizeContainerHeight || 0);
769          var resolutionChangeRatioThreshold = this.options.resolutionChangeRatioThreshold || 0.0;
770
771          // check for big changes in container size
772          if ((newContainerWidth !== this._lastResizeContainerWidth ||
773               newContainerHeight !== this._lastResizeContainerHeight) &&
774              ((Math.abs(changeRatioW - 1) >= resolutionChangeRatioThreshold || isNaN(changeRatioW)) ||
775              (Math.abs(changeRatioH - 1) >= resolutionChangeRatioThreshold || isNaN(changeRatioH)))) {
776
777            this._lastResizeContainerWidth = newContainerWidth;
778            this._lastResizeContainerHeight = newContainerHeight;
779
780            // Big change: rebuild the entire images array
781            this.images = optimalSizeImages($resTest, this.originalImages);
782
783            // Preload them (they will be automatically inserted on the next cycle)
784            if (this.options.preload) {
785              preload(this.images, (this.index + 1) % this.images.length, this.options.preload);
786            }
787
788            // In case there is no cycle and the new source is different than the current
789            if (this.images.length === 1 &&
790                this._currentImage.url !== this.images[0].url) {
791
792              // Wait a little an update the image being showed
793              var that = this;
794              clearTimeout(that._selectAnotherResolutionTimeout);
795              that._selectAnotherResolutionTimeout = setTimeout(function () {
796                that.show(0);
797              }, this.options.resolutionRefreshRate);
798            }
799          }
800
801          var bgCSS = {left: 0, top: 0, right: 'auto', bottom: 'auto'}
802          
803            , boxWidth = this.isBody ? this.$root.width() : this.$root.innerWidth()
804            , boxHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight()
805            
806            , naturalWidth = this.$itemWrapper.data('width')
807            , naturalHeight = this.$itemWrapper.data('height')
808            
809            , ratio = (naturalWidth / naturalHeight) || 1
810                    
811            , alignX = this._currentImage.alignX === undefined ? this.options.alignX : this._currentImage.alignX
812            , alignY = this._currentImage.alignY === undefined ? this.options.alignY : this._currentImage.alignY
813            , scale = validScale(this._currentImage.scale || this.options.scale);
814          
815          var width, height;
816                
817          if (scale === 'fit' || scale === 'fit-smaller') {
818            width = naturalWidth;
819            height = naturalHeight;
820            
821            if (width > boxWidth || 
822                height > boxHeight || 
823                scale === 'fit-smaller') {
824              var boxRatio = boxWidth / boxHeight;
825              if (boxRatio > ratio) {
826                width = Math.floor(boxHeight * ratio);
827                height = boxHeight;
828              } else if (boxRatio < ratio) {
829                width = boxWidth;
830                height = Math.floor(boxWidth / ratio);
831              } else {
832                width = boxWidth;
833                height = boxHeight;
834              }
835            }
836          } else if (scale === 'fill') {
837            width = boxWidth;
838            height = boxHeight;
839          } else { // 'cover'
840            width = Math.max(boxHeight * ratio, boxWidth);
841            height = Math.max(width / ratio, boxHeight);
842          }
843          
844          // Make adjustments based on image ratio
845          bgCSS.top = -(height - boxHeight) * alignY;
846          bgCSS.left = -(width - boxWidth) * alignX;
847          bgCSS.width = width;
848          bgCSS.height = height;
849          
850          if (!this.options.bypassCss) {
851
852            this.$wrap
853                .css({width: boxWidth, height: boxHeight})
854                .find('>.backstretch-item').not('.deleteable')
855                .each(function () {
856                  var $wrapper = $(this);
857                  $wrapper.find('img,video,iframe')
858                          .css(bgCSS);
859                });
860          }
861
862          var evt = $.Event('backstretch.resize', {
863                      relatedTarget: this.$container[0]
864                    });
865          this.$container.trigger(evt, this);
866          
867        } catch(err) {
868            // IE7 seems to trigger resize before the image is loaded.
869            // This try/catch block is a hack to let it fail gracefully.
870        }
871
872        return this;
873      }
874
875      // Show the slide at a certain position
876    , show: function (newIndex, overrideOptions) {
877
878        // Validate index
879        if (Math.abs(newIndex) > this.images.length - 1) {
880          return;
881        }
882
883        // Vars
884        var that = this
885          , $oldItemWrapper = that.$wrap.find('>.backstretch-item').addClass('deleteable')
886          , oldVideoWrapper = that.videoWrapper
887          , evtOptions = { relatedTarget: that.$container[0] };
888
889        // Trigger the "before" event
890        that.$container.trigger($.Event('backstretch.before', evtOptions), [that, newIndex]);
891
892        // Set the new frame index
893        this.index = newIndex;
894        var selectedImage = that.images[newIndex];
895
896        // Pause the slideshow
897        clearTimeout(that._cycleTimeout);
898
899        // New image
900
901        delete that.videoWrapper; // Current item may not be a video
902
903        var isVideo = isVideoSource(selectedImage);
904        if (isVideo) {
905          that.videoWrapper = new VideoWrapper(selectedImage);
906          that.$item = that.videoWrapper.$video.css('pointer-events', 'none');
907        } else {
908          that.$item = $('<img />');
909        }
910
911        that.$itemWrapper = $('<div class="backstretch-item">')
912            .append(that.$item);
913
914        if (this.options.bypassCss) {
915            that.$itemWrapper.css({
916              'display': 'none'
917            });
918        } else {
919          that.$itemWrapper.css(styles.itemWrapper);
920          that.$item.css(styles.item);
921        }
922
923        that.$item.bind(isVideo ? 'canplay' : 'load', function (e) {
924            var $this = $(this)
925              , $wrapper = $this.parent()
926              , options = $wrapper.data('options');
927              
928            if (overrideOptions) {
929              options = $.extend({}, options, overrideOptions);
930            }
931
932            var imgWidth = this.naturalWidth || this.videoWidth || this.width
933              , imgHeight = this.naturalHeight || this.videoHeight || this.height;
934
935            // Save the natural dimensions
936            $wrapper
937                .data('width', imgWidth)
938                .data('height', imgHeight);
939
940            var getOption = function (opt) {
941              return options[opt] !== undefined ?
942                options[opt] :
943                that.options[opt];
944            };
945
946            var transition = getOption('transition');
947            var transitionEasing = getOption('transitionEasing');
948            var transitionDuration = getOption('transitionDuration');
949
950            // Show the image, then delete the old one
951            var bringInNextImage = function () {
952              
953              if (oldVideoWrapper) {
954                oldVideoWrapper.stop();
955                oldVideoWrapper.destroy();
956              }
957              
958              $oldItemWrapper.remove();
959
960              // Resume the slideshow
961              if (!that.paused && that.images.length > 1) {
962                that.cycle();
963              }
964
965              // Now we can clear the background on the element, to spare memory
966              if (!that.options.bypassCss && !that.isBody) {
967                that.$container.css('background-image', 'none');
968              }
969
970              // Trigger the "after" and "show" events
971              // "show" is being deprecated
972              $(['after', 'show']).each(function () {
973                that.$container.trigger($.Event('backstretch.' + this, evtOptions), [that, newIndex]);
974              });
975              
976              if (isVideo) {
977                that.videoWrapper.play();
978              }
979            };
980
981            if ((that.firstShow && !that.options.animateFirst) || !transitionDuration || !transition) {
982                // Avoid transition on first show or if there's no transitionDuration value
983                $wrapper.show();
984                bringInNextImage();
985            } else {
986
987                performTransition({
988                    'new': $wrapper,
989                    old: $oldItemWrapper,
990                    transition: transition,
991                    duration: transitionDuration,
992                    easing: transitionEasing,
993                    complete: bringInNextImage
994                });
995
996            }
997
998            that.firstShow = false;
999
1000            // Resize
1001            that.resize();
1002        });
1003
1004        that.$itemWrapper.appendTo(that.$wrap);
1005
1006        that.$item.attr('alt', selectedImage.alt || '');
1007        that.$itemWrapper.data('options', selectedImage);
1008
1009        if (!isVideo) {
1010          that.$item.attr('src', selectedImage.url);
1011        }
1012        
1013        that._currentImage = selectedImage;
1014
1015        return that;
1016      }
1017
1018    , current: function () {
1019        return this.index;
1020      }
1021
1022    , next: function () {
1023        var args = Array.prototype.slice.call(arguments, 0);
1024        args.unshift(this.index < this.images.length - 1 ? this.index + 1 : 0);
1025        return this.show.apply(this, args);
1026      }
1027
1028    , prev: function () {
1029        var args = Array.prototype.slice.call(arguments, 0);
1030        args.unshift(this.index === 0 ? this.images.length - 1 : this.index - 1);
1031        return this.show.apply(this, args);
1032      }
1033
1034    , pause: function () {
1035        // Pause the slideshow
1036        this.paused = true;
1037        
1038        if (this.videoWrapper) {
1039          this.videoWrapper.pause();
1040        }
1041        
1042        return this;
1043      }
1044
1045    , resume: function () {
1046        // Resume the slideshow
1047        this.paused = false;
1048        
1049        if (this.videoWrapper) {
1050          this.videoWrapper.play();
1051        }
1052        
1053        this.cycle();
1054        return this;
1055      }
1056
1057    , cycle: function () {
1058        // Start/resume the slideshow
1059        if(this.images.length > 1) {
1060          // Clear the timeout, just in case
1061          clearTimeout(this._cycleTimeout);
1062
1063          var duration = (this._currentImage && this._currentImage.duration) || this.options.duration;
1064          var isVideo = isVideoSource(this._currentImage);
1065          
1066          var callNext = function () {
1067            this.$item.off('.cycle');
1068            
1069            // Check for paused slideshow
1070            if (!this.paused) {
1071              this.next();
1072            }
1073          };
1074
1075          // Special video handling
1076          if (isVideo) {
1077
1078            // Leave video at last frame
1079            if (!this._currentImage.loop) {
1080              var lastFrameTimeout = 0;
1081
1082              this.$item
1083                .on('playing.cycle', function () {
1084                  var player = $(this).data('player');
1085
1086                  clearTimeout(lastFrameTimeout);
1087                  lastFrameTimeout = setTimeout(function () {
1088                    player.pause();
1089                    player.$video.trigger('ended');
1090                  }, (player.getDuration() - player.getCurrentTime()) * 1000);
1091                })
1092                .on('ended.cycle', function () {
1093                  clearTimeout(lastFrameTimeout);
1094                });
1095            }
1096
1097            // On error go to next
1098            this.$item.on('error.cycle initerror.cycle', $.proxy(callNext, this));
1099          }
1100
1101          if (isVideo && !this._currentImage.duration) {
1102            // It's a video - playing until end
1103            this.$item.on('ended.cycle', $.proxy(callNext, this));
1104            
1105          } else {
1106            // Cycling according to specified duration
1107            this._cycleTimeout = setTimeout($.proxy(callNext, this), duration);
1108          }
1109          
1110        }
1111        return this;
1112      }
1113
1114    , destroy: function (preserveBackground) {
1115        // Stop the resize events
1116        $(window).off('resize.backstretch orientationchange.backstretch');
1117
1118        // Stop any videos
1119        if (this.videoWrapper) {
1120          this.videoWrapper.destroy();
1121        }
1122        
1123        // Clear the timeout
1124        clearTimeout(this._cycleTimeout);
1125
1126        // Remove Backstretch
1127        if(!preserveBackground) {
1128          this.$wrap.remove();
1129        }
1130        this.$container.removeData('backstretch');
1131      }
1132  };
1133    
1134 /**
1135  * Video Abstraction Layer
1136  *
1137  * Static methods:
1138  * > VideoWrapper.loadYoutubeAPI() -> Call in order to load the Youtube API. 
1139  *                                   An 'youtube_api_load' event will be triggered on $(window) when the API is loaded.
1140  *
1141  * Generic:
1142  * > player.type -> type of the video
1143  * > player.video / player.$video -> contains the element holding the video
1144  * > player.play() -> plays the video
1145  * > player.pause() -> pauses the video
1146  * > player.setCurrentTime(position) -> seeks to a position by seconds
1147  * 
1148  * Youtube:
1149  * > player.ytId will contain the youtube ID if the source is a youtube url
1150  * > player.ytReady is a flag telling whether the youtube source is ready for playback
1151  * */
1152
1153  var VideoWrapper = function () { this.init.apply(this, arguments); };
1154
1155  /**
1156   * @param {Object} options
1157   * @param {String|Array<String>|Array<{{src: String, type: String?}}>} options.url
1158   * @param {Boolean} options.loop=false
1159   * @param {Boolean?} options.mute=true
1160   * @param {String?} options.poster
1161   * loop, mute, poster
1162   */
1163  VideoWrapper.prototype.init = function (options) {
1164
1165    var that = this;
1166    
1167    var $video;
1168
1169    var setVideoElement = function () {
1170      that.$video = $video;
1171      that.video = $video[0];
1172    };
1173    
1174    // Determine video type
1175    
1176    var videoType = 'video';
1177    
1178    if (!(options.url instanceof Array) &&
1179      YOUTUBE_REGEXP.test(options.url)) {
1180      videoType = 'youtube';
1181    }
1182    
1183    that.type = videoType;
1184
1185    if (videoType === 'youtube') {
1186
1187      // Try to load the API in the meantime
1188      VideoWrapper.loadYoutubeAPI();
1189
1190      that.ytId = options.url.match(YOUTUBE_REGEXP)[2];
1191      var src = 'https://www.youtube.com/embed/' + that.ytId +
1192        '?rel=0&autoplay=0&showinfo=0&controls=0&modestbranding=1' +
1193        '&cc_load_policy=0&disablekb=1&iv_load_policy=3&loop=0' +
1194        '&enablejsapi=1&origin=' + encodeURIComponent(window.location.origin);
1195
1196      that.__ytStartMuted = !!options.mute || options.mute === undefined;
1197
1198      $video = $('<iframe />')
1199        .attr({ 'src_to_load': src })
1200        .css({ 'border': 0, 'margin': 0, 'padding': 0 })
1201        .data('player', that);
1202        
1203      if (options.loop) {
1204        $video.on('ended.loop', function () {
1205          if (!that.__manuallyStopped) {
1206           that.play();
1207          }
1208        });
1209      }
1210
1211      that.ytReady = false;
1212
1213      setVideoElement();
1214
1215      if (window['YT']) {
1216        that._initYoutube();
1217        $video.trigger('initsuccess');
1218      } else {
1219        $(window).one('youtube_api_load', function () {
1220          that._initYoutube();
1221          $video.trigger('initsuccess');
1222        });
1223      }
1224      
1225    }
1226    else {
1227      // Traditional <video> tag with multiple sources
1228      
1229      $video = $('<video>')
1230        .prop('autoplay', false)
1231        .prop('controls', false)
1232        .prop('loop', !!options.loop)
1233        .prop('muted', !!options.mute || options.mute === undefined)
1234        
1235        // Let the first frames be available before playback, as we do transitions
1236        .prop('preload', 'auto')
1237        .prop('poster', options.poster || '');
1238        
1239      var sources = (options.url instanceof Array) ? options.url : [options.url];
1240
1241      for (var i = 0; i < sources.length; i++) {
1242        var sourceItem = sources[i];
1243        if (typeof(sourceItem) === 'string') {
1244          sourceItem = { src: sourceItem };
1245        }
1246        $('<source>')
1247          .attr('src', sourceItem.src)
1248          // Make sure to not specify type if unknown - 
1249          //   so the browser will try to autodetect.
1250          .attr('type', sourceItem.type || null)
1251          .appendTo($video);
1252      }
1253      
1254      if (!$video[0].canPlayType || !sources.length) {
1255        $video.trigger('initerror');
1256      } else {
1257        $video.trigger('initsuccess');
1258      }
1259
1260      setVideoElement();
1261    }
1262
1263  };
1264
1265  VideoWrapper.prototype._initYoutube = function () {
1266    var that = this;
1267    
1268    var YT = window['YT'];
1269
1270    that.$video
1271      .attr('src', that.$video.attr('src_to_load'))
1272      .removeAttr('src_to_load');
1273
1274    // It won't init if it's not in the DOM, so we emulate that
1275    var hasParent = !!that.$video[0].parentNode;
1276    if (!hasParent) {
1277      var $tmpParent = $('<div>').css('display', 'none !important').appendTo(document.body);
1278      that.$video.appendTo($tmpParent);
1279    }
1280
1281    var player = new YT.Player(that.video, {
1282      events: {
1283        'onReady': function () {
1284
1285          if (that.__ytStartMuted) {
1286            player.mute();
1287          }
1288
1289          if (!hasParent) {
1290            // Restore parent to old state - without interrupting any changes
1291            if (that.$video[0].parentNode === $tmpParent[0]) {
1292              that.$video.detach();
1293            }
1294            $tmpParent.remove();
1295          }
1296
1297          that.ytReady = true;
1298          that._updateYoutubeSize();
1299          that.$video.trigger('canplay');
1300        },
1301        'onStateChange': function (event) {
1302          switch (event.data) {
1303            case YT.PlayerState.PLAYING:
1304              that.$video.trigger('playing');
1305              break;
1306            case YT.PlayerState.ENDED:
1307              that.$video.trigger('ended');
1308              break;
1309            case YT.PlayerState.PAUSED:
1310              that.$video.trigger('pause');
1311              break;
1312            case YT.PlayerState.BUFFERING:
1313              that.$video.trigger('waiting');
1314              break;
1315            case YT.PlayerState.CUED:
1316              that.$video.trigger('canplay');
1317              break;
1318          }
1319        },
1320        'onPlaybackQualityChange': function () {
1321          that._updateYoutubeSize();
1322          that.$video.trigger('resize');
1323        },
1324        'onError': function (err) {
1325          that.hasError = true;
1326          that.$video.trigger({ 'type': 'error', 'error': err });
1327        }
1328      }
1329    });
1330
1331    that.ytPlayer = player;
1332
1333    return that;
1334  };    
1335    
1336  VideoWrapper.prototype._updateYoutubeSize = function () {
1337    var that = this;
1338
1339    switch (that.ytPlayer.getPlaybackQuality() || 'medium') {
1340      case 'small':
1341        that.video.videoWidth = 426;
1342        that.video.videoHeight = 240;
1343        break;
1344      case 'medium':
1345        that.video.videoWidth = 640;
1346        that.video.videoHeight = 360;
1347        break;
1348      default:
1349      case 'large':
1350        that.video.videoWidth = 854;
1351        that.video.videoHeight = 480;
1352        break;
1353      case 'hd720':
1354        that.video.videoWidth = 1280;
1355        that.video.videoHeight = 720;
1356        break;
1357      case 'hd1080':
1358        that.video.videoWidth = 1920;
1359        that.video.videoHeight = 1080;
1360        break;
1361      case 'highres':
1362        that.video.videoWidth = 2560;
1363        that.video.videoHeight = 1440;
1364        break;
1365    }
1366
1367    return that;
1368  };
1369
1370  VideoWrapper.prototype.play = function () {
1371    var that = this;
1372
1373    that.__manuallyStopped = false;
1374    
1375    if (that.type === 'youtube') {
1376      if (that.ytReady) {
1377        that.$video.trigger('play');
1378        that.ytPlayer.playVideo();
1379      }
1380    } else {
1381      that.video.play();
1382    }
1383
1384    return that;
1385  };
1386
1387  VideoWrapper.prototype.pause = function () {
1388    var that = this;
1389
1390    that.__manuallyStopped = false;
1391    
1392    if (that.type === 'youtube') {
1393      if (that.ytReady) {
1394        that.ytPlayer.pauseVideo();
1395      }
1396    } else {
1397      that.video.pause();
1398    }
1399
1400    return that;
1401  };
1402
1403  VideoWrapper.prototype.stop = function () {
1404    var that = this;
1405
1406    that.__manuallyStopped = true;
1407    
1408    if (that.type === 'youtube') {
1409      if (that.ytReady) {
1410        that.ytPlayer.pauseVideo();
1411        that.ytPlayer.seekTo(0);
1412      }
1413    } else {
1414      that.video.pause();
1415      that.video.currentTime = 0;
1416    }
1417
1418    return that;
1419  };
1420
1421  VideoWrapper.prototype.destroy = function () {
1422    var that = this;
1423
1424    if (that.ytPlayer) {
1425      that.ytPlayer.destroy();
1426    }
1427
1428    that.$video.remove();
1429
1430    return that;
1431  };
1432
1433  VideoWrapper.prototype.getCurrentTime = function (seconds) {
1434    var that = this;
1435
1436    if (that.type === 'youtube') {
1437      if (that.ytReady) {
1438        return that.ytPlayer.getCurrentTime();
1439      }
1440    } else {
1441      return that.video.currentTime;
1442    }
1443
1444    return 0;
1445  };
1446
1447  VideoWrapper.prototype.setCurrentTime = function (seconds) {
1448    var that = this;
1449
1450    if (that.type === 'youtube') {
1451      if (that.ytReady) {
1452        that.ytPlayer.seekTo(seconds, true);
1453      }
1454    } else {
1455      that.video.currentTime = seconds;
1456    }
1457
1458    return that;
1459  };
1460
1461  VideoWrapper.prototype.getDuration = function () {
1462    var that = this;
1463
1464    if (that.type === 'youtube') {
1465      if (that.ytReady) {
1466        return that.ytPlayer.getDuration();
1467      }
1468    } else {
1469      return that.video.duration;
1470    }
1471
1472    return 0;
1473  };
1474
1475  /**
1476   * This will load the youtube API (if not loaded yet)
1477   * Use $(window).one('youtube_api_load', ...) to listen for API loaded event
1478   */
1479  VideoWrapper.loadYoutubeAPI = function () {
1480    if (window['YT']) {
1481      return;
1482    }
1483    if (!$('script[src*=www\\.youtube\\.com\\/iframe_api]').length) {
1484      $('<script type="text/javascript" src="https://www.youtube.com/iframe_api">').appendTo('body');
1485    }
1486    var ytAPILoadInt = setInterval(function () {
1487      if (window['YT'] && window['YT'].loaded) {
1488        $(window).trigger('youtube_api_load');
1489        clearTimeout(ytAPILoadInt);
1490      }
1491    }, 50);
1492  };
1493
1494  var getDeviceOrientation = function () {
1495
1496    if ('matchMedia' in window) {
1497      if (window.matchMedia("(orientation: portrait)").matches) {
1498         return 'portrait';
1499      } else if (window.matchMedia("(orientation: landscape)").matches) {
1500         return 'landscape';
1501      }
1502    }
1503
1504    if (screen.height > screen.width) {
1505      return 'portrait';
1506    }
1507
1508    // Even square devices have orientation,
1509    //   but a desktop browser may be too old for `matchMedia`.
1510    // Defaulting to `landscape` for the VERY rare case of a square desktop screen is good enough.
1511    return 'landscape';
1512  };
1513
1514  var getWindowOrientation = function () {
1515    if (window.innerHeight > window.innerWidth) {
1516      return 'portrait';
1517    }
1518    if (window.innerWidth > window.innerHeight) {
1519      return 'landscape';
1520    }
1521
1522    return 'square';
1523  };
1524
1525  /* SUPPORTS FIXED POSITION?
1526   *
1527   * Based on code from jQuery Mobile 1.1.0
1528   * http://jquerymobile.com/
1529   *
1530   * In a nutshell, we need to figure out if fixed positioning is supported.
1531   * Unfortunately, this is very difficult to do on iOS, and usually involves
1532   * injecting content, scrolling the page, etc.. It's ugly.
1533   * jQuery Mobile uses this workaround. It's not ideal, but works.
1534   *
1535   * Modified to detect IE6
1536   * ========================= */
1537
1538  var supportsFixedPosition = (function () {
1539    var ua = navigator.userAgent
1540      , platform = navigator.platform
1541        // Rendering engine is Webkit, and capture major version
1542      , wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ )
1543      , wkversion = !!wkmatch && wkmatch[ 1 ]
1544      , ffmatch = ua.match( /Fennec\/([0-9]+)/ )
1545      , ffversion = !!ffmatch && ffmatch[ 1 ]
1546      , operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ )
1547      , omversion = !!operammobilematch && operammobilematch[ 1 ]
1548      , iematch = ua.match( /MSIE ([0-9]+)/ )
1549      , ieversion = !!iematch && iematch[ 1 ];
1550
1551    return !(
1552      // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
1553      ((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1  || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) ||
1554
1555      // Opera Mini
1556      (window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") ||
1557      (operammobilematch && omversion < 7458) ||
1558
1559      //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
1560      (ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) ||
1561
1562      // Firefox Mobile before 6.0 -
1563      (ffversion && ffversion < 6) ||
1564
1565      // WebOS less than 3
1566      ("palmGetResource" in window && wkversion && wkversion < 534) ||
1567
1568      // MeeGo
1569      (ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) ||
1570
1571      // IE6
1572      (ieversion && ieversion <= 6)
1573    );
1574  }());
1575
1576}(jQuery, window));

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.