7ac0875f839dffcfc1831f341bf688d1f1617139
[motion.git] / public / bower_components / jquery-backstretch / jquery.backstretch.js
1 /*! Backstretch - v2.0.4 - 2013-06-19
2 * http://srobbin.com/jquery-plugins/backstretch/
3 * Copyright (c) 2013 Scott Robbin; Licensed MIT */
4
5 ;(function ($, window, undefined) {
6   'use strict';
7
8   /* PLUGIN DEFINITION
9    * ========================= */
10
11   $.fn.backstretch = function (images, options) {
12     // We need at least one image or method name
13     if (images === undefined || images.length === 0) {
14       $.error("No images were supplied for Backstretch");
15     }
16
17     /*
18      * Scroll the page one pixel to get the right window height on iOS
19      * Pretty harmless for everyone else
20     */
21     if ($(window).scrollTop() === 0 ) {
22       window.scrollTo(0, 0);
23     }
24
25     return this.each(function () {
26       var $this = $(this)
27         , obj = $this.data('backstretch');
28
29       // Do we already have an instance attached to this element?
30       if (obj) {
31
32         // Is this a method they're trying to execute?
33         if (typeof images == 'string' && typeof obj[images] == 'function') {
34           // Call the method
35           obj[images](options);
36
37           // No need to do anything further
38           return;
39         }
40
41         // Merge the old options with the new
42         options = $.extend(obj.options, options);
43
44         // Remove the old instance
45         obj.destroy(true);
46       }
47
48       obj = new Backstretch(this, images, options);
49       $this.data('backstretch', obj);
50     });
51   };
52
53   // If no element is supplied, we'll attach to body
54   $.backstretch = function (images, options) {
55     // Return the instance
56     return $('body')
57             .backstretch(images, options)
58             .data('backstretch');
59   };
60
61   // Custom selector
62   $.expr[':'].backstretch = function(elem) {
63     return $(elem).data('backstretch') !== undefined;
64   };
65
66   /* DEFAULTS
67    * ========================= */
68
69   $.fn.backstretch.defaults = {
70       centeredX: true   // Should we center the image on the X axis?
71     , centeredY: true   // Should we center the image on the Y axis?
72     , duration: 5000    // Amount of time in between slides (if slideshow)
73     , fade: 0           // Speed of fade transition between slides
74   };
75
76   /* STYLES
77    * 
78    * Baked-in styles that we'll apply to our elements.
79    * In an effort to keep the plugin simple, these are not exposed as options.
80    * That said, anyone can override these in their own stylesheet.
81    * ========================= */
82   var styles = {
83       wrap: {
84           left: 0
85         , top: 0
86         , overflow: 'hidden'
87         , margin: 0
88         , padding: 0
89         , height: '100%'
90         , width: '100%'
91         , zIndex: -999999
92       }
93     , img: {
94           position: 'absolute'
95         , display: 'none'
96         , margin: 0
97         , padding: 0
98         , border: 'none'
99         , width: 'auto'
100         , height: 'auto'
101         , maxHeight: 'none'
102         , maxWidth: 'none'
103         , zIndex: -999999
104       }
105   };
106
107   /* CLASS DEFINITION
108    * ========================= */
109   var Backstretch = function (container, images, options) {
110     this.options = $.extend({}, $.fn.backstretch.defaults, options || {});
111
112     /* In its simplest form, we allow Backstretch to be called on an image path.
113      * e.g. $.backstretch('/path/to/image.jpg')
114      * So, we need to turn this back into an array.
115      */
116     this.images = $.isArray(images) ? images : [images];
117
118     // Preload images
119     $.each(this.images, function () {
120       $('<img />')[0].src = this;
121     });    
122
123     // Convenience reference to know if the container is body.
124     this.isBody = container === document.body;
125
126     /* We're keeping track of a few different elements
127      *
128      * Container: the element that Backstretch was called on.
129      * Wrap: a DIV that we place the image into, so we can hide the overflow.
130      * Root: Convenience reference to help calculate the correct height.
131      */
132     this.$container = $(container);
133     this.$root = this.isBody ? supportsFixedPosition ? $(window) : $(document) : this.$container;
134
135     // Don't create a new wrap if one already exists (from a previous instance of Backstretch)
136     var $existing = this.$container.children(".backstretch").first();
137     this.$wrap = $existing.length ? $existing : $('<div class="backstretch"></div>').css(styles.wrap).appendTo(this.$container);
138
139     // Non-body elements need some style adjustments
140     if (!this.isBody) {
141       // If the container is statically positioned, we need to make it relative,
142       // and if no zIndex is defined, we should set it to zero.
143       var position = this.$container.css('position')
144         , zIndex = this.$container.css('zIndex');
145
146       this.$container.css({
147           position: position === 'static' ? 'relative' : position
148         , zIndex: zIndex === 'auto' ? 0 : zIndex
149         , background: 'none'
150       });
151       
152       // Needs a higher z-index
153       this.$wrap.css({zIndex: -999998});
154     }
155
156     // Fixed or absolute positioning?
157     this.$wrap.css({
158       position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute'
159     });
160
161     // Set the first image
162     this.index = 0;
163     this.show(this.index);
164
165     // Listen for resize
166     $(window).on('resize.backstretch', $.proxy(this.resize, this))
167              .on('orientationchange.backstretch', $.proxy(function () {
168                 // Need to do this in order to get the right window height
169                 if (this.isBody && window.pageYOffset === 0) {
170                   window.scrollTo(0, 1);
171                   this.resize();
172                 }
173              }, this));
174   };
175
176   /* PUBLIC METHODS
177    * ========================= */
178   Backstretch.prototype = {
179       resize: function () {
180         try {
181           var bgCSS = {left: 0, top: 0}
182             , rootWidth = this.isBody ? this.$root.width() : this.$root.innerWidth()
183             , bgWidth = rootWidth
184             , rootHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight()
185             , bgHeight = bgWidth / this.$img.data('ratio')
186             , bgOffset;
187
188             // Make adjustments based on image ratio
189             if (bgHeight >= rootHeight) {
190                 bgOffset = (bgHeight - rootHeight) / 2;
191                 if(this.options.centeredY) {
192                   bgCSS.top = '-' + bgOffset + 'px';
193                 }
194             } else {
195                 bgHeight = rootHeight;
196                 bgWidth = bgHeight * this.$img.data('ratio');
197                 bgOffset = (bgWidth - rootWidth) / 2;
198                 if(this.options.centeredX) {
199                   bgCSS.left = '-' + bgOffset + 'px';
200                 }
201             }
202
203             this.$wrap.css({width: rootWidth, height: rootHeight})
204                       .find('img:not(.deleteable)').css({width: bgWidth, height: bgHeight}).css(bgCSS);
205         } catch(err) {
206             // IE7 seems to trigger resize before the image is loaded.
207             // This try/catch block is a hack to let it fail gracefully.
208         }
209
210         return this;
211       }
212
213       // Show the slide at a certain position
214     , show: function (newIndex) {
215
216         // Validate index
217         if (Math.abs(newIndex) > this.images.length - 1) {
218           return;
219         }
220
221         // Vars
222         var self = this
223           , oldImage = self.$wrap.find('img').addClass('deleteable')
224           , evtOptions = { relatedTarget: self.$container[0] };
225
226         // Trigger the "before" event
227         self.$container.trigger($.Event('backstretch.before', evtOptions), [self, newIndex]); 
228
229         // Set the new index
230         this.index = newIndex;
231
232         // Pause the slideshow
233         clearInterval(self.interval);
234
235         // New image
236         self.$img = $('<img />')
237                       .css(styles.img)
238                       .bind('load', function (e) {
239                         var imgWidth = this.width || $(e.target).width()
240                           , imgHeight = this.height || $(e.target).height();
241                         
242                         // Save the ratio
243                         $(this).data('ratio', imgWidth / imgHeight);
244
245                         // Show the image, then delete the old one
246                         // "speed" option has been deprecated, but we want backwards compatibilty
247                         $(this).fadeIn(self.options.speed || self.options.fade, function () {
248                           oldImage.remove();
249
250                           // Resume the slideshow
251                           if (!self.paused) {
252                             self.cycle();
253                           }
254
255                           // Trigger the "after" and "show" events
256                           // "show" is being deprecated
257                           $(['after', 'show']).each(function () {
258                             self.$container.trigger($.Event('backstretch.' + this, evtOptions), [self, newIndex]);
259                           });
260                         });
261
262                         // Resize
263                         self.resize();
264                       })
265                       .appendTo(self.$wrap);
266
267         // Hack for IE img onload event
268         self.$img.attr('src', self.images[newIndex]);
269         return self;
270       }
271
272     , next: function () {
273         // Next slide
274         return this.show(this.index < this.images.length - 1 ? this.index + 1 : 0);
275       }
276
277     , prev: function () {
278         // Previous slide
279         return this.show(this.index === 0 ? this.images.length - 1 : this.index - 1);
280       }
281
282     , pause: function () {
283         // Pause the slideshow
284         this.paused = true;
285         return this;
286       }
287
288     , resume: function () {
289         // Resume the slideshow
290         this.paused = false;
291         this.next();
292         return this;
293       }
294
295     , cycle: function () {
296         // Start/resume the slideshow
297         if(this.images.length > 1) {
298           // Clear the interval, just in case
299           clearInterval(this.interval);
300
301           this.interval = setInterval($.proxy(function () {
302             // Check for paused slideshow
303             if (!this.paused) {
304               this.next();
305             }
306           }, this), this.options.duration);
307         }
308         return this;
309       }
310
311     , destroy: function (preserveBackground) {
312         // Stop the resize events
313         $(window).off('resize.backstretch orientationchange.backstretch');
314
315         // Clear the interval
316         clearInterval(this.interval);
317
318         // Remove Backstretch
319         if(!preserveBackground) {
320           this.$wrap.remove();          
321         }
322         this.$container.removeData('backstretch');
323       }
324   };
325
326   /* SUPPORTS FIXED POSITION?
327    *
328    * Based on code from jQuery Mobile 1.1.0
329    * http://jquerymobile.com/
330    *
331    * In a nutshell, we need to figure out if fixed positioning is supported.
332    * Unfortunately, this is very difficult to do on iOS, and usually involves
333    * injecting content, scrolling the page, etc.. It's ugly.
334    * jQuery Mobile uses this workaround. It's not ideal, but works.
335    *
336    * Modified to detect IE6
337    * ========================= */
338
339   var supportsFixedPosition = (function () {
340     var ua = navigator.userAgent
341       , platform = navigator.platform
342         // Rendering engine is Webkit, and capture major version
343       , wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ )
344       , wkversion = !!wkmatch && wkmatch[ 1 ]
345       , ffmatch = ua.match( /Fennec\/([0-9]+)/ )
346       , ffversion = !!ffmatch && ffmatch[ 1 ]
347       , operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ )
348       , omversion = !!operammobilematch && operammobilematch[ 1 ]
349       , iematch = ua.match( /MSIE ([0-9]+)/ )
350       , ieversion = !!iematch && iematch[ 1 ];
351
352     return !(
353       // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
354       ((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1  || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) ||
355       
356       // Opera Mini
357       (window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") ||
358       (operammobilematch && omversion < 7458) ||
359       
360       //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
361       (ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) ||
362       
363       // Firefox Mobile before 6.0 -
364       (ffversion && ffversion < 6) ||
365       
366       // WebOS less than 3
367       ("palmGetResource" in window && wkversion && wkversion < 534) ||
368       
369       // MeeGo
370       (ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) ||
371       
372       // IE6
373       (ieversion && ieversion <= 6)
374     );
375   }());
376
377 }(jQuery, window));