|
| 1 | +/** |
| 2 | + * Animation handler class |
| 3 | + * |
| 4 | + * Manages animation events & timing between a Viewport and Layer. |
| 5 | + * This level of abstraction allows multiple animations to exist on |
| 6 | + * and update a layer simultaneously - these animations may or may not be tied |
| 7 | + * to the same input event. |
| 8 | + * |
| 9 | + * @requires jcparallax.js |
| 10 | + * @requires jcp-viewport.js |
| 11 | + * @requires jcp-layer.js |
| 12 | + * @author Sam Pospischil <[email protected]> |
| 13 | + */ |
| 14 | +jcparallax.Animator = function(layer, options) |
| 15 | +{ |
| 16 | + var defaults = { |
| 17 | + movementRangeX : true, // autodetect |
| 18 | + movementRangeY : true, |
| 19 | + |
| 20 | + inputEvent : 'mousemove', |
| 21 | + inputHandler : 'mousemove', |
| 22 | + |
| 23 | + animHandler : 'position', |
| 24 | + }; |
| 25 | + |
| 26 | + this.layer = layer; |
| 27 | + this.viewport = layer.viewport; |
| 28 | + options = $.extend(true, defaults, options); |
| 29 | + |
| 30 | + // set default range calculation callbacks for builtin animHandlers passed as strings |
| 31 | + if (options.movementRangeX === true || options.movementRangeY === true && !$.isFunction(options.animHandler)) { |
| 32 | + // infer layer movement range calculators for builtin animation handlers |
| 33 | + switch (options.animHandler) { |
| 34 | + case 'position': |
| 35 | + case 'padding': |
| 36 | + case 'margins': |
| 37 | + case 'background': |
| 38 | + case 'stretch': |
| 39 | + switch (options.inputHandler) { |
| 40 | + case 'scroll': |
| 41 | + options.movementRangeX = jcparallax.Layer.rangeCalculators.scrollWidth; |
| 42 | + options.movementRangeY = jcparallax.Layer.rangeCalculators.scrollHeight; |
| 43 | + break; |
| 44 | + default: |
| 45 | + options.movementRangeX = jcparallax.Layer.rangeCalculators.width; |
| 46 | + options.movementRangeY = jcparallax.Layer.rangeCalculators.height; |
| 47 | + break; |
| 48 | + } |
| 49 | + break; |
| 50 | + case 'textShadow': |
| 51 | + options.movementRangeX = jcparallax.Layer.rangeCalculators.fontSize; |
| 52 | + options.movementRangeY = jcparallax.Layer.rangeCalculators.lineHeight; |
| 53 | + break; |
| 54 | + case 'opacity': |
| 55 | + options.movementRangeX = [0, 0]; |
| 56 | + options.movementRangeY = jcparallax.Layer.rangeCalculators.opacity; |
| 57 | + break; |
| 58 | + } |
| 59 | + } |
| 60 | + if (typeof options.movementRangeX == 'string') { |
| 61 | + options.movementRangeX = jcparallax.Layer.rangeCalculators[options.movementRangeX]; |
| 62 | + } |
| 63 | + if (typeof options.movementRangeY == 'string') { |
| 64 | + options.movementRangeY = jcparallax.Layer.rangeCalculators[options.movementRangeY]; |
| 65 | + } |
| 66 | + |
| 67 | + // interpret animation handler |
| 68 | + if ($.isFunction(options.animHandler)) { // custom handler callback |
| 69 | + this.animHandler = options.animHandler; |
| 70 | + } else { // single callback from the predefined set |
| 71 | + this.animHandler = jcparallax.Animator.animHandlers[options.animHandler]; |
| 72 | + } |
| 73 | + |
| 74 | + this.options = options; |
| 75 | + |
| 76 | + // refresh target element coordinates |
| 77 | + this.refreshCoords(); |
| 78 | + |
| 79 | + // bind input events |
| 80 | + this.bindEvent(options.inputEvent, options.inputHandler); |
| 81 | +}; |
| 82 | + |
| 83 | +$.extend(jcparallax.Animator.prototype, { |
| 84 | + |
| 85 | + inputEvent : null, // DOM input event this animation is bound to |
| 86 | + inputHandler : null, // input event handler callback to output range normalised value |
| 87 | + animHandler : null, // animation update handler callback |
| 88 | + |
| 89 | + minX : null, |
| 90 | + minY : null, |
| 91 | + rangeX : null, // scaling factors for the animation over input 0-1 |
| 92 | + rangeY : null, // call refreshCoords() to update from the callbacks supplied in options |
| 93 | + |
| 94 | + // last input sampling coordinates |
| 95 | + lastSampledX : 0, |
| 96 | + lastSampledY : 0, |
| 97 | + lastProcessedX : 0, |
| 98 | + lastProcessedY : 0, |
| 99 | + |
| 100 | + /** |
| 101 | + * Binds the DOM event responsible for handling our updates |
| 102 | + * @param {string} eventName name of the DOM event to bind to for updates |
| 103 | + * @param {function} handler Callback for handling the event. |
| 104 | + * Accepts the bound layer element, x position (0 <= x <= 1), y position and event object as parameters. |
| 105 | + */ |
| 106 | + bindEvent : function(eventName, handler) |
| 107 | + { |
| 108 | + var that = this; |
| 109 | + eventName += jcparallax.eventNamespace; |
| 110 | + |
| 111 | + // infer handler from predefined set if a string |
| 112 | + if (typeof handler == 'string') { |
| 113 | + handler = jcparallax.Viewport.inputHandlers[handler]; |
| 114 | + } |
| 115 | + |
| 116 | + // detach old callback first if present |
| 117 | + if (this.inputHandler && this.inputEvent) { |
| 118 | + this.viewport.element.off(this.inputEvent, this.inputHandler); |
| 119 | + } |
| 120 | + |
| 121 | + // create new callback & bind it to the viewport |
| 122 | + this.inputHandler = function(e) { |
| 123 | + handler.call(that, that.viewport.element, e); |
| 124 | + }; |
| 125 | + this.inputEvent = eventName; |
| 126 | + this.viewport.element.on(eventName, this.inputHandler); |
| 127 | + }, |
| 128 | + |
| 129 | + /** |
| 130 | + * Updates the sampled input position for calculating the effect. |
| 131 | + * This method should be called for every event encountered to update |
| 132 | + * the X and Y values, which are then applied at the next frame interval. |
| 133 | + */ |
| 134 | + updateLastSamplePos : function(xVal, yVal) |
| 135 | + { |
| 136 | + this.lastSampledX = xVal; |
| 137 | + this.lastSampledY = yVal; |
| 138 | + }, |
| 139 | + |
| 140 | + /** |
| 141 | + * Refreshes all computed coordinates from our movement range handler |
| 142 | + * callbacks after layer DOM element is modified externally. |
| 143 | + */ |
| 144 | + refreshCoords : function() |
| 145 | + { |
| 146 | + var xRange, yRange; |
| 147 | + |
| 148 | + if ($.isFunction(this.options.movementRangeX)) { |
| 149 | + xRange = this.options.movementRangeX.call(this.layer, this.layer.element, this.viewport); |
| 150 | + } else { |
| 151 | + xRange = this.options.movementRangeX; |
| 152 | + } |
| 153 | + if ($.isFunction(this.options.movementRangeY)) { |
| 154 | + yRange = this.options.movementRangeY.call(this.layer, this.layer.element, this.viewport); |
| 155 | + } else { |
| 156 | + yRange = this.options.movementRangeY; |
| 157 | + } |
| 158 | + |
| 159 | + this.minX = parseFloat(xRange[0]); |
| 160 | + this.rangeX = parseFloat(xRange[1] - xRange[0]); |
| 161 | + this.minY = parseFloat(yRange[0]); |
| 162 | + this.rangeY = parseFloat(yRange[1] - yRange[0]); |
| 163 | + }, |
| 164 | + |
| 165 | + /** |
| 166 | + * Generate a CSS object for modification of our layer, |
| 167 | + * using our last sampled input values or the ones provided. |
| 168 | + * Input values should be 0 < x < 1. |
| 169 | + * |
| 170 | + * @return {object} CSS properties to set on the layer element |
| 171 | + */ |
| 172 | + makeCss : function(xVal, yVal) |
| 173 | + { |
| 174 | + // when positions not passed, just redraw |
| 175 | + if (xVal === undefined && yVal === undefined) { |
| 176 | + xVal = this.lastSampledX; |
| 177 | + yVal = this.lastSampledY; |
| 178 | + } |
| 179 | + |
| 180 | + if (xVal == this.lastProcessedX && yVal == this.lastProcessedY) { |
| 181 | + return {}; // no change in input |
| 182 | + } |
| 183 | + this.lastProcessedX = xVal; |
| 184 | + this.lastProcessedY = yVal; |
| 185 | + |
| 186 | + return this.animHandler.call(this, xVal, yVal); |
| 187 | + } |
| 188 | +}); |
| 189 | + |
| 190 | +//------------------------------------------------------------------------------ |
| 191 | +// Layer animation handlers |
| 192 | +//------------------------------------------------------------------------------ |
| 193 | + |
| 194 | +jcparallax.Animator.animHandlers = { |
| 195 | + |
| 196 | + // standard css attributes - minimal support |
| 197 | + |
| 198 | + position : function(xVal, yVal) |
| 199 | + { |
| 200 | + return { |
| 201 | + left : this.minX + (xVal * this.rangeX), |
| 202 | + top : this.minY + (yVal * this.rangeY), |
| 203 | + }; |
| 204 | + }, |
| 205 | + |
| 206 | + padding : function(xVal, yVal) |
| 207 | + { |
| 208 | + |
| 209 | + }, |
| 210 | + |
| 211 | + margins : function(xVal, yVal) |
| 212 | + { |
| 213 | + |
| 214 | + }, |
| 215 | + |
| 216 | + background : function(xVal, yVal) |
| 217 | + { |
| 218 | + return { |
| 219 | + 'background-position' : (this.minX + (xVal * this.rangeX)) + 'px ' + (this.minY + (yVal * this.rangeY)) + 'px', |
| 220 | + }; |
| 221 | + }, |
| 222 | + |
| 223 | + stretch : function(xVal, yVal) |
| 224 | + { |
| 225 | + |
| 226 | + }, |
| 227 | + |
| 228 | + // CSS3 attributes |
| 229 | + |
| 230 | + textShadow : function(xVal, yVal) |
| 231 | + { |
| 232 | + |
| 233 | + }, |
| 234 | + |
| 235 | + opacity : function(xVal, yVal) |
| 236 | + { |
| 237 | + |
| 238 | + } |
| 239 | +}; |
0 commit comments