コード例 #1
0
		var ThrowPropsPlugin = function(props, priority) {
				TweenPlugin.call(this, "throwProps");
				this._overwriteProps.length = 0;
			},
コード例 #2
0
_gsScope._gsDefine("plugins.ThrowPropsPlugin", ["utils.VelocityTracker", "plugins.TweenPlugin", "TweenLite", "easing.Ease"], function(VelocityTracker) {

		var ThrowPropsPlugin = function(props, priority) {
				TweenPlugin.call(this, "throwProps");
				this._overwriteProps.length = 0;
			},
			_max = 999999999999999,
			_min = 0.0000000001,
			_globals = _gsScope._gsDefine.globals,
			_recordEndMode = false,//in a typical throwProps css tween that has an "end" defined as a function, it grabs that value initially when the tween is rendered, then again when we calculate the necessary duration, and then a 3rd time after we invalidate() the tween, so we toggle _recordEndMode to true when we're about to begin such a tween which tells the engine to grab the end value(s) once and record them as "max" and "min" on the throwProps object, thus we can skip those extra calls. Then we set it back to false when we're done with our fancy initialization routine.
			_transforms = {x:1,y:1,z:2,scale:1,scaleX:1,scaleY:1,rotation:1,rotationZ:1,rotationX:2,rotationY:2,skewX:1,skewY:1,xPercent:1,yPercent:1},
			_getClosest = function(n, values, max, min, radius) {
				var i = values.length,
					closest = 0,
					absDif = _max,
					val, dif, p, dist;
				if (typeof(n) === "object") {
					while (--i > -1) {
						val = values[i];
						dif = 0;
						for (p in n) {
							dist = val[p] - n[p];
							dif += dist * dist;
						}
						if (dif < absDif) {
							closest = i;
							absDif = dif;
						}
					}
					if ((radius || _max) < _max && radius < Math.sqrt(absDif)) {
						return n;
					}
				} else {
					while (--i > -1) {
						val = values[i];
						dif = val - n;
						if (dif < 0) {
							dif = -dif;
						}
						if (dif < absDif && val >= min && val <= max) {
							closest = i;
							absDif = dif;
						}
					}
				}
				return values[closest];
			},
			_parseEnd = function(curProp, end, max, min, name, radius) {
				if (curProp.end === "auto") {
					return curProp;
				}
				var endVar = curProp.end,
					adjustedEnd, p;
				max = isNaN(max) ? _max : max;
				min = isNaN(min) ? -_max : min;
				if (typeof(end) === "object") { //for objects, like {x, y} where they're linked and we must pass an object to the function or find the closest value in an array.
					adjustedEnd = end.calculated ? end : ((typeof(endVar) === "function") ? endVar(end) : _getClosest(end, endVar, max, min, radius)) || end;
					if (!end.calculated) {
						for (p in adjustedEnd) {
							end[p] = adjustedEnd[p];
						}
						end.calculated = true;
					}
					adjustedEnd = adjustedEnd[name];
				} else {
					adjustedEnd = (typeof(endVar) === "function") ? endVar(end) : (endVar instanceof Array) ? _getClosest(end, endVar, max, min, radius) : Number(endVar);
				}
				if (adjustedEnd > max) {
					adjustedEnd = max;
				} else if (adjustedEnd < min) {
					adjustedEnd = min;
				}
				return {max:adjustedEnd, min:adjustedEnd, unitFactor:curProp.unitFactor};
			},
			_extend = function(decoratee, extras, exclude) {
				for (var p in extras) {
					if (decoratee[p] === undefined && p !== exclude) {
						decoratee[p] = extras[p];
					}
				}
				return decoratee;
			},
			_calculateChange = ThrowPropsPlugin.calculateChange = function(velocity, ease, duration, checkpoint) {
				if (checkpoint == null) {
					checkpoint = 0.05;
				}
				var e = (ease instanceof Ease) ? ease : (!ease) ? TweenLite.defaultEase : new Ease(ease);
				return (duration * checkpoint * velocity) / e.getRatio(checkpoint);
			},
			_calculateDuration = ThrowPropsPlugin.calculateDuration = function(start, end, velocity, ease, checkpoint) {
				checkpoint = checkpoint || 0.05;
				var e = (ease instanceof Ease) ? ease : (!ease) ? TweenLite.defaultEase : new Ease(ease);
				return Math.abs( (end - start) * e.getRatio(checkpoint) / velocity / checkpoint );
			},
			_calculateTweenDuration = ThrowPropsPlugin.calculateTweenDuration = function(target, vars, maxDuration, minDuration, overshootTolerance, recordEnd) {
				if (typeof(target) === "string") {
					target = TweenLite.selector(target);
				}
				if (!target) {
					return 0;
				}
				if (maxDuration == null) {
					maxDuration = 10;
				}
				if (minDuration == null) {
					minDuration = 0.2;
				}
				if (overshootTolerance == null) {
					overshootTolerance = 1;
				}
				if (target.length) {
					target = target[0] || target;
				}
				var duration = 0,
					clippedDuration = 9999999999,
					throwPropsVars = vars.throwProps || vars,
					ease = (vars.ease instanceof Ease) ? vars.ease : (!vars.ease) ? TweenLite.defaultEase : new Ease(vars.ease),
					checkpoint = isNaN(throwPropsVars.checkpoint) ? 0.05 : Number(throwPropsVars.checkpoint),
					resistance = isNaN(throwPropsVars.resistance) ? ThrowPropsPlugin.defaultResistance : Number(throwPropsVars.resistance),
					p, curProp, curDuration, curVelocity, curResistance, curVal, end, curClippedDuration, tracker, unitFactor,
					linkedProps, linkedPropNames, i;

				if (throwPropsVars.linkedProps) { //when there are linkedProps (typically "x,y" where snapping has to factor in multiple properties, we must first populate an object with all of those end values, then feed it to the function that make any necessary alterations. So the point of this first loop is to simply build an object (like {x:100, y:204.5}) for feeding into that function which we'll do later in the "real" loop.
					linkedPropNames = throwPropsVars.linkedProps.split(",");
					linkedProps = {};
					for (i = 0; i < linkedPropNames.length; i++) {
						p = linkedPropNames[i];
						curProp = throwPropsVars[p];
						if (curProp) {
							if (curProp.velocity !== undefined && typeof(curProp.velocity) === "number") {
								curVelocity = Number(curProp.velocity) || 0;
							} else {
								tracker = tracker || VelocityTracker.getByTarget(target);
								curVelocity =  (tracker && tracker.isTrackingProp(p)) ? tracker.getVelocity(p) : 0;
							}
							curResistance = isNaN(curProp.resistance) ? resistance : Number(curProp.resistance);
							curDuration = (curVelocity * curResistance > 0) ? curVelocity / curResistance : curVelocity / -curResistance;
							curVal = (typeof(target[p]) === "function") ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() : target[p] || 0;
							linkedProps[p] = curVal + _calculateChange(curVelocity, ease, curDuration, checkpoint);
						}
					}
				}

				for (p in throwPropsVars) {

					if (p !== "resistance" && p !== "checkpoint" && p !== "preventOvershoot" && p !== "linkedProps" && p !== "radius") {
						curProp = throwPropsVars[p];
						if (typeof(curProp) !== "object") {
							tracker = tracker || VelocityTracker.getByTarget(target);
							if (tracker && tracker.isTrackingProp(p)) {
								curProp = (typeof(curProp) === "number") ? {velocity:curProp} : {velocity:tracker.getVelocity(p)}; //if we're tracking this property, we should use the tracking velocity and then use the numeric value that was passed in as the min and max so that it tweens exactly there.
							} else {
								curVelocity = Number(curProp) || 0;
								curDuration = (curVelocity * resistance > 0) ? curVelocity / resistance : curVelocity / -resistance;
							}
						}
						if (typeof(curProp) === "object") {

							if (curProp.velocity !== undefined && typeof(curProp.velocity) === "number") {
								curVelocity = Number(curProp.velocity) || 0;
							} else {
								tracker = tracker || VelocityTracker.getByTarget(target);
								curVelocity =  (tracker && tracker.isTrackingProp(p)) ? tracker.getVelocity(p) : 0;
							}
							curResistance = isNaN(curProp.resistance) ? resistance : Number(curProp.resistance);
							curDuration = (curVelocity * curResistance > 0) ? curVelocity / curResistance : curVelocity / -curResistance;
							curVal = (typeof(target[p]) === "function") ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() : target[p] || 0;
							end = curVal + _calculateChange(curVelocity, ease, curDuration, checkpoint);
							if (curProp.end !== undefined) {
								curProp = _parseEnd(curProp, (linkedProps && p in linkedProps) ? linkedProps : end, curProp.max, curProp.min, p, throwPropsVars.radius);
								if (recordEnd || _recordEndMode) {
									throwPropsVars[p] = _extend(curProp, throwPropsVars[p], "end");
								}
							}
							if (curProp.max !== undefined && end > Number(curProp.max) + _min) {
								unitFactor = curProp.unitFactor || ThrowPropsPlugin.defaultUnitFactors[p] || 1; //some values are measured in special units like radians in which case our thresholds need to be adjusted accordingly.
								//if the value is already exceeding the max or the velocity is too low, the duration can end up being uncomfortably long but in most situations, users want the snapping to occur relatively quickly (0.75 seconds), so we implement a cap here to make things more intuitive. If the max and min match, it means we're animating to a particular value and we don't want to shorten the time unless the velocity is really slow. Example: a rotation where the start and natural end value are less than the snapping spot, but the natural end is pretty close to the snap.
								curClippedDuration = ((curVal > curProp.max && curProp.min !== curProp.max) || (curVelocity * unitFactor > -15 && curVelocity * unitFactor < 45)) ? (minDuration + (maxDuration - minDuration) * 0.1) : _calculateDuration(curVal, curProp.max, curVelocity, ease, checkpoint);
								if (curClippedDuration + overshootTolerance < clippedDuration) {
									clippedDuration = curClippedDuration + overshootTolerance;
								}

							} else if (curProp.min !== undefined && end < Number(curProp.min) - _min) {
								unitFactor = curProp.unitFactor || ThrowPropsPlugin.defaultUnitFactors[p] || 1; //some values are measured in special units like radians in which case our thresholds need to be adjusted accordingly.
								//if the value is already exceeding the min or if the velocity is too low, the duration can end up being uncomfortably long but in most situations, users want the snapping to occur relatively quickly (0.75 seconds), so we implement a cap here to make things more intuitive.
								curClippedDuration = ((curVal < curProp.min && curProp.min !== curProp.max) || (curVelocity * unitFactor > -45 && curVelocity * unitFactor < 15)) ? (minDuration + (maxDuration - minDuration) * 0.1) : _calculateDuration(curVal, curProp.min, curVelocity, ease, checkpoint);
								if (curClippedDuration + overshootTolerance < clippedDuration) {
									clippedDuration = curClippedDuration + overshootTolerance;
								}
							}

							if (curClippedDuration > duration) {
								duration = curClippedDuration;
							}
						}

						if (curDuration > duration) {
							duration = curDuration;
						}

					}
				}
				if (duration > clippedDuration) {
					duration = clippedDuration;
				}
				if (duration > maxDuration) {
					return maxDuration;
				} else if (duration < minDuration) {
					return minDuration;
				}
				return duration;
			},
			p = ThrowPropsPlugin.prototype = new TweenPlugin("throwProps"),
			_cssProxy, _cssVars, _last, _lastValue; //these serve as a cache of sorts, recording the last css-related proxy and the throwProps vars that get calculated in the _cssRegister() method. This allows us to grab them in the ThrowPropsPlugin.to() function and calculate the duration. Of course we could have structured things in a more "clean" fashion, but performance is of paramount importance.



		p.constructor = ThrowPropsPlugin;
		ThrowPropsPlugin.version = "0.11.1";
		ThrowPropsPlugin.API = 2;
		ThrowPropsPlugin._autoCSS = true; //indicates that this plugin can be inserted into the "css" object using the autoCSS feature of TweenLite
		ThrowPropsPlugin.defaultResistance = 100;
		ThrowPropsPlugin.defaultUnitFactors = {time:1000, totalTime:1000}; //setting the unitFactor to a higher value (default is 1) reduces the chance of the auto-accelerating behavior kicking in when determining durations when the initial velocity is adequately low - imagine dragging something past a boundary and then letting go - snapping back relatively quickly should be prioritized over matching the initial velocity (at least that's the behavior most people consider intuitive). But in some situations when the units are very low (like "time" of a timeline or rotation when using radians), it can kick in too frequently so this allows tweaking.

		ThrowPropsPlugin.track = function(target, props, types) {
			return VelocityTracker.track(target, props, types);
		};

		ThrowPropsPlugin.untrack = function(target, props) {
			VelocityTracker.untrack(target, props);
		};

		ThrowPropsPlugin.isTracking = function(target, prop) {
			return VelocityTracker.isTracking(target, prop);
		};

		ThrowPropsPlugin.getVelocity = function(target, prop) {
			var vt = VelocityTracker.getByTarget(target);
			return vt ? vt.getVelocity(prop) : NaN;
		};

		ThrowPropsPlugin._cssRegister = function() {
			var CSSPlugin = _globals.com.greensock.plugins.CSSPlugin;
			if (!CSSPlugin) {
				return;
			}
			var _internals = CSSPlugin._internals,
				_parseToProxy = _internals._parseToProxy,
				_setPluginRatio = _internals._setPluginRatio,
				CSSPropTween = _internals.CSSPropTween;
			_internals._registerComplexSpecialProp("throwProps", {parser:function(t, e, prop, cssp, pt, plugin) {
				plugin = new ThrowPropsPlugin();
				var velocities = {},
					min = {},
					max = {},
					end = {},
					res = {},
					preventOvershoot = {},
					hasResistance, val, p, data, tracker;
				_cssVars = {};
				for (p in e) {
					if (p !== "resistance" && p !== "preventOvershoot" && p !== "linkedProps" && p !== "radius") {
						val = e[p];
						if (typeof(val) === "object") {
							if (val.velocity !== undefined && typeof(val.velocity) === "number") {
								velocities[p] = Number(val.velocity) || 0;
							} else {
								tracker = tracker || VelocityTracker.getByTarget(t);
								velocities[p] = (tracker && tracker.isTrackingProp(p)) ? tracker.getVelocity(p) : 0; //rotational values are actually converted to radians in CSSPlugin, but our tracking velocity is in radians already, so make it into degrees to avoid a funky conversion
							}
							if (val.end !== undefined) {
								end[p] = val.end;
							}
							if (val.min !== undefined) {
								min[p] = val.min;
							}
							if (val.max !== undefined) {
								max[p] = val.max;
							}
							if (val.preventOvershoot) {
								preventOvershoot[p] = true;
							}
							if (val.resistance !== undefined) {
								hasResistance = true;
								res[p] = val.resistance;
							}
						} else if (typeof(val) === "number") {
							velocities[p] = val;
						} else {
							tracker = tracker || VelocityTracker.getByTarget(t);
							if (tracker && tracker.isTrackingProp(p)) {
								velocities[p] = tracker.getVelocity(p);
							} else {
								velocities[p] = val || 0;
							}
						}
						if (_transforms[p]) {
							cssp._enableTransforms((_transforms[p] === 2));
						}
					}
				}
				data = _parseToProxy(t, velocities, cssp, pt, plugin);
				_cssProxy = data.proxy;
				velocities = data.end;
				for (p in _cssProxy) {
					_cssVars[p] = {velocity:velocities[p], min:min[p], max:max[p], end:end[p], resistance:res[p], preventOvershoot:preventOvershoot[p]};
				}
				if (e.resistance != null) {
					_cssVars.resistance = e.resistance;
				}
				if (e.linkedProps != null) {
					_cssVars.linkedProps = e.linkedProps;
				}
				if (e.radius != null) {
					_cssVars.radius = e.radius;
				}
				if (e.preventOvershoot) {
					_cssVars.preventOvershoot = true;
				}
				pt = new CSSPropTween(t, "throwProps", 0, 0, data.pt, 2);
				cssp._overwriteProps.pop(); //don't overwrite all other throwProps tweens. In the CSSPropTween constructor, we add the property to the _overwriteProps, so remove it here.
				pt.plugin = plugin;
				pt.setRatio = _setPluginRatio;
				pt.data = data;
				plugin._onInitTween(_cssProxy, _cssVars, cssp._tween);
				return pt;
			}});
		};


		ThrowPropsPlugin.to = function(target, vars, maxDuration, minDuration, overshootTolerance) {
			if (!vars.throwProps) {
				vars = {throwProps:vars};
			}
			if (overshootTolerance === 0) {
				vars.throwProps.preventOvershoot = true;
			}
			_recordEndMode = true; //if we encounter a function-based "end" value, ThrowPropsPlugin will record it as "max" and "min" properties, replacing "end" (this is an optimization so that the function only gets called once)
			var tween = new TweenLite(target, minDuration || 1, vars);
			tween.render(0, true, true); //we force a render so that the CSSPlugin instantiates and populates the _cssProxy and _cssVars which we need in order to calculate the tween duration. Remember, we can't use the regular target for calculating the duration because the current values wouldn't be able to be grabbed like target["propertyName"], as css properties can be complex like boxShadow:"10px 10px 20px 30px red" or backgroundPosition:"25px 50px". The proxy is the result of breaking all that complex data down and finding just the numeric values and assigning them to a generic proxy object with unique names. THAT is what the _calculateTweenDuration() can look at. We also needed to do the same break down of any min or max or velocity data
			if (tween.vars.css) {
				tween.duration(_calculateTweenDuration(_cssProxy, {throwProps:_cssVars, ease:vars.ease}, maxDuration, minDuration, overshootTolerance));
				if (tween._delay && !tween.vars.immediateRender) {
					tween.invalidate(); //if there's a delay, the starting values could be off, so invalidate() to force reinstantiation when the tween actually starts.
				} else {
					_last._onInitTween(_cssProxy, _lastValue, tween);
				}
				_recordEndMode = false;
				return tween;
			} else {
				tween.kill();
				tween = new TweenLite(target, _calculateTweenDuration(target, vars, maxDuration, minDuration, overshootTolerance), vars);
				_recordEndMode = false;
				return tween;
			}
		};

		p._onInitTween = function(target, value, tween, index) {
			this.target = target;
			this._props = [];
			_last = this;
			_lastValue = value;
			var ease = tween._ease,
				checkpoint = isNaN(value.checkpoint) ? 0.05 : Number(value.checkpoint),
				duration = tween._duration,
				preventOvershoot = value.preventOvershoot,
				cnt = 0,
				p, curProp, curVal, isFunc, velocity, change1, end, change2, tracker,
				linkedProps, linkedPropNames, i;

			if (value.linkedProps) { //when there are linkedProps (typically "x,y" where snapping has to factor in multiple properties, we must first populate an object with all of those end values, then feed it to the function that make any necessary alterations. So the point of this first loop is to simply build an object (like {x:100, y:204.5}) for feeding into that function which we'll do later in the "real" loop.
				linkedPropNames = value.linkedProps.split(",");
				linkedProps = {};
				for (i = 0; i < linkedPropNames.length; i++) {
					p = linkedPropNames[i];
					curProp = value[p];
					if (curProp) {
						if (curProp.velocity !== undefined && typeof(curProp.velocity) === "number") {
							velocity = Number(curProp.velocity) || 0;
						} else {
							tracker = tracker || VelocityTracker.getByTarget(target);
							velocity =  (tracker && tracker.isTrackingProp(p)) ? tracker.getVelocity(p) : 0;
						}
						curVal = (typeof(target[p]) === "function") ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() : target[p] || 0;
						linkedProps[p] = curVal + _calculateChange(velocity, ease, duration, checkpoint);
					}
				}
			}

			for (p in value) {
				if (p !== "resistance" && p !== "checkpoint" && p !== "preventOvershoot" && p !== "linkedProps" && p !== "radius") {
					curProp = value[p];
					if (typeof(curProp) === "function") {
						curProp = curProp(index, target);
					}
					if (typeof(curProp) === "number") {
						velocity = Number(curProp) || 0;
					} else if (typeof(curProp) === "object" && !isNaN(curProp.velocity)) {
						velocity = Number(curProp.velocity);
					} else {
						tracker = tracker || VelocityTracker.getByTarget(target);
						if (tracker && tracker.isTrackingProp(p)) {
							velocity = tracker.getVelocity(p);
						} else {
							throw("ERROR: No velocity was defined in the throwProps tween of " + target + " property: " + p);
						}
					}
					change1 = _calculateChange(velocity, ease, duration, checkpoint);
					change2 = 0;
					isFunc = (typeof(target[p]) === "function");
					curVal = (isFunc) ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() : target[p];
					if (typeof(curProp) === "object") {
						end = curVal + change1;
						if (curProp.end !== undefined) {
							curProp = _parseEnd(curProp, (linkedProps && p in linkedProps) ? linkedProps : end, curProp.max, curProp.min, p, value.radius);
							if (_recordEndMode) {
								value[p] = _extend(curProp, value[p], "end");
							}
						}
						if (curProp.max !== undefined && Number(curProp.max) < end) {
							if (preventOvershoot || curProp.preventOvershoot) {
								change1 = curProp.max - curVal;
							} else {
								change2 = (curProp.max - curVal) - change1;
							}
						} else if (curProp.min !== undefined && Number(curProp.min) > end) {
							if (preventOvershoot || curProp.preventOvershoot) {
								change1 = curProp.min - curVal;
							} else {
								change2 = (curProp.min - curVal) - change1;
							}
						}
					}
					this._overwriteProps[cnt] = p;
					this._props[cnt++] = {p:p, s:curVal, c1:change1, c2:change2, f:isFunc, r:false};
				}
			}
			return true;
		};

		p._kill = function(lookup) {
			var i = this._props.length;
			while (--i > -1) {
				if (lookup[this._props[i].p] != null) {
					this._props.splice(i, 1);
				}
			}
			return TweenPlugin.prototype._kill.call(this, lookup);
		};

		p._mod = function(lookup) {
			var p = this._props,
				i = p.length,
				val;
			while (--i > -1) {
				val = lookup[p[i].p] || lookup.throwProps;
				if (typeof(val) === "function") {
					p[i].m = val;
				}
			}
		};

		p.setRatio = function(v) {
			var i = this._props.length,
				cp, val;
			while (--i > -1) {
				cp = this._props[i];
				val = cp.s + cp.c1 * v + cp.c2 * v * v;
				if (cp.m) {
					val = cp.m(val, this.target);
				} else if (v === 1) {
					val = ((val * 10000 + (val < 0 ? -0.5 : 0.5)) | 0) / 10000; //if we don't round things at the very end, binary math issues can creep in and cause snapping not to be exact (like landing on 20.000000000001 instead of 20).
				}
				if (cp.f) {
					this.target[cp.p](val);
				} else {
					this.target[cp.p] = val;
				}
			}
		};

		TweenPlugin.activate([ThrowPropsPlugin]);

		return ThrowPropsPlugin;

	}, true);