diff options
Diffstat (limited to 'js/plax.js')
| -rw-r--r-- | js/plax.js | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/js/plax.js b/js/plax.js new file mode 100644 index 0000000..d79d8b4 --- /dev/null +++ b/js/plax.js | |||
| @@ -0,0 +1,381 @@ | |||
| 1 | /* Plax version 1.4.1 */ | ||
| 2 | |||
| 3 | /* | ||
| 4 | Copyright (c) 2011 Cameron McEfee | ||
| 5 | |||
| 6 | Permission is hereby granted, free of charge, to any person obtaining | ||
| 7 | a copy of this software and associated documentation files (the | ||
| 8 | "Software"), to deal in the Software without restriction, including | ||
| 9 | without limitation the rights to use, copy, modify, merge, publish, | ||
| 10 | distribute, sublicense, and/or sell copies of the Software, and to | ||
| 11 | permit persons to whom the Software is furnished to do so, subject to | ||
| 12 | the following conditions: | ||
| 13 | |||
| 14 | The above copyright notice and this permission notice shall be | ||
| 15 | included in all copies or substantial portions of the Software. | ||
| 16 | |||
| 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
| 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
| 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
| 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
| 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
| 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 24 | */ | ||
| 25 | |||
| 26 | (function ($) { | ||
| 27 | |||
| 28 | var maxfps = 25, | ||
| 29 | delay = 1 / maxfps * 1000, | ||
| 30 | lastRender = new Date().getTime(), | ||
| 31 | layers = [], | ||
| 32 | plaxActivityTarget = $(window), | ||
| 33 | motionDegrees = 30, | ||
| 34 | motionMax = 1, | ||
| 35 | motionMin = -1, | ||
| 36 | motionStartX = null, | ||
| 37 | motionStartY = null, | ||
| 38 | ignoreMoveable = false, | ||
| 39 | options = null; | ||
| 40 | |||
| 41 | var defaults = { | ||
| 42 | useTransform : true | ||
| 43 | }; | ||
| 44 | |||
| 45 | // Public Methods | ||
| 46 | $.fn.plaxify = function (params){ | ||
| 47 | options = $.extend({}, defaults, params); | ||
| 48 | options.useTransform = (options.useTransform ? supports3dTransform() : false); | ||
| 49 | |||
| 50 | return this.each(function () { | ||
| 51 | |||
| 52 | var layerExistsAt = -1; | ||
| 53 | var layer = { | ||
| 54 | "xRange": $(this).data('xrange') || 0, | ||
| 55 | "yRange": $(this).data('yrange') || 0, | ||
| 56 | "zRange": $(this).data('zrange') || 0, | ||
| 57 | "invert": $(this).data('invert') || false, | ||
| 58 | "background": $(this).data('background') || false | ||
| 59 | }; | ||
| 60 | |||
| 61 | for (var i=0;i<layers.length;i++){ | ||
| 62 | if (this === layers[i].obj.get(0)){ | ||
| 63 | layerExistsAt = i; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | for (var param in params) { | ||
| 68 | if (layer[param] == 0) { | ||
| 69 | layer[param] = params[param]; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | layer.inversionFactor = (layer.invert ? -1 : 1); // inversion factor for calculations | ||
| 74 | |||
| 75 | // Add an object to the list of things to parallax | ||
| 76 | layer.obj = $(this); | ||
| 77 | if(layer.background) { | ||
| 78 | // animate using the element's background | ||
| 79 | pos = (layer.obj.css('background-position') || "0px 0px").split(/ /); | ||
| 80 | if(pos.length != 2) { | ||
| 81 | return; | ||
| 82 | } | ||
| 83 | x = pos[0].match(/^((-?\d+)\s*px|0+\s*%|left)$/); | ||
| 84 | y = pos[1].match(/^((-?\d+)\s*px|0+\s*%|top)$/); | ||
| 85 | if(!x || !y) { | ||
| 86 | // no can-doesville, babydoll, we need pixels or top/left as initial values (it mightbe possible to construct a temporary image from the background-image property and get the dimensions and run some numbers, but that'll almost definitely be slow) | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | layer.originX = layer.startX = x[2] || 0; | ||
| 90 | layer.originY = layer.startY = y[2] || 0; | ||
| 91 | layer.transformOriginX = layer.transformStartX = 0; | ||
| 92 | layer.transformOriginY = layer.transformStartY = 0; | ||
| 93 | layer.transformOriginZ = layer.transformStartZ = 0; | ||
| 94 | |||
| 95 | } else { | ||
| 96 | |||
| 97 | // Figure out where the element is positioned, then reposition it from the top/left, same for transform if using translate3d | ||
| 98 | var position = layer.obj.position(), | ||
| 99 | transformTranslate = get3dTranslation(layer.obj); | ||
| 100 | |||
| 101 | layer.obj.css({ | ||
| 102 | 'transform' : transformTranslate.join() + 'px', | ||
| 103 | 'top' : position.top, | ||
| 104 | 'left' : position.left, | ||
| 105 | 'right' :'', | ||
| 106 | 'bottom':'' | ||
| 107 | }); | ||
| 108 | layer.originX = layer.startX = position.left; | ||
| 109 | layer.originY = layer.startY = position.top; | ||
| 110 | layer.transformOriginX = layer.transformStartX = transformTranslate[0]; | ||
| 111 | layer.transformOriginY = layer.transformStartY = transformTranslate[1]; | ||
| 112 | layer.transformOriginZ = layer.transformStartZ = transformTranslate[2]; | ||
| 113 | } | ||
| 114 | |||
| 115 | layer.startX -= layer.inversionFactor * Math.floor(layer.xRange/2); | ||
| 116 | layer.startY -= layer.inversionFactor * Math.floor(layer.yRange/2); | ||
| 117 | |||
| 118 | layer.transformStartX -= layer.inversionFactor * Math.floor(layer.xRange/2); | ||
| 119 | layer.transformStartY -= layer.inversionFactor * Math.floor(layer.yRange/2); | ||
| 120 | layer.transformStartZ -= layer.inversionFactor * Math.floor(layer.zRange/2); | ||
| 121 | |||
| 122 | if(layerExistsAt >= 0){ | ||
| 123 | layers.splice(layerExistsAt,1,layer); | ||
| 124 | } else { | ||
| 125 | layers.push(layer); | ||
| 126 | } | ||
| 127 | |||
| 128 | }); | ||
| 129 | }; | ||
| 130 | |||
| 131 | // Get the translate position of the element | ||
| 132 | // | ||
| 133 | // return 3 element array for translate3d | ||
| 134 | function get3dTranslation(obj) { | ||
| 135 | var translate = [0,0,0], | ||
| 136 | matrix = obj.css("-webkit-transform") || | ||
| 137 | obj.css("-moz-transform") || | ||
| 138 | obj.css("-ms-transform") || | ||
| 139 | obj.css("-o-transform") || | ||
| 140 | obj.css("transform"); | ||
| 141 | |||
| 142 | if(matrix !== 'none') { | ||
| 143 | var values = matrix.split('(')[1].split(')')[0].split(','); | ||
| 144 | var x = 0, | ||
| 145 | y = 0, | ||
| 146 | z = 0; | ||
| 147 | if(values.length == 16){ | ||
| 148 | // 3d matrix | ||
| 149 | x = (parseFloat(values[values.length - 4])); | ||
| 150 | y = (parseFloat(values[values.length - 3])); | ||
| 151 | z = (parseFloat(values[values.length - 2])); | ||
| 152 | }else{ | ||
| 153 | // z is not transformed as is not a 3d matrix | ||
| 154 | x = (parseFloat(values[values.length - 2])); | ||
| 155 | y = (parseFloat(values[values.length - 1])); | ||
| 156 | z = 0; | ||
| 157 | } | ||
| 158 | translate = [x,y,z]; | ||
| 159 | } | ||
| 160 | return translate; | ||
| 161 | } | ||
| 162 | |||
| 163 | // Check if element is in viewport area | ||
| 164 | // | ||
| 165 | // Returns boolean | ||
| 166 | function inViewport(element) { | ||
| 167 | if (element.offsetWidth === 0 || element.offsetHeight === 0) return false; | ||
| 168 | |||
| 169 | var height = document.documentElement.clientHeight, | ||
| 170 | rects = element.getClientRects(); | ||
| 171 | |||
| 172 | for (var i = 0, l = rects.length; i < l; i++) { | ||
| 173 | |||
| 174 | var r = rects[i], | ||
| 175 | in_viewport = r.top > 0 ? r.top <= height : (r.bottom > 0 && r.bottom <= height); | ||
| 176 | |||
| 177 | if (in_viewport) return true; | ||
| 178 | } | ||
| 179 | return false; | ||
| 180 | } | ||
| 181 | |||
| 182 | // Check support for 3dTransform | ||
| 183 | // | ||
| 184 | // Returns boolean | ||
| 185 | function supports3dTransform() { | ||
| 186 | var el = document.createElement('p'), | ||
| 187 | has3d, | ||
| 188 | transforms = { | ||
| 189 | 'webkitTransform':'-webkit-transform', | ||
| 190 | 'OTransform':'-o-transform', | ||
| 191 | 'msTransform':'-ms-transform', | ||
| 192 | 'MozTransform':'-moz-transform', | ||
| 193 | 'transform':'transform' | ||
| 194 | }; | ||
| 195 | |||
| 196 | document.body.insertBefore(el, null); | ||
| 197 | |||
| 198 | for (var t in transforms) { | ||
| 199 | if (el.style[t] !== undefined) { | ||
| 200 | el.style[t] = "translate3d(1px,1px,1px)"; | ||
| 201 | has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | document.body.removeChild(el); | ||
| 206 | return (has3d !== undefined && has3d.length > 0 && has3d !== "none"); | ||
| 207 | } | ||
| 208 | |||
| 209 | // Determine if the device has an accelerometer | ||
| 210 | // | ||
| 211 | // returns true if the browser has window.DeviceMotionEvent (mobile) | ||
| 212 | function moveable(){ | ||
| 213 | return (ignoreMoveable===true) ? false : window.DeviceOrientationEvent !== undefined; | ||
| 214 | } | ||
| 215 | |||
| 216 | // The values pulled from the gyroscope of a motion device. | ||
| 217 | // | ||
| 218 | // Returns an object literal with x and y as options. | ||
| 219 | function valuesFromMotion(e) { | ||
| 220 | x = e.gamma; | ||
| 221 | y = e.beta; | ||
| 222 | |||
| 223 | // Swap x and y in Landscape orientation | ||
| 224 | if (Math.abs(window.orientation) === 90) { | ||
| 225 | var a = x; | ||
| 226 | x = y; | ||
| 227 | y = a; | ||
| 228 | } | ||
| 229 | |||
| 230 | // Invert x and y in upsidedown orientations | ||
| 231 | if (window.orientation < 0) { | ||
| 232 | x = -x; | ||
| 233 | y = -y; | ||
| 234 | } | ||
| 235 | |||
| 236 | motionStartX = (motionStartX === null) ? x : motionStartX; | ||
| 237 | motionStartY = (motionStartY === null) ? y : motionStartY; | ||
| 238 | |||
| 239 | return { | ||
| 240 | x: x - motionStartX, | ||
| 241 | y: y - motionStartY | ||
| 242 | }; | ||
| 243 | } | ||
| 244 | |||
| 245 | // Move the elements in the `layers` array within their ranges, | ||
| 246 | // based on mouse or motion input | ||
| 247 | // | ||
| 248 | // Parameters | ||
| 249 | // | ||
| 250 | // e - mousemove or devicemotion event | ||
| 251 | // | ||
| 252 | // returns nothing | ||
| 253 | function plaxifier(e) { | ||
| 254 | if (new Date().getTime() < lastRender + delay) return; | ||
| 255 | lastRender = new Date().getTime(); | ||
| 256 | |||
| 257 | var leftOffset = (plaxActivityTarget.offset() != null) ? plaxActivityTarget.offset().left : 0, | ||
| 258 | topOffset = (plaxActivityTarget.offset() != null) ? plaxActivityTarget.offset().top : 0, | ||
| 259 | x = e.pageX-leftOffset, | ||
| 260 | y = e.pageY-topOffset; | ||
| 261 | |||
| 262 | if (!inViewport(layers[0].obj[0].parentNode)) return; | ||
| 263 | |||
| 264 | if(moveable()){ | ||
| 265 | if(e.gamma === undefined){ | ||
| 266 | ignoreMoveable = true; | ||
| 267 | return; | ||
| 268 | } | ||
| 269 | values = valuesFromMotion(e); | ||
| 270 | |||
| 271 | // Admittedly fuzzy measurements | ||
| 272 | x = values.x / motionDegrees; | ||
| 273 | y = values.y / motionDegrees; | ||
| 274 | // Ensure not outside of expected range, -1 to 1 | ||
| 275 | x = x < motionMin ? motionMin : (x > motionMax ? motionMax : x); | ||
| 276 | y = y < motionMin ? motionMin : (y > motionMax ? motionMax : y); | ||
| 277 | // Normalize from -1 to 1 => 0 to 1 | ||
| 278 | x = (x + 1) / 2; | ||
| 279 | y = (y + 1) / 2; | ||
| 280 | } | ||
| 281 | |||
| 282 | var hRatio = x/((moveable() === true) ? motionMax : plaxActivityTarget.width()), | ||
| 283 | vRatio = y/((moveable() === true) ? motionMax : plaxActivityTarget.height()), | ||
| 284 | layer, i; | ||
| 285 | |||
| 286 | for (i = layers.length; i--;) { | ||
| 287 | layer = layers[i]; | ||
| 288 | if(options.useTransform && !layer.background){ | ||
| 289 | newX = layer.transformStartX + layer.inversionFactor*(layer.xRange*hRatio); | ||
| 290 | newY = layer.transformStartY + layer.inversionFactor*(layer.yRange*vRatio); | ||
| 291 | newZ = layer.transformStartZ; | ||
| 292 | layer.obj | ||
| 293 | .css({'transform':'translate3d('+newX+'px,'+newY+'px,'+newZ+'px)'}); | ||
| 294 | }else{ | ||
| 295 | newX = layer.startX + layer.inversionFactor*(layer.xRange*hRatio); | ||
| 296 | newY = layer.startY + layer.inversionFactor*(layer.yRange*vRatio); | ||
| 297 | if(layer.background) { | ||
| 298 | layer.obj | ||
| 299 | .css('background-position', newX+'px '+newY+'px'); | ||
| 300 | } else { | ||
| 301 | layer.obj | ||
| 302 | .css('left', newX) | ||
| 303 | .css('top', newY); | ||
| 304 | } | ||
| 305 | } | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | $.plax = { | ||
| 310 | // Begin parallaxing | ||
| 311 | // | ||
| 312 | // Parameters | ||
| 313 | // | ||
| 314 | // opts - options for plax | ||
| 315 | // activityTarget - optional; plax will only work within the bounds of this element, if supplied. | ||
| 316 | // | ||
| 317 | // Examples | ||
| 318 | // | ||
| 319 | // $.plax.enable({ "activityTarget": $('#myPlaxDiv')}) | ||
| 320 | // # plax only happens when the mouse is over #myPlaxDiv | ||
| 321 | // | ||
| 322 | // returns nothing | ||
| 323 | enable: function(opts){ | ||
| 324 | if (opts) { | ||
| 325 | if (opts.activityTarget) plaxActivityTarget = opts.activityTarget || $(window); | ||
| 326 | if (typeof opts.gyroRange === 'number' && opts.gyroRange > 0) motionDegrees = opts.gyroRange; | ||
| 327 | } | ||
| 328 | |||
| 329 | plaxActivityTarget.bind('mousemove.plax', function (e) { | ||
| 330 | plaxifier(e); | ||
| 331 | }); | ||
| 332 | |||
| 333 | if(moveable()){ | ||
| 334 | window.ondeviceorientation = function(e){plaxifier(e);}; | ||
| 335 | } | ||
| 336 | |||
| 337 | }, | ||
| 338 | |||
| 339 | // Stop parallaxing | ||
| 340 | // | ||
| 341 | // Examples | ||
| 342 | // | ||
| 343 | // $.plax.disable() | ||
| 344 | // # plax no longer runs | ||
| 345 | // | ||
| 346 | // $.plax.disable({ "clearLayers": true }) | ||
| 347 | // # plax no longer runs and all layers are forgotten | ||
| 348 | // | ||
| 349 | // returns nothing | ||
| 350 | disable: function(opts){ | ||
| 351 | $(document).unbind('mousemove.plax'); | ||
| 352 | window.ondeviceorientation = undefined; | ||
| 353 | if (opts && typeof opts.restorePositions === 'boolean' && opts.restorePositions) { | ||
| 354 | for(var i = layers.length; i--;) { | ||
| 355 | layer = layers[i]; | ||
| 356 | if(options.useTransform && !layer.background){ | ||
| 357 | layer.obj | ||
| 358 | .css('transform', 'translate3d('+layer.transformOriginX+'px,'+layer.transformOriginY+'px,'+layer.transformOriginZ+'px)') | ||
| 359 | .css('top', layer.originY); | ||
| 360 | }else{ | ||
| 361 | if(layers[i].background) { | ||
| 362 | layer.obj.css('background-position', layer.originX+'px '+layer.originY+'px'); | ||
| 363 | } else { | ||
| 364 | layer.obj | ||
| 365 | .css('left', layer.originX) | ||
| 366 | .css('top', layer.originY); | ||
| 367 | } | ||
| 368 | } | ||
| 369 | } | ||
| 370 | } | ||
| 371 | if (opts && typeof opts.clearLayers === 'boolean' && opts.clearLayers) layers = []; | ||
| 372 | } | ||
| 373 | }; | ||
| 374 | |||
| 375 | if (typeof ender !== 'undefined') { | ||
| 376 | $.ender($.fn, true); | ||
| 377 | } | ||
| 378 | |||
| 379 | })(function () { | ||
| 380 | return typeof jQuery !== 'undefined' ? jQuery : ender; | ||
| 381 | }()); | ||
