summaryrefslogtreecommitdiffstats
path: root/js/plax.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/plax.js')
-rw-r--r--js/plax.js381
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}());