var_gsScope=(typeof(module)!=="undefined"&&module.exports&&typeof(global)!=="undefined")?global:this||window;//helps ensure compatibility with AMD/RequireJS and CommonJS/Node
_clickedOnce,//we don't preventDefault() on the first mousedown/touchstart/pointerdown so that iframes get focus properly.
_addListener=function(e,type,callback,capture){
varhandler,altType;
if(type==="mousedown"||type==="mouseup"){
e.style.cursor="pointer";
}
if(type==="mousedown"){
//some browsers call BOTH mousedown AND touchstart, for example, on a single interaction so we need to skip one of them if both are called within 100ms.
//moves everything from _recordedTemp into _recordedRoot and updates the _rootTween if it is currently controlling the Global timeline (_recordedRoot). _recordedTemp is just a temporary recording area for anything that happens while _recordedRoot is paused. Returns true if the _recordedRoot's duration changed due to the merge.
if(!(typeof(t.target)==="function"&&t.target===t.vars.onComplete&&!t._duration)&&!(t.target&&t.target._gsIgnore)){//typically, delayedCalls aren't included in the _recordedTemp, but since the hijacked add() below fires BEFORE TweenLite's constructor sets the target, we couldn't check that target === vars.onComplete there. And Draggable creates a tween with just an onComplete (no onReverseComplete), thus it fails that test. Therefore, we test again here to avoid merging that in.
//if it's not the _recordedRoot that's currently playing ("global timeline"), merge the temp tweens back into the real global timeline instead and kill any that are already completed. No need to keep them recorded.
vars.id=vars.id||(typeof(vars.animation)==="string"?vars.animation:_idSeed++);//try to find a unique ID so that sessionStorage can be mapped to it (otherwise, for example, all the embedded codepens on a page would share the same settings). So if no id is defined, see if there's a string-based "animation" defined. Last of all, we default to a numeric counter that we increment.
_lookup[vars.id+""]=this;
if(vars.animation&&!_recording&&vars.globalSync!==true){//if the user calls create() and passes in an animation AFTER the initial recording time has elapsed, there's a good chance the animation won't be in the recordedRoot, so we change the default globalSync to false because that's the most intuitive behavior.
//spits back a common onPress function for anything that's dragged along the timeline (playhead, inPoint, outPoint). The originRatio is a value from 0-1 indicating how far along the x-axis the origin is located (0.5 is in the center, 0 is left, 1 is on right side). limitElement is optional, and sets the bounds such that the element can't be dragged past the limitElement.
if(linkedAnimation.target===selectedAnimation){//in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them.
if(linkedAnimation.target===selectedAnimation){//in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them.
linkedAnimation.totalProgress(0,true);//for repeating animations, don't get stuck in the last iteration - jump all the way back to the start.
}else{
linkedAnimation.progress(inProgress/100,true);
}
}
playPauseMorph.play();
linkedAnimation.resume();
if(paused){
_self.update();
}
paused=false;
},
pause=function(){
playPauseMorph.reverse();
if(linkedAnimation){
linkedAnimation.pause();
}
paused=true;
},
togglePlayPause=function(){
if(paused){
play();
}else{
pause();
}
},
//REWIND button
onPressRewind=function(e){
if(progressDrag.isPressed){
return;
}
//_self.update();
if(linkedAnimation.target===selectedAnimation){//in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them.
if(linkedAnimation.target===selectedAnimation){//in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them.
selectedAnimation,//the currently selected animation
linkedAnimation,//the animation that's linked to all the controls and scrubber. This is always _rootTween if globalSync is true, so it can be different than the selectedAnimation!
declaredAnimation,//whatever the user defines in the config object initially (often this will be null). If the user defines a string, it'll be resolved to a real Animation instance for this variable.
if(_fullyInitialized){//don't record inProgress/outProgress unless we're fully instantiated because people may call GSDevTools.create() before creating/defining their animations, thus the inTime/outTime may not exist yet.
//wait for a tick before starting because some browsers freeze things immediately following a <select> selection, like on MacOS it flashes a few times before disappearing, so this prevents a "jump".
if(paused){
//jump forward and then back in order to make sure the start/end values are recorded internally right away and don't drift outside this tween.
}else{//if an animation is declared in the config object, and the user chooses a sub-animation (nested), we tween the playhead of the declaredAnimation to keep everything synchronized even though globalSync isn't true.
tl=selectedAnimation;
endTime=tl.totalDuration();
if(endTime>99999999){//in the case of an infinitely repeating animation, just use a single iteration's duration instead.
_recordedRoot.progress(1,true).time(time,true);//jump to the end and back again because sometimes a tween that hasn't rendered yet will affect duration, like a TimelineMax.tweenTo() where the duration gets set in the onStart.
if(e.target&&e.target.blur){//so that if an option is selected, and then the user tries to hit the up/down arrow, it doesn't just try selecting something else in the <select>.
if(linkedAnimation.target===selectedAnimation){//in case there are callbacks on the timeline, when we jump back to the start we should seek() so that the playhead doesn't drag [backward] past those and trigger them.
if(timeScale.blur){//so that if an option is selected, and then the user tries to hit the up/down arrow, it doesn't just try selecting something else in the <select>.
_fullyInitialized,//we call initialize() initially, and then again on the very next tick just in case someone called GSDevTools.create() BEFORE they create their animations. This variable tracks that state. Note: we don't record sessionStorage.setItem() until we're fully initialized, otherwise we may inadvertently set in/out points to the defaults just because the animation couldn't be found (yet).
keyboardHandler,
initialize=function(preliminary){
//if on startup, someone does a timeline.seek(), we must honor it, so when initialize() is called, we record _recordedRoot._startTime so that we can use that as an offset. Remember, however, that we call initialize() twice on startup, once after a tick has elapsed just in case someone called GSDevTools.create() before their animation code, so we must record the value (once).
if(inProgress===100&&!vars.animation&&savedAnimation){//in case there's a recorded animation (sessionStorage) and then the user defines an inTime that exceeds that animation's duration, just default back to the Global Timeline. Otherwise the in/out point will be at the very end and it'd be weird.
TweenLite.set(playhead,{xPercent:-50,x:0,data:"root"});//so that when we drag, x is properly discerned (browsers report in pure pixels rather than percents)
//developers may call GSDevTools.create() before they even create some of their animations, so the inTime/outTime or animation values may not exist, thus we wait for 1 tick and initialize again, just in case.
skipDragUpdates=true;//just in case there's actually a tween/timeline in the linkedAnimation that is altering this GSDevTool instance's "minimal()" value, it could trigger a recursive loop in the drag handlers, like if they update linkedAnimation's time/progress which in turn triggers this minimal() function which in turn dues the same, and so on.
_startupPhase=true,//for the first 2 seconds, we don't record any zero-duration tweens because they're typically just setup stuff and/or the "from" or "startAt" tweens. In version 1.20.3 we started flagging those with data:"isStart"|"isFromStart" but this logic helps GSDevTools work with older versions too.
_onOverwrite=TweenLite.onOverwrite,
_globalStartTime=0;//if on startup, someone does a timeline.seek(), we need to honor it, so when initialize() is called, it'll check the _recordedRoot._startTime so that we can use that as an offset. Remember, however, that we call initialize() twice on startup, once after a tick has elapsed just in case someone called GSDevTools.create() before their animation code, so we must record the value (once).
//in case GSDevTools.create() is called before anything is actually on the global timeline, we've gotta update it or else the duration will be 0 and it'll be stuck.
TweenLite.delayedCall(0.01,function(){
if(_rootInstance){
_rootInstance.update();
}else{
_merge();
}
});
//initially we record everything into the _recordedRoot TimelineLite because developers might call GSDevTools.create() AFTER some of their code executes, but after 2 seconds if there aren't any GSDevTool instances that have globalSync enabled, we should dump all the stuff from _recordedRoot into the global timeline to improve performance and avoid issues where _recordedRoot is paused and reaches its end and wants to stop the playhead.
TweenLite.delayedCall(2,function(){
vart,next,offset;
if(!_rootInstance){
_merge();
t=_recordedRoot._first;
offset=_recordedRoot._startTime;
while(t){
next=t._next;
//any animations that aren't finished should be dumped into the root timeline. If they're done, just kill them.
//hijack the adding of all children of the global timeline and dump them into _recordedRoot instead, unless they are "rooted" or "exported". If _recordedRoot is paused, we dump them into _recordedTemp so that they actually work, and we merge them back into _recordedRoot when it becomes unpaused.
//TODO: idea: what if we ignore any zero-duration tweens that occur in the first second or two, and then lock the "global timeline" down after that anyway? Perhaps offer a way to update() manually?
if(_recording&&child.vars&&data!=="root"&&data!=="ignore"&&data!=="isStart"&&data!=="isFromStart"&&data!=="_draggable"&&!(_startupPhase&&!child._duration&&childinstanceofTweenLite)&&!(child.vars.onComplete&&child.vars.onComplete===child.vars.onReverseComplete)){//skip delayedCalls too
//TODO: what if timeScale is set initially low, and a delayedCall() fires another animation?? It'd fire early.
if(child.vars.repeat){//before 1.20.3, repeated TweenMax/TimelineMax instances didn't have their repeats applied until after the constructor ran (which was after it was added to its timeline), so we need to make sure _dirty is true in order to trigger re-calculation of totalDuration. Otherwise, _recordedRoot/_recordedTemp might stop at the end of the first iteration of a repeated tween.
//skip enabling/disabling the nested animations (performance improvement). SimpleTimeline._enabled() doesn't do that - only TimelineLite._enabled() does.
TimelineLite.prototype._remove=function(tween,skipDisable){//replace TimelineLite's _remove because before 1.19.1, there was a bug that could cause _time to jump to the end. We want to make sure GSDevTools can still work properly with slightly older versions.
console.log("[Overwrite warning] the following properties were overwritten: ",overwrittenProperties,"| target:",target,"| overwritten tween: ",overwrittenTween,"| overwriting tween:",overwritingTween);
}else{
console.log("[Overwrite warning] the following tween was overwritten:",overwrittenTween,"by",overwritingTween);
var_gsScope=(typeof(module)!=="undefined"&&module.exports&&typeof(global)!=="undefined")?global:this||window;//helps ensure compatibility with AMD/RequireJS and CommonJS/Node
var_tempVarsXY={css:{},data:"_draggable"},//speed optimization - we reuse the same vars object for x/y TweenLite.set() calls to minimize garbage collection tasks and improve performance.
_tempVarsX={css:{},data:"_draggable"},
_tempVarsY={css:{},data:"_draggable"},
_tempVarsRotation={css:{}},
_globals=_gsScope._gsDefine.globals,
_tempEvent={},//for populating with pageX/pageY in old versions of IE
_lookup={},//when a Draggable is created, the target gets a unique _gsDragID property that allows gets associated with the Draggable instance for quick lookups in Draggable.get(). This avoids circular references that could cause gc problems.
_dragCount=0,//total number of elements currently being dragged
_prefix,
_isMultiTouching,
_isAndroid=(_gsScope.navigator&&_gsScope.navigator.userAgent.toLowerCase().indexOf("android")!==-1),//Android handles touch events in an odd way and it's virtually impossible to "feature test" so we resort to UA sniffing
_lastDragTime=0,
_temp1={},// a simple object we reuse and populate (usually x/y properties) to conserve memory and improve performance.
_windowProxy={},//memory/performance optimization - we reuse this object during autoScroll to store window-related bounds/offsets.
_supportsPassive,
_slice=function(a){//don't use Array.prototype.slice.call(target, 0) because that doesn't work in IE8 with a NodeList that's returned by querySelectorAll()
if(typeof(a)==="string"){
a=TweenLite.selector(a);
}
if(!a||a.nodeType){//if it's not an array, wrap it in one.
TweenLite.to(_renderQueueTimeout,0,{overwrite:"all",delay:15,onComplete:_renderQueueTimeout,data:"_draggable"});//remove the "tick" listener only after the render queue is empty for 15 seconds (to improve performance). Adding/removing it constantly for every click/touch wouldn't deliver optimal speed, and we also don't want the ticker to keep calling the render method when things are idle for long periods of time (we want to improve battery life on mobile devices).
_recordMaxScrolls=function(e){//records _gsMaxScrollX and _gsMaxScrollY properties for the element and all ancestors up the chain so that we can cap it, otherwise dragging beyond the edges with autoScroll on can endlessly scroll.
varisRoot=_isRoot(e),
x=_getMaxScroll(e,"x"),
y=_getMaxScroll(e,"y");
if(isRoot){
e=_windowProxy;
}else{
_recordMaxScrolls(e.parentNode);
}
e._gsMaxScrollX=x;
e._gsMaxScrollY=y;
e._gsScrollX=e.scrollLeft||0;
e._gsScrollY=e.scrollTop||0;
},
//just used for IE8 and earlier to normalize events and populate pageX/pageY
//grabs the first element it finds (and we include the window as an element), so if it's selector text, it'll feed that value to TweenLite.selector, if it's a jQuery object or some other selector engine's result, it'll grab the first one, and same for an array. If the value doesn't contain a DOM element, it'll just return null.
rv=(rv||cs.length)?rv:cs[prop];//Opera behaves VERY strangely - length is usually 0 and cs[prop] is the only way to get accurate results EXCEPT when checking for -o-transform which only works with cs.getPropertyValue()!
_getBounds=function(obj,context){//accepts any of the following: a DOM element, jQuery object, selector text, or an object defining bounds as {top, left, width, height} or {minX, maxX, minY, maxY}. Returns an object with left, top, width, and height properties.
vare=_unwrapElement(obj),
top,left,offset;
if(!e){
if(obj.left!==undefined){
offset=_getOffsetTransformOrigin(context);//the bounds should be relative to the origin
_hasReparentBug,//some browsers, like Chrome 49, alter the offsetTop/offsetLeft/offsetParent of elements when a non-identity transform is applied.
_setEnvironmentVariables=function(){//some browsers factor the border into the SVG coordinate space, some don't (like Firefox). Some apply transforms to them, some don't. We feature-detect here so we know how to handle the border(s). We can't do this immediately - we must wait for the document.body to exist.
isFlex=(_getStyle(parent,"display",true)==="flex"),//Firefox bug causes getScreenCTM() to return null when parent is display:flex and the element isn't rendered inside the window (like if it's below the scroll position)
matrix,e1,point,oldValue;
if(_doc.body&&_transformProp){
style.position="absolute";
parent.appendChild(wrapper);
wrapper.appendChild(div);
oldValue=div.offsetParent;
wrapper.style[_transformProp]="rotate(1deg)";
_hasReparentBug=(div.offsetParent===oldValue);
wrapper.style.position="absolute";
style.height="10px";
oldValue=div.offsetTop;
wrapper.style.border="5px solid red";
_hasBorderBug=(oldValue!==div.offsetTop);//some browsers, like Firefox 38, cause the offsetTop/Left to be affected by a parent's border.
parent.removeChild(wrapper);
}
style=svg.style;
svg.setAttributeNS(null,"width","400px");
svg.setAttributeNS(null,"height","400px");
svg.setAttributeNS(null,"viewBox","0 0 400 400");
style.display="block";
style.boxSizing="border-box";
style.border="0px solid red";
style.transform="none";
// in some browsers (like certain flavors of Android), the getScreenCTM() matrix is contaminated by the scroll position. We can run some logic here to detect that condition, but we ended up not needing this because we found another workaround using getBoundingClientRect().
parent.style.display="block";//Firefox bug causes getScreenCTM() to return null when parent is display:flex and the element isn't rendered inside the window (like if it's below the scroll position)
}
matrix=svg.getScreenCTM();
e1=matrix.e;
style.border="50px solid red";
matrix=svg.getScreenCTM();
if(e1===0&&matrix.e===0&&matrix.f===0&&matrix.a===1){//Opera has a bunch of bugs - it doesn't adjust the x/y of the matrix, nor does it scale when box-sizing is border-box but it does so elsewhere; to get the correct behavior we set _svgBorderScales to true.
_isIE10orBelow=(_gsScope.navigator&&(((/MSIE ([0-9]{1,}[\.0-9]{0,})/).exec(_gsScope.navigator.userAgent)||(/Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/).exec(_gsScope.navigator.userAgent))&&parseFloat(RegExp.$1)<11)),//Ideally we'd avoid user agent sniffing, but there doesn't seem to be a way to feature-detect and sense a border-related bug that only affects IE10 and IE9.
_tempTransforms=[],
_tempElements=[],
_getSVGOffsets=function(e){//SVG elements don't always report offsetTop/offsetLeft/offsetParent at all (I'm looking at you, Firefox 29 and Android), so we have to do some work to manufacture those values. You can pass any SVG element and it'll spit back an object with offsetTop, offsetLeft, offsetParent, scaleX, and scaleY properties. We need the scaleX and scaleY to handle the way SVG can resize itself based on the container.
if(Draggable.cacheSVGData!==false&&e._dCache&&e._dCache.lastUpdate===TweenLite.ticker.frame){//performance optimization. Assume that if the offsets are requested again on the same tick, we can just feed back the values we already calculated (no need to keep recalculating until another tick elapses).
offsetParent=_docElement;//avoids problems with margins/padding on the body
}
//walk up the ancestors and record any non-identity transforms (and reset them to "none") until we reach the offsetParent. We must do this so that the getBoundingClientRect() is accurate for measuring the offsetTop/offsetLeft. We'll revert the values later...
if(cache.borderBox&&!_svgBorderScales&&e.getAttribute("width")){//some browsers (like Safari) don't properly scale the matrix to accommodate the border when box-sizing is border-box, so we must calculate it here...
if(_svgScrollOffset){//some browsers (like Chrome for Android) have bugs in the way getScreenCTM() is reported (it doesn't factor in scroll position), so we must revert to a more expensive technique for calculating offsetTop/Left.
_getOffsetTransformOrigin=function(e,decoratee){//returns the x/y position of the transformOrigin of the element, in its own local coordinate system (pixels), offset from the top left corner.
if(x==="center"||isNaN(parseFloat(x))){//remember, the user could flip-flop the values and say "bottom center" or "center bottom", etc. "center" is ambiguous because it could be used to describe horizontal or vertical, hence the isNaN(). If there's an "=" sign in the value, it's relative.
x="50%";
}
if(e.getBBox&&_isSVG(e)){//SVG elements must be handled in a special way because their origins are calculated from the top left.
if(!e._gsTransform){
TweenLite.set(e,{x:"+=0",overwrite:false});//forces creation of the _gsTransform where we store all the transform components including xOrigin and yOrigin for SVG elements, as of GSAP 1.15.0 which also takes care of calculating the origin from the upper left corner of the SVG canvas.
if(e._gsTransform.xOrigin===undefined){
console.log("Draggable requires at least GSAP 1.17.0");
}
}
v=e.getBBox();
decoratee.x=(e._gsTransform.xOrigin-v.x);
decoratee.y=(e._gsTransform.yOrigin-v.y);
}else{
if(e.getBBox&&(x+y).indexOf("%")!==-1){//Firefox doesn't report offsetWidth/height on <svg> elements.
_cache=function(e){//computes some important values and stores them in a _dCache object attached to the element itself so that we can optimize performance
if(Draggable.cacheSVGData!==false&&e._dCache&&e._dCache.lastUpdate===TweenLite.ticker.frame){//performance optimization. Assume that if the offsets are requested again on the same tick, we can just feed back the values we already calculated (no need to keep recalculating until another tick elapses).
if(isSVGRoot){//some browsers don't report parentNode on SVG elements.
curSVG=e.parentNode||_docElement;
curSVG.insertBefore(_tempDiv,e);
cache.offsetParent=_tempDiv.offsetParent||_docElement;//in some cases, Firefox still reports offsetParent as null.
curSVG.removeChild(_tempDiv);
}elseif(isSVG){
curSVG=e.parentNode;
while(curSVG&&(curSVG.nodeName+"").toLowerCase()!=="svg"){//offsetParent is always the SVG canvas for SVG elements.
curSVG=curSVG.parentNode;
}
cache.offsetParent=curSVG;
}else{
cache.offsetParent=e.offsetParent;
}
returncache;
},
_getOffset2DMatrix=function(e,offsetOrigin,parentOffsetOrigin,zeroOrigin,isBase){//"isBase" helps us discern context - it should only be true when the element is the base one (the one at which we're starting to walk up the chain). It only matters in cases when it's an <svg> element itself because that's a case when we don't apply scaling.
m=(cache.isSVG&&(e.style[_transformProp]+"").indexOf("matrix")!==-1)?e.style[_transformProp]:cs?cs.getPropertyValue(_transformPropCSS):e.currentStyle?e.currentStyle[_transformProp]:"1,0,0,1,0,0";//some browsers (like Chrome 40) don't correctly report transforms that are applied inline on an SVG element (they don't get included in the computed style), so we double-check here and accept matrix values
if(e.getBBox&&(e.getAttribute("transform")+"").indexOf("matrix")!==-1){//SVG can store transform data in its "transform" attribute instead of the CSS, so look for that here (only accept matrix()).
if(cache.isSVG){//don't just rely on "instanceof _SVGElement" because if the SVG is embedded via an object tag, it won't work (SVGElement is mapped to a different object))
offsetX=(sx-(sx*m[0]+sy*m[2]));//accommodate the SVG root's transforms when the origin isn't in the top left.
offsetY=(sy-(sx*m[1]+sy*m[3]));
m[4]=parseFloat(m[4])+offsetX;
m[5]=parseFloat(m[5])+offsetY;
offsetOrigin.x-=offsetX;
offsetOrigin.y-=offsetY;
sx=offsets.scaleX;
sy=offsets.scaleY;
if(!isBase){//when getting the matrix for a root <svg> element itself (NOT in the context of an SVG element that's nested inside of it like a <path>), we do NOT apply the scaling!
offsetOrigin.x*=sx;
offsetOrigin.y*=sy;
}
m[0]*=sx;
m[1]*=sy;
m[2]*=sx;
m[3]*=sy;
if(!_isIE10orBelow){
offsetOrigin.x+=borderTranslateX;
offsetOrigin.y+=borderTranslateY;
}
if(parentOffsetParent===_doc.body&&offsets.offsetParent===_docElement){//to avoid issues with margin/padding on the <body>, we always set the offsetParent to _docElement in the _getSVGOffsets() function but there's a condition we check later in this function for (parentOffsetParent === offsets.offsetParent) which would fail if we don't run this logic. In other words, parentOffsetParent may be <body> and the <svg>'s offsetParent is also <body> but artificially set to _docElement to avoid margin/padding issues.
bottom=top+(((e.innerHeight||0)-20<_docElement.clientHeight)?_docElement.clientHeight:e.innerHeight||_doc.body.clientHeight||0);//some browsers (like Firefox) ignore absolutely positioned elements, and collapse the height of the documentElement, so it could be 8px, for example, if you have just an absolutely positioned div. In that case, we use the innerHeight to resolve this.
}elseif(context===undefined||context===window){
returne.getBoundingClientRect();
}else{
origin=_getOffsetTransformOrigin(e);
left=-origin.x;
top=-origin.y;
if(isSVG){
bbox=e.getBBox();
width=bbox.width;
height=bbox.height;
}elseif((e.nodeName+"").toLowerCase()!=="svg"&&e.offsetWidth){//Chrome dropped support for "offsetWidth" on SVG elements
width=e.offsetWidth;
height=e.offsetHeight;
}else{
computedDimensions=_getComputedStyle(e);
width=parseFloat(computedDimensions.width);
height=parseFloat(computedDimensions.height);
}
right=left+width;
bottom=top+height;
if(e.nodeName.toLowerCase()==="svg"&&!_isOldIE){//root SVG elements are a special beast because they have 2 types of scaling - transforms on themselves as well as the stretching of the SVG canvas itself based on the outer size and the viewBox. If, for example, the SVG's viewbox is "0 0 100 100" but the CSS is set to width:200px; height:200px, that'd make it appear at 2x scale even though the element itself has no CSS transforms but the offsetWidth/offsetHeight are based on that css, not the viewBox so we need to adjust them accordingly.
return(e&&e.length&&e[0]&&((e[0].nodeType&&e[0].style&&!e.nodeType)||(e[0].length&&e[0][0])))?true:false;//could be an array of jQuery objects too, so accommodate that.
_touchEventLookup=(function(types){//we create an object that makes it easy to translate touch event types into their "pointer" counterparts if we're in a browser that uses those instead. Like IE10 uses "MSPointerDown" instead of "touchstart", for example.
if(factor!==1&&snapinstanceofArray){//some data must be altered to make sense, like if the user passes in an array of rotational values in degrees, we must convert it to radians. Or for scrollLeft and scrollTop, we invert the values.
vars.end=a=[];
l=snap.length;
if(typeof(snap[0])==="object"){//if the array is populated with objects, like points ({x:100, y:200}), make copies before multiplying by the factor, otherwise we'll mess up the originals and the user may reuse it elsewhere.
for(i=0;i<l;i++){
a[i]=_copy(snap[i],factor);
}
}else{
for(i=0;i<l;i++){
a[i]=snap[i]*factor;
}
}
max+=1.1;//allow 1.1 pixels of wiggle room when snapping in order to work around some browser inconsistencies in the way bounds are reported which can make them roughly a pixel off. For example, if "snap:[-$('#menu').width(), 0]" was defined and #menu had a wrapper that was used as the bounds, some browsers would be one pixel off, making the minimum -752 for example when snap was [-753,0], thus instead of snapping to -753, it would snap to 0 since -753 was below the minimum.
min-=1.1;
}elseif(typeof(snap)==="function"){
vars.end=function(value){
varresult=snap.call(draggable,value),
copy,p;
if(factor!==1){
if(typeof(result)==="object"){
copy={};
for(pinresult){
copy[p]=result[p]*factor;
}
result=copy;
}else{
result*=factor;
}
}
returnresult;//we need to ensure that we can scope the function call to the Draggable instance itself so that users can access important values like maxX, minX, maxY, minY, x, and y from within that function.
};
}else{
vars.end=snap;
}
}
if(max||max===0){
vars.max=max;
}
if(min||min===0){
vars.min=min;
}
if(forceZeroVelocity){
vars.velocity=0;
}
returnvars;
},
_isClickable=function(e){//sometimes it's convenient to mark an element as clickable by adding a data-clickable="true" attribute (in which case we won't preventDefault() the mouse/touch event). This method checks if the element is an <a>, <input>, or <button> or has an onclick or has the data-clickable or contentEditable attribute set to true (or any of its parent elements).
_addPaddingBR=(function(){//this function is in charge of analyzing browser behavior related to padding. It sets the _addPaddingBR to true if the browser doesn't normally factor in the bottom or right padding on the element inside the scrolling area, and it sets _addPaddingLeft to true if it's a browser that requires the extra offset (offsetLeft) to be added to the paddingRight (like Opera).
val=(child.offsetHeight+18>div.scrollHeight);//div.scrollHeight should be child.offsetHeight + 20 because of the 10px of padding on each side, but some browsers ignore one side. We allow a 2px margin of error.
parent.removeChild(div);
returnval;
}()),
//The ScrollProxy class wraps an element's contents into another div (we call it "content") that we either add padding when necessary or apply a translate3d() transform in order to overscroll (scroll past the boundaries). This allows us to simply set the scrollTop/scrollLeft (or top/left for easier reverse-axis orientation, which is what we do in Draggable) and it'll do all the work for us. For example, if we tried setting scrollTop to -100 on a normal DOM element, it wouldn't work - it'd look the same as setting it to 0, but if we set scrollTop of a ScrollProxy to -100, it'll give the correct appearance by either setting paddingTop of the wrapper to 100 or applying a 100-pixel translateY.
if((dif>2||dif<-2)&&!force){//if the user interacts with the scrollbar (or something else scrolls it, like the mouse wheel), we should kill any tweens of the ScrollProxy.
prevLeft=element.scrollLeft;//don't merge this with the line above because some browsers adjsut the scrollLeft after it's set, so in order to be 100% accurate in tracking it, we need to ask the browser to report it.
};
this.top=function(value,force){
if(!arguments.length){
return-(element.scrollTop+offsetTop);
}
vardif=element.scrollTop-prevTop,
oldOffset=offsetTop;
if((dif>2||dif<-2)&&!force){//if the user interacts with the scrollbar (or something else scrolls it, like the mouse wheel), we should kill any tweens of the ScrollProxy.
return;//no need to recalculate things if the width and height haven't changed.
}
if(offsetTop||offsetLeft){
x=this.left();
y=this.top();
this.left(-element.scrollLeft);
this.top(-element.scrollTop);
}
//first, we need to remove any width constraints to see how the content naturally flows so that we can see if it's wider than the containing element. If so, we've got to record the amount of overage so that we can apply that as padding in order for browsers to correctly handle things. Then we switch back to a width of 100% (without that, some browsers don't flow the content correctly)
//some browsers neglect to factor in the bottom padding when calculating the scrollHeight, so we need to add that padding to the content when that happens. Allow a 2px margin for error
onContextMenu=function(e){//used to prevent long-touch from triggering a context menu.
if(self.isPressed&&e.which<2){
self.endDrag();
}else{
e.preventDefault();
e.stopPropagation();
returnfalse;
}
},
//this method gets called on every tick of TweenLite.ticker which allows us to synchronize the renders to the core engine (which is typically synchronized with the display refresh via requestAnimationFrame). This is an optimization - it's better than applying the values inside the "mousemove" or "touchmove" event handler which may get called many times inbetween refreshes.
if(x<min&&x>-min){//browsers don't handle super small decimals well.
x=0;
}
if(y<min&&y>-min){
y=0;
}
if(rotationMode){
self.deltaX=x-applyObj.data.rotation;
applyObj.data.rotation=self.rotation=x;
applyObj.setRatio(1);//note: instead of doing TweenLite.set(), as a performance optimization we skip right to the method that renders the transforms inside CSSPlugin. For old versions of IE, though, we do a normal TweenLite.set() to leverage its ability to re-reroute to an IE-specific 2D renderer.
}else{
if(scrollProxy){
if(allowY){
self.deltaY=y-scrollProxy.top();
scrollProxy.top(y);
}
if(allowX){
self.deltaX=x-scrollProxy.left();
scrollProxy.left(x);
}
}elseif(xyMode){
if(allowY){
self.deltaY=y-applyObj.data.y;
applyObj.data.y=y;
}
if(allowX){
self.deltaX=x-applyObj.data.x;
applyObj.data.x=x;
}
applyObj.setRatio(1);//note: instead of doing TweenLite.set(), as a performance optimization we skip right to the method that renders the transforms inside CSSPlugin. For old versions of IE, though, we do a normal TweenLite.set() to leverage its ability to re-reroute to an IE-specific 2D renderer.
isDispatching=true;//in case onDrag has an update() call (avoid endless loop)
_dispatchEvent(self,"drag","onDrag");
isDispatching=false;
}
}
dirty=false;
},
//copies the x/y from the element (whether that be transforms, top/left, or ScrollProxy's top/left) to the Draggable's x and y (and rotation if necessary) properties so that they reflect reality and it also (optionally) applies any snapping necessary. This is used by the ThrowPropsPlugin tween in an onUpdate to ensure things are synced and snapped.
syncXY=function(skipOnUpdate,skipSnap){
varx=self.x,
y=self.y,
snappedValue;
if(!target._gsTransform&&(xyMode||rotationMode)){//just in case the _gsTransform got wiped, like if the user called clearProps on the transform or something (very rare), doing an x tween forces a re-parsing of the transforms and population of the _gsTransform.
bounds=_getBounds(vars.bounds,target.parentNode);//could be a selector/jQuery object or a DOM element or a generic object like {top:0, left:100, width:1000, height:800} or {minX:100, maxX:1100, minY:0, maxY:800}
throwProps.radius=snap.radius;//note: we also disable liveSnapping while throwing if there's a "radius" defined, otherwise it looks weird to have the item thrown past a snapping point but live-snapping mid-tween. We do this by altering the onUpdateParams so that "skipSnap" parameter is true for syncXY.
//to populate the end values, we just scrub the tween to the end, record the values, and then jump back to the beginning.
if(scrollProxy){
scrollProxy._suspendTransforms=true;//Microsoft browsers have a bug that causes them to briefly render the position incorrectly (it flashes to the end state when we seek() the tween even though we jump right back to the current position, and this only seems to happen when we're affecting both top and left), so we set a _suspendTransforms flag to prevent it from actually applying the values in the ScrollProxy.
if(shiftStart&&self.isPressed&&start.join(",")!==matrix.join(",")){//if the matrix changes WHILE the element is pressed, we must adjust the startPointerX and startPointerY accordingly, so we invert the original matrix and figure out where the pointerX and pointerY were in the global space, then apply the new matrix to get the updated coordinates.
if(!matrix[1]&&!matrix[2]&&matrix[0]==1&&matrix[3]==1&&matrix[4]==0&&matrix[5]==0){//if there are no transforms, we can optimize performance by not factoring in the matrix
matrix=null;
}
},
recordStartPositions=function(){
varedgeTolerance=1-self.edgeResistance;
updateMatrix(false);
if(matrix){
startPointerX=self.pointerX*matrix[0]+self.pointerY*matrix[2]+matrix[4];//translate to local coordinate system
//if the element is in the process of tweening, don't force snapping to occur because it could make it jump. Imagine the user throwing, then before it's done, clicking on the element in its inbetween state.
if(_placeholderDiv.parentNode&&!isTweening()&&!self.isDragging){//_placeholderDiv just props open auto-scrolling containers so they don't collapse as the user drags left/up. We remove it after dragging (and throwing, if necessary) finishes.
varedgeTolerance=!self.isPressed?1:1-self.edgeResistance;//if we're tweening, disable the edgeTolerance because it's already factored into the tweening values (we don't want to apply it multiple times)
result,dx,dy;//if we're tweening, disable the edgeTolerance because it's already factored into the tweening values (we don't want to apply it multiple times)
//called when the mouse is pressed (or touch starts)
onPress=function(e,force){
vari;
if(!enabled||self.isPressed||!e||((e.type==="mousedown"||e.type==="pointerdown")&&!force&&_getTime()-clickTime<30&&_touchEventLookup[self.pointerEvent.type])){//when we DON'T preventDefault() in order to accommodate touch-scrolling and the user just taps, many browsers also fire a mousedown/mouseup sequence AFTER the touchstart/touchend sequence, thus it'd result in two quick "click" events being dispatched. This line senses that condition and halts it on the subsequent mousedown.
return;
}
interrupted=isTweening();
self.pointerEvent=e;
if(_touchEventLookup[e.type]){//note: on iOS, BOTH touchmove and mousemove are dispatched, but the mousemove has pageY and pageX of 0 which would mess up the calculations and needlessly hurt performance.
touchEventTarget=(e.type.indexOf("touch")!==-1)?(e.currentTarget||e.target):_doc;//pointer-based touches (for Microsoft browsers) don't remain locked to the original target like other browsers, so we must use the document instead. The event type would be "MSPointerDown" or "pointerdown".
_addListener(_doc,"mousemove",onMove);//attach these to the document instead of the box itself so that if the user's mouse moves too quickly (and off of the box), things still work.
}
touchDragAxis=null;
_addListener(_doc,"mouseup",onRelease);
if(e&&e.target){
_addListener(e.target,"mouseup",onRelease);//we also have to listen directly on the element because some browsers don't bubble up the event to the _doc on elements with contentEditable="true"
_addListener(e.target,"change",onRelease);//in some browsers, when you mousedown on a <select> element, no mouseup gets dispatched! So we listen for a "change" event instead.
_dispatchEvent(self,"pressInit","onPressInit");
_dispatchEvent(self,"press","onPress");
_setSelectable(triggers,true);//accommodates things like inputs and elements with contentEditable="true" (otherwise user couldn't drag to select text)
return;
}
allowNativeTouchScrolling=(!touchEventTarget||allowX===allowY||self.vars.allowNativeTouchScrolling===false||(self.vars.allowContextMenu&&e&&(e.ctrlKey||e.which>2)))?false:allowX?"y":"x";//note: in Chrome, right-clicking (for a context menu) fires onPress and it doesn't have the event.which set properly, so we must look for event.ctrlKey. If the user wants to allow context menus we should of course sense it here and not allow native touch scrolling.
e.preventManipulation();//for some Microsoft browsers
}
}
if(e.changedTouches){//touch events store the data slightly differently
e=touch=e.changedTouches[0];
touchID=e.identifier;
}elseif(e.pointerId){
touchID=e.pointerId;//for some Microsoft browsers
}else{
touch=touchID=null;
}
_dragCount++;
_addToRenderQueue(render);//causes the Draggable to render on each "tick" of TweenLite.ticker (performance optimization - updating values in a mousemove can cause them to happen too frequently, like multiple times between frame redraws which is wasteful, and it also prevents values from updating properly in IE8)
startPointerY=self.pointerY=e.pageY;//record the starting x and y so that we can calculate the movement from the original in _onMouseMove
startPointerX=self.pointerX=e.pageX;
_dispatchEvent(self,"pressInit","onPressInit");
if(allowNativeTouchScrolling||self.autoScroll){
_recordMaxScrolls(target.parentNode);
}
if(target.parentNode&&self.autoScroll&&!scrollProxy&&!rotationMode&&target.parentNode._gsMaxScrollX&&!_placeholderDiv.parentNode&&!target.getBBox){//add a placeholder div to prevent the parent container from collapsing when the user drags the element left.
if(touches){//touch events store the data slightly differently
e=touches[0];
if(e!==touch&&e.identifier!==touchID){//Usually changedTouches[0] will be what we're looking for, but in case it's not, look through the rest of the array...(and Android browsers don't reuse the event like iOS)
}elseif(e.pointerId&&touchID&&e.pointerId!==touchID){//for some Microsoft browsers, we must attach the listener to the doc rather than the trigger so that when the finger moves outside the bounds of the trigger, things still work. So if the event we're receiving has a pointerId that doesn't match the touchID, ignore it (for multi-touch)
return;
}
if(_isOldIE){
e=_populateIEEvent(e,true);
}else{
if(touchEventTarget&&allowNativeTouchScrolling&&!touchDragAxis){//Android browsers force us to decide on the first "touchmove" event if we should allow the default (scrolling) behavior or preventDefault(). Otherwise, a "touchcancel" will be fired and then no "touchmove" or "touchend" will fire during the scrolling (no good).
x=Math.round(x);//helps work around an issue with some Win Touch devices
y=Math.round(y);
}
if(self.x!==x||(self.y!==y&&!rotationMode)){
if(rotationMode){
self.endRotation=self.x=self.endX=x;
dirty=true;
}else{
if(allowY){
self.y=self.endY=y;
dirty=true;//a flag that indicates we need to render the target next time the TweenLite.ticker dispatches a "tick" event (typically on a requestAnimationFrame) - this is a performance optimization (we shouldn't render on every move because sometimes many move events can get dispatched between screen refreshes, and that'd be wasteful to render every time)
}
if(allowX){
self.x=self.endX=x;
dirty=true;
}
}
if(!self.isDragging&&self.isPressed){
self.isDragging=true;
_dispatchEvent(self,"dragstart","onDragStart");
}
}
},
//called when the mouse/touch is released
onRelease=function(e,force){
if(!enabled||!self.isPressed||(e&&touchID!=null&&!force&&((e.pointerId&&e.pointerId!==touchID)||(e.changedTouches&&!_hasTouchID(e.changedTouches,touchID))))){//for some Microsoft browsers, we must attach the listener to the doc rather than the trigger so that when the finger moves outside the bounds of the trigger, things still work. So if the event we're receiving has a pointerId that doesn't match the touchID, ignore it (for multi-touch)
if(touches){//touch events store the data slightly differently
e=touches[0];
if(e!==touch&&e.identifier!==touchID){//Usually changedTouches[0] will be what we're looking for, but in case it's not, look through the rest of the array...(and Android browsers don't reuse the event like iOS)
originalEvent.preventManipulation();//for some Microsoft browsers
}
_dispatchEvent(self,"release","onRelease");
}elseif(originalEvent&&!wasDragging){
if(interrupted&&(vars.snap||vars.bounds)){//otherwise, if the user clicks on the object while it's animating to a snapped position, and then releases without moving 3 pixels, it will just stay there (it should animate/snap)
animate(vars.throwProps);
}
_dispatchEvent(self,"release","onRelease");
if((!_isAndroid||originalEvent.type!=="touchmove")&&originalEvent.type.indexOf("cancel")===-1){//to accommodate native scrolling on Android devices, we have to immediately call onRelease() on the first touchmove event, but that shouldn't trigger a "click".
syntheticClick=function(){// some browsers (like Firefox) won't trust script-generated clicks, so if the user tries to click on a video to play it, for example, it simply won't work. Since a regular "click" event will most likely be generated anyway (one that has its isTrusted flag set to true), we must slightly delay our script-generated click so that the "real"/trusted one is prioritized. Remember, when there are duplicate events in quick succession, we suppress all but the first one. Some browsers don't even trigger the "real" one at all, so our synthetic one is a safety valve that ensures that no matter what, a click event does get dispatched.
if(!_isAndroid&&!originalEvent.defaultPrevented){//iOS Safari requires the synthetic click to happen immediately or else it simply won't work, but Android doesn't play nice.
TweenLite.delayedCall(0.00001,syntheticClick);//in addition to the iOS bug workaround, there's a Firefox issue with clicking on things like a video to play, so we must fake a click event in a slightly delayed fashion. Previously, we listened for the "click" event with "capture" false which solved the video-click-to-play issue, but it would allow the "click" event to be dispatched twice like if you were using a jQuery.click() because that was handled in the capture phase, thus we had to switch to the capture phase to avoid the double-dispatching, but do the delayed synthetic click.
}
}
}else{
animate(vars.throwProps);//will skip if throwProps isn't defined or ThrowPropsPlugin isn't loaded.
onClick=function(e){//this was a huge pain in the neck to align all the various browsers and their behaviors. Chrome, Firefox, Safari, Opera, Android, and Microsoft Edge all handle events differently! Some will only trigger native behavior (like checkbox toggling) from trusted events. Others don't even support isTrusted, but require 2 events to flow through before triggering native behavior. Edge treats everything as trusted but also mandates that 2 flow through to trigger the correct native behavior.
trusted=e.isTrusted||(e.isTrusted==null&&recentlyClicked&&alreadyDispatched);//note: Safari doesn't support isTrusted, and it won't properly execute native behavior (like toggling checkboxes) on the first synthetic "click" event - we must wait for the 2nd and treat it as trusted (but stop propagation at that point). Confusing, I know. Don't you love cross-browser compatibility challenges?
if(recentlyClicked&&!(self.pointerEvent&&self.pointerEvent.defaultPrevented)&&(!alreadyDispatched||(trusted!==alreadyDispatchedTrusted))){//let the first click pass through unhindered. Let the next one only if it's trusted, then no more (stop quick-succession ones)
scrollProxy=this.scrollProxy=newScrollProxy(target,_extend({onKill:function(){//ScrollProxy's onKill() gets called if/when the ScrollProxy senses that the user interacted with the scroll position manually (like using the scrollbar). IE9 doesn't fire the "mouseup" properly when users drag the scrollbar of an element, so this works around that issue.
if(self.isPressed){
onRelease(null);
}}},vars));
//a bug in many Android devices' stock browser causes scrollTop to get forced back to 0 after it is altered via JS, so we set overflow to "hidden" on mobile/touch devices (they hide the scroll bar anyway). That works around the bug. (This bug is discussed at https://code.google.com/p/android/issues/detail?id=19625)
//some older Android devices intermittently stop dispatching "touchmove" events if we don't listen for "touchcancel" on the document. Very strange indeed.
_parseRect=function(e,undefined){//accepts a DOM element, a mouse event, or a rectangle object and returns the corresponding rectangle with left, right, width, height, top, and bottom properties
}elseif(r.width===undefined){//some browsers don't include width and height properties. We can't just set them directly on r because some browsers throw errors, so create a new generic object.