1/*! Backstretch - v2.0.3 - 2012-11-30 2* http://srobbin.com/jquery-plugins/backstretch/ 3* Copyright (c) 2012 Scott Robbin; Licensed MIT 4 5Modified by Ben Beckford and Jonathan Spicer, Chris Curd Design Ltd. to allow for HTML5 video 6Video sources should be passed in the images parameter as an array with an 7image as the first option for backwards compatability with browsers that 8don't support HTML5 video. Also added support for Vimeo Embed links. 9*/ 10 11;(function ($, window, undefined) { 12 'use strict'; 13 14 /* PLUGIN DEFINITION 15 * ========================= */ 16 17 $.fn.backstretch = function (images, options) { 18 // We need at least one image 19 if (images === undefined || images.length === 0) { 20 $.error("No images were supplied for Backstretch"); 21 } 22 23 /* 24 * Scroll the page one pixel to get the right window height on iOS 25 * Pretty harmless for everyone else 26 */ 27 if ($(window).scrollTop() === 0 ) { 28 window.scrollTo(0, 0); 29 } 30 31 return this.each(function () { 32 var $this = $(this) 33 , obj = $this.data('backstretch'); 34 35 // If we've already attached Backstretch to this element, remove the old instance. 36 if (obj) { 37 // Merge the old options with the new 38 options = $.extend(obj.options, options); 39 40 // Remove the old instance 41 obj.destroy(true); 42 } 43 44 obj = new Backstretch(this, images, options); 45 $this.data('backstretch', obj); 46 }); 47 }; 48 49 // If no element is supplied, we'll attach to body 50 $.backstretch = function (images, options) { 51 // Return the instance 52 return $('body') 53 .backstretch(images, options) 54 .data('backstretch'); 55 }; 56 57 // Custom selector 58 $.expr[':'].backstretch = function(elem) { 59 return $(elem).data('backstretch') !== undefined; 60 }; 61 62 /* DEFAULTS 63 * ========================= */ 64 65 $.fn.backstretch.defaults = { 66 centeredX: true // Should we center the image on the X axis? 67 , centeredY: true // Should we center the image on the Y axis? 68 , duration: 5000 // Amount of time in between slides (if slideshow) 69 , fade: 0 // Speed of fade transition between slides 70 , wrapInner: false // Add backstretchInner wrapper inside backstretch wrapper (useful for parallax) 71 , resizeMethod: 'cover' // Based off of background size property 72 , videoMuted: true // Add muted="true" to videos 73 , videoPreload: "auto" // video preload="" attribute 74 , videoOnPlaying: "" // video onplaying="" attribute 75 , videoPoster: "" // video poster="" attribute 76 , videoAutoPlay: "true" // video autoplay="" attribute 77 , videoLoop: "loop" // video loop="" attribute 78 , videoID: "" // video id="" attribute 79 , lazyLoad: false // video lazyload attribute 80 , videoPlaysInline: true // video playsinline attribute 81 , alignRight: false // Align the image or video to the right hand side 82 }; 83 84 /* STYLES 85 * 86 * Baked-in styles that we'll apply to our elements. 87 * In an effort to keep the plugin simple, these are not exposed as options. 88 * That said, anyone can override these in their own stylesheet. 89 * ========================= */ 90 var styles = { 91 wrap: { 92 left: 0 93 , top: 0 94 , overflow: 'hidden' 95 , margin: 0 96 , padding: 0 97 , height: '100%' 98 , width: '100%' 99 , zIndex: -999999 100 , '-webkit-transform': 'translateZ(0)' 101 , '-moz-transform' : 'translateZ(0)' 102 , '-ms-transform' : 'translateZ(0)' 103 , '-o-transform' : 'translateZ(0)' 104 , 'transform' : 'translateZ(0)' 105 , '-webkit-backface-visibility': 'hidden' 106 , '-moz-backface-visibility' : 'hidden' 107 , '-ms-backface-visibility' : 'hidden' 108 , 'backface-visibility' : 'hidden' 109 } 110 , 111 wrapInner: { 112 position: 'relative' 113 } 114 , img: { 115 position: 'absolute' 116 , display: 'none' 117 , margin: 0 118 , padding: 0 119 , border: 'none' 120 , width: 'auto' 121 , height: 'auto' 122 , maxWidth: 'none' 123 , zIndex: -999999 124 , '-webkit-transform': 'translateZ(0)' 125 , '-moz-transform' : 'translateZ(0)' 126 , '-ms-transform' : 'translateZ(0)' 127 , '-o-transform' : 'translateZ(0)' 128 , 'transform' : 'translateZ(0)' 129 , '-webkit-backface-visibility': 'hidden' 130 , '-moz-backface-visibility' : 'hidden' 131 , '-ms-backface-visibility' : 'hidden' 132 , 'backface-visibility' : 'hidden' 133 } 134 , video: { 135 position: 'absolute' 136 , display: 'block' 137 , margin: 0 138 , padding: 0 139 , border: 'none' 140 , width: 'auto' 141 , height: 'auto' 142 , maxWidth: 'none' 143 , zIndex: -999999 144 } 145 }; 146 147 /* CLASS DEFINITION 148 * ========================= */ 149 var Backstretch = function (container, images, options) { 150 this.options = $.extend({}, $.fn.backstretch.defaults, options || {}); 151 152 /* In its simplest form, we allow Backstretch to be called on an image path. 153 * e.g. $.backstretch('/path/to/image.jpg') 154 * So, we need to turn this back into an array.
155 */ 156 var lazyLoadAttribute = this.options.lazyLoad ? ' loading="lazy"' : ''; 157 this.images = $.isArray(images) ? images : [images]; 158 159 if(this.images.length > 1) 160 { 161 this.videoMode = isVideo(this.images[1]); 162 if (!this.videoMode) { 163 this.vimeoMode = isVimeo(this.images[1]); 164 } 165 } 166 else 167 { 168 this.videoMode = isVideo(this.images[0]); 169 if (!this.videoMode) { 170 this.vimeoMode = isVimeo(this.images[0]); 171 } 172 } 173 174 if(this.videoMode || this.vimeoMode) 175 { 176 if(!document.createElement('video').canPlayType) 177 { 178 this.videoMode = false; 179 this.images = this.images[0]; 180 this.images = [this.images]; 181 } 182 else 183 { 184 if(!isVideo(this.images[0]) && !isVimeo(this.images[0])) 185 { 186 this.images.shift(); 187 } 188 } 189 } 190 191 192 if(!this.videoMode && !this.vimeoMode) 193 { 194 // Preload images 195 $.each(this.images, function () { 196 $('<img '+lazyLoadAttribute+' />')[0].src = this; 197 }); 198 } 199 200 // Convenience reference to know if the container is body. 201 this.isBody = container === document.body; 202 203 /* We're keeping track of a few different elements 204 * 205 * Container: the element that Backstretch was called on. 206 * Wrap: a DIV that we place the image into, so we can hide the overflow. 207 * Root: Convenience reference to help calculate the correct height. 208 */ 209 this.$container = $(container); 210 211 if(options == null || options.resizeMethod == null) 212 { 213 if(this.$container.css('background-size') == 'contain') 214 { 215 this.options.resizeMethod = 'contain'; 216 } 217 } 218 219 this.$wrap = $('<div class="backstretch"></div>').css(styles.wrap).appendTo(this.$container); 220 221 if(this.options.wrapInner || this.vimeoMode) 222 { 223 if (this.vimeoMode) { 224 this.$wrapInner = $('<div class="backstretchInner loading"></div>').css(styles.wrapInner).appendTo(this.$wrap); 225 } 226 else { 227 this.$wrapInner = $('<div class="backstretchInner"></div>').css(styles.wrapInner).appendTo(this.$wrap); 228 } 229 } 230 else 231 { 232 this.$wrapInner = this.$wrap; 233 } 234 this.$root = this.isBody ? supportsFixedPosition ? $(window) : $(document) : this.$container; 235 236 // Non-body elements need some style adjustments 237 if (!this.isBody) { 238 // If the container is statically positioned, we need to make it relative, 239 // and if no zIndex is defined, we should set it to zero. 240 var position = this.$container.css('position') 241 , zIndex = this.$container.css('zIndex'); 242 243 this.$container.css({ 244 position: position === 'static' ? 'relative' : position 245 , zIndex: zIndex === 'auto' ? 0 : zIndex 246 , background: 'none' 247 }); 248 249 // Needs a higher z-index 250 this.$wrap.css({zIndex: -999998}); 251 } 252 253 // Fixed or absolute positioning? 254 this.$wrap.css({ 255 position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute' 256 }); 257 258 // Set the first image 259 this.index = 0; 260 this.show(this.index); 261 262 // Listen for resize 263 $(window).on('resize.backstretch', $.proxy(this.resize, this)) 264 .on('orientationchange.backstretch', $.proxy(function () { 265 // Need to do this in order to get the right window height 266 if (this.isBody && window.pageYOffset === 0) { 267 window.scrollTo(0, 1); 268 this.resize(); 269 } 270 }, this)); 271 }; 272 273 /* PUBLIC METHODS 274 * ========================= */ 275 Backstretch.prototype = { 276 resize: function () { 277 try { 278 var bgCSS = {left: 0, top: 0, right: 'auto'} 279 , rootWidth = this.isBody ? this.$root.width() : this.$root.innerWidth() 280 , bgWidth = rootWidth 281 , rootHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight() 282 , bgHeight = bgWidth / this.$img.data('ratio') 283 , bgOffset; 284 285 if(this.options.resizeMethod == 'contain') 286 { 287 // Make adjustments based on image ratio 288 if (bgHeight >= rootHeight) { 289 bgHeight = rootHeight; 290 bgWidth = bgHeight * this.$img.data('ratio'); 291 292 bgOffset = (rootWidth - bgWidth) / 2; 293 if(this.options.centeredX) { 294 bgCSS.left = bgOffset + 'px'; 295 } 296 if(this.options.alignRight) 297 { 298 bgCSS.left = 'auto'; 299 bgCSS.right = 0; 300 } 301 } else { 302 bgOffset = (rootHeight - bgHeight) / 2; 303 if(this.options.centeredY) { 304 bgCSS.top = bgOffset + 'px'; 305 } 306 } 307 } 308 else 309 { 310 // Make adjustments based on image ratio 311 if (bgHeight >= rootHeight) {
312 bgOffset = (bgHeight - rootHeight) / 2; 313 if(this.options.centeredY) { 314 bgCSS.top = '-' + bgOffset + 'px'; 315 } 316 } else { 317 bgHeight = rootHeight; 318 bgWidth = bgHeight * this.$img.data('ratio'); 319 bgOffset = (bgWidth - rootWidth) / 2; 320 if(this.options.centeredX) { 321 bgCSS.left = '-' + bgOffset + 'px'; 322 } 323 if(this.options.alignRight) 324 { 325 bgCSS.left = 'auto'; 326 bgCSS.right = 0; 327 } 328 } 329 } 330 this.$wrap.css({width: rootWidth, height: rootHeight}) 331 this.$wrap.find('img:not(.deleteable),video:not(.deleteable),iframe:not(.deleteable)').css({width: bgWidth, height: bgHeight}).css(bgCSS); 332 } catch(err) { 333 // IE7 seems to trigger resize before the image is loaded. 334 // This try/catch block is a hack to let it fail gracefully. 335 } 336 337 return this; 338 } 339 340 // Show the slide at a certain position 341 , show: function (index) { 342 // Validate index 343 if (Math.abs(index) > this.images.length - 1) { 344 return; 345 } else { 346 this.index = index; 347 } 348 349 // Vars 350 var self = this 351 , oldImage = self.$wrap.find('img').addClass('deleteable') 352 , evt = $.Event('backstretch.show', { 353 relatedTarget: self.$container[0] 354 }); 355 var lazyLoad = this.options.lazyLoad == true; 356 var lazyLoadClass = this.options.lazyLoad ? ' class="lazy"' : ''; 357 var lazyLoadAttribute = this.options.lazyLoad ? ' loading="lazy"' : ''; 358 // Pause the slideshow 359 clearInterval(self.interval); 360 361 if(this.videoMode) 362 { 363 var mutevideo = this.options.videoMuted == true; 364 365 // New video 366 var onplayingAttr = ' onplaying="'+this.options.videoOnPlaying.toString()+'"'; 367 var posterAttr = ' poster="'+this.options.videoPoster.toString()+'"'; 368 var preloadAttr = ' preload="'+this.options.videoPreload.toString()+'"'; 369 var autoplayAttr = this.options.videoAutoPlay ? ' autoplay="autoplay"' : ''; 370 var mutedAttr = this.options.videoMuted ? ' muted="muted"' : ''; 371 var loopAttr = this.options.videoLoop ? ' loop="loop"' : ''; 372 var inlineAttr = this.options.videoPlaysInline ? ' playsinline' : ''; 373 var videoSourceHTML = ""; 374 for(var j in self.images) 375 { 376 if (lazyLoad) { 377 videoSourceHTML += getVideoSourceHTML(self.images[j], true); 378 } else { 379 videoSourceHTML += getVideoSourceHTML(self.images[j]); 380 } 381 } 382 self.$img = $('<video '+lazyLoadClass+' id="'+this.options.videoID+'" canplay="false" '+loopAttr+autoplayAttr+posterAttr+onplayingAttr+preloadAttr+mutedAttr+inlineAttr+'>'+videoSourceHTML+'</video>') 383 .css(styles.video) 384 .bind('canplay', function (e) { 385 var imgWidth = this.width || $(e.target).width() 386 , imgHeight = this.height || $(e.target).height(); 387 388 // Save the ratio 389 $(this).data('ratio', imgWidth / imgHeight); 390 391 // Resize 392 self.resize(); 393 394 }) 395 .appendTo(self.$wrapInner); 396 397 // fix for browsers that don't support the muted attribute 398 self.$img[0].muted = mutevideo ? "muted" : ""; 399 400 401 } 402 else if(this.vimeoMode) 403 { 404 405 var vimeoSource = ""; 406 for(var j in self.images) 407 { 408 vimeoSource += self.images[j]; 409 } 410 var vimeoWidth = 0; 411 var vimeoHeight = 0; 412 self.$img = $('<iframe style="opacity: 0" canplay="false" src="'+vimeoSource+'?background=1&dnt=1" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>') 413 .css(styles.video).on('load', function (e) { 414 var imgWidth = this.width || $(e.target).width(), 415 imgHeight = this.height || $(e.target).height(); 416 }).appendTo(self.$wrapInner); 417 $(window).on('beforeunload', function() { 418 self.$img.css('opacity', '0'); 419 }); 420 // This is the URL of the video you want to load 421 var videourl = vimeoSource; 422 // This is the oEmbed endpoint for Vimeo (we're using JSON) 423 var endpoint = 'https://vimeo.com/api/oembed.json'; 424 // Put together the URL 425 videourl = endpoint + '?url=' + encodeURIComponent(videourl); 426 $.ajax({ 427 type : 'GET', 428 url : videourl, 429 format: "json", 430 dataType : 'jsonP', 431 success : function(data){ 432 vimeoWidth = data.width - 1; 433 vimeoHeight = data.height; 434 // Save the ratio 435 self.$img.data('ratio', vimeoWidth / vimeoHeight); 436 // Resize 437 self.resize(); 438 }, 439 error : function(XMLHttpRequest, textStatus, errorThrown) { 440 //alert('error'); 441 } 442 }); 443 444 $.getScript( "https://player.vimeo.com/api/player.js", function( data, textStatus, jqxhr ) { 445 var player = new Vimeo.Player(self.$img[0]); 446 player.on('timeupdate', function(data) { 447 if (data.seconds > 0) { 448 self.$img.stop().animate({'opacity': '1'}, 500); 449 self.$img.parent().removeClass('loading'); 450 player.off('timeupdate'); 451 } 452 }); 453 }); 454 455 } 456 else 457 { 458 // New image 459 self.$img = $('<img '+lazyLoadAttribute+' />') 460 .css(styles.img) 461 .bind('load', function (e) { 462 var imgWidth = this.width || $(e.target).width() 463 , imgHeight = this.height || $(e.target).height(); 464 465 // Save the ratio 466 $(this).data('ratio', imgWidth / imgHeight); 467 468 // Add alt text 469 $(this).attr('alt', self.$container.data('alt')); 470 471 if(self.options.resizeMethod == 'contain') 472 { 473 oldImage.fadeOut(self.options.speed || self.options.fade); 474 } 475 476 // Show the image, then delete the old one 477 // "speed" option has been deprecated, but we want backwards compatibilty 478 $(this).fadeIn(self.options.speed || self.options.fade, function () { 479 oldImage.remove(); 480 481 // Resume the slideshow 482 if (!self.paused) { 483 self.cycle(); 484 } 485 486 // Trigger the event 487 self.$container.trigger(evt, self); 488 }); 489 490 // Resize 491 self.resize(); 492 }) 493 .appendTo(self.$wrapInner); 494 495 // Hack for IE img onload event 496 self.$img.attr('src', self.images[index]); 497 } 498 return self; 499 } 500 501 , next: function () { 502 // Next slide 503 return this.show(this.index < this.images.length - 1 ? this.index + 1 : 0); 504 } 505 506 , prev: function () { 507 // Previous slide 508 return this.show(this.index === 0 ? this.images.length - 1 : this.index - 1); 509 } 510 511 , pause: function () { 512 // Pause the slideshow 513 this.paused = true; 514 return this; 515 } 516 517 , resume: function () { 518 // Resume the slideshow 519 this.paused = false;
520 this.next(); 521 return this; 522 } 523 524 , cycle: function () { 525 // Start/resume the slideshow 526 if(this.images.length > 1) { 527 // Clear the interval, just in case 528 clearInterval(this.interval); 529 530 this.interval = setInterval($.proxy(function () { 531 // Check for paused slideshow 532 if (!this.paused) { 533 this.next(); 534 } 535 }, this), this.options.duration); 536 } 537 return this; 538 } 539 540 , destroy: function (preserveBackground) { 541 // Stop the resize events 542 $(window).off('resize.backstretch orientationchange.backstretch'); 543 544 // Clear the interval 545 clearInterval(this.interval); 546 547 // Remove Backstretch 548 if(!preserveBackground) { 549 this.$wrap.remove(); 550 } 551 this.$container.removeData('backstretch'); 552 } 553 }; 554 555 var isVideo = function(path) { 556 var ext = path.split("."); 557 ext = ext[ext.length-1]; 558 ext = ext.split("?").shift(); 559 if(ext == "mp4" || ext == "m4v" || ext == "ogv" || ext == "flv" || ext == "webm" || ext == "asf" || ext == "avi" || path.indexOf("progressive_redirect") >= 0) 560 { 561 return(true); 562 } 563 else 564 { 565 return(false); 566 } 567 } 568 569 var isVimeo= function(path) { 570 if (path.indexOf("player.vimeo") >= 0) 571 { 572 return(true); 573 } 574 else 575 { 576 return(false); 577 } 578 } 579 580 var getVideoSourceHTML = function(path, lazy) { 581 var ext = path.split("."); 582 ext = ext[ext.length-1]; 583 ext = ext.split("?").shift(); 584 if (path.indexOf("progressive_redirect") >= 0) // Added to accomodate 585 { 586 ext = 'mp4'; 587 } 588 if (lazy) { 589 return('<source data-src="'+path+'" type="video/'+ext+'">'); 590 } else { 591 return('<source src="'+path+'" type="video/'+ext+'">'); 592 } 593 } 594 595 /* SUPPORTS FIXED POSITION? 596 * 597 * Based on code from jQuery Mobile 1.1.0 598 * http://jquerymobile.com/ 599 * 600 * In a nutshell, we need to figure out if fixed positioning is supported. 601 * Unfortunately, this is very difficult to do on iOS, and usually involves 602 * injecting content, scrolling the page, etc.. It's ugly. 603 * jQuery Mobile uses this workaround. It's not ideal, but works. 604 * 605 * Modified to detect IE6 606 * ========================= */ 607 608 var supportsFixedPosition = (function () { 609 var ua = navigator.userAgent 610 , platform = navigator.platform 611 // Rendering engine is Webkit, and capture major version 612 , wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ) 613 , wkversion = !!wkmatch && wkmatch[ 1 ] 614 , ffmatch = ua.match( /Fennec\/([0-9]+)/ ) 615 , ffversion = !!ffmatch && ffmatch[ 1 ] 616 , operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ) 617 , omversion = !!operammobilematch && operammobilematch[ 1 ] 618 , iematch = ua.match( /MSIE ([0-9]+)/ ) 619 , ieversion = !!iematch && iematch[ 1 ]; 620 621 return !( 622 // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5) 623 ((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) || 624 625 // Opera Mini 626 (window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") || 627 (operammobilematch && omversion < 7458) || 628 629 //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2) 630 (ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) || 631 632 // Firefox Mobile before 6.0 - 633 (ffversion && ffversion < 6) || 634 635 // WebOS less than 3 636 ("palmGetResource" in window && wkversion && wkversion < 534) || 637 638 // MeeGo 639 (ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) || 640 641 // IE6 642 (ieversion && ieversion <= 6) 643 ); 644 }()); 645 646}(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.