1/* 2 Copyright (C) 2013 Ziad Saab ziad(dot)saab(at)gmail(dot)com 3 4 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 documentation files (the "Software"), to deal in the Software without restriction, including without limitation 6 the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 9 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 11 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 13 OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15*/ 16;(function($) { 17 var ParallaxManager = function(options) { 18 this.options = options; 19 this.vendor_prefixes = ['webkit', 'moz', 'o', 'ms']; 20 this.num_vendor_prefixes = this.vendor_prefixes.length; 21 22 // Function used to detect support for a certain CSS style 23 var thisBrowserSupportsStyle = function(style) { 24 var vendors = ['Webkit', 'Moz', 'ms', 'O']; 25 var num_vendors = vendors.length; 26 var dummy_el = window.document.createElement('parallax'); 27 28 // First test the bare style without prefix 29 if (dummy_el.style[style] !== undefined) { 30 return true; 31 } 32 33 // Test the camel-cased vendor-prefixed styles 34 style = style.replace(/./, function(first) {return first.toUpperCase();}); 35 for (var i = 0; i < num_vendors; i++) { 36 var pfx_style = vendors[i] + style; 37 // The browser will return an empty string if a style is supported but not present, and undefined if the style is not supported 38 if (dummy_el.style[pfx_style] !== undefined) { 39 return true; 40 } 41 } 42 return false; 43 }; 44 45 // Detect if we have 2d or 3d transforms by checking for support of the transform (2d) and perspective (3d) styles 46 this.has_3dtransforms = thisBrowserSupportsStyle('perspective'); 47 // Additional test due to false positives returned with WebKit on some browsers: they support perspective but can't do 3d transforms 48 if (this.has_3dtransforms && thisBrowserSupportsStyle('WebkitPerspective')) { 49 var $test_el = $('<div><style type="text/css">@media (transform-3d),(-webkit-transform-3d) {#parallax-3dtest {position: absolute;left: 9px;height: 5px;margin: 0;padding: 0;border: 0;}</style><div id="parallax-3dtest"></div></div>').appendTo('body'); 50 var $el = $('#parallax-3dtest'); 51 this.has_3dtransforms = $el.height() == 5 && $el.offset().left == 9; 52 $test_el.remove(); 53 } 54 this.has_2dtransforms = thisBrowserSupportsStyle('transform'); 55 }; 56 57 // Define "methods" on the ParallaxManager class 58 $.extend(ParallaxManager.prototype, { 59 init: function() { 60 this.scroll_factor = this.options.scroll_factor; 61 var parallax_blocks = this.parallax_blocks = []; 62 var image_attr = this.options.image_attr 63 64 var $body = $('body'); 65 var $origins = this.options.origins; 66 $origins.each(function() { 67 var $origin = $(this); 68 var $parallax_block; 69 if ($origin.data(image_attr)) { 70 $parallax_block = $('<div class="parallax-block"><img class="parallax-image" src="' + $origin.data(image_attr) + '"></div>'); 71 parallax_blocks.push({ 72 origin: $origin, 73 block: $parallax_block, 74 bg_ratio: $origin.data('width') / $origin.data('height') 75 }); 76 $body.prepend($parallax_block); 77 } 78 else if ($origin.data('tile')) { 79 $parallax_block = $('<div class="parallax-block"><div class="parallax-image" style="background-image: url(' + $origin.data('tile') + ')"></div></div>') 80 parallax_blocks.push({ 81 origin: $origin, 82 block: $parallax_block, 83 bg_ratio: 1 84 }); 85 $body.prepend($parallax_block); 86 } 87 }); 88 89 var manager = this; 90 var reconfigure = function() { 91 manager.redrawBlocks(); 92 manager.render(); 93 }; 94 var $window = $(window); 95 $window.on('load', reconfigure); 96 $window.on('resize', reconfigure); 97 $window.on('hwparallax.reconfigure', reconfigure); 98 $window.on('scroll', function() {manager.render();}); 99 }, 100 redrawBlocks: function() { 101 var window_width = $(window).width(); 102 var window_height = this.window_height = $(window).height(); 103 104 // Calculate document height to fix Chrome issue when scrolling past the content... 105 var body = document.body; 106 var html = document.documentElement; 107 108 var document_height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); 109 this.max_scrolltop = Math.max(0, document_height - window_height); 110 111 var num_parallax_blocks = this.parallax_blocks.length; 112 for (var i = 0; i < num_parallax_blocks; i++) { 113 var parallax_block_data = this.parallax_blocks[i]; 114 var $parallax_block = parallax_block_data.block; 115 var bg_ratio = parallax_block_data.bg_ratio; 116 var $parallax_image = $parallax_block.children('.parallax-image'); 117 var $origin = parallax_block_data.origin; 118 119 // Calculate the minimum background image height so that we may scroll by the scroll factor and keep the image in the bounds of its container 120 var origin_height = $origin.outerHeight(); 121 var min_height = window_height - ((window_height - origin_height) * this.scroll_factor); 122 123 // Calculate the size of the parallax background image so that it covers the target block while still respecting the previously calculated minimum height 124 // Also, if the image's width is larger than the window's, center it about its parent element 125 var img_width = window_width;
126 var img_height = Math.ceil(img_width / bg_ratio); 127 var img_xoff = 0; 128 if (img_height < min_height) { 129 img_height = min_height; 130 img_width = img_height * bg_ratio; 131 img_xoff = Math.floor(img_width - window_width) / 2; 132 } 133 $parallax_image.width(img_width).height(img_height); 134 135 // Set the dimensions of the parallax block and hide it 136 $parallax_block.width(window_width).height(origin_height).css('visibility', 'hidden'); 137 138 // Cache some data about the parallax block so that we don't have to recalculate it on every tick 139 // This data potentially changes, only changes when the window is resized 140 $.extend(parallax_block_data, { 141 origin_position: $origin.offset().top, 142 origin_height: origin_height, 143 image: $parallax_image, 144 image_xoff: img_xoff, 145 image_height: img_height 146 }); 147 } 148 }, 149 render: function() { 150 if (!this.drawing) { 151 this.drawing = true; 152 var manager = this; 153 if (window.requestAnimationFrame) { 154 window.requestAnimationFrame(function() {manager.draw();}, document); 155 } 156 else { 157 manager.draw(); 158 } 159 } 160 }, 161 draw: function() { 162 var scroll_top = Math.min(Math.max(0, $(window).scrollTop()), this.max_scrolltop); 163 164 // Loop thru all the parallax blocks 165 var num_blocks = this.parallax_blocks.length; 166 var data; 167 for (var i = 0; i < num_blocks; i++) { 168 data = this.parallax_blocks[i]; 169 // Determine if parallax block is in view 170 // i.e. its bottom is below the window's upper bound and its top is above the window's lower bound 171 if (data.origin_position < scroll_top + this.window_height && data.origin_position + data.origin_height > scroll_top) { 172 // The block's y position in its fixed plane is the position of the original block relative to the top of the window 173 var new_block_position = data.origin_position - scroll_top; 174 // The background image's position is parallaxed to follow the block by the scroll factor 175 var new_image_position = new_block_position * (this.scroll_factor - 1); 176 177 // Apply the translations to block and image using the best method possible: 3d transform, 2d transform or regular top/left 178 var block_styles = { 179 visibility: 'visible' 180 }; 181 var image_styles = {}; 182 var block_transform, image_transform, prefixed_style; 183 var j; 184 185 if (this.has_3dtransforms) { 186 block_transform = block_styles.transform = 'translate3d(0px, ' + new_block_position + 'px, 0px)'; 187 image_transform = image_styles.transform = 'translate3d(-' + data.image_xoff + 'px, ' + new_image_position + 'px, 0px)'; 188 for (j = 0; j < this.num_vendor_prefixes; j++) { 189 prefixed_style = '-' + this.vendor_prefixes[j] + '-transform'; 190 block_styles[prefixed_style] = block_transform; 191 image_styles[prefixed_style] = image_transform; 192 } 193 } 194 else if (this.has_2dtransforms) { 195 block_transform = block_styles.transform = 'translate(0px, ' + new_block_position + 'px)'; 196 image_transform = image_styles.transform = 'translate(-' + data.image_xoff + 'px, ' + new_image_position + 'px)'; 197 for (j = 0; j < this.num_vendor_prefixes; j++) { 198 prefixed_style = '-' + this.vendor_prefixes[j] + '-transform'; 199 block_styles[prefixed_style] = block_transform; 200 image_styles[prefixed_style] = image_transform; 201 } 202 } 203 else { 204 block_styles.top = new_block_position + 'px'; 205 block_styles.left = 0 + 'px'; 206 image_styles.top = new_image_position + 'px'; 207 image_styles.left = -data.image_xoff + 'px'; 208 } 209 data.block.css(block_styles); 210 data.image.css(image_styles); 211 } 212 else { 213 data.block.css('visibility', 'hidden'); 214 } 215 } 216 this.drawing = false;
217 } 218 }); 219 220 // Hook into jQuery 221 $.extend($.fn, { 222 parallax: function(options) { 223 var settings = $.extend( 224 { 225 scroll_factor: 0.2, 226 image_attr: 'image' 227 }, 228 options, 229 { 230 origins: $(this) 231 } 232 ); 233 var pm = new ParallaxManager(settings); 234 pm.init(); 235 } 236 }); 237})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.