From 522e777ab83758883afd5df4e160ffc5a4756c36 Mon Sep 17 00:00:00 2001 From: Patrick Nelson Date: Wed, 11 May 2016 18:35:15 -0700 Subject: [PATCH] FIX for #88 (cont.): Simplifying further and refactored to 'followPointer'.by removing need for separate on/off frame animation functions. --- js/jquery.parallax.js | 387 ++++++++++++++++++++---------------------- 1 file changed, 181 insertions(+), 206 deletions(-) diff --git a/js/jquery.parallax.js b/js/jquery.parallax.js index 54b8306..6ff4092 100644 --- a/js/jquery.parallax.js +++ b/js/jquery.parallax.js @@ -9,73 +9,70 @@ // github.com/stephband/jparallax (function(jQuery, undefined) { - // VAR - var debug = true, - - options = { - mouseport: 'body', // jQuery object or selector of DOM node to use as mouseport - xparallax: true, // boolean | 0-1 | 'npx' | 'n%' - yparallax: true, // - xorigin: 0.5, // 0-1 - Sets default alignment. Only has effect when parallax values are something other than 1 (or true, or '100%') - yorigin: 0.5, // - decay: 0.66, // 0-1 (0 instant, 1 forever) - Sets rate of decay curve for catching up with target mouse position - frameDuration: 30, // Int (milliseconds) - freezeClass: 'freeze' // String - Class added to layer when frozen - }, - - value = { - left: 0, - top: 0, - middle: 0.5, - center: 0.5, - right: 1, - bottom: 1 - }, - - rpx = /^\d+\s?px$/, - rpercent = /^\d+\s?%$/, - - win = jQuery(window), - doc = jQuery(document), - mouse = [0, 0]; - + var options = { + mouseport: 'body', // jQuery object or selector of DOM node to use as mouseport + xparallax: true, // boolean | 0-1 | 'npx' | 'n%' + yparallax: true, // + xorigin: 0.5, // 0-1 - Sets default alignment. Only has effect when parallax values are something other than 1 (or true, or '100%') + yorigin: 0.5, // + decay: 0.66, // 0-1 (0 instant, 1 forever) - Sets rate of decay curve for catching up with target mouse position + frameDuration: 30, // Int (milliseconds) + freezeClass: 'freeze' // String - Class added to layer when frozen + }, + + value = { + left: 0, + top: 0, + middle: 0.5, + center: 0.5, + right: 1, + bottom: 1 + }, + + rpx = /^\d+\s?px$/, + rpercent = /^\d+\s?%$/, + + win = jQuery(window), + doc = jQuery(document), + mouse = [0, 0]; + var Timer = (function(){ var debug = false; - + // Shim for requestAnimationFrame, falling back to timer. See: // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ var requestFrame = (function(){ - return ( - window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(fn, node){ - return window.setTimeout(function(){ - fn(); - }, 25); - } - ); - })(); - + return ( + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(fn, node){ + return window.setTimeout(function(){ + fn(); + }, 25); + } + ); + })(); + function Timer() { var callbacks = [], nextFrame; - + function noop() {} - + function frame(){ var cbs = callbacks.slice(0), - l = cbs.length, - i = -1; - + l = cbs.length, + i = -1; + if (debug) { console.log('timer frame()', l); } - + while(++i < l) { cbs[i].call(this); } requestFrame(nextFrame); } - + function start() { if (debug) { console.log('timer start()'); } this.start = noop; @@ -83,14 +80,14 @@ nextFrame = frame; requestFrame(nextFrame); } - + function stop() { if (debug) { console.log('timer stop()'); } this.start = start; this.stop = noop; nextFrame = noop; } - + this.callbacks = callbacks; this.start = start; this.stop = stop; @@ -99,74 +96,74 @@ Timer.prototype = { add: function(fn) { var callbacks = this.callbacks, - l = callbacks.length; - + l = callbacks.length; + // Check to see if this callback is already in the list. // Don't add it twice. while (l--) { if (callbacks[l] === fn) { return; } } - + this.callbacks.push(fn); if (debug) { console.log('timer add()', this.callbacks.length); } }, - + remove: function(fn) { var callbacks = this.callbacks, - l = callbacks.length; - + l = callbacks.length; + // Remove all instances of this callback. while (l--) { if (callbacks[l] === fn) { callbacks.splice(l, 1); } } - + if (debug) { console.log('timer remove()', this.callbacks.length); } - + if (callbacks.length === 0) { this.stop(); } } }; - + return Timer; })(); - + function parseCoord(x) { return (rpercent.exec(x)) ? parseFloat(x)/100 : x; } - + function parseBool(x) { return typeof x === "boolean" ? x : !!( parseFloat(x) ) ; } - + function portData(port) { var events = { - 'mouseenter.parallax': mouseenter, - 'mouseleave.parallax': mouseleave - }, - winEvents = { - 'resize.parallax': resize - }, - data = { - elem: port, - events: events, - winEvents: winEvents, - timer: new Timer() - }, - layers, size, offset; - + 'mouseenter.parallax': mouseenter, + 'mouseleave.parallax': mouseleave + }, + winEvents = { + 'resize.parallax': resize + }, + data = { + elem: port, + events: events, + winEvents: winEvents, + timer: new Timer() + }, + layers, size, offset; + function updatePointer() { data.pointer = getPointer(mouse, [true, true], offset, size); } - + function resize() { size = getSize(port); offset = getOffset(port); data.threshold = getThreshold(size); } - + function mouseenter() { data.timer.add(updatePointer); } - + function mouseleave(e) { data.timer.remove(updatePointer); data.pointer = getPointer([e.pageX, e.pageY], [true, true], offset, size); @@ -174,152 +171,152 @@ win.on(winEvents); port.on(events); - + resize(); - + return data; } - + function getData(elem, name, fn) { var data = elem.data(name); - + if (!data) { data = fn ? fn(elem) : {} ; elem.data(name, data); } - + return data; } - + function getPointer(mouse, parallax, offset, size){ var pointer = [], - x = 2; - + x = 2; + while (x--) { pointer[x] = (mouse[x] - offset[x]) / size[x] ; pointer[x] = pointer[x] < 0 ? 0 : pointer[x] > 1 ? 1 : pointer[x] ; } - + return pointer; } - + function getSize(elem) { return [elem.width(), elem.height()]; } - + function getOffset(elem) { var offset = elem.offset() || {left: 0, top: 0}, borderLeft = elem.css('borderLeftStyle') === 'none' ? 0 : parseInt(elem.css('borderLeftWidth'), 10), borderTop = elem.css('borderTopStyle') === 'none' ? 0 : parseInt(elem.css('borderTopWidth'), 10), paddingLeft = parseInt(elem.css('paddingLeft'), 10), paddingTop = parseInt(elem.css('paddingTop'), 10); - + return [offset.left + borderLeft + paddingLeft, offset.top + borderTop + paddingTop]; } - + function getThreshold(size) { return [1/size[0], 1/size[1]]; } - + function layerSize(elem, x, y) { return [x || elem.outerWidth(), y || elem.outerHeight()]; } - + function layerOrigin(xo, yo) { var o = [xo, yo], i = 2, origin = []; - + while (i--) { origin[i] = typeof o[i] === 'string' ? o[i] === undefined ? 1 : - value[origin[i]] || parseCoord(origin[i]) : + value[origin[i]] || parseCoord(origin[i]) : o[i] ; } - + return origin; } - + function layerPx(xp, yp) { return [rpx.test(xp), rpx.test(yp)]; } - + function layerParallax(xp, yp, px) { var p = [xp, yp], - i = 2, - parallax = []; - + i = 2, + parallax = []; + while (i--) { parallax[i] = px[i] ? parseInt(p[i], 10) : parallax[i] = p[i] === true ? 1 : parseCoord(p[i]) ; } - + return parallax; } - + function layerOffset(parallax, px, origin, size) { var i = 2, - offset = []; - + offset = []; + while (i--) { offset[i] = px[i] ? - origin[i] * (size[i] - parallax[i]) : + origin[i] * (size[i] - parallax[i]) : parallax[i] ? origin[i] * ( 1 - parallax[i] ) : 0 ; } - + return offset; } - + function layerPosition(px, origin) { var i = 2, - position = []; - + position = []; + while (i--) { if (px[i]) { // Set css position constant position[i] = origin[i] * 100 + '%'; } else { - + } } - + return position; } - + function layerPointer(elem, parallax, px, offset, size) { var viewport = elem.offsetParent(), pos = elem.position(), position = [], pointer = [], i = 2; - + // Reverse calculate ratio from elem's current position while (i--) { position[i] = px[i] ? // TODO: reverse calculation for pixel case 0 : - pos[i === 0 ? 'left' : 'top'] / (viewport[i === 0 ? 'outerWidth' : 'outerHeight']() - size[i]) ; - + pos[i === 0 ? 'left' : 'top'] / (viewport[i === 0 ? 'outerWidth' : 'outerHeight']() - size[i]) ; + pointer[i] = (position[i] - offset[i]) / parallax[i] ; } - + return pointer; } - + function layerCss(parallax, px, offset, size, position, pointer) { var pos = [], - cssPosition, - cssMargin, - x = 2, - css = {}; - + cssPosition, + cssMargin, + x = 2, + css = {}; + while (x--) { if (parallax[x]) { pos[x] = parallax[x] * pointer[x] + offset[x]; - + // We're working in pixels if (px[x]) { cssPosition = position[x]; @@ -330,7 +327,7 @@ cssPosition = pos[x] * 100 + '%'; cssMargin = pos[x] * size[x] * -1; } - + // Fill in css object if (x === 0) { css.left = cssPosition; @@ -342,85 +339,76 @@ } } } - + return css; } - - function pointerOffTarget(targetPointer, prevPointer, threshold, decay, parallax, targetFn, updateFn, portElem) { + + function followPointer(targetPointer, prevPointer, threshold, decay, parallax, targetFn, updateFn) { var pointer, x; - + if ((!parallax[0] || Math.abs(targetPointer[0] - prevPointer[0]) < threshold[0]) && - (!parallax[1] || Math.abs(targetPointer[1] - prevPointer[1]) < threshold[1])) { - // Pointer has hit the target - if (targetFn) { targetFn(); } - return updateFn(targetPointer); + (!parallax[1] || Math.abs(targetPointer[1] - prevPointer[1]) < threshold[1])) { + + // Pointer has hit the target, so do nothing (just idle). + if (targetFn) targetFn(); + return updateFn(targetPointer); } - + // Pointer is nowhere near the target pointer = []; x = 2; - + while (x--) { if (parallax[x]) { pointer[x] = targetPointer[x] + decay * (prevPointer[x] - targetPointer[x]); } } - + return updateFn(pointer); } - - function pointerOnTarget(targetPointer, prevPointer, threshold, decay, parallax, targetFn, updateFn, portElem) { - // Don't bother updating if the pointer hasn't changed. - if (targetPointer[0] === prevPointer[0] && targetPointer[1] === prevPointer[1]) { - return; - } - // Pass the responsibility for updating back to "pointerOffTarget" now that we're off the target again. - portElem.trigger('parallax.setPointerFn', [pointerOffTarget]); - } - function unport(elem, events, winEvents) { elem.off(events).removeData('parallax_port'); win.off(winEvents); } - + function unparallax(node, port, events) { port.elem.off(events); - + // Remove this node from layers port.layers = port.layers.not(node); - + // If port.layers is empty, destroy the port if (port.layers.length === 0) { unport(port.elem, port.events, port.winEvents); } } - + function unstyle(parallax) { var css = {}; - + if (parallax[0]) { css.left = ''; css.marginLeft = ''; } - + if (parallax[1]) { css.top = ''; css.marginTop = ''; } - + elem.css(css); } - + jQuery.fn.parallax = function(o){ var options = jQuery.extend({}, jQuery.fn.parallax.options, o), - args = arguments, - elem = options.mouseport instanceof jQuery ? - options.mouseport : - jQuery(options.mouseport) , - port = getData(elem, 'parallax_port', portData), - timer = port.timer; - + args = arguments, + elem = options.mouseport instanceof jQuery ? + options.mouseport : + jQuery(options.mouseport), + port = getData(elem, 'parallax_port', portData), + timer = port.timer; + return this.each(function(i) { var node = this, elem = jQuery(this), @@ -433,94 +421,81 @@ offset = layerOffset(parallax, px, origin, size), position = layerPosition(px, origin), pointer = layerPointer(elem, parallax, px, offset, size), - pointerFn = pointerOffTarget, - targetFn = targetInside, + targetFn = null, // Function which should be called once target is reached, if applicable. events = { 'mouseenter.parallax': function mouseenter(e) { - pointerFn = pointerOffTarget; - targetFn = targetInside; + targetFn = null; timer.add(frame); timer.start(); }, 'mouseleave.parallax': function mouseleave(e) { - // Make the layer come to rest at it's limit with inertia - pointerFn = pointerOffTarget; - // Stop the timer when the the pointer hits target + // Now that user has moved mouse out of view port, completely stop timer, but only do so when + // the target is finally reached. targetFn = targetOutside; - }, - - // TODO: Needed to set this up as an event listener due to scope issues. - 'parallax.setPointerFn': function setPointerFn(e, newPointerFn) { - pointerFn = newPointerFn; } }; - + function updateCss(newPointer) { var css = layerCss(parallax, px, offset, size, position, newPointer); elem.css(css); pointer = newPointer; } - + function frame() { // TODO: Might make sense to eventually refactor calls to pointerFN to simply pass the port instance. // TODO: Also to have the entire initialization to be based on a jQuery set of just the port [port] and // TODO: pass the layers as an option instead of the other way around, i.e. [layer, layer, ...] - pointerFn(port.pointer, pointer, port.threshold, decay, parallax, targetFn, updateCss, elem); - } - - function targetInside() { - // Pointer hits the target pointer inside the port - pointerFn = pointerOnTarget; + followPointer(port.pointer, pointer, port.threshold, decay, parallax, targetFn, updateCss, elem); } - + function targetOutside() { - // Pointer hits the target pointer outside the port + // Stop the timer when the the pointer hits target timer.remove(frame); } - - + + if (jQuery.data(node, 'parallax')) { elem.unparallax(); } - + jQuery.data(node, 'parallax', { port: port, events: events, parallax: parallax }); - + port.elem.on(events); port.layers = port.layers? port.layers.add(node): jQuery(node); - + /*function freeze() { - freeze = true; - } - - function unfreeze() { - freeze = false; - }*/ - + freeze = true; + } + + function unfreeze() { + freeze = false; + }*/ + /*jQuery.event.add(this, 'freeze.parallax', freeze); - jQuery.event.add(this, 'unfreeze.parallax', unfreeze);*/ + jQuery.event.add(this, 'unfreeze.parallax', unfreeze);*/ }); }; - + jQuery.fn.unparallax = function(bool) { return this.each(function() { var data = jQuery.data(this, 'parallax'); - + // This elem is not parallaxed if (!data) { return; } - + jQuery.removeData(this, 'parallax'); unparallax(this, data.port, data.events); if (bool) { unstyle(data.parallax); } }); }; - + jQuery.fn.parallax.options = options; - + // Pick up and store mouse position on document: IE does not register // mousemove on window. doc.on('mousemove.parallax', function(e){