iwmlib/lib/3rdparty/greensock/src/bonus-files-for-npm-users/umd/GSDevTools.js

3821 lines
188 KiB
JavaScript

/*!
* VERSION: 0.1.9
* DATE: 2019-02-07
* UPDATES AND DOCS AT: http://greensock.com
*
* @license Copyright (c) 2008-2019, GreenSock. All rights reserved.
* GSDevTools is a Club GreenSock membership benefit; You must have a valid membership to use
* this code without violating the terms of use. Visit http://greensock.com/club/ to sign up or get more details.
* This work is subject to the software agreement that was issued with your membership.
*
* @author: Jack Doyle, jack@greensock.com
**/
/* eslint-disable */
var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node
(_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() {
"use strict";
_gsScope._gsDefine("GSDevTools", ["TweenLite", "core.Animation", "core.SimpleTimeline", "TimelineLite", "utils.Draggable", "plugins.CSSPlugin"], function(TweenLite, Animation, SimpleTimeline, TimelineLite, Draggable) {
var _doc = document,
_docElement = _doc.documentElement,
_svgNS = "http://www.w3.org/2000/svg",
_domNS = "http://www.w3.org/1999/xhtml",
_idSeed = 0, //we assign an ID to each GSDevTools instance so that we can segregate the sessionStorage data accordingly.
_lookup = {},
_createElement = function(type, container, cssText) {
var element = _doc.createElementNS ? _doc.createElementNS(type === "svg" ? _svgNS : _domNS, type) : _doc.createElement(type);
if (container) {
if (typeof(container) === "string") {
container = _doc.querySelector(container);
}
container.appendChild(element);
}
if (type === "svg") {
element.setAttribute("xmlns", _svgNS);
element.setAttribute("xmlns:xlink", _domNS);
}
if (cssText) {
element.style.cssText = cssText;
}
return element;
},
_clearSelection = function() {
if (_doc.selection) {
_doc.selection.empty()
} else if (window.getSelection) {
window.getSelection().removeAllRanges();
}
},
_globalTimeline = Animation._rootTimeline,
_getChildrenOf = function(timeline, includeTimelines) {
var a = [],
cnt = 0,
tween = timeline._first;
while (tween) {
if (tween instanceof TweenLite) {
if (tween.vars.id) {
a[cnt++] = tween;
}
} else {
if (includeTimelines && tween.vars.id) {
a[cnt++] = tween;
}
a = a.concat(_getChildrenOf(tween, includeTimelines));
cnt = a.length;
}
tween = tween._next;
}
return a;
},
//eliminates any very long repeating children (direct children, not deeply nested) and returns the duration accordingly.
_getClippedDuration = function(animation, excludeRootRepeats) {
var max = 0,
repeat = Math.max(0, animation._repeat),
t = animation._first;
if (!t) {
max = animation.duration();
}
while (t) {
max = Math.max(max, t.totalDuration() > 999 ? t.endTime(false) : t._startTime + t._totalDuration / t._timeScale);
t = t._next;
}
return (!excludeRootRepeats && repeat) ? max * (repeat + 1) + (animation._repeatDelay * repeat) : max;
},
_getAnimationById = function(id) {
if (!id) {
return null;
}
if (id instanceof Animation) {
return id;
}
var a = _getChildrenOf(_globalTimeline, true),
i = a.length;
while (--i > -1) {
if (a[i].vars.id === id) {
return a[i];
}
}
},
_timeToProgress = function(time, animation, defaultValue, relativeProgress) {
var add, i, a;
if (typeof(time) === "string") {
if (time.charAt(1) === "=") {
add = parseInt(time.charAt(0) + "1", 10) * parseFloat(time.substr(2));
if (add < 0 && relativeProgress === 0) { //if something like inTime:"-=2", we measure it from the END, not the beginning
relativeProgress = 100;
}
time = (relativeProgress / 100 * animation.duration()) + add;
} else if (isNaN(time) && animation.getLabelTime && animation.getLabelTime(time) !== -1) {
time = animation.getLabelTime(time);
} else if (animation === _recordedRoot) { //perhaps they defined an id of an animation, like "myAnimation+=2"
i = time.indexOf("=");
if (i > 0) {
add = parseInt(time.charAt(i-1) + "1", 10) * parseFloat(time.substr(i+1));
time = time.substr(0, i-1);
} else {
add = 0;
}
a = _getAnimationById(time);
if (a) {
time = _getGlobalTime(a, defaultValue / 100 * a.duration()) + add;
}
}
}
time = isNaN(time) ? defaultValue : parseFloat(time);
return Math.min(100, Math.max(0, time / animation.duration() * 100));
},
_getGlobalTime = function(animation, time) {
var tl = animation;
time = time || 0;
if (tl.timeline) {
while (tl.timeline.timeline) {
time = (time / tl._timeScale) + tl._startTime;
tl = tl.timeline;
}
}
return time;
},
_addedCSS,
_createRootElement = function(element, minimal, css) {
if (!_addedCSS) {
_createElement("style", _docElement).innerHTML = '.gs-dev-tools{height:51px;bottom:0;left:0;right:0;display:block;position:fixed;overflow:visible;padding:0}.gs-dev-tools *{box-sizing:content-box;visibility:visible}.gs-dev-tools .gs-top{position:relative;z-index:499}.gs-dev-tools .gs-bottom{display:flex;align-items:center;justify-content:space-between;background-color:rgba(0,0,0,.6);height:42px;border-top:1px solid #999;position:relative}.gs-dev-tools .timeline{position:relative;height:8px;margin-left:15px;margin-right:15px;overflow:visible}.gs-dev-tools .progress-bar,.gs-dev-tools .timeline-track{height:8px;width:100%;position:absolute;top:0;left:0}.gs-dev-tools .timeline-track{background-color:#999;opacity:.6}.gs-dev-tools .progress-bar{background-color:#91e600;height:8px;top:0;width:0;pointer-events:none}.gs-dev-tools .seek-bar{width:100%;position:absolute;height:24px;top:-12px;left:0;background-color:transparent}.gs-dev-tools .in-point,.gs-dev-tools .out-point{width:15px;height:26px;position:absolute;top:-18px}.gs-dev-tools .in-point-shape{fill:#6d9900;stroke:rgba(0,0,0,.5);stroke-width:1}.gs-dev-tools .out-point-shape{fill:#994242;stroke:rgba(0,0,0,.5);stroke-width:1}.gs-dev-tools .in-point{transform:translateX(-100%)}.gs-dev-tools .out-point{left:100%}.gs-dev-tools .grab{stroke:rgba(255,255,255,.3);stroke-width:1}.gs-dev-tools .playhead{position:absolute;top:-5px;transform:translate(-50%,0);left:0;border-radius:50%;width:16px;height:16px;border:1px solid #6d9900;background-color:#91e600}.gs-dev-tools .gs-btn-white{fill:#fff}.gs-dev-tools .pause{opacity:0}.gs-dev-tools .select-animation{vertical-align:middle;position:relative;padding:6px 10px}.gs-dev-tools .select-animation-container{flex-grow:4;width:40%}.gs-dev-tools .select-arrow{display:inline-block;width:12px;height:7px;margin:0 7px;transform:translate(0,-2px)}.gs-dev-tools .select-arrow-shape{stroke:rgba(255,255,255,.6);stroke-width:2px;fill:none}.gs-dev-tools .rewind{height:16px;width:19px;padding:10px 4px;min-width:24px}.gs-dev-tools .rewind-path{opacity:.6}.gs-dev-tools .play-pause{width:24px;height:24px;padding:6px 10px;min-width:24px}.gs-dev-tools .ease{width:30px;height:30px;padding:10px;min-width:30px;display:none}.gs-dev-tools .ease-path{fill:none;stroke:rgba(255,255,255,.6);stroke-width:2px}.gs-dev-tools .ease-border{fill:rgba(255,255,255,.25)}.gs-dev-tools .time-scale{font-family:monospace;font-size:18px;text-align:center;color:rgba(255,255,255,.6);padding:4px 4px 4px 0;min-width:30px;margin-left:7px}.gs-dev-tools .loop{width:20px;padding:5px;min-width:20px}.gs-dev-tools .loop-path{fill:rgba(255,255,255,.6)}.gs-dev-tools label span{color:#fff;font-family:monospace;text-decoration:none;font-size:16px;line-height:18px}.gs-dev-tools .time-scale span{color:rgba(255,255,255,.6)}.gs-dev-tools button:focus,.gs-dev-tools select:focus{outline:0}.gs-dev-tools label{position:relative;cursor:pointer}.gs-dev-tools label.locked{text-decoration:none;cursor:auto}.gs-dev-tools label input,.gs-dev-tools label select{position:absolute;left:0;top:0;z-index:1;font:inherit;font-size:inherit;line-height:inherit;height:100%;width:100%;color:#000!important;opacity:0;background:0 0;border:none;padding:0;margin:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer}.gs-dev-tools label input+.display{position:relative;z-index:2}.gs-dev-tools .gs-bottom-right{vertical-align:middle;display:flex;align-items:center;flex-grow:4;width:40%;justify-content:flex-end}.gs-dev-tools .time-container{font-size:18px;font-family:monospace;color:rgba(255,255,255,.6);margin:0 5px}.gs-dev-tools .logo{width:32px;height:32px;position:relative;top:2px;margin:0 12px}.gs-dev-tools .gs-hit-area{background-color:transparent;width:100%;height:100%;top:0;position:absolute}.gs-dev-tools.minimal{height:auto;display:flex;align-items:stretch}.gs-dev-tools.minimal .gs-top{order:2;flex-grow:4;background-color:rgba(0,0,0,1)}.gs-dev-tools.minimal .gs-bottom{background-color:rgba(0,0,0,1);border-top:none}.gs-dev-tools.minimal .timeline{top:50%;transform:translate(0,-50%)}.gs-dev-tools.minimal .in-point,.gs-dev-tools.minimal .out-point{display:none}.gs-dev-tools.minimal .select-animation-container{display:none}.gs-dev-tools.minimal .rewind{display:none}.gs-dev-tools.minimal .play-pause{width:20px;height:20px;padding:4px 6px;margin-left:14px}.gs-dev-tools.minimal .time-scale{min-width:26px}.gs-dev-tools.minimal .loop{width:18px;min-width:18px;display:none}.gs-dev-tools.minimal .gs-bottom-right{display:none}@media only screen and (max-width:600px){.gs-dev-tools{height:auto;display:flex;align-items:stretch}.gs-dev-tools .gs-top{order:2;flex-grow:4;background-color:rgba(0,0,0,1);height:42px}.gs-dev-tools .gs-bottom{background-color:rgba(0,0,0,1);border-top:none}.gs-dev-tools .timeline{top:50%;transform:translate(0,-50%)}.gs-dev-tools .in-point,.gs-dev-tools .out-point{display:none}.gs-dev-tools .select-animation-container{display:none}.gs-dev-tools .rewind{display:none}.gs-dev-tools .play-pause{width:20px;height:20px;padding:4px 6px;margin-left:14px}.gs-dev-tools .time-scale{min-width:26px}.gs-dev-tools .loop{width:18px;min-width:18px;display:none}.gs-dev-tools .gs-bottom-right{display:none}}';
_addedCSS = true;
}
if (typeof(element) === "string") {
element = document.querySelector(element);
}
var root = _createElement("div", element || _docElement.getElementsByTagName("body")[0] || _docElement);
root.setAttribute("class", "gs-dev-tools" + (minimal ? " minimal" : ""));
root.innerHTML = '<div class=gs-hit-area></div><div class=gs-top><div class=timeline><div class=timeline-track></div><div class=progress-bar></div><div class=seek-bar></div><svg class=in-point viewBox="0 0 15 26"xmlns=http://www.w3.org/2000/svg><polygon class=in-point-shape points=".5 .5 14.5 .5 14.5 25.5 .5 17.5"/><polyline class=grab points="5.5 4 5.5 15"/><polyline class=grab points="9.5 4 9.5 17"/></svg> <svg class=out-point viewBox="0 0 15 26"xmlns=http://www.w3.org/2000/svg><polygon class=out-point-shape points=".5 .5 14.5 .5 14.5 17.5 .5 25.5"/><polyline class=grab points="5.5 4 5.5 17"/><polyline class=grab points="9.5 4 9.5 15"/></svg><div class=playhead></div></div></div><div class=gs-bottom><div class=select-animation-container><label class=select-animation><select class=animation-list><option>Global Timeline<option>myTimeline</select><nobr><span class="display animation-label">Global Timeline</span> <svg class=select-arrow viewBox="0 0 12.05 6.73"xmlns=http://www.w3.org/2000/svg><polyline class=select-arrow-shape points="0.35 0.35 6.03 6.03 11.7 0.35"/></svg></nobr></label></div><svg class=rewind viewBox="0 0 12 15.38"xmlns=http://www.w3.org/2000/svg><path d=M0,.38H2v15H0Zm2,7,10,7.36V0Z class="gs-btn-white rewind-path"/></svg> <svg class=play-pause viewBox="0 0 20.97 25.67"xmlns=http://www.w3.org/2000/svg><g class=play><path d="M8,4.88 C8,10.18 8,15.48 8,20.79 5.33,22.41 2.66,24.04 0,25.67 0,17.11 0,8.55 0,0 2.66,1.62 5.33,3.25 8,4.88"class="gs-btn-white play-1"style=stroke:#fff;stroke-width:.6px /><path d="M14.485,8.855 C16.64,10.18 18.8,11.5 20.97,12.83 16.64,15.48 12.32,18.13 8,20.79 8,15.48 8,10.18 8,4.88 10.16,6.2 12.32,7.53 14.48,8.85"class="gs-btn-white play-2"style=stroke:#fff;stroke-width:.6px /></g></svg> <svg class=loop viewBox="0 0 29 25.38"xmlns=http://www.w3.org/2000/svg><path d=M27.44,5.44,20.19,0V3.06H9.06A9.31,9.31,0,0,0,0,12.41,9.74,9.74,0,0,0,.69,16l3.06-2.23a6,6,0,0,1-.12-1.22,5.49,5.49,0,0,1,5.43-5.5H20.19v3.81Z class=loop-path /><path d=M25.25,11.54a5.18,5.18,0,0,1,.12,1.12,5.41,5.41,0,0,1-5.43,5.41H9.19V14.5L1.94,19.94l7.25,5.44V22.06H19.94A9.2,9.2,0,0,0,29,12.84a9.42,9.42,0,0,0-.68-3.53Z class=loop-path /></svg> <svg class=ease viewBox="0 0 25.67 25.67"xmlns=http://www.w3.org/2000/svg><path d=M.48,25.12c1.74-3.57,4.28-12.6,8.8-10.7s4.75,1.43,6.5-1.11S19.89,1.19,25.2.55 class=ease-path /><path d=M24.67,1V24.67H1V1H24.67m1-1H0V25.67H25.67V0Z class=ease-border /></svg><label class=time-scale><select><option value=10>10x<option value=5>5x<option value=2>2x<option value=1 selected>1x<option value=0.5>0.5x<option value=0.25>0.25x<option value=0.1>0.1x</select><span class="display time-scale-label">1x</span></label><div class=gs-bottom-right><div class=time-container><span class=time>0.00</span> / <span class=duration>0.00</span></div><a href="https://greensock.com/docs/Utilities/GSDevTools?source=GSDevTools"target=_blank title=Docs><svg class=logo viewBox="0 0 100 100"xmlns=http://www.w3.org/2000/svg><path d="M60 15.4c-.3-.4-.5-.6-.5-.7.1-.6.2-1 .2-1.7v-.4c.6.6 1.3 1.3 1.8 1.7.2.2.5.3.8.3.2 0 .3 0 .5.1h1.6c.8 0 1.6.1 2 0 .1 0 .2 0 .3-.1.6-.3 1.4-1 2.1-1.6 0 .6.1 1.2.1 1.7v1.5c0 .3 0 .5.1.7-.1.1-.2.1-.4.2-.7.4-1.7 1-2.3.9-.5-.1-1.5-.3-2.6-.7-1.2-.3-2.4-.8-3.2-1.2 0 0-.1 0-.1-.1s-.2-.4-.4-.6zm24.6 21.9c-.5-1.7-1.9-2-4.2-.7.9-1.5 2.1-1.5 2.3-2.1.9-2.5-.6-4.6-1.2-5.3.7-1.8 1.4-4.5-1-6.8-1-1-2.4-1.2-3.6-1.1 1.8 1.7 3.4 4.4 2.5 7.2-.1.3-.9.7-1.7 1 0 0 .4 2-.3 3.5-.3.6-.8 1.5-1.3 2.6 1 .9 1.6 1 3 1.3-.9.1-1.2.4-1.2.5-.7 3 1 3.4 1.4 4.8 0 .1 0 .2.1.3v.4c-.3.3-1.4.5-2.5.5s-1.8 1-1.8 1c-.2.1-.3.3-.4.4v1c0 .1 0 .4.1.6.1.5.3 1.3.4 1.8.9.6 1.4.9 2.2 1.1.5.1 1 .2 1.5.1.3-.1.7-.3 1-.7 1.5-1.7 1.9-3.2 2.2-4.1 0-.1 0-.2.1-.2 0 .1.1.1.1.2 0 0 .1-.1.1-.2l.1-.1c1.3-1.6 2.9-4.5 2.1-7zM74.3 49.9c-.1-.3-.1-.7-.2-1.1v-.2c-.1-.2-.1-.4-.2-.6 0-.1-.1-.3-.1-.5s-.1-.5-.1-.7v-.1c0-.2-.1-.5-.1-.7-.1-.3-.1-.7-.2-1.1v-.1c0-.2 0-.3-.1-.5v-.9c0-.1 0-.2.1-.3V43h-.3c-1.1.1-3.8.4-6.7.2-1.2-.1-2.4-.3-3.6-.6-1-.3-1.8-.5-2.3-.7-1.2-.4-1.6-.6-1.8-.7 0 .2-.1.4-.1.7 0 .3-.1.5-.1.8-.1.2-.1.4-.2.6l.1.1c.5.5 1.5 1.3 1.5 2.1v.2c-.1.4-.4.5-.8.9-.1.1-.6.7-1.1 1.1l-.6.6c-.1 0-.1.1-.2.1-.1.1-.3.2-.4.3-.2.1-.7.5-.8.6-.1.1-.2.1-.3.1-2.8 8.8-2.2 13.5-1.5 16.1.1.5.3 1 .4 1.3-.4.5-.8 1-1.2 1.4-1.2 1.5-2 2.6-2.6 4.2 0 .1 0 .1-.1.2 0 .1 0 .2-.1.2-.2.5-.3 1-.4 1.5-.6 2.3-.8 4.5-.9 6.6-.1 2.4-.2 4.6-.5 6.9.7.3 3.1.9 4.7.6.2-.1 0-3.9.6-5.7l.6-1.5c.4-.9.9-1.9 1.3-3.1.3-.7.5-1.5.7-2.4.1-.5.2-1 .3-1.6V74v-.1c.1-.6.1-1.3.1-2 0-.2-.7.3-1.1.9.3-1.8 1.3-2.1 2-3.2.3-.5.6-1.1.6-2 2.5-1.7 4-3.7 5-5.7.2-.4.4-.9.6-1.4.3-.8.5-1.6.7-2.4.3-1.4.8-3.2 1.2-4.8v-.1c.4-1.2.8-2.2 1.2-2.6-.2.9-.4 1.7-.6 2.5v.2c-.6 3.5-.7 6.2-2 9.2 1 2.6 1.9 3.9 2 7.6-2 0-3.2 1.6-3.7 3.2 1.2.3 3.9.7 8.3.1h.3c.1-.5.3-1.1.5-1.5.3-.8.5-1.5.6-2.2.2-1.3.1-2.4 0-3.2 3.9-3.7 2.6-11 1.6-16.6zm.3-15.1c.1-.3.2-.6.4-.8.2-.3.3-.7.5-1 .1-.3.3-.6.4-.9.5-1.5.4-2.8.3-3.5-.1 0-.1-.1-.2-.1-.5-.2-.9-.4-1.4-.6-.1 0-.2-.1-.3-.1-3.8-1.2-7.9-.9-11.9.1-1 .2-1.9.5-2.9.1-2.3-.8-3.9-1.9-4.6-2.8l-.2-.2c-.1.2-.2.4-.4.6.2 2.3-.5 3.9-1.4 5.1.9 1.2 2.6 2.8 3.6 3.4 1.1.6 1.7.7 3.4.4-.6.7-1.1 1-1.9 1.4.1.7.2 2 .5 3.4.3.3 1.2.8 2.3 1.3.5.3 1.1.5 1.7.7.8.3 1.7.6 2.4.8.1 0 .2.1.3.1.5.1 1.1.2 1.8.2h.9c2.1 0 4.5-.2 5.4-.3h.1c-.1-2.7.2-4.6.7-6.2.2-.3.4-.7.5-1.1zm-23.2 9.3v.2c-.3 1.7.5 2.4 1.9 3.4.6.5 0 .5.5.8.3.2.7.3 1 .3.3 0 .5 0 .8-.1.2-.1.4-.3.6-.5.1-.1.3-.2.5-.4.3-.2.6-.5.7-.6.1-.1.2-.1.3-.2.2-.2.5-.5.6-.7.2-.2.4-.5.5-.7 0-.1.1-.1.1-.1v-.1c.1-.4-.3-.8-.8-1.3-.2-.2-.4-.3-.5-.5-.3-.3-.6-.5-1-.7-.9-.5-1.9-.7-3-.7l-.3-.3c-2.2-2.5-3.2-4.8-3.9-6.5-.9-2.1-1.9-3.3-3.9-4.9 1 .4 1.8.8 2.3 1.1.5.4 1.3.4 1.9.2.2-.1.5-.2.7-.3.2-.1.4-.2.6-.4 1.6-1.3 2.5-3.8 2.6-5.6v-.1c.2-.3.6-1.1.8-1.4l.1.1c.1.1.3.2.6.5.1 0 .1.1.2.1.1.1.2.1.2.2.8.6 1.9 1.3 2.6 1.7 1.4.7 2.3.7 5.3-.1 2.2-.6 4.8-.8 6.8-.8 1.4 0 2.7.3 4 .7.2.1.4.1.5.2.3.1.6.2.9.4 0 0 .1 0 .1.1.8.4 2.1 1.2 2.5-.3.1-2-.6-3.9-1.6-5.3 0 0-.1 0-.1-.1-.1-.1-.2-.2-.4-.3-.1-.1-.2-.1-.3-.2-.1-.1-.2-.2-.4-.2-.6-.4-1.2-.8-1.6-.9-.1-.1-.3-.1-.4-.2h-.1-.1c-.1 0-.3-.1-.4-.1-.1 0-.1 0-.2-.1h-.1l-.2-.4c-.2-.1-.4-.2-.5-.2h-.6c-.3 0-.5.1-.7.1-.7.1-1.2.3-1.7.4-.2 0-.3.1-.5.1-.5.1-1 .2-1.6.2-.4 0-.9-.1-1.5-.2-.4-.1-.8-.2-1.1-.3-.2-.1-.4-.1-.6-.2-.6-.2-1.1-.3-1.7-.4h-.2-1.8c-.3 0-.6.1-1 .1H57.9c-.8 0-1.5 0-2.3-.1-.2 0-.5-.1-.7-.1-.5-.1-.9-.2-1.3-.4-.2-.1-.3-.1-.4-.2-.1 0-.2 0-.2-.1-.3-.1-.6-.1-.9-.1H51h-.1c-.4 0-.9.1-1.4.2-1.1.2-2.1.6-3 1.3-.3.2-.6.5-.8.8-.1.1-.2.2-.2.3-.4.6-.8 1.2-.9 2 0 .2-.1.4-.1.6 0 .2 1.7.7 2.3 2.8-.8-1.2-2.3-2.5-4.1-1.4-1.5 1-1.1 3.1-2.4 5.4-.3.5-.6.9-1 1.4-.8 1-.7 2.1.2 4.4 1.4 3.4 7.6 5.3 11.5 8.3l.4.4zm8.7-36.3c0 .6.1 1 .2 1.6v.1c0 .3.1.6.1.9.1 1.2.4 2 1 2.9 0 .1.1.1.1.2.3.2.5.3.8.4 1.1.2 3.1.3 4.2 0 .2-.1.5-.3.7-.5.4-.4.7-1.1.9-1.7.1-.7.3-1.3.4-1.8 0-.2.1-.4.1-.5v-.1c0-.2 0-.3.1-.5.2-.7.2-2.4.3-2.8.1-.7 0-1.8-.1-2.5 0-.2-.1-.4-.1-.5v-.1c-.2-.5-1.4-1.4-4.3-1.4-3.1 0-4 1-4.1 1.5v.1c0 .1 0 .3-.1.5-.1.4-.2 1.4-.2 1.9v2.3zm-6 88.6c0-.1-.1-.2-.1-.3-.7-1.5-1.1-3.5-1.3-4.6.4.1.7.6.8.3.2-.5-.4-1.5-.5-2.2v-.1c-.5-.5-4-.5-3.7-.3-.4.8-1 .6-1.3 2.1-.1.7.8.1 1.7.1-1.4.9-3 2.1-3.4 3.2-.1.1-.1.2-.1.3 0 .2-.1.4-.1.5-.1 1.2.5 1.6 2 2.4H48.4c1.4.3 3 .3 4.3.3 1.2-.2 1.6-.7 1.6-1.4-.2-.1-.2-.2-.2-.3z"style=fill:#efefef /><path d="M56.1 36.5c.3 1.4.5 2.4.8 4.2h-.2c-.1.5-.1.9-.1 1.3-1-.4-2.2-.5-2.6-.5-3.7-4.4-2.9-6.1-4.4-8.3.4-.2 1-.4 1.5-.8 1.6 1.9 3.3 3 5 4.1zm-1.7 13.2s-1.4 0-2.3-1c0 0-.1-.5.1-.7 0 0-1.2-1-1.5-1.7-.2-.5-.3-1.1-.2-1.6-4.4-3.7-10.9-4.2-12.9-9.1-.5-1.2-1.3-2.9-.9-3.9-.3.1-.5.2-.8.3-2.9.9-11.7 5.3-17.9 8.8 1.6 1.7 2.6 4.3 3.2 7.2l.3 1.5c.1.5.1 1 .2 1.5.1 1.4.4 2.7.8 3.9.2.8.6 1.5.9 2.2.6 1 1.2 1.9 2.1 2.6.6.5 1.2.9 1.9 1.3 2.1 1.1 5 1.6 8.6 1.5H37.9c.5 0 1 .1 1.5.1h.1c.4.1.9.1 1.3.2h.2c.4.1.9.2 1.3.4h.1c.4.1.8.3 1.1.5h.1c.4.2.7.4 1.1.6h.1c.7.4 1.3.9 1.9 1.5l.1.1c.6.5 1.1 1.1 1.5 1.8 0 .1.1.1.1.2s.1.1.1.2c.4.6 1.2 1.1 1.9 1.3.7-.9 1.5-1.8 2.2-2.8-1.6-6 0-11.7 1.8-16.9zm-26-15.9c5-2.4 9-4.1 9.9-4.5.3-.6.6-1.4.9-2.6.1-.3.2-.5.3-.8 1-2.7 2.7-2.8 3.5-3v-.2c.1-1.1.5-2 1-2.8-8.8 2.5-18 5.5-28 11.7-.1.1-.2.2-.4.2C11.3 34.5 3 40.3 1.3 51c2.4-2.7 6-5.6 10.5-8.5.1-.1.3-.2.5-.3.2-.1.5-.3.7-.4 1.2-.7 2.4-1.4 3.6-2.2 2.2-1.2 4.5-2.4 6.7-3.5 1.8-.8 3.5-1.6 5.1-2.3zm54.9 61.3l-.3-.3c-.8-.6-4.1-1.2-5.5-2.3-.4-.3-1.1-.7-1.7-1.1-1.6-.9-3.5-1.8-3.5-2.1v-.1c-.2-1.7-.2-7 .1-8.8.3-1.8.7-4.4.8-5.1.1-.6.5-1.2.1-1.2h-.4c-.2 0-.4.1-.8.1-1.5.3-4.3.6-6.6.4-.9-.1-1.6-.2-2-.3-.5-.1-.7-.2-.9-.3H62.3c-.4.5 0 2.7.6 4.8.3 1.1.8 2 1.2 3 .3.8.6 1.8.8 3.1 0 .2.1.4.1.7.2 2.8.3 3.6-.2 4.9-.1.3-.3.6-.4 1-.4.9-.7 1.7-.6 2.3 0 .2.1.4.1.5.2.4.6.7 1.2.8.2 0 .3.1.5.1.3 0 .6.1.9.1 3.4 0 5.2 0 8.6.4 2.5.4 3.9.6 5.1.5.4 0 .9-.1 1.4-.1 1.2-.2 1.8-.5 1.9-.9-.1.2-.1.1-.2-.1zM60.2 16.4zm-.5 1.7zm3.8.5c.1 0 .3.1.5.1.4.1.7.2 1.2.3.3.1.6.1.9.1h1.3c.3-.1.7-.1 1-.2.7-.2 1.5-.4 2.7-.6h.3c.3 0 .6.1.9.3.1.1.2.1.4.2.3.2.8.2 1.2.4h.1c.1 0 .1.1.2.1.6.3 1.3.7 1.9 1.1l.3.3c.9-.1 1.6-.2 2.1-.2h.1c-.2-.4-.3-1.3-1.8-.6-.6-.7-.8-1.3-2.1-.9-.1-.2-.2-.3-.3-.4l-.1-.1c-.1-.1-.2-.3-.3-.4 0-.1-.1-.1-.1-.2-.2-.3-.5-.5-.9-.7-.7-.4-1.5-.6-2.3-.5-.2 0-.4.1-.6.2-.1 0-.2.1-.2.1-.1 0-.2.1-.3.2-.5.3-1.3.8-2.1 1-.1 0-.1 0-.2.1-.2 0-.4.1-.5.1H66.5h-.1c-.4-.1-1.1-.2-2-.5-.1 0-.2-.1-.3-.1-.9-.2-1.8-.5-2.7-.8-.3-.1-.7-.2-1-.3-.1 0-.1 0-.2-.1h-.1s-.1 0-.1-.1c-.3-.3-.7-.6-1.3-.8-.5-.2-1.2-.4-2.1-.5-.2 0-.5 0-.7.1-.4.2-.8.6-1.2.9.1.1.3.3.4.5.1.2.2.4.3.7l-.6-.6c-.5-.4-1.1-.8-1.7-.9-.8-.2-1.4.4-2.3.9 1 0 1.8.1 2.5.4.1 0 .1 0 .2.1h.1c.1 0 .2.1.3.1.9.4 1.8.6 2.7.6h1.3c.5 0 .8-.1 1.1-.1.1 0 .4 0 .7-.1h2.2c.4.4.9.6 1.6.8z"style=fill:#88ce02 /><path d="M100 51.8c0-19.5-12.5-36.1-30-42.1.1-1.2.2-2.4.3-3.1.1-1.5.2-3.9-.5-4.9-1.6-2.3-9.1-2.1-10.5-.1-.4.6-.7 3.6-.6 5.9-1.1-.1-2.2-.1-3.3-.1-16.5 0-30.9 9-38.6 22.3-2.4 1.4-4.7 2.8-6.1 4C5.4 38 2.2 43.2 1 47c-1.6 4.7-1.1 7.6.4 5.8 1.2-1.5 6.6-5.9 10.1-8.2-.4 2.3-.6 4.8-.6 7.2 0 21 14.5 38.5 34 43.3-.1 1.1.1 2 .7 2.6.9.8 3.2 2 6.4 1.6 2.9-.3 3.5-.5 3.2-2.9h.2c2.7 0 5.3-.2 7.8-.7.1.1.2.2.4.3 1.5 1 7.1.8 9.6.7s6.2.9 8.6.5c2.9-.5 3.4-2.3 1.6-3.2-1.5-.8-3.8-1.3-6.7-3.1C90.6 83.4 100 68.7 100 51.8zM60.1 5.5c0-.5.1-1.5.2-2.1 0-.2 0-.4.1-.5v-.1c.1-.5 1-1.5 4.1-1.5 2.9 0 4.2.9 4.3 1.4v.1c0 .1 0 .3.1.5.1.8.2 1.9.1 2.7 0 .5-.1 2.1-.2 2.9 0 .1 0 .3-.1.5v.1c0 .2-.1.3-.1.5-.1.5-.2 1.1-.4 1.8-.1.6-.5 1.2-.9 1.7-.2.3-.5.5-.7.5-1.1.3-3.1.3-4.2 0-.3-.1-.5-.2-.8-.4 0-.1-.1-.1-.1-.2-.6-.9-.9-1.7-1-2.9 0-.4-.1-.6-.1-.9v-.1c-.1-.6-.2-1-.2-1.6v-.3c-.1-1.3-.1-2.1-.1-2.1zm-.4 7.5v-.4c.6.6 1.3 1.3 1.8 1.7.2.2.5.3.8.3.2 0 .3 0 .5.1h1.6c.8 0 1.6.1 2 0 .1 0 .2 0 .3-.1.6-.3 1.4-1 2.1-1.6 0 .6.1 1.2.1 1.7v1.5c0 .3 0 .5.1.7-.1.1-.2.1-.4.2-.7.4-1.7 1-2.3.9-.5-.1-1.5-.3-2.6-.7-1.2-.3-2.4-.8-3.2-1.2 0 0-.1 0-.1-.1-.2-.3-.4-.5-.6-.7-.3-.4-.5-.6-.5-.7.3-.4.4-.9.4-1.6zm.5 3.4zm-7.3-.3c.6.1 1.2.5 1.7.9.2.2.5.4.6.6-.1-.2-.2-.5-.3-.7-.1-.2-.3-.4-.4-.5.4-.3.8-.7 1.2-.9.2-.1.4-.1.7-.1.9.1 1.6.2 2.1.5.6.2 1 .5 1.3.8 0 0 .1 0 .1.1h.1c.1 0 .1 0 .2.1.3.1.6.2 1 .3.9.3 1.9.6 2.7.8.1 0 .2.1.3.1.9.2 1.6.4 2 .5h.4c.2 0 .4 0 .5-.1.1 0 .1 0 .2-.1.7-.2 1.5-.7 2.1-1 .1-.1.2-.1.3-.2.1 0 .2-.1.2-.1.2-.1.4-.2.6-.2.8-.2 1.7.1 2.3.5.3.2.6.4.9.7 0 .1.1.1.1.2.1.2.2.3.3.4l.1.1c.1.1.2.2.3.4 1.3-.4 1.5.2 2.1.9 1.6-.7 1.7.2 1.8.6h-.1c-.5 0-1.2 0-2.1.2l-.3-.3c-.5-.4-1.2-.8-1.9-1.1-.1 0-.1-.1-.2-.1h-.1c-.4-.2-.8-.2-1.2-.4-.1-.1-.2-.1-.4-.2-.3-.1-.6-.3-.9-.3h-.3c-1.2.1-2 .4-2.7.6-.3.1-.7.2-1 .2-.4.1-.8.1-1.3 0-.3 0-.6-.1-.9-.1-.5-.1-.8-.2-1.2-.3-.2 0-.3-.1-.5-.1h-.1c-.6-.2-1.2-.3-1.8-.4h-.1-2.1c-.4.1-.6.1-.7.1-.3 0-.7.1-1.1.1h-1.3c-.9 0-1.9-.2-2.7-.6-.1 0-.2-.1-.3-.1H53c-.1 0-.1 0-.2-.1-.7-.3-1.6-.4-2.5-.4 1.2-.8 1.8-1.4 2.6-1.3zm6.8 2zm-15.2 4.1c.1-.7.4-1.4.9-2 .1-.1.2-.2.2-.3l.8-.8c.9-.6 1.9-1.1 3-1.3.5-.1 1-.2 1.4-.2H52c.3 0 .6.1.9.1.1 0 .2 0 .2.1.1.1.2.1.4.2.4.2.8.3 1.3.4.2 0 .5.1.7.1.7.1 1.5.1 2.3.1H58.7c.4 0 .7-.1 1-.1H61.7c.6.1 1.1.2 1.7.4.2 0 .4.1.6.2.3.1.7.2 1.1.3.6.1 1.1.2 1.5.2.6 0 1.1-.1 1.6-.2.2 0 .3-.1.5-.1.5-.1 1-.3 1.7-.4.2 0 .5-.1.7-.1h.6c.2 0 .4.1.5.2l.1.1h.1c.1 0 .1 0 .2.1.2.1.3.1.4.1h.2c.1.1.3.1.4.2.4.2 1 .6 1.6.9.1.1.2.2.4.2.1.1.2.1.3.2.2.1.3.3.4.3l.1.1c1.1 1.4 1.8 3.3 1.6 5.3-.3 1.5-1.6.7-2.5.3 0 0-.1 0-.1-.1-.3-.1-.6-.2-.9-.4-.2-.1-.4-.1-.5-.2-1.2-.4-2.5-.7-4-.7-2 0-4.6.1-6.8.8-3 .8-4 .8-5.3.1-.8-.4-1.8-1.1-2.6-1.7-.1-.1-.2-.1-.2-.2-.1-.1-.1-.1-.2-.1-.3-.2-.6-.4-.6-.5l-.1-.1c-.2.3-.6 1-.8 1.4v.1c-.1 1.7-1 4.2-2.6 5.6-.2.1-.4.3-.6.4-.2.1-.5.2-.7.3-.7.2-1.4.2-1.9-.2-.5-.3-1.3-.7-2.3-1.1 2 1.6 3 2.8 3.9 4.9.7 1.7 1.7 4 3.9 6.5l.3.3c1.1 0 2.1.2 3 .7.4.2.7.4 1 .7.2.2.4.3.5.5.5.4.9.8.8 1.3v.1s0 .1-.1.1c-.1.2-.3.5-.5.7-.1.1-.4.4-.6.7-.1.1-.2.2-.3.2-.1.1-.4.3-.7.6-.2.2-.4.3-.5.4-.2.1-.4.4-.6.5-.3.1-.5.2-.8.1-.3 0-.7-.2-1-.3-.5-.3.1-.3-.5-.8-1.4-1-2.2-1.7-1.9-3.4v-.2c-.2-.1-.3-.3-.5-.4-3.9-3-10.1-4.9-11.5-8.3-.9-2.3-1-3.4-.2-4.4.4-.5.8-1 1-1.4 1.3-2.3.9-4.4 2.4-5.4 1.8-1.2 3.3.2 4.1 1.4-.5-2.1-2.3-2.6-2.3-2.8.3.1.3-.1.3-.3zm29 20s-.1 0 0 0c-.1 0-.1 0 0 0-.9.1-3.3.3-5.4.3h-.9c-.7 0-1.3-.1-1.8-.2-.1 0-.2 0-.3-.1-.7-.2-1.6-.5-2.4-.8-.6-.2-1.2-.5-1.7-.7-1.1-.5-2.1-1.1-2.3-1.3-.5-1.4-.7-2.7-.7-3.4.8-.4 1.3-.7 1.9-1.4-1.7.3-2.4.2-3.4-.4-1-.5-2.6-2.2-3.6-3.4 1-1.2 1.7-2.9 1.4-5.1.1-.2.3-.4.4-.6 0 .1.1.1.2.2.7.9 2.4 2 4.6 2.8 1.1.4 2 .1 2.9-.1 4-1 8.1-1.3 11.9-.1.1 0 .2.1.3.1.5.2.9.4 1.4.6.1 0 .1.1.2.1.1.7.2 2-.3 3.5-.1.3-.2.6-.4.9-.2.3-.3.6-.5 1-.1.3-.2.5-.4.8-.2.4-.3.8-.5 1.3-.4 1.4-.7 3.4-.6 6zm-23.9-9c.4-.2 1-.4 1.5-.8 1.6 1.8 3.3 3 5 4.1.3 1.4.5 2.4.8 4.2h-.2c-.1.5-.1.9-.1 1.3-1-.4-2.2-.5-2.6-.5-3.7-4.3-3-6-4.4-8.3zm-32.9 6.5c-1.3.7-2.5 1.4-3.6 2.2-.2.1-.5.3-.7.4-.1.1-.3.2-.5.3-4.5 2.9-8.1 5.8-10.5 8.5 1.7-10.8 10-16.5 14.3-19.2.1-.1.2-.2.4-.2 10-6.2 19.2-9.2 28-11.7-.5.8-.9 1.7-1 2.8v.2c-.8.1-2.5.2-3.5 3-.1.2-.2.5-.3.8-.3 1.2-.6 2-.9 2.6-.9.4-5 2.2-9.9 4.5-1.6.8-3.3 1.6-5 2.4-2.3 1-4.6 2.2-6.8 3.4zm28 24.8s0-.1 0 0c-.4-.3-.8-.5-1.2-.7h-.1c-.4-.2-.7-.3-1.1-.5h-.1c-.4-.1-.8-.3-1.3-.4h-.2c-.4-.1-.8-.2-1.3-.2h-.1c-.5-.1-1-.1-1.5-.1H35.9c-3.7.1-6.5-.4-8.6-1.5-.7-.4-1.4-.8-1.9-1.3-.9-.7-1.5-1.6-2.1-2.6-.4-.7-.7-1.4-.9-2.2-.4-1.2-.6-2.5-.8-3.9 0-.5-.1-1-.2-1.5l-.3-1.5c-.6-2.9-1.6-5.5-3.2-7.2 6.3-3.5 15-7.9 17.8-8.8.3-.1.6-.2.8-.3-.3 1.1.4 2.7.9 3.9 2.1 4.9 8.6 5.4 12.9 9.1 0 .5 0 1.1.2 1.6.5.6 1.7 1.6 1.7 1.6-.2.2-.1.7-.1.7.9 1 2.3 1 2.3 1-1.8 5.2-3.4 10.9-1.9 16.9-.7 1-1.5 1.8-2.2 2.8-.7-.2-1.4-.6-1.9-1.3 0-.1-.1-.1-.1-.2s-.1-.1-.1-.2l-1.5-1.8-.1-.1c-.5-.4-1.2-.9-1.9-1.3zm7.9 33.6c-1.3.1-2.9 0-4.3-.3h-.2-.1c-1.5-.8-2.1-1.2-2-2.4 0-.2 0-.3.1-.5 0-.1.1-.2.1-.3.5-1.1 2.1-2.2 3.4-3.2-.8 0-1.8.7-1.7-.1.2-1.5.9-1.3 1.3-2.1-.2-.3 3.3-.2 3.8.3v.1c0 .7.7 1.7.5 2.2-.1.3-.4-.2-.8-.3.2 1.1.6 3.1 1.3 4.6.1.1.1.2.1.3 0 .1.1.2.1.3 0 .7-.4 1.2-1.6 1.4zM59 67.7c0 .9-.3 1.6-.6 2-.7 1.1-1.7 1.4-2 3.2.4-.6 1.1-1.1 1.1-.9 0 .8-.1 1.4-.1 2v.2c-.1.6-.2 1.1-.3 1.6-.2.9-.5 1.7-.7 2.4-.4 1.2-.9 2.1-1.3 3.1l-.6 1.5c-.6 1.7-.4 5.6-.6 5.7-1.6.3-4.1-.3-4.7-.6.3-2.2.4-4.5.5-6.9.1-2.1.3-4.3.9-6.6.1-.5.3-1 .4-1.5 0-.1 0-.2.1-.2 0-.1 0-.1.1-.2.5-1.6 1.4-2.7 2.6-4.2.4-.4.7-.9 1.2-1.4-.1-.4-.2-.8-.4-1.3-.7-2.6-1.3-7.3 1.5-16.1.1 0 .2-.1.3-.1.2-.1.7-.5.8-.6.1-.1.3-.2.4-.3.1 0 .1-.1.2-.1l.6-.6 1.1-1.1c.4-.4.7-.5.8-.9v-.2c0-.8-1.1-1.5-1.5-2.1l-.1-.1c.1-.2.1-.4.2-.6 0-.2.1-.5.1-.8 0-.2.1-.5.1-.7.1.1.6.4 1.8.7.6.2 1.3.4 2.3.7 1.1.3 2.4.5 3.6.6 2.9.2 5.6 0 6.7-.2h.3v.1c0 .1 0 .2-.1.3v.9c0 .2 0 .3.1.5v.1c0 .4.1.7.2 1.1 0 .3.1.5.1.7v.1c0 .3.1.5.1.7 0 .2.1.3.1.5.1.2.1.4.2.6v.2c.1.4.2.8.2 1.1 1 5.7 2.3 12.9-1.1 16.7.2.8.3 1.9 0 3.2-.1.7-.3 1.4-.6 2.2-.2.5-.3 1-.5 1.5h-.3c-4.5.6-7.1.2-8.3-.1.5-1.6 1.7-3.3 3.7-3.2-.1-3.7-1.1-5-2-7.6 1.3-3 1.3-5.7 2-9.2v-.2c.2-.8.3-1.6.6-2.5-.4.5-.8 1.5-1.2 2.6v.1c-.5 1.5-.9 3.4-1.2 4.8-.2.8-.4 1.6-.7 2.4-.2.5-.4.9-.6 1.4-1.5 1.9-3 3.9-5.5 5.6zm18.5 24.9c1.5 1.1 4.7 1.8 5.5 2.3l.3.3c.1.1.1.2.1.3-.1.4-.7.7-1.9.9-.5.1-.9.1-1.4.1-1.3 0-2.6-.2-5.1-.5-3.4-.5-5.2-.4-8.6-.4-.3 0-.6 0-.9-.1-.2 0-.4-.1-.5-.1-.6-.2-1-.5-1.2-.8-.1-.2-.1-.3-.1-.5-.1-.7.2-1.5.6-2.3.2-.4.3-.7.4-1 .5-1.3.4-2.1.2-4.9 0-.2-.1-.4-.1-.7-.2-1.3-.5-2.3-.8-3.1-.4-1.1-.9-1.9-1.2-3-.6-2.1-1-4.3-.6-4.8H62.5c.2.1.5.2.9.3.5.1 1.1.2 2 .3 2.2.2 5.1-.2 6.6-.4.3-.1.6-.1.8-.1h.4c.4 0 .1.6-.1 1.2-.1.7-.5 3.3-.8 5.1-.3 1.8-.2 7.1-.1 8.8v.1c0 .3 1.9 1.2 3.5 2.1.7.2 1.4.5 1.8.9zm4.8-48.2c0 .1 0 .1 0 0-.1.1-.2.2-.2.3 0-.1-.1-.1-.1-.2 0 .1 0 .2-.1.2-.2.9-.6 2.4-2.2 4.1-.4.4-.7.6-1 .7-.5.1-.9 0-1.5-.1-.9-.2-1.3-.6-2.2-1.1-.1-.6-.3-1.3-.4-1.8 0-.3-.1-.5-.1-.6v-1l.4-.4s.7-1 1.8-1 2.2-.2 2.5-.5v-.1-.3c0-.1 0-.2-.1-.3-.4-1.4-2.1-1.8-1.4-4.8 0-.2.3-.5 1.2-.5-1.4-.3-2-.4-3-1.3.5-1.1 1-1.9 1.3-2.6.8-1.5.3-3.5.3-3.5.8-.3 1.6-.7 1.7-1 .9-2.8-.7-5.5-2.5-7.2 1.2-.1 2.6.1 3.6 1.1 2.4 2.4 1.8 5 1 6.8.6.7 2.1 2.9 1.2 5.3-.2.6-1.4.6-2.3 2.1 2.3-1.3 3.7-1 4.2.7 1 2.4-.6 5.3-2.1 7z"/><path d="M22 53.4v-.2c0-.2-.1-.5-.2-.9s-.1-.8-.2-1.3c-.5-4.7-1.9-9.4-4.9-11.3 3.7-2 16.8-8.5 21.9-10.5 2.9-1.2.8-.4-.2 1.4-.8 1.4-.3 2.9-.5 3.2-.6.8-12.6 10.5-15.9 19.6zm32.2-2.3c-3.4 3.8-12 11-18.2 11.4 8.7-.2 12.2 4.1 14.7 9.7 2.6-5.2 2.7-10.3 2.6-16.1 0-2.6 1.8-6 .9-5zm5.3-23L54.3 24s-1.1 3.1-1 4.6c.1 1.6-1.8 2.7-.9 3.6.9.9 3.2 2.5 4 3.4.7.9 1.1 7.1 1.1 7.1l2.2 2.7s1-1.8 1.1-6.3c.2-5.4-2.9-7.1-3.3-8.6-.4-1.4.6-2.9 2-2.4zm3.1 45.6l3.9.3s1.2-2.2 2.1-3.5c.9-1.4.4-1.6 0-4.6-.4-3-1.4-9.3-1.2-13.6l-3.1 10.2s1.8 5.6 1.6 6.4c-.1.8-3.3 4.8-3.3 4.8zm5 18.8c-1.1 0-2.5-.4-3.5-.8l-1 .3.2 4s5.2.7 4.6-.4c-.6-1.2-.3-3.1-.3-3.1zm12 .6c-1 0-.3.2.4 1.2.8 1 .1 2-.8 2.3l3.2.5 1.9-1.7c.1 0-3.7-2.3-4.7-2.3zM73 76c-1.6.5-4.2.8-5.9.8-1.7.1-3.7-.1-5-.5v1.4s1.2.5 5.4.5c3.5.1 5.7-.8 5.7-.8l.9-.8c-.1.1.5-1.1-1.1-.6zm-.2 3.1c-1.6.6-3.9.6-5.6.7-1.7.1-3.7-.1-5-.5l.1 1.4s.7.3 4.9.4c3.5.1 5.7-.7 5.7-.7l.3-.5c-.1-.1.3-1-.4-.8zm5.9-42.7c-.9-.8-1.4-2.4-1.5-3.3l-1.9 2.5.7 1.2s2.5.1 2.8.1c.4 0 .3-.1-.1-.5zM69 14.7c.6-.7.2-2.7.2-2.7L66 14.6l-4.4-.8-.5-1.3-1.3-.1c.8 1.8 1.8 2.5 3.3 3.1.9.4 4.5.9 5.9-.8z"style=opacity:.4;fill-rule:evenodd;clip-rule:evenodd /></svg></a></div></div>';
if (element) {
root.style.position = "absolute";
root.style.top = minimal ? "calc(100% - 42px)" : "calc(100% - 51px)";
}
if (css) {
if (typeof(css) === "string") {
root.style.cssText = css;
} else if (typeof(css) === "object") {
css.data = "root";
TweenLite.set(root, css).kill();
}
if (root.style.top) {
root.style.bottom = "auto";
}
if (root.style.width) {
TweenLite.set(root, {xPercent: -50, left: "50%", right: "auto", data:"root"}).kill();
}
}
if (!minimal && root.offsetWidth < 600) {
root.setAttribute("class", "gs-dev-tools minimal");
if (element) {
root.style.top = "calc(100% - 42px)";
}
}
return root;
},
_clickedOnce, //we don't preventDefault() on the first mousedown/touchstart/pointerdown so that iframes get focus properly.
_addListener = function(e, type, callback, capture) {
var handler, 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.
altType = e.onpointerdown !== undefined ? "pointerdown" : e.ontouchstart !== undefined ? "touchstart" : null;
if (altType) {
handler = function(event) {
if (event.target.nodeName.toLowerCase() !== "select" && event.type === altType) { //don't preventDefault() on a <select> or else it won't open!
event.stopPropagation();
if (_clickedOnce) { //otherwise, both touchstart and mousedown will get called.
event.preventDefault();
callback.call(e, event);
}
} else if (event.type !== altType) {
callback.call(e, event);
}
_clickedOnce = true;
};
e.addEventListener(altType, handler, capture);
e.addEventListener(type, handler, capture);
return;
}
}
e.addEventListener(type, callback, capture);
},
_removeListener = function(e, type, callback) {
e.removeEventListener(type, callback);
type = type !== "mousedown" ? null : e.onpointerdown !== undefined ? "pointerdown" : e.ontouchstart !== undefined ? "touchstart" : null;
if (type) {
e.removeEventListener(type, callback);
}
},
_selectValue = function(element, value, label, insertIfAbsent) {
var options = element.options,
i = options.length,
option;
value += "";
while (--i > -1) {
if (options[i].innerHTML === value || options[i].value === value) {
element.selectedIndex = i;
label.innerHTML = options[i].innerHTML;
return options[i];
}
}
if (insertIfAbsent) {
option = _createElement("option", element);
option.setAttribute("value", value);
option.innerHTML = label.innerHTML = typeof(insertIfAbsent) === "string" ? insertIfAbsent : value;
element.selectedIndex = options.length - 1;
}
},
//increments the selected value of a <select> up or down by a certain amount.
_shiftSelectedValue = function(element, amount, label) {
var options = element.options,
i = Math.min(options.length - 1, Math.max(0, element.selectedIndex + amount));
element.selectedIndex = i;
if (label) {
label.innerHTML = options[i].innerHTML;
}
return options[i].value;
},
_recordedRoot = new TimelineLite({data:"root", id:"Global Timeline", autoRemoveChildren:false, smoothChildTiming:true}),
_recordedTemp = new TimelineLite({data:"root", id:"Global Temp", autoRemoveChildren:false, smoothChildTiming:true}),
_rootTween = TweenLite.to(_recordedRoot, 1, {time:1, ease:Linear.easeNone, data:"root", id:"_rootTween", paused:true, immediateRender:false}),
_rootInstance, _rootIsDirty, _keyboardInstance,
//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.
_merge = function() {
var t = _recordedTemp._first,
duration, next;
if (t) {
if (_rootInstance && _rootInstance.animation() === _recordedRoot) {
duration = _recordedRoot._duration;
while (t) {
next = t._next;
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.
_recordedRoot.add(t, t._startTime - t._delay);
} else {
SimpleTimeline.prototype.add.call(_globalTimeline, t, t._startTime - t._delay);
}
t = next;
}
return (duration !== _recordedRoot.duration());
} else {
//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.
while (t) {
next = t._next;
if (t._gc || t._totalTime === t._totalDuration) {
t.kill();
} else {
//SimpleTimeline.prototype.add.call(_globalTimeline, t, t._recordedTime);
//_globalTimeline.add(t, t._startTime - t._delay - _recordedTemp._startTime);
SimpleTimeline.prototype.add.call(_globalTimeline, t, t._startTime - t._delay);
}
t = next;
}
}
}
},
_updateRootDuration = function() {
if (_rootInstance) {
_rootInstance.update();
_rootIsDirty = false;
}
TweenLite.ticker.removeEventListener("tick", _updateRootDuration);
},
_buildPlayPauseMorph = function(svg) {
var tl = new TimelineLite({data:"root", onComplete:function() { tl.kill(); }});
tl.to(svg.querySelector(".play-1"), 0.5, {attr:{d:"M5.75,3.13 C5.75,9.79 5.75,16.46 5.75,23.13 4.08,23.13 2.41,23.13 0.75,23.13 0.75,16.46 0.75,9.79 0.75,3.12 2.41,3.12 4.08,3.12 5.75,3.12"}, ease:Power3.easeInOut, rotation:360, transformOrigin:"50% 50%"})
.to(svg.querySelector(".play-2"), 0.5, {attr:{d:"M16.38,3.13 C16.38,9.79 16.38,16.46 16.38,23.13 14.71,23.13 13.04,23.13 11.38,23.13 11.38,16.46 11.38,9.79 11.38,3.12 13.04,3.12 14.71,3.12 16.38,3.12"}, ease:Power3.easeInOut, rotation:360, transformOrigin:"50% 50%"}, 0.05);
return tl;
},
_buildLoopAnimation = function(svg) {
var tl = new TimelineLite({data:"root", paused:true, onComplete:function() { tl.kill(); }});
tl.to(svg, 0.5, {rotation:360, ease:Power3.easeInOut, transformOrigin:"50% 50%"})
.to(svg.querySelectorAll(".loop-path"), 0.5, {fill:"#91e600", ease:Linear.easeNone}, 0);
return tl;
},
GSDevTools = function(vars) {
this.vars = vars = vars || {};
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.
vars.globalSync = false;
}
//GENERAL/UTILITY
var _self = this,
root = _createRootElement(vars.container, vars.minimal, vars.css),
find = function(s) {
return root.querySelector(s);
},
record = function(key, value) {
if (vars.persist !== false && typeof(sessionStorage) !== "undefined") {
sessionStorage.setItem("gs-dev-" + key + vars.id, value);
}
return value;
},
recall = function(key) {
var value;
if (vars.persist !== false && typeof(sessionStorage) !== "undefined") {
value = sessionStorage.getItem("gs-dev-" + key + vars.id);
return (key === "animation") ? value : (key === "loop") ? (value === "true") : parseFloat(value); // handle data typing too.
}
},
//SCRUBBER/PROGRESS
playhead = find(".playhead"),
timelineTrack = find(".timeline-track"),
progressBar = find(".progress-bar"),
timeLabel = find(".time"),
durationLabel = find(".duration"),
pixelToTimeRatio, timeAtDragStart, dragged, skipDragUpdates,
progress = 0,
//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.
onPressTimeline = function(element, originRatio, limitToInOut) {
return function(e) {
var trackBounds = timelineTrack.getBoundingClientRect(),
elementBounds = element.getBoundingClientRect(),
left = elementBounds.width * originRatio,
x = element._gsTransform.x,
minX = trackBounds.left - elementBounds.left - left + x,
maxX = trackBounds.right - elementBounds.right + (elementBounds.width - left) + x,
unlimitedMinX = minX,
limitBounds;
if (limitToInOut) {
if (element !== inPoint) {
limitBounds = inPoint.getBoundingClientRect();
if (limitBounds.left) { //if inPoint is hidden (like display:none), ignore.
minX += (limitBounds.left + limitBounds.width) - trackBounds.left;
}
}
if (element !== outPoint) {
limitBounds = outPoint.getBoundingClientRect();
if (limitBounds.left) { //if outPoint is hidden (like display:none), ignore.
maxX -= (trackBounds.left + trackBounds.width) - limitBounds.left;
}
}
}
pausedWhenDragStarted = paused;
this.applyBounds({minX:minX, maxX:maxX});
//_merge();
pixelToTimeRatio = linkedAnimation.duration() / trackBounds.width;
timeAtDragStart = -unlimitedMinX * pixelToTimeRatio;
if (!skipDragUpdates) {
linkedAnimation.pause(timeAtDragStart + pixelToTimeRatio * this.x);
} else {
linkedAnimation.pause();
}
if (this.target === playhead) {
if (this.activated) {
this.allowEventDefault = false;
}
this.activated = true;
}
dragged = true;
};
},
progressDrag = Draggable.create(playhead, {
type:"x",
cursor: "ew-resize",
allowNativeTouchScrolling: false,
allowEventDefault:true, //otherwise, when dragged outside an iframe, the mouseup doesn't bubble up so it could seem "stuck" to the mouse.
onPress: onPressTimeline(playhead, 0.5, true),
onDrag: function() {
var time = timeAtDragStart + pixelToTimeRatio * this.x;
if (time < 0) {
time = 0;
} else if (time > linkedAnimation._duration) {
time = linkedAnimation._duration;
}
if (!skipDragUpdates) {
linkedAnimation.time(time);
}
progressBar.style.width = Math.min(outProgress - inProgress, Math.max(0, (time / linkedAnimation._duration) * 100 - inProgress)) + "%";
timeLabel.innerHTML = time.toFixed(2);
},
onRelease: function(e) {
if (!paused) {
linkedAnimation.resume();
}
}
})[0],
inPoint = find(".in-point"),
outPoint = find(".out-point"),
resetInOut = function() {
inProgress = 0;
outProgress = 100;
inPoint.style.left = "0%";
outPoint.style.left = "100%";
record("in", inProgress);
record("out", outProgress);
updateProgress(true);
},
inProgress = 0,
outProgress = 100,
pausedWhenDragStarted,
inDrag = Draggable.create(inPoint, {
type:"x",
cursor:"ew-resize",
zIndexBoost:false,
allowNativeTouchScrolling: false,
allowEventDefault:true, //otherwise, when dragged outside an iframe, the mouseup doesn't bubble up so it could seem "stuck" to the mouse.
onPress:onPressTimeline(inPoint, 1, true),
onDoubleClick: resetInOut,
onDrag: function() {
inProgress = (timeAtDragStart + pixelToTimeRatio * this.x) / linkedAnimation.duration() * 100;
linkedAnimation.progress(inProgress / 100);
updateProgress(true);
},
onRelease: function() {
if (inProgress < 0) {
inProgress = 0;
}
_clearSelection();
//for responsiveness, convert the px-based transform into %-based left position.
inPoint.style.left = inProgress + "%";
record("in", inProgress);
TweenLite.set(inPoint, {x:0, data:"root", display:"block"}); //set display:block so that it remains visible even when the minimal skin is enabled.
if (!paused) {
linkedAnimation.resume();
}
}
})[0],
outDrag = Draggable.create(outPoint, {
type:"x",
cursor:"ew-resize",
allowNativeTouchScrolling: false,
allowEventDefault:true, //otherwise, when dragged outside an iframe, the mouseup doesn't bubble up so it could seem "stuck" to the mouse.
zIndexBoost:false,
onPress:onPressTimeline(outPoint, 0, true),
onDoubleClick: resetInOut,
onDrag: function() {
outProgress = (timeAtDragStart + pixelToTimeRatio * this.x) / linkedAnimation.duration() * 100;
linkedAnimation.progress(outProgress / 100);
updateProgress(true);
},
onRelease: function() {
if (outProgress > 100) {
outProgress = 100;
}
_clearSelection();
//for responsiveness, convert the px-based transform into %-based left position.
outPoint.style.left = outProgress + "%";
record("out", outProgress);
TweenLite.set(outPoint, {x:0, data:"root", display:"block"}); //set display:block so that it remains visible even when the minimal skin is enabled.
if (!pausedWhenDragStarted) {
play();
linkedAnimation.resume();
}
}
})[0],
updateProgress = function(force) {
if (progressDrag.isPressed && !force) {
return;
}
var p = (!loopEnabled && selectedAnimation._repeat === -1) ? selectedAnimation.totalTime() / selectedAnimation.duration() * 100 : (linkedAnimation.progress() * 100) || 0,
repeatDelayPhase = (selectedAnimation._repeat && selectedAnimation._repeatDelay && selectedAnimation.totalTime() % (selectedAnimation.duration() + selectedAnimation._repeatDelay) > selectedAnimation.duration());
if (p > 100) {
p = 100;
}
if (p >= outProgress) {
if (loopEnabled && !linkedAnimation.paused() && !progressDrag.isDragging) {
if (!repeatDelayPhase) {
p = inProgress;
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.target.seek(startTime + ((endTime - startTime) * inProgress / 100));
}
if (selectedAnimation._repeat > 0 && !inProgress && outProgress === 100) {
if (selectedAnimation.totalProgress() === 1) {
linkedAnimation.totalProgress(0, true).resume();
}
} else {
linkedAnimation.progress(p / 100, true).resume();
}
}
} else {
if (p !== outProgress || selectedAnimation._repeat === -1) {
p = outProgress;
linkedAnimation.progress(p / 100);
}
if (!paused && (selectedAnimation.totalProgress() === 1 || selectedAnimation._repeat === -1)) {
pause();
}
}
} else if (p < inProgress) {
p = inProgress;
linkedAnimation.progress(p / 100, true);
}
if (p !== progress || force) {
progressBar.style.left = inProgress + "%";
progressBar.style.width = Math.max(0, p - inProgress) + "%";
playhead.style.left = p + "%";
timeLabel.innerHTML = linkedAnimation._time.toFixed(2);
durationLabel.innerHTML = linkedAnimation._duration.toFixed(2);
if (dragged) {
playhead.style.transform = "translate(-50%,0)";
playhead._gsTransform.x = 0;
playhead._gsTransform.xPercent = -50;
dragged = false;
}
progress = p;
} else if (linkedAnimation._paused !== paused) { //like if the user has an addPause() in the middle of the animation.
togglePlayPause();
}
},
onPressSeekBar = function(e) {
if (progressDrag.isPressed) {
return;
}
var bounds = e.target.getBoundingClientRect(),
x = (e.changedTouches ? e.changedTouches[0] : e).clientX,
p = ((x - bounds.left) / bounds.width) * 100;
if (p < inProgress) {
inProgress = p = Math.max(0, p);
inPoint.style.left = inProgress + "%";
inDrag.startDrag(e);
return;
} else if (p > outProgress) {
outProgress = p = Math.min(100, p);
outPoint.style.left = outProgress + "%";
outDrag.startDrag(e);
return;
}
linkedAnimation.progress(p / 100).pause();
updateProgress(true);
progressDrag.startDrag(e);
},
//PLAY/PAUSE button
playPauseButton = find(".play-pause"),
playPauseMorph = _buildPlayPauseMorph(playPauseButton),
paused = false,
play = function() {
if (linkedAnimation.progress() >= outProgress / 100) {
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.target.seek(startTime + ((endTime - startTime) * inProgress / 100));
}
if (linkedAnimation._repeat && !inProgress) {
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.
linkedAnimation.target.seek(startTime + ((endTime - startTime) * inProgress / 100));
}
linkedAnimation.progress(inProgress / 100, true);
if (!paused) {
linkedAnimation.resume();
}
},
//LOOP button
loopButton = find(".loop"),
loopAnimation = _buildLoopAnimation(loopButton),
loopEnabled,
loop = function(value) {
loopEnabled = value;
record("loop", loopEnabled);
if (loopEnabled) {
loopAnimation.play();
if (linkedAnimation.progress() >= outProgress / 100) {
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.target.seek(startTime + ((endTime - startTime) * inProgress / 100));
}
if (selectedAnimation._repeat && !inProgress && outProgress === 100) {
linkedAnimation.totalProgress(0, true);
} else {
linkedAnimation.progress(inProgress / 100, true);
}
play();
}
} else {
loopAnimation.reverse();
}
},
toggleLoop = function() {
loop(!loopEnabled);
},
//ANIMATIONS list
list = find(".animation-list"),
animationLabel = find(".animation-label"),
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.
startTime, endTime,
updateList = function() {
var animations = _getChildrenOf((declaredAnimation && vars.globalSync === false) ? declaredAnimation : _recordedRoot, true),
options = list.children,
matches = 0,
option, i;
if (declaredAnimation && vars.globalSync === false) {
animations.unshift(declaredAnimation);
} else if (!vars.hideGlobalTimeline) {
animations.unshift(_recordedRoot);
}
for (i = 0; i < animations.length; i++) {
option = options[i] || _createElement("option", list);
option.animation = animations[i];
matches = (i && animations[i].vars.id === animations[i-1].vars.id) ? matches + 1 : 0;
option.setAttribute("value", (option.innerHTML = animations[i].vars.id + (matches ? " [" + matches + "]" : (animations[i+1] && animations[i+1].vars.id === animations[i].vars.id) ? " [0]" : "")));
}
for (; i < options.length; i++) {
list.removeChild(options[i]);
}
},
animation = function(anim) {
var ts = parseFloat(timeScale.options[timeScale.selectedIndex].value) || 1,
tl, maxDuration;
if (!arguments.length) {
return selectedAnimation;
}
if (typeof(anim) === "string") {
anim = _getAnimationById(anim);
}
//console.log("animation() ", anim.vars.id);
if (!(anim instanceof Animation)) {
console.log("GSDevTools error: invalid animation.");
}
if (anim === selectedAnimation) {
return;
}
if (selectedAnimation) {
selectedAnimation._inProgress = inProgress;
selectedAnimation._outProgress = outProgress;
}
selectedAnimation = anim;
if (linkedAnimation) {
ts = linkedAnimation.timeScale();
if (linkedAnimation.target === declaredAnimation) {
declaredAnimation.resume();
linkedAnimation.kill();
}
}
inProgress = selectedAnimation._inProgress || 0;
outProgress = selectedAnimation._outProgress || 100;
inPoint.style.left = inProgress + "%";
outPoint.style.left = outProgress + "%";
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.
record("animation", selectedAnimation.vars.id);
record("in", inProgress);
record("out", outProgress);
}
startTime = 0;
maxDuration = Math.min(1000, vars.maxDuration || 1000, _getClippedDuration(selectedAnimation));
if (selectedAnimation === _recordedRoot || vars.globalSync !== false) {
_merge();
linkedAnimation = _rootTween;
if (_rootInstance && _rootInstance !== _self) {
console.log("Error: GSDevTools can only have one instance that's globally synchronized.");
}
_rootInstance = _self;
//_recording = true;
if (selectedAnimation !== _recordedRoot) {
tl = selectedAnimation;
endTime = tl.totalDuration();
if (endTime > 99999999) { //in the case of an infinitely repeating animation, just use a single iteration's duration instead.
endTime = tl.duration();
}
while (tl.timeline.timeline) {
startTime = (startTime / tl._timeScale) + tl._startTime;
endTime = (endTime / tl._timeScale) + tl._startTime;
tl = tl.timeline;
}
} else {
endTime = _recordedRoot.duration();
}
if (endTime - startTime > maxDuration) { //cap end time at 1000 because it doesn't seem reasonable to accommodate super long stuff.
endTime = startTime + maxDuration;
}
_recordedRoot.pause(startTime);
_rootTween.vars.time = endTime;
_rootTween.invalidate();
_rootTween.duration(endTime - startTime).timeScale(ts);
//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.
_rootTween.progress(1).pause(0);
} else {
TweenLite.delayedCall(0.01, function() {
_rootTween.resume().progress(inProgress / 100);
if (paused) {
play();
}
});
}
} else {
if (_rootInstance === _self) {
_rootInstance = null;
}
if (selectedAnimation === declaredAnimation || !declaredAnimation) {
linkedAnimation = selectedAnimation;
if (!loopEnabled && linkedAnimation._repeat) {
loop(true);
}
} 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.
endTime = tl.duration();
}
while (tl.timeline.timeline && tl !== declaredAnimation) {
startTime = (startTime / tl._timeScale) + tl._startTime;
endTime = (endTime / tl._timeScale) + tl._startTime;
tl = tl.timeline;
}
if (endTime - startTime > maxDuration) { //cap end time at 1000 because it doesn't seem reasonable to accommodate super long stuff.
endTime = startTime + maxDuration;
}
declaredAnimation.pause(startTime);
linkedAnimation = TweenLite.to(declaredAnimation, endTime - startTime, {time:endTime, ease:Linear.easeNone, data:"root"});
}
linkedAnimation.timeScale(ts);
_rootTween.pause();
_recordedRoot.resume();
linkedAnimation.seek(0);
}
durationLabel.innerHTML = linkedAnimation.duration().toFixed(2);
_selectValue(list, selectedAnimation.vars.id, animationLabel);
},
updateRootDuration = function() {
var time, ratio, duration;
if (selectedAnimation === _recordedRoot) {
time = _recordedRoot._time;
_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.
time = (_rootTween._timeline._time - _rootTween._startTime) * _rootTween._timeScale;
duration = Math.min(1000, _recordedRoot.duration());
if (duration === 1000) {
duration = Math.min(1000, _getClippedDuration(_recordedRoot));
}
ratio = _rootTween.duration() / duration;
if (ratio !== 1 && duration) {
inProgress *= ratio;
if (outProgress < 100) {
outProgress *= ratio;
}
_rootTween.seek(0);
_rootTween.vars.time = duration;
_rootTween.invalidate();
_rootTween.duration(duration);
_rootTween.time(time);
durationLabel.innerHTML = duration.toFixed(2);
updateProgress(true);
}
}
},
onChangeAnimation = function(e) {
animation(list.options[list.selectedIndex].animation);
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>.
e.target.blur();
}
if (paused) {
play();
}
},
//TIMESCALE button
timeScale = find(".time-scale select"),
timeScaleLabel = find(".time-scale-label"),
onChangeTimeScale = function(e) {
var ts = parseFloat(timeScale.options[timeScale.selectedIndex].value) || 1;
linkedAnimation.timeScale(ts);
record("timeScale", ts);
if (!paused) {
if (linkedAnimation.progress() >= outProgress / 100) {
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.target.seek(startTime + ((endTime - startTime) * inProgress / 100));
}
linkedAnimation.progress(inProgress / 100, true).pause();
} else {
linkedAnimation.pause();
}
TweenLite.delayedCall(0.01, function() {
linkedAnimation.resume();
});
}
timeScaleLabel.innerHTML = ts + "x";
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>.
timeScale.blur();
}
},
//AUTOHIDE
autoHideTween = TweenLite.to([find(".gs-bottom"), find(".gs-top")], 0.3, {autoAlpha:0, y:50, ease:Power2.easeIn, data:"root", paused:true}),
hidden = false,
onMouseOut = function(e) {
if (!Draggable.hitTest(e, root) && !progressDrag.isDragging && !inDrag.isDragging && !outDrag.isDragging) {
autoHideDelayedCall.restart(true);
}
},
hide = function() {
if (!hidden) {
autoHideTween.play();
autoHideDelayedCall.pause();
hidden = true;
}
},
show = function() {
autoHideDelayedCall.pause();
if (hidden) {
autoHideTween.reverse();
hidden = false;
}
},
toggleHide = function() {
if (hidden) {
show();
} else {
hide();
}
},
autoHideDelayedCall = TweenLite.delayedCall(1.3, hide).pause(),
_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 (_startupPhase && !_globalStartTime) {
_globalStartTime = _recordedRoot._startTime;
}
_fullyInitialized = !preliminary;
declaredAnimation = _getAnimationById(vars.animation);
if (declaredAnimation && !declaredAnimation.vars.id) {
declaredAnimation.vars.id = "[no id]";
}
updateList();
var savedAnimation = _getAnimationById(recall("animation"));
if (savedAnimation) {
savedAnimation._inProgress = recall("in") || 0;
savedAnimation._outProgress = recall("out") || 100;
}
if (vars.paused) {
pause();
}
selectedAnimation = null;
animation(declaredAnimation || savedAnimation || _recordedRoot);
var ts = vars.timeScale || recall("timeScale"),
savedInOut = (savedAnimation === selectedAnimation);
if (ts) {
_selectValue(timeScale, ts, timeScaleLabel, ts + "x");
linkedAnimation.timeScale(ts);
}
inProgress = (("inTime" in vars) ? _timeToProgress(vars.inTime, selectedAnimation, 0, 0) : savedInOut ? savedAnimation._inProgress : 0) || 0;
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.
animation(_recordedRoot);
inProgress = _timeToProgress(vars.inTime, selectedAnimation, 0, 0) || 0;
}
if (inProgress) {
inPoint.style.left = inProgress + "%";
inPoint.style.display = outPoint.style.display = "block"; //set display:block so that it remains visible even when the minimal skin is enabled.
}
outProgress = (("outTime" in vars) ? _timeToProgress(vars.outTime, selectedAnimation, 100, inProgress) : savedInOut ? savedAnimation._outProgress : 0) || 100;
if (outProgress < inProgress) {
outProgress = 100;
}
if (outProgress !== 100) {
outPoint.style.left = outProgress + "%";
inPoint.style.display = outPoint.style.display = "block"; //set display:block so that it remains visible even when the minimal skin is enabled.
}
loopEnabled = ("loop" in vars) ? vars.loop : recall("loop");
if (loopEnabled) {
loop(true);
}
if (vars.paused) {
linkedAnimation.progress(inProgress / 100, true).pause();
}
if (_startupPhase && selectedAnimation === _recordedRoot && _globalStartTime && vars.globalSync !== false && !paused) {
linkedAnimation.time(-_globalStartTime, true);
}
updateProgress(true);
};
//INITIALIZATION TASKS
_addListener(list, "change", onChangeAnimation);
_addListener(list, "mousedown", updateList);
_addListener(playPauseButton, "mousedown", togglePlayPause);
_addListener(find(".seek-bar"), "mousedown", onPressSeekBar);
_addListener(find(".rewind"), "mousedown", onPressRewind);
_addListener(loopButton, "mousedown", toggleLoop);
_addListener(timeScale, "change", onChangeTimeScale);
if (vars.visibility === "auto") {
_addListener(root, "mouseout", onMouseOut);
//_addListener(find(".gs-hit-area"), "mouseover", show);
_addListener(root, "mouseover", show);
} else if (vars.visibility === "hidden") {
hidden = true;
autoHideTween.progress(1);
}
if (vars.keyboard !== false) {
if (_keyboardInstance && vars.keyboard) {
console.log("[GSDevTools warning] only one instance can be affected by keyboard shortcuts. There is already one active.");
} else {
_keyboardInstance = _self; //we can't have multiple instances all affected by the keyboard.
keyboardHandler = function(e) { //window.parent allows things to work inside of an iframe, like on codepen.
var key = e.keyCode ? e.keyCode : e.which,
ts;
if (key === 32) { //spacebar
togglePlayPause();
} else if (key === 38) { //up arrow
ts = parseFloat(_shiftSelectedValue(timeScale, -1, timeScaleLabel));
linkedAnimation.timeScale(ts);
record("timeScale", ts);
} else if (key === 40) { //down arrow
ts = parseFloat(_shiftSelectedValue(timeScale, 1, timeScaleLabel));
linkedAnimation.timeScale(ts);
record("timeScale", ts);
} else if (key === 37) { //left arrow
onPressRewind(e);
} else if (key === 39) { //right arrow
linkedAnimation.progress(outProgress / 100);
} else if (key === 76) { //"L" key
toggleLoop();
} else if (key === 72) { //"H" key
toggleHide();
} else if (key === 73) { //"I" key
inProgress = linkedAnimation.progress() * 100;
record("in", inProgress);
inPoint.style.left = inProgress + "%";
updateProgress(true);
} else if (key === 79) { //"O" key
outProgress = linkedAnimation.progress() * 100;
record("out", outProgress);
outPoint.style.left = outProgress + "%";
updateProgress(true);
}
};
_addListener(_docElement, "keydown", keyboardHandler);
}
}
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)
TweenLite.set(inPoint, {xPercent:-100, x:0, data:"root"});
inPoint._gsIgnore = outPoint._gsIgnore = playhead._gsIgnore = playPauseButton._gsIgnore = loopButton._gsIgnore = true;
//Draggable fires off a TweenLite.set() that affects the transforms, and we don't want them to get into the _recordedRoot, so kill those tweens.
TweenLite.killTweensOf([inPoint, outPoint, playhead]);
initialize(_startupPhase);
if (_startupPhase) {
//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.
TweenLite.delayedCall(0.0001, initialize, [false], this);
}
TweenLite.ticker.addEventListener("tick", updateProgress);
this.update = function(forceMerge) {
if (_rootInstance === _self) {
if (!_rootTween._paused || forceMerge) {
_merge();
}
updateRootDuration();
}
};
this.kill = function() {
_removeListener(list, "change", onChangeAnimation);
_removeListener(list, "mousedown", updateList);
_removeListener(playPauseButton, "mousedown", togglePlayPause);
_removeListener(find(".seek-bar"), "mousedown", onPressSeekBar);
_removeListener(find(".rewind"), "mousedown", onPressRewind);
_removeListener(loopButton, "mousedown", toggleLoop);
_removeListener(timeScale, "change", onChangeTimeScale);
progressDrag.disable();
inDrag.disable();
outDrag.disable();
TweenLite.ticker.removeEventListener("tick", updateProgress);
_removeListener(root, "mouseout", onMouseOut);
_removeListener(root, "mouseover", show);
_removeListener(_docElement, "keydown", keyboardHandler);
root.parentNode.removeChild(root);
if (_rootInstance === _self) {
_rootInstance = null;
}
delete _lookup[vars.id + ""];
};
this.minimal = function(value) {
var isMinimal = root.classList.contains("minimal"),
p;
if (!arguments.length || isMinimal === value) {
return isMinimal;
}
if (value) {
root.classList.add("minimal");
} else {
root.classList.remove("minimal");
}
if (vars.container) {
root.style.top = value ? "calc(100% - 42px)" : "calc(100% - 51px)";
}
if (progressDrag.isPressed) {
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.
progressDrag.endDrag(progressDrag.pointerEvent);
skipDragUpdates = false;
p = linkedAnimation.progress() * 100;
progressBar.style.width = Math.max(0, p - inProgress) + "%";
playhead.style.left = p + "%";
playhead.style.transform = "translate(-50%,0)";
playhead._gsTransform.x = 0;
playhead._gsTransform.xPercent = -50;
progressDrag.startDrag(progressDrag.pointerEvent, true);
}
};
//expose methods:
this.animation = animation;
this.updateList = updateList;
},
_recording = true,
_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).
GSDevTools.version = "0.1.9";
GSDevTools.logOverwrites = false;
GSDevTools.globalRecordingTime = 2;
GSDevTools.getById = function(id) {
return id ? _lookup[id] : _rootInstance;
};
//align the all of the playheads so they're starting at 0 now.
_globalTimeline._startTime += _globalTimeline._time;
_recordedRoot._startTime = _recordedTemp._startTime = _globalTimeline._time = _globalTimeline._totalTime = 0;
//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() {
var t, 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.
if (t._totalDuration !== t._totalTime || (!t._duration && t.ratio !== 1)) {
SimpleTimeline.prototype.add.call(_globalTimeline, t, t._startTime - t._delay + offset);
} else {
t.kill();
}
t = next;
}
}
if (GSDevTools.globalRecordingTime > 2) {
TweenLite.delayedCall(GSDevTools.globalRecordingTime - 2, function() {
if (_rootInstance) {
_rootInstance.update();
}
_recording = false;
});
} else {
_recording = false;
}
_startupPhase = false;
});
//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.
_globalTimeline.add = function(child, position, align, stagger) {
var data = child.data;
//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 && child instanceof TweenLite) && !(child.vars.onComplete && child.vars.onComplete === child.vars.onReverseComplete)) { //skip delayedCalls too
var tl = _recordedRoot;
if (_rootTween._time) {
if (_rootTween._paused) {
tl = _recordedTemp;
child._recordedTime = _recordedRoot.rawTime();
} else {
position = (_globalTimeline._time - _rootTween._startTime) * _rootTween._timeScale;
if (!_rootIsDirty) {
TweenLite.ticker.addEventListener("tick", _updateRootDuration);
_rootIsDirty = true;
}
}
}
tl.add(child, position, align, stagger);
//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.
tl._dirty = true;
}
return this;
}
return SimpleTimeline.prototype.add.apply(this, arguments);
};
_recordedRoot._enabled = _recordedTemp._enabled = function(value, ignoreTimeline) {
//skip enabling/disabling the nested animations (performance improvement). SimpleTimeline._enabled() doesn't do that - only TimelineLite._enabled() does.
return SimpleTimeline.prototype._enabled.apply(this, arguments);
};
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.
SimpleTimeline.prototype._remove.apply(this, arguments);
var last = this._last;
if (!last) {
this._time = this._totalTime = this._duration = this._totalDuration = 0;
} else if (this._time > this.duration()) {
this._time = this._duration;
this._totalTime = this._totalDuration;
}
return this;
};
TweenLite.onOverwrite = function(overwrittenTween, overwritingTween, target, overwrittenProperties) {
if (GSDevTools.logOverwrites) {
if (overwrittenProperties) {
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);
}
}
if (typeof(_onOverwrite) === "function") {
_onOverwrite(overwrittenTween, overwritingTween, target, overwrittenProperties);
}
};
GSDevTools.create = function(vars) {
return new GSDevTools(vars);
};
return GSDevTools;
}, true);
}); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); }
/*!
* VERSION: 0.17.0
* DATE: 2019-02-07
* UPDATES AND DOCS AT: http://greensock.com
*
* Requires TweenLite and CSSPlugin version 1.17.0 or later (TweenMax contains both TweenLite and CSSPlugin). ThrowPropsPlugin is required for momentum-based continuation of movement after the mouse/touch is released (ThrowPropsPlugin is a membership benefit of Club GreenSock - http://greensock.com/club/).
*
* @license Copyright (c) 2008-2019, GreenSock. All rights reserved.
* This work is subject to the terms at http://greensock.com/standard-license or for
* Club GreenSock members, the software agreement that was issued with your membership.
*
* @author: Jack Doyle, jack@greensock.com
*/
var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node
(_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() {
"use strict";
_gsScope._gsDefine("utils.Draggable", ["events.EventDispatcher","TweenLite","plugins.CSSPlugin"], function(EventDispatcher, TweenLite, CSSPlugin) {
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
_emptyFunc = function() { return false; },
_dummyElement = {style:{}, appendChild:_emptyFunc, removeChild:_emptyFunc},
_doc = _gsScope.document || {createElement: function() {return _dummyElement;}},
_docElement = _doc.documentElement || {},
_createElement = function(type) {
return _doc.createElementNS ? _doc.createElementNS("http://www.w3.org/1999/xhtml", type) : _doc.createElement(type);
},
_tempDiv = _createElement("div"),
_emptyArray = [],
_RAD2DEG = 180 / Math.PI,
_max = 999999999999999,
_getTime = Date.now || function() {return new Date().getTime();},
_isOldIE = !!(!_doc.addEventListener && _doc.all),
_placeholderDiv = _doc.createElement("div"),
_renderQueue = [],
_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.
_lookupCount = 0,
_clickableTagExp = /^(?:a|input|textarea|button|select)$/i,
_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.
return [a];
}
var b = [],
l = a.length,
i;
for (i = 0; i !== l; b.push(a[i++]));
return b;
},
_copy = function(obj, factor) {
var copy = {}, p;
if (factor) {
for (p in obj) {
copy[p] = obj[p] * factor;
}
} else {
for (p in obj) {
copy[p] = obj[p];
}
}
return copy;
},
ThrowPropsPlugin,
_renderQueueTick = function() {
var i = _renderQueue.length;
while (--i > -1) {
_renderQueue[i]();
}
},
_addToRenderQueue = function(func) {
_renderQueue.push(func);
if (_renderQueue.length === 1) {
TweenLite.ticker.addEventListener("tick", _renderQueueTick, this, false, 1);
}
},
_removeFromRenderQueue = function(func) {
var i = _renderQueue.length;
while (--i > -1) {
if (_renderQueue[i] === func) {
_renderQueue.splice(i, 1);
}
}
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).
},
_renderQueueTimeout = function() {
if (!_renderQueue.length) {
TweenLite.ticker.removeEventListener("tick", _renderQueueTick);
}
},
_extend = function(obj, defaults) {
var p;
for (p in defaults) {
if (obj[p] === undefined) {
obj[p] = defaults[p];
}
}
return obj;
},
_getDocScrollTop = function() {
return (window.pageYOffset != null) ? window.pageYOffset : (_doc.scrollTop != null) ? _doc.scrollTop : _docElement.scrollTop || _doc.body.scrollTop || 0;
},
_getDocScrollLeft = function() {
return (window.pageXOffset != null) ? window.pageXOffset : (_doc.scrollLeft != null) ? _doc.scrollLeft : _docElement.scrollLeft || _doc.body.scrollLeft || 0;
},
_addScrollListener = function(e, callback) {
_addListener(e, "scroll", callback);
if (!_isRoot(e.parentNode)) {
_addScrollListener(e.parentNode, callback);
}
},
_removeScrollListener = function(e, callback) {
_removeListener(e, "scroll", callback);
if (!_isRoot(e.parentNode)) {
_removeScrollListener(e.parentNode, callback);
}
},
_isRoot = function (e) {
return !!(!e || e === _docElement || e === _doc || e === _doc.body || e === window || !e.nodeType || !e.parentNode);
},
_getMaxScroll = function(element, axis) {
var dim = (axis === "x") ? "Width" : "Height",
scroll = "scroll" + dim,
client = "client" + dim,
body = _doc.body;
return Math.max(0, _isRoot(element) ? Math.max(_docElement[scroll], body[scroll]) - (window["inner" + dim] || _docElement[client] || body[client]) : element[scroll] - element[client]);
},
_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.
var isRoot = _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
_populateIEEvent = function(e, preventDefault) {
e = e || window.event;
_tempEvent.pageX = e.clientX + _doc.body.scrollLeft + _docElement.scrollLeft;
_tempEvent.pageY = e.clientY + _doc.body.scrollTop + _docElement.scrollTop;
if (preventDefault) {
e.returnValue = false;
}
return _tempEvent;
},
//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.
_unwrapElement = function(value) {
if (!value) {
return value;
}
if (typeof(value) === "string") {
value = TweenLite.selector(value);
}
if (value.length && value !== window && value[0] && value[0].style && !value.nodeType) {
value = value[0];
}
return (value === window || (value.nodeType && value.style)) ? value : null;
},
_checkPrefix = function(e, p) {
var s = e.style,
capped, i, a;
if (s[p] === undefined) {
a = ["O","Moz","ms","Ms","Webkit"];
i = 5;
capped = p.charAt(0).toUpperCase() + p.substr(1);
while (--i > -1 && s[a[i]+capped] === undefined) { }
if (i < 0) {
return "";
}
_prefix = (i === 3) ? "ms" : a[i];
p = _prefix + capped;
}
return p;
},
_setStyle = function(e, p, value) {
var s = e.style;
if (!s) {
return;
}
if (s[p] === undefined) {
p = _checkPrefix(e, p);
}
if (value == null) {
if (s.removeProperty) {
s.removeProperty(p.replace(/([A-Z])/g, "-$1").toLowerCase());
} else { //note: old versions of IE use "removeAttribute()" instead of "removeProperty()"
s.removeAttribute(p);
}
} else if (s[p] !== undefined) {
s[p] = value;
}
},
_computedStyleScope = (typeof(window) !== "undefined" ? window : _doc.defaultView || {getComputedStyle:function() {}}),
_getComputedStyle = function(e, s) {
return _computedStyleScope.getComputedStyle((e instanceof Element) ? e : e.host || (e.parentNode || {}).host || e, s); //the "host" stuff helps to accommodate ShadowDom objects.
},
_horizExp = /(?:Left|Right|Width)/i,
_suffixExp = /(?:\d|\-|\+|=|#|\.)*/g,
_convertToPixels = function(t, p, v, sfx, recurse) {
if (sfx === "px" || !sfx) { return v; }
if (sfx === "auto" || !v) { return 0; }
var horiz = _horizExp.test(p),
node = t,
style = _tempDiv.style,
neg = (v < 0),
pix;
if (neg) {
v = -v;
}
if (sfx === "%" && p.indexOf("border") !== -1) {
pix = (v / 100) * (horiz ? t.clientWidth : t.clientHeight);
} else {
style.cssText = "border:0 solid red;position:" + _getStyle(t, "position", true) + ";line-height:0;";
if (sfx === "%" || !node.appendChild) {
node = t.parentNode || _doc.body;
style[(horiz ? "width" : "height")] = v + sfx;
} else {
style[(horiz ? "borderLeftWidth" : "borderTopWidth")] = v + sfx;
}
node.appendChild(_tempDiv);
pix = parseFloat(_tempDiv[(horiz ? "offsetWidth" : "offsetHeight")]);
node.removeChild(_tempDiv);
if (pix === 0 && !recurse) {
pix = _convertToPixels(t, p, v, sfx, true);
}
}
return neg ? -pix : pix;
},
_calculateOffset = function(t, p) { //for figuring out "top" or "left" in px when it's "auto". We need to factor in margin with the offsetLeft/offsetTop
if (_getStyle(t, "position", true) !== "absolute") { return 0; }
var dim = ((p === "left") ? "Left" : "Top"),
v = _getStyle(t, "margin" + dim, true);
return t["offset" + dim] - (_convertToPixels(t, p, parseFloat(v), (v + "").replace(_suffixExp, "")) || 0);
},
_getStyle = function(element, prop, keepUnits) {
var rv = (element._gsTransform || {})[prop],
cs;
if (rv || rv === 0) {
return rv;
} else if (element.style && element.style[prop]) { //shadow dom elements don't have "style".
rv = element.style[prop];
} else if ((cs = _getComputedStyle(element))) {
rv = cs.getPropertyValue(prop.replace(/([A-Z])/g, "-$1").toLowerCase());
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()!
} else if (element.currentStyle) {
rv = element.currentStyle[prop];
}
if (rv === "auto" && (prop === "top" || prop === "left")) {
rv = _calculateOffset(element, prop);
}
return keepUnits ? rv : parseFloat(rv) || 0;
},
_dispatchEvent = function(instance, type, callbackName) {
var vars = instance.vars,
callback = vars[callbackName],
listeners = instance._listeners[type];
if (typeof(callback) === "function") {
callback.apply(vars[callbackName + "Scope"] || vars.callbackScope || instance, vars[callbackName + "Params"] || [instance.pointerEvent]);
}
if (listeners) {
instance.dispatchEvent(type);
}
},
_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.
var e = _unwrapElement(obj),
top, left, offset;
if (!e) {
if (obj.left !== undefined) {
offset = _getOffsetTransformOrigin(context); //the bounds should be relative to the origin
return {left: obj.left - offset.x, top: obj.top - offset.y, width: obj.width, height: obj.height};
}
left = obj.min || obj.minX || obj.minRotation || 0;
top = obj.min || obj.minY || 0;
return {left:left, top:top, width:(obj.max || obj.maxX || obj.maxRotation || 0) - left, height:(obj.max || obj.maxY || 0) - top};
}
return _getElementBounds(e, context);
},
_svgBorderFactor,
_svgBorderScales,
_svgScrollOffset,
_hasBorderBug,
_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.
if (!_doc.createElementNS) {
_svgBorderFactor = 0;
_svgBorderScales = false;
return;
}
var div = _createElement("div"),
svg = _doc.createElementNS("http://www.w3.org/2000/svg", "svg"),
wrapper = _createElement("div"),
style = div.style,
parent = _doc.body || _docElement,
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().
div.style.cssText = "width:100px;height:100px;overflow:scroll;-ms-overflow-style:none;";
parent.appendChild(div);
div.appendChild(svg);
point = svg.createSVGPoint().matrixTransform(svg.getScreenCTM());
e1 = point.y;
div.scrollTop = 100;
point.x = point.y = 0;
point = point.matrixTransform(svg.getScreenCTM());
_svgScrollOffset = (e1 - point.y < 100.1) ? 0 : e1 - point.y - 150;
div.removeChild(svg);
parent.removeChild(div);
// -- end _svgScrollOffset calculation.
parent.appendChild(svg);
if (isFlex) {
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.
_svgBorderFactor = 1;
_svgBorderScales = true;
} else {
_svgBorderFactor = (e1 !== matrix.e) ? 1 : 0;
_svgBorderScales = (matrix.a !== 1);
}
if (isFlex) {
parent.style.display = "flex";
}
parent.removeChild(svg);
},
_supports3D = (_checkPrefix(_tempDiv, "perspective") !== ""),
// start matrix and point conversion methods...
_transformOriginProp = _checkPrefix(_tempDiv, "transformOrigin").replace(/^ms/g, "Ms").replace(/([A-Z])/g, "-$1").toLowerCase(),
_transformProp = _checkPrefix(_tempDiv, "transform"),
_transformPropCSS = _transformProp.replace(/^ms/g, "Ms").replace(/([A-Z])/g, "-$1").toLowerCase(),
_point1 = {}, //we reuse _point1 and _point2 objects inside matrix and point conversion methods to conserve memory and minimize garbage collection tasks.
_point2 = {},
_SVGElement = _gsScope.SVGElement,
_isSVG = function(e) {
return !!(_SVGElement && typeof(e.getBBox) === "function" && e.getCTM && (!e.parentNode || (e.parentNode.getBBox && e.parentNode.getCTM)));
},
_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 (!e.getBoundingClientRect || !e.parentNode || !_transformProp) {
return {offsetTop:0, offsetLeft:0, scaleX:1, scaleY:1, offsetParent:_docElement};
}
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).
return e._dCache;
}
var curElement = e,
cache = _cache(e),
eRect, parentRect, offsetParent, cs, m, i, point1, point2, borderWidth, borderHeight, width, height;
cache.lastUpdate = TweenLite.ticker.frame;
if (e.getBBox && !cache.isSVGRoot) { //if it's a nested/child SVG element, we must find the parent SVG canvas and measure the offset from there.
curElement = e.parentNode;
eRect = e.getBBox();
while (curElement && (curElement.nodeName + "").toLowerCase() !== "svg") {
curElement = curElement.parentNode;
}
cs = _getSVGOffsets(curElement);
cache.offsetTop = eRect.y * cs.scaleY;
cache.offsetLeft = eRect.x * cs.scaleX;
cache.scaleX = cs.scaleX;
cache.scaleY = cs.scaleY;
cache.offsetParent = curElement || _docElement;
return cache;
}
//only root SVG elements continue here...
offsetParent = cache.offsetParent;
if (offsetParent === _doc.body) {
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...
_tempElements.length = _tempTransforms.length = 0;
while (curElement && curElement.parentNode) {
m = _getStyle(curElement, _transformProp, true);
if (m !== "matrix(1, 0, 0, 1, 0, 0)" && m !== "none" && m !== "translate3d(0px, 0px, 0px)") {
_tempElements.push(curElement);
_tempTransforms.push(curElement.style[_transformProp]);
curElement.style[_transformProp] = "none";
}
curElement = curElement.parentNode;
}
parentRect = offsetParent.getBoundingClientRect();
m = e.getScreenCTM();
point2 = e.createSVGPoint();
point1 = point2.matrixTransform(m);
cache.scaleX = Math.sqrt(m.a * m.a + m.b * m.b);
cache.scaleY = Math.sqrt(m.d * m.d + m.c * m.c);
if (_svgBorderFactor === undefined) {
_setEnvironmentVariables();
}
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...
cs = _getComputedStyle(e) || {};
borderWidth = (parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth)) || 0;
borderHeight = (parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth)) || 0;
width = parseFloat(cs.width) || 0;
height = parseFloat(cs.height) || 0;
cache.scaleX *= (width - borderWidth) / width;
cache.scaleY *= (height - borderHeight) / height;
}
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.
eRect = e.getBoundingClientRect();
cache.offsetLeft = eRect.left - parentRect.left;
cache.offsetTop = eRect.top - parentRect.top;
} else {
cache.offsetLeft = point1.x - parentRect.left;
cache.offsetTop = point1.y - parentRect.top;
}
cache.offsetParent = offsetParent;
i = _tempElements.length;
while (--i > -1) {
_tempElements[i].style[_transformProp] = _tempTransforms[i];
}
return cache;
},
_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.
decoratee = decoratee || {};
if (!e || e === _docElement || !e.parentNode || e === window) {
return {x:0, y:0};
}
var cs = _getComputedStyle(e),
v = (_transformOriginProp && cs) ? cs.getPropertyValue(_transformOriginProp) : "50% 50%",
a = v.split(" "),
x = (v.indexOf("left") !== -1) ? "0%" : (v.indexOf("right") !== -1) ? "100%" : a[0],
y = (v.indexOf("top") !== -1) ? "0%" : (v.indexOf("bottom") !== -1) ? "100%" : a[1];
if (y === "center" || y == null) {
y = "50%";
}
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.
e = e.getBBox();
e = {offsetWidth: e.width, offsetHeight: e.height};
}
decoratee.x = ((x.indexOf("%") !== -1) ? e.offsetWidth * parseFloat(x) / 100 : parseFloat(x));
decoratee.y = ((y.indexOf("%") !== -1) ? e.offsetHeight * parseFloat(y) / 100 : parseFloat(y));
}
return decoratee;
},
_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).
return e._dCache;
}
var cache = e._dCache = e._dCache || {},
cs = _getComputedStyle(e),
isSVG = (e.getBBox && _isSVG(e)),
isSVGRoot = ((e.nodeName + "").toLowerCase() === "svg"),
curSVG;
cache.isSVG = isSVG;
cache.isSVGRoot = isSVGRoot;
cache.borderBox = (cs.boxSizing === "border-box");
cache.computedStyle = cs;
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);
} else if (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;
}
return cache;
},
_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.
if (e === window || !e || !e.style || !e.parentNode) {
return [1,0,0,1,0,0];
}
var cache = e._dCache || _cache(e),
parent = e.parentNode,
parentCache = parent._dCache || _cache(parent),
cs = cache.computedStyle,
parentOffsetParent = cache.isSVG ? parentCache.offsetParent : parent.offsetParent,
m, isRoot, offsets, rect, t, sx, sy, offsetX, offsetY, parentRect, borderTop, borderLeft, borderTranslateX, borderTranslateY;
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()).
m = e.getAttribute("transform");
}
m = (m + "").match(/(?:\-|\.|\b)(\d|\.|e\-)+/g) || [1,0,0,1,0,0];
if (m.length > 6) {
m = [m[0], m[1], m[4], m[5], m[12], m[13]];
}
if (zeroOrigin) {
m[4] = m[5] = 0;
} else if (cache.isSVG && (t = e._gsTransform) && (t.xOrigin || t.yOrigin)) {
//SVGs handle origin very differently. Factor in GSAP's handling of origin values here:
m[0] = parseFloat(m[0]);
m[1] = parseFloat(m[1]);
m[2] = parseFloat(m[2]);
m[3] = parseFloat(m[3]);
m[4] = parseFloat(m[4]) - (t.xOrigin - (t.xOrigin * m[0] + t.yOrigin * m[2]));
m[5] = parseFloat(m[5]) - (t.yOrigin - (t.xOrigin * m[1] + t.yOrigin * m[3]));
}
if (offsetOrigin) {
if (_svgBorderFactor === undefined) {
_setEnvironmentVariables();
}
offsets = (cache.isSVG || cache.isSVGRoot) ? _getSVGOffsets(e) : e;
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))
rect = e.getBBox();
parentRect = (parentCache.isSVGRoot) ? {x:0, y:0} : parent.getBBox();
offsets = {offsetLeft:rect.x - parentRect.x, offsetTop:rect.y - parentRect.y, offsetParent:cache.offsetParent};
} else if (cache.isSVGRoot) {
borderTop = parseInt(cs.borderTopWidth, 10) || 0;
borderLeft = parseInt(cs.borderLeftWidth, 10) || 0;
borderTranslateX = ((m[0] - _svgBorderFactor) * borderLeft + m[2] * borderTop);
borderTranslateY = (m[1] * borderLeft + (m[3] - _svgBorderFactor) * borderTop);
sx = offsetOrigin.x;
sy = offsetOrigin.y;
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.
parentOffsetParent = _docElement;
}
} else if (!_hasBorderBug && e.offsetParent) {
offsetOrigin.x += parseInt(_getStyle(e.offsetParent, "borderLeftWidth"), 10) || 0;
offsetOrigin.y += parseInt(_getStyle(e.offsetParent, "borderTopWidth"), 10) || 0;
}
isRoot = (parent === _docElement || parent === _doc.body);
m[4] = Number(m[4]) + offsetOrigin.x + (offsets.offsetLeft || 0) - parentOffsetOrigin.x - (isRoot ? 0 : parent.scrollLeft || 0);
m[5] = Number(m[5]) + offsetOrigin.y + (offsets.offsetTop || 0) - parentOffsetOrigin.y - (isRoot ? 0 : parent.scrollTop || 0);
if (parent && _getStyle(e, "position", true) === "fixed") { //fixed position elements should factor in the scroll position of the document.
m[4] += _getDocScrollLeft();
m[5] += _getDocScrollTop();
parent = parent.offsetParent;
while (parent) {
m[4] -= parent.offsetLeft;
m[5] -= parent.offsetTop;
parent = parent.offsetParent;
}
} else if (parent && parent !== _docElement && parentOffsetParent === offsets.offsetParent && !parentCache.isSVG && (!_hasReparentBug || _getOffset2DMatrix(parent).join("") === "100100")) {
offsets = (parentCache.isSVGRoot) ? _getSVGOffsets(parent) : parent;
m[4] -= offsets.offsetLeft || 0;
m[5] -= offsets.offsetTop || 0;
if (!_hasBorderBug && parentCache.offsetParent && !cache.isSVG && !cache.isSVGRoot) {
m[4] -= parseInt(_getStyle(parentCache.offsetParent, "borderLeftWidth"), 10) || 0;
m[5] -= parseInt(_getStyle(parentCache.offsetParent, "borderTopWidth"), 10) || 0;
}
}
}
return m;
},
_getConcatenatedMatrix = function(e, invert) {
if (!e || e === window || !e.parentNode) {
return [1,0,0,1,0,0];
}
//note: we keep reusing _point1 and _point2 in order to minimize memory usage and garbage collection chores.
var originOffset = _getOffsetTransformOrigin(e, _point1),
parentOriginOffset = _getOffsetTransformOrigin(e.parentNode, _point2),
m = _getOffset2DMatrix(e, originOffset, parentOriginOffset, false, !invert),
a, b, c, d, tx, ty, m2, determinant;
while ((e = e.parentNode) && e.parentNode && e !== _docElement) {
originOffset = parentOriginOffset;
parentOriginOffset = _getOffsetTransformOrigin(e.parentNode, (originOffset === _point1) ? _point2 : _point1);
m2 = _getOffset2DMatrix(e, originOffset, parentOriginOffset);
a = m[0];
b = m[1];
c = m[2];
d = m[3];
tx = m[4];
ty = m[5];
m[0] = a * m2[0] + b * m2[2];
m[1] = a * m2[1] + b * m2[3];
m[2] = c * m2[0] + d * m2[2];
m[3] = c * m2[1] + d * m2[3];
m[4] = tx * m2[0] + ty * m2[2] + m2[4];
m[5] = tx * m2[1] + ty * m2[3] + m2[5];
}
if (invert) {
a = m[0];
b = m[1];
c = m[2];
d = m[3];
tx = m[4];
ty = m[5];
determinant = (a * d - b * c);
m[0] = d / determinant;
m[1] = -b / determinant;
m[2] = -c / determinant;
m[3] = a / determinant;
m[4] = (c * ty - d * tx) / determinant;
m[5] = -(a * ty - b * tx) / determinant;
}
return m;
},
_localToGlobal = function(e, p, fromTopLeft, decoratee) {
e = _unwrapElement(e);
var m = _getConcatenatedMatrix(e, false),
x = p.x,
y = p.y;
if (fromTopLeft) {
_getOffsetTransformOrigin(e, p);
x -= p.x;
y -= p.y;
}
decoratee = (decoratee === true) ? p : decoratee || {};
decoratee.x = x * m[0] + y * m[2] + m[4];
decoratee.y = x * m[1] + y * m[3] + m[5];
return decoratee;
},
_localizePoint = function(p, localToGlobal, globalToLocal) {
var x = p.x * localToGlobal[0] + p.y * localToGlobal[2] + localToGlobal[4],
y = p.x * localToGlobal[1] + p.y * localToGlobal[3] + localToGlobal[5];
p.x = x * globalToLocal[0] + y * globalToLocal[2] + globalToLocal[4];
p.y = x * globalToLocal[1] + y * globalToLocal[3] + globalToLocal[5];
return p;
},
_getElementBounds = function(e, context, fromTopLeft) {
if (!(e = _unwrapElement(e))) {
return null;
}
context = _unwrapElement(context);
var isSVG = (e.getBBox && _isSVG(e)),
origin, left, right, top, bottom, mLocalToGlobal, mGlobalToLocal, p1, p2, p3, p4, bbox, width, height, cache, borderLeft, borderTop, viewBox, viewBoxX, viewBoxY, computedDimensions, cs;
if (e === window) {
top = _getDocScrollTop();
left = _getDocScrollLeft();
right = left + (_docElement.clientWidth || e.innerWidth || _doc.body.clientWidth || 0);
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.
} else if (context === undefined || context === window) {
return e.getBoundingClientRect();
} else {
origin = _getOffsetTransformOrigin(e);
left = -origin.x;
top = -origin.y;
if (isSVG) {
bbox = e.getBBox();
width = bbox.width;
height = bbox.height;
} else if ((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.
cache = _getSVGOffsets(e);
cs = cache.computedStyle || {};
viewBox = (e.getAttribute("viewBox") || "0 0").split(" ");
viewBoxX = parseFloat(viewBox[0]);
viewBoxY = parseFloat(viewBox[1]);
borderLeft = parseFloat(cs.borderLeftWidth) || 0;
borderTop = parseFloat(cs.borderTopWidth) || 0;
left /= cache.scaleX;
top /= cache.scaleY;
right = left + width - (width - ((width - borderLeft) / cache.scaleX) - viewBoxX);
bottom = top + height - (height - ((height - borderTop) / cache.scaleY) - viewBoxY);
left -= borderLeft / cache.scaleX - viewBoxX;
top -= borderTop / cache.scaleY - viewBoxY;
if (computedDimensions) { //when we had to use computed styles, factor in the border now.
right += (parseFloat(cs.borderRightWidth) + borderLeft) / cache.scaleX;
bottom += (borderTop + parseFloat(cs.borderBottomWidth)) / cache.scaleY;
}
}
}
if (e === context) {
return {left:left, top:top, width: right - left, height: bottom - top};
}
mLocalToGlobal = _getConcatenatedMatrix(e);
mGlobalToLocal = _getConcatenatedMatrix(context, true);
p1 = _localizePoint({x:left, y:top}, mLocalToGlobal, mGlobalToLocal);
p2 = _localizePoint({x:right, y:top}, mLocalToGlobal, mGlobalToLocal);
p3 = _localizePoint({x:right, y:bottom}, mLocalToGlobal, mGlobalToLocal);
p4 = _localizePoint({x:left, y:bottom}, mLocalToGlobal, mGlobalToLocal);
left = Math.min(p1.x, p2.x, p3.x, p4.x);
top = Math.min(p1.y, p2.y, p3.y, p4.y);
_temp1.x = _temp1.y = 0;
if (fromTopLeft) {
_getOffsetTransformOrigin(context, _temp1);
}
return {left:left + _temp1.x, top:top + _temp1.y, width:Math.max(p1.x, p2.x, p3.x, p4.x) - left, height:Math.max(p1.y, p2.y, p3.y, p4.y) - top};
},
// end matrix and point conversion methods
_isArrayLike = function(e) {
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.
},
_flattenArray = function(a) {
var result = [],
l = a.length,
i, e, j;
for (i = 0; i < l; i++) {
e = a[i];
if (_isArrayLike(e)) {
j = e.length;
for (j = 0; j < e.length; j++) {
result.push(e[j]);
}
} else if (e && e.length !== 0) {
result.push(e);
}
}
return result;
},
_isTouchDevice = (typeof(window) !== "undefined" && ("ontouchstart" in _docElement) && ("orientation" in window)),
_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.
var standard = types.split(","),
converted = ((_tempDiv.onpointerdown !== undefined) ? "pointerdown,pointermove,pointerup,pointercancel" : (_tempDiv.onmspointerdown !== undefined) ? "MSPointerDown,MSPointerMove,MSPointerUp,MSPointerCancel" : types).split(","),
obj = {},
i = 4;
while (--i > -1) {
obj[standard[i]] = converted[i];
obj[converted[i]] = standard[i];
}
//to avoid problems in iOS 9, test to see if the browser supports the "passive" option on addEventListener().
try {
_docElement.addEventListener("test", null, Object.defineProperty({}, "passive", {
get: function () {
_supportsPassive = 1;
}
}));
} catch (e) {}
return obj;
}("touchstart,touchmove,touchend,touchcancel")),
_addListener = function(element, type, func, capture) {
if (element.addEventListener) {
var touchType = _touchEventLookup[type];
capture = capture || (_supportsPassive ? {passive:false} : null);
element.addEventListener(touchType || type, func, capture);
if (touchType && type !== touchType) { //some browsers actually support both, so must we.
element.addEventListener(type, func, capture);
}
} else if (element.attachEvent) {
element.attachEvent("on" + type, func);
}
},
_removeListener = function(element, type, func) {
if (element.removeEventListener) {
var touchType = _touchEventLookup[type];
element.removeEventListener(touchType || type, func);
if (touchType && type !== touchType) {
element.removeEventListener(type, func);
}
} else if (element.detachEvent) {
element.detachEvent("on" + type, func);
}
},
_hasTouchID = function(list, ID) {
var i = list.length;
while (--i > -1) {
if (list[i].identifier === ID) {
return true;
}
}
return false;
},
_onMultiTouchDocumentEnd = function(e) {
_isMultiTouching = (e.touches && _dragCount < e.touches.length);
_removeListener(e.target, "touchend", _onMultiTouchDocumentEnd);
},
_onMultiTouchDocument = function(e) {
_isMultiTouching = (e.touches && _dragCount < e.touches.length);
_addListener(e.target, "touchend", _onMultiTouchDocumentEnd);
},
_parseThrowProps = function(draggable, snap, max, min, factor, forceZeroVelocity) {
var vars = {},
a, i, l;
if (snap) {
if (factor !== 1 && snap instanceof Array) { //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;
} else if (typeof(snap) === "function") {
vars.end = function(value) {
var result = snap.call(draggable, value),
copy, p;
if (factor !== 1) {
if (typeof(result) === "object") {
copy = {};
for (p in result) {
copy[p] = result[p] * factor;
}
result = copy;
} else {
result *= factor;
}
}
return result; //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;
}
return vars;
},
_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).
var data;
return (!e || !e.getAttribute || e.nodeName === "BODY") ? false : ((data = e.getAttribute("data-clickable")) === "true" || (data !== "false" && (e.onclick || _clickableTagExp.test(e.nodeName + "") || e.getAttribute("contentEditable") === "true"))) ? true : _isClickable(e.parentNode);
},
_setSelectable = function(elements, selectable) {
var i = elements.length,
e;
while (--i > -1) {
e = elements[i];
e.ondragstart = e.onselectstart = selectable ? null : _emptyFunc;
_setStyle(e, "userSelect", (selectable ? "text" : "none"));
}
},
_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).
var div = _doc.createElement("div"),
child = _doc.createElement("div"),
childStyle = child.style,
parent = _doc.body || _tempDiv,
val;
childStyle.display = "inline-block";
childStyle.position = "relative";
div.style.cssText = child.innerHTML = "width:90px; height:40px; padding:10px; overflow:auto; visibility: hidden";
div.appendChild(child);
parent.appendChild(div);
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);
return val;
}()),
//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.
ScrollProxy = function(element, vars) {
element = _unwrapElement(element);
vars = vars || {};
var content = _doc.createElement("div"),
style = content.style,
node = element.firstChild,
offsetTop = 0,
offsetLeft = 0,
prevTop = element.scrollTop,
prevLeft = element.scrollLeft,
scrollWidth = element.scrollWidth,
scrollHeight = element.scrollHeight,
extraPadRight = 0,
maxLeft = 0,
maxTop = 0,
elementWidth, elementHeight, contentHeight, nextNode, transformStart, transformEnd;
if (_supports3D && vars.force3D !== false) {
transformStart = "translate3d(";
transformEnd = "px,0px)";
} else if (_transformProp) {
transformStart = "translate(";
transformEnd = "px)";
}
this.scrollTop = function(value, force) {
if (!arguments.length) {
return -this.top();
}
this.top(-value, force);
};
this.scrollLeft = function(value, force) {
if (!arguments.length) {
return -this.left();
}
this.left(-value, force);
};
this.left = function(value, force) {
if (!arguments.length) {
return -(element.scrollLeft + offsetLeft);
}
var dif = element.scrollLeft - prevLeft,
oldOffset = offsetLeft;
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;
TweenLite.killTweensOf(this, true, {left:1, scrollLeft:1});
this.left(-prevLeft);
if (vars.onKill) {
vars.onKill();
}
return;
}
value = -value; //invert because scrolling works in the opposite direction
if (value < 0) {
offsetLeft = (value - 0.5) | 0;
value = 0;
} else if (value > maxLeft) {
offsetLeft = (value - maxLeft) | 0;
value = maxLeft;
} else {
offsetLeft = 0;
}
if (offsetLeft || oldOffset) {
if (transformStart) {
if (!this._suspendTransforms) {
style[_transformProp] = transformStart + -offsetLeft + "px," + -offsetTop + transformEnd;
}
} else {
style.left = -offsetLeft + "px";
}
if (offsetLeft + extraPadRight >= 0) {
style.paddingRight = offsetLeft + extraPadRight + "px";
}
}
element.scrollLeft = value | 0;
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);
}
var dif = 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.
prevTop = element.scrollTop;
TweenLite.killTweensOf(this, true, {top:1, scrollTop:1});
this.top(-prevTop);
if (vars.onKill) {
vars.onKill();
}
return;
}
value = -value; //invert because scrolling works in the opposite direction
if (value < 0) {
offsetTop = (value - 0.5) | 0;
value = 0;
} else if (value > maxTop) {
offsetTop = (value - maxTop) | 0;
value = maxTop;
} else {
offsetTop = 0;
}
if (offsetTop || oldOffset) {
if (transformStart) {
if (!this._suspendTransforms) {
style[_transformProp] = transformStart + -offsetLeft + "px," + -offsetTop + transformEnd;
}
} else {
style.top = -offsetTop + "px";
}
}
element.scrollTop = value | 0;
prevTop = element.scrollTop;
};
this.maxScrollTop = function() {
return maxTop;
};
this.maxScrollLeft = function() {
return maxLeft;
};
this.disable = function() {
node = content.firstChild;
while (node) {
nextNode = node.nextSibling;
element.appendChild(node);
node = nextNode;
}
if (element === content.parentNode) { //in case disable() is called when it's already disabled.
element.removeChild(content);
}
};
this.enable = function() {
node = element.firstChild;
if (node === content) {
return;
}
while (node) {
nextNode = node.nextSibling;
content.appendChild(node);
node = nextNode;
}
element.appendChild(content);
this.calibrate();
};
this.calibrate = function(force) {
var widthMatches = (element.clientWidth === elementWidth),
x, y;
prevTop = element.scrollTop;
prevLeft = element.scrollLeft;
if (widthMatches && element.clientHeight === elementHeight && content.offsetHeight === contentHeight && scrollWidth === element.scrollWidth && scrollHeight === element.scrollHeight && !force) {
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)
if (!widthMatches || force) {
style.display = "block";
style.width = "auto";
style.paddingRight = "0px";
extraPadRight = Math.max(0, element.scrollWidth - element.clientWidth);
//if the content is wider than the container, we need to add the paddingLeft and paddingRight in order for things to behave correctly.
if (extraPadRight) {
extraPadRight += _getStyle(element, "paddingLeft") + (_addPaddingBR ? _getStyle(element, "paddingRight") : 0);
}
}
style.display = "inline-block";
style.position = "relative";
style.overflow = "visible";
style.verticalAlign = "top";
style.width = "100%";
style.paddingRight = extraPadRight + "px";
//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
if (_addPaddingBR) {
style.paddingBottom = _getStyle(element, "paddingBottom", true);
}
if (_isOldIE) {
style.zoom = "1";
}
elementWidth = element.clientWidth;
elementHeight = element.clientHeight;
scrollWidth = element.scrollWidth;
scrollHeight = element.scrollHeight;
maxLeft = element.scrollWidth - elementWidth;
maxTop = element.scrollHeight - elementHeight;
contentHeight = content.offsetHeight;
style.display = "block";
if (x || y) {
this.left(x);
this.top(y);
}
};
this.content = content;
this.element = element;
this._suspendTransforms = false;
this.enable();
},
Draggable = function(target, vars) {
EventDispatcher.call(this, target);
target = _unwrapElement(target); //in case the target is a selector object or selector text
if (!ThrowPropsPlugin) {
ThrowPropsPlugin = _globals.com.greensock.plugins.ThrowPropsPlugin;
}
this.vars = vars = _copy(vars || {});
this.target = target;
this.x = this.y = this.rotation = 0;
this.dragResistance = parseFloat(vars.dragResistance) || 0;
this.edgeResistance = isNaN(vars.edgeResistance) ? 1 : parseFloat(vars.edgeResistance) || 0;
this.lockAxis = vars.lockAxis;
this.autoScroll = vars.autoScroll || 0;
this.lockedAxis = null;
this.allowEventDefault = !!vars.allowEventDefault;
var type = (vars.type || (_isOldIE ? "top,left" : "x,y")).toLowerCase(),
xyMode = (type.indexOf("x") !== -1 || type.indexOf("y") !== -1),
rotationMode = (type.indexOf("rotation") !== -1),
xProp = rotationMode ? "rotation" : xyMode ? "x" : "left",
yProp = xyMode ? "y" : "top",
allowX = (type.indexOf("x") !== -1 || type.indexOf("left") !== -1 || type === "scroll"),
allowY = (type.indexOf("y") !== -1 || type.indexOf("top") !== -1 || type === "scroll"),
minimumMovement = vars.minimumMovement || 2,
self = this,
triggers = _slice(vars.trigger || vars.handle || target),
killProps = {},
dragEndTime = 0,
checkAutoScrollBounds = false,
autoScrollMarginTop = vars.autoScrollMarginTop || 40,
autoScrollMarginRight = vars.autoScrollMarginRight || 40,
autoScrollMarginBottom = vars.autoScrollMarginBottom || 40,
autoScrollMarginLeft = vars.autoScrollMarginLeft || 40,
isClickable = vars.clickableTest || _isClickable,
clickTime = 0,
enabled, scrollProxy, startPointerX, startPointerY, startElementX, startElementY, hasBounds, hasDragCallback, maxX, minX, maxY, minY, tempVars, cssVars, touch, touchID, rotationOrigin, dirty, old, snapX, snapY, snapXY, isClicking, touchEventTarget, matrix, interrupted, startScrollTop, startScrollLeft, applyObj, allowNativeTouchScrolling, touchDragAxis, isDispatching, clickDispatch, trustedClickDispatch,
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();
return false;
}
},
//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.
render = function(suppressEvents) {
if (self.autoScroll && self.isDragging && (checkAutoScrollBounds || dirty)) {
var e = target,
autoScrollFactor = self.autoScroll * 15, //multiplying by 15 just gives us a better "feel" speed-wise.
parent, isRoot, rect, pointerX, pointerY, changeX, changeY, gap;
checkAutoScrollBounds = false;
_windowProxy.scrollTop = ((window.pageYOffset != null) ? window.pageYOffset : (_docElement.scrollTop != null) ? _docElement.scrollTop : _doc.body.scrollTop);
_windowProxy.scrollLeft = ((window.pageXOffset != null) ? window.pageXOffset : (_docElement.scrollLeft != null) ? _docElement.scrollLeft : _doc.body.scrollLeft);
pointerX = self.pointerX - _windowProxy.scrollLeft;
pointerY = self.pointerY - _windowProxy.scrollTop;
while (e && !isRoot) { //walk up the chain and sense wherever the pointer is within 40px of an edge that's scrollable.
isRoot = _isRoot(e.parentNode);
parent = isRoot ? _windowProxy : e.parentNode;
rect = isRoot ? {bottom:Math.max(_docElement.clientHeight, window.innerHeight || 0), right: Math.max(_docElement.clientWidth, window.innerWidth || 0), left:0, top:0} : parent.getBoundingClientRect();
changeX = changeY = 0;
if (allowY) {
gap = parent._gsMaxScrollY - parent.scrollTop;
if (gap < 0) {
changeY = gap;
} else if (pointerY > rect.bottom - autoScrollMarginBottom && gap) {
checkAutoScrollBounds = true;
changeY = Math.min(gap, (autoScrollFactor * (1 - Math.max(0, (rect.bottom - pointerY)) / autoScrollMarginBottom)) | 0);
} else if (pointerY < rect.top + autoScrollMarginTop && parent.scrollTop) {
checkAutoScrollBounds = true;
changeY = -Math.min(parent.scrollTop, (autoScrollFactor * (1 - Math.max(0, (pointerY - rect.top)) / autoScrollMarginTop)) | 0);
}
if (changeY) {
parent.scrollTop += changeY;
}
}
if (allowX) {
gap = parent._gsMaxScrollX - parent.scrollLeft;
if (gap < 0) {
changeX = gap;
} else if (pointerX > rect.right - autoScrollMarginRight && gap) {
checkAutoScrollBounds = true;
changeX = Math.min(gap, (autoScrollFactor * (1 - Math.max(0, (rect.right - pointerX)) / autoScrollMarginRight)) | 0);
} else if (pointerX < rect.left + autoScrollMarginLeft && parent.scrollLeft) {
checkAutoScrollBounds = true;
changeX = -Math.min(parent.scrollLeft, (autoScrollFactor * (1 - Math.max(0, (pointerX - rect.left)) / autoScrollMarginLeft)) | 0);
}
if (changeX) {
parent.scrollLeft += changeX;
}
}
if (isRoot && (changeX || changeY)) {
window.scrollTo(parent.scrollLeft, parent.scrollTop);
setPointerPosition(self.pointerX + changeX, self.pointerY + changeY);
}
e = parent;
}
}
if (dirty) {
var x = self.x,
y = self.y,
min = 0.000001;
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);
}
} else if (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.
} else {
if (allowY) {
self.deltaY = y - parseFloat(target.style.top || 0);
target.style.top = y + "px";
}
if (allowX) {
self.deltaY = x - parseFloat(target.style.left || 0);
target.style.left = x + "px";
}
}
}
if (hasDragCallback && !suppressEvents && !isDispatching) {
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) {
var x = 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.
TweenLite.set(target, {x:"+=0", overwrite:false, data:"_draggable"});
}
if (xyMode) {
self.y = target._gsTransform.y;
self.x = target._gsTransform.x;
} else if (rotationMode) {
self.x = self.rotation = target._gsTransform.rotation;
} else if (scrollProxy) {
self.y = scrollProxy.top();
self.x = scrollProxy.left();
} else {
self.y = parseInt(target.style.top, 10) || 0;
self.x = parseInt(target.style.left, 10) || 0;
}
if ((snapX || snapY || snapXY) && !skipSnap && (self.isDragging || self.isThrowing)) {
if (snapXY) {
_temp1.x = self.x;
_temp1.y = self.y;
snappedValue = snapXY(_temp1);
if (snappedValue.x !== self.x) {
self.x = snappedValue.x;
dirty = true;
}
if (snappedValue.y !== self.y) {
self.y = snappedValue.y;
dirty = true;
}
}
if (snapX) {
snappedValue = snapX(self.x);
if (snappedValue !== self.x) {
self.x = snappedValue;
if (rotationMode) {
self.rotation = snappedValue;
}
dirty = true;
}
}
if (snapY) {
snappedValue = snapY(self.y);
if (snappedValue !== self.y) {
self.y = snappedValue;
}
dirty = true;
}
}
if (dirty) {
render(true);
}
if (!skipOnUpdate) {
self.deltaX = self.x - x;
self.deltaY = self.y - y;
_dispatchEvent(self, "throwupdate", "onThrowUpdate");
}
},
calculateBounds = function() {
var bounds, targetBounds, snap, snapIsRaw;
hasBounds = false;
if (scrollProxy) {
scrollProxy.calibrate();
self.minX = minX = -scrollProxy.maxScrollLeft();
self.minY = minY = -scrollProxy.maxScrollTop();
self.maxX = maxX = self.maxY = maxY = 0;
hasBounds = true;
} else if (!!vars.bounds) {
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}
if (rotationMode) {
self.minX = minX = bounds.left;
self.maxX = maxX = bounds.left + bounds.width;
self.minY = minY = self.maxY = maxY = 0;
} else if (vars.bounds.maxX !== undefined || vars.bounds.maxY !== undefined) {
bounds = vars.bounds;
self.minX = minX = bounds.minX;
self.minY = minY = bounds.minY;
self.maxX = maxX = bounds.maxX;
self.maxY = maxY = bounds.maxY;
} else {
targetBounds = _getBounds(target, target.parentNode);
self.minX = minX = _getStyle(target, xProp) + bounds.left - targetBounds.left;
self.minY = minY = _getStyle(target, yProp) + bounds.top - targetBounds.top;
self.maxX = maxX = minX + (bounds.width - targetBounds.width);
self.maxY = maxY = minY + (bounds.height - targetBounds.height);
}
if (minX > maxX) {
self.minX = maxX;
self.maxX = maxX = minX;
minX = self.minX;
}
if (minY > maxY) {
self.minY = maxY;
self.maxY = maxY = minY;
minY = self.minY;
}
if (rotationMode) {
self.minRotation = minX;
self.maxRotation = maxX;
}
hasBounds = true;
}
if (vars.liveSnap) {
snap = (vars.liveSnap === true) ? (vars.snap || {}) : vars.liveSnap;
snapIsRaw = (snap instanceof Array || typeof(snap) === "function");
if (rotationMode) {
snapX = buildSnapFunc((snapIsRaw ? snap : snap.rotation), minX, maxX, 1);
snapY = null;
} else {
if (snap.points) {
snapXY = buildPointSnapFunc((snapIsRaw ? snap : snap.points), minX, maxX, minY, maxY, snap.radius, scrollProxy ? -1 : 1);
} else {
if (allowX) {
snapX = buildSnapFunc((snapIsRaw ? snap : snap.x || snap.left || snap.scrollLeft), minX, maxX, scrollProxy ? -1 : 1);
}
if (allowY) {
snapY = buildSnapFunc((snapIsRaw ? snap : snap.y || snap.top || snap.scrollTop), minY, maxY, scrollProxy ? -1 : 1);
}
}
}
}
},
onThrowComplete = function() {
self.isThrowing = false;
_dispatchEvent(self, "throwcomplete", "onThrowComplete");
},
onThrowOverwrite = function() {
self.isThrowing = false;
},
animate = function(throwProps, forceZeroVelocity) {
var snap, snapIsRaw, tween, overshootTolerance;
if (throwProps && ThrowPropsPlugin) {
if (throwProps === true) {
snap = vars.snap || vars.liveSnap || {};
snapIsRaw = (snap instanceof Array || typeof(snap) === "function");
throwProps = {resistance:(vars.throwResistance || vars.resistance || 1000) / (rotationMode ? 10 : 1)};
if (rotationMode) {
throwProps.rotation = _parseThrowProps(self, snapIsRaw ? snap : snap.rotation, maxX, minX, 1, forceZeroVelocity);
} else {
if (allowX) {
throwProps[xProp] = _parseThrowProps(self, snapIsRaw ? snap : snap.points || snap.x || snap.left || snap.scrollLeft, maxX, minX, scrollProxy ? -1 : 1, forceZeroVelocity || (self.lockedAxis === "x"));
}
if (allowY) {
throwProps[yProp] = _parseThrowProps(self, snapIsRaw ? snap : snap.points || snap.y || snap.top || snap.scrollTop, maxY, minY, scrollProxy ? -1 : 1, forceZeroVelocity || (self.lockedAxis === "y"));
}
if (snap.points || (snap instanceof Array && typeof(snap[0]) === "object")) {
throwProps.linkedProps = xProp + "," + yProp;
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.
}
}
}
self.isThrowing = true;
overshootTolerance = (!isNaN(vars.overshootTolerance)) ? vars.overshootTolerance : (vars.edgeResistance === 1) ? 0 : (1 - self.edgeResistance) + 0.2;
self.tween = tween = ThrowPropsPlugin.to(scrollProxy || target, {throwProps:throwProps, data:"_draggable", ease:(vars.ease || _globals.Power3.easeOut), onComplete:onThrowComplete, onOverwrite:onThrowOverwrite, onUpdate:(vars.fastMode ? _dispatchEvent : syncXY), onUpdateParams:(vars.fastMode ? [self, "onthrowupdate", "onThrowUpdate"] : (snap && snap.radius) ? [false, true] : _emptyArray)}, Math.max(vars.minDuration || 0, vars.maxDuration || 0) || 2, (!isNaN(vars.minDuration) ? vars.minDuration : (overshootTolerance === 0 || (typeof(throwProps) === "object" && throwProps.resistance > 1000)) ? 0 : 0.5), overshootTolerance);
if (!vars.fastMode) {
//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.
}
tween.render(tween.duration(), true, true);
syncXY(true, true);
self.endX = self.x;
self.endY = self.y;
if (rotationMode) {
self.endRotation = self.x;
}
tween.play(0);
syncXY(true, true);
if (scrollProxy) {
scrollProxy._suspendTransforms = false;
}
}
} else if (hasBounds) {
self.applyBounds();
}
},
updateMatrix = function(shiftStart) {
var start = matrix || [1,0,0,1,0,0],
a, b, c, d, tx, ty, determinant, pointerX, pointerY;
matrix = _getConcatenatedMatrix(target.parentNode, true);
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.
a = start[0];
b = start[1];
c = start[2];
d = start[3];
tx = start[4];
ty = start[5];
determinant = (a * d - b * c);
pointerX = startPointerX * (d / determinant) + startPointerY * (-c / determinant) + ((c * ty - d * tx) / determinant);
pointerY = startPointerX * (-b / determinant) + startPointerY * (a / determinant) + (-(a * ty - b * tx) / determinant);
startPointerY = pointerX * matrix[1] + pointerY * matrix[3] + matrix[5];
startPointerX = pointerX * matrix[0] + pointerY * matrix[2] + matrix[4];
}
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() {
var edgeTolerance = 1 - self.edgeResistance;
updateMatrix(false);
if (matrix) {
startPointerX = self.pointerX * matrix[0] + self.pointerY * matrix[2] + matrix[4]; //translate to local coordinate system
startPointerY = self.pointerX * matrix[1] + self.pointerY * matrix[3] + matrix[5];
}
if (dirty) {
setPointerPosition(self.pointerX, self.pointerY);
render(true);
}
if (scrollProxy) {
calculateBounds();
startElementY = scrollProxy.top();
startElementX = scrollProxy.left();
} else {
//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 (isTweening()) {
syncXY(true, true);
calculateBounds();
} else {
self.applyBounds();
}
if (rotationMode) {
rotationOrigin = self.rotationOrigin = _localToGlobal(target, {x:0, y:0});
syncXY(true, true);
startElementX = self.x; //starting rotation (x always refers to rotation in type:"rotation", measured in degrees)
startElementY = self.y = Math.atan2(rotationOrigin.y - self.pointerY, self.pointerX - rotationOrigin.x) * _RAD2DEG;
} else {
startScrollTop = target.parentNode ? target.parentNode.scrollTop || 0 : 0;
startScrollLeft = target.parentNode ? target.parentNode.scrollLeft || 0 : 0;
startElementY = _getStyle(target, yProp); //record the starting top and left values so that we can just add the mouse's movement to them later.
startElementX = _getStyle(target, xProp);
}
}
if (hasBounds && edgeTolerance) {
if (startElementX > maxX) {
startElementX = maxX + (startElementX - maxX) / edgeTolerance;
} else if (startElementX < minX) {
startElementX = minX - (minX - startElementX) / edgeTolerance;
}
if (!rotationMode) {
if (startElementY > maxY) {
startElementY = maxY + (startElementY - maxY) / edgeTolerance;
} else if (startElementY < minY) {
startElementY = minY - (minY - startElementY) / edgeTolerance;
}
}
}
self.startX = startElementX;
self.startY = startElementY;
},
isTweening = function() {
return (self.tween && self.tween.isActive());
},
removePlaceholder = function() {
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.
_placeholderDiv.parentNode.removeChild(_placeholderDiv);
}
},
buildSnapFunc = function(snap, min, max, factor) {
if (min == null) {
min = -_max;
}
if (max == null) {
max = _max;
}
if (typeof(snap) === "function") {
return function(n) {
var edgeTolerance = !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)
return snap.call(self, (n > max ? max + (n - max) * edgeTolerance : (n < min) ? min + (n - min) * edgeTolerance : n)) * factor;
};
}
if (snap instanceof Array) {
return function(n) {
var i = snap.length,
closest = 0,
absDif = _max,
val, dif;
while (--i > -1) {
val = snap[i];
dif = val - n;
if (dif < 0) {
dif = -dif;
}
if (dif < absDif && val >= min && val <= max) {
closest = i;
absDif = dif;
}
}
return snap[closest];
};
}
return isNaN(snap) ? function(n) { return n; } : function() { return snap * factor; };
},
buildPointSnapFunc = function(snap, minX, maxX, minY, maxY, radius, factor) {
radius = (radius && radius < _max) ? radius * radius : _max; //so we don't have to Math.sqrt() in the functions. Performance optimization.
if (typeof(snap) === "function") {
return function(point) {
var edgeTolerance = !self.isPressed ? 1 : 1 - self.edgeResistance,
x = point.x,
y = point.y,
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)
point.x = x = (x > maxX ? maxX + (x - maxX) * edgeTolerance : (x < minX) ? minX + (x - minX) * edgeTolerance : x);
point.y = y = (y > maxY ? maxY + (y - maxY) * edgeTolerance : (y < minY) ? minY + (y - minY) * edgeTolerance : y);
result = snap.call(self, point);
if (result !== point) {
point.x = result.x;
point.y = result.y;
}
if (factor !== 1) {
point.x *= factor;
point.y *= factor;
}
if (radius < _max) {
dx = point.x - x;
dy = point.y - y;
if (dx * dx + dy * dy > radius) {
point.x = x;
point.y = y;
}
}
return point;
};
}
if (snap instanceof Array) {
return function(p) {
var i = snap.length,
closest = 0,
minDist = _max,
x, y, point, dist;
while (--i > -1) {
point = snap[i];
x = point.x - p.x;
y = point.y - p.y;
dist = x * x + y * y;
if (dist < minDist) {
closest = i;
minDist = dist;
}
}
return (minDist <= radius) ? snap[closest] : p;
};
}
return function(n) { return n; };
},
//called when the mouse is pressed (or touch starts)
onPress = function(e, force) {
var i;
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(touchEventTarget, "touchend", onRelease);
_addListener(touchEventTarget, "touchmove", onMove);
_addListener(touchEventTarget, "touchcancel", onRelease);
_addListener(_doc, "touchstart", _onMultiTouchDocument);
} else {
touchEventTarget = null;
_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"
}
isClicking = (isClickable.call(self, e.target) && vars.dragClickables !== false && !force);
if (isClicking) {
_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.
if (_isOldIE) {
e = _populateIEEvent(e, true);
} else if (!allowNativeTouchScrolling && !self.allowEventDefault) {
e.preventDefault();
if (e.preventManipulation) {
e.preventManipulation(); //for some Microsoft browsers
}
}
if (e.changedTouches) { //touch events store the data slightly differently
e = touch = e.changedTouches[0];
touchID = e.identifier;
} else if (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.
_placeholderDiv.style.width = target.parentNode.scrollWidth + "px";
target.parentNode.appendChild(_placeholderDiv);
}
recordStartPositions();
if (self.tween) {
self.tween.kill();
}
self.isThrowing = false;
TweenLite.killTweensOf(scrollProxy || target, true, killProps); //in case the user tries to drag it before the last tween is done.
if (scrollProxy) {
TweenLite.killTweensOf(target, true, {scrollTo:1}); //just in case the original target's scroll position is being tweened somewhere else.
}
self.tween = self.lockedAxis = null;
if (vars.zIndexBoost || (!rotationMode && !scrollProxy && vars.zIndexBoost !== false)) {
target.style.zIndex = Draggable.zIndex++;
}
self.isPressed = true;
hasDragCallback = !!(vars.onDrag || self._listeners.drag);
if (!rotationMode && (vars.cursor !== false || vars.activeCursor)) {
i = triggers.length;
while (--i > -1) {
_setStyle(triggers[i], "cursor", vars.activeCursor || vars.cursor || "move");
}
}
_dispatchEvent(self, "press", "onPress");
},
//called every time the mouse/touch moves
onMove = function(e) {
var originalEvent = e,
touches, pointerX, pointerY, i, dx, dy;
if (!enabled || _isMultiTouching || !self.isPressed || !e) {
return;
}
self.pointerEvent = e;
touches = e.changedTouches;
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)
i = touches.length;
while (--i > -1 && (e = touches[i]).identifier !== touchID) {}
if (i < 0) {
return;
}
}
} else if (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).
pointerX = e.pageX;
pointerY = e.pageY;
if (matrix) {
i = pointerX * matrix[0] + pointerY * matrix[2] + matrix[4];
pointerY = pointerX * matrix[1] + pointerY * matrix[3] + matrix[5];
pointerX = i;
}
dx = Math.abs(pointerX - startPointerX);
dy = Math.abs(pointerY - startPointerY);
if ((dx !== dy && (dx > minimumMovement || dy > minimumMovement)) || (_isAndroid && allowNativeTouchScrolling === touchDragAxis)) {
touchDragAxis = (dx > dy && allowX) ? "x" : "y";
if (self.vars.lockAxisOnTouchScroll !== false) {
self.lockedAxis = (touchDragAxis === "x") ? "y" : "x";
if (typeof(self.vars.onLockAxis) === "function") {
self.vars.onLockAxis.call(self, originalEvent);
}
}
if (_isAndroid && allowNativeTouchScrolling === touchDragAxis) {
onRelease(originalEvent);
return;
}
}
}
if (!self.allowEventDefault && (!allowNativeTouchScrolling || (touchDragAxis && allowNativeTouchScrolling !== touchDragAxis)) && originalEvent.cancelable !== false) {
originalEvent.preventDefault();
if (originalEvent.preventManipulation) { //for some Microsoft browsers
originalEvent.preventManipulation();
}
}
}
if (self.autoScroll) {
checkAutoScrollBounds = true;
}
setPointerPosition(e.pageX, e.pageY);
},
setPointerPosition = function(pointerX, pointerY) {
var dragTolerance = 1 - self.dragResistance,
edgeTolerance = 1 - self.edgeResistance,
xChange, yChange, x, y, dif, temp;
self.pointerX = pointerX;
self.pointerY = pointerY;
if (rotationMode) {
y = Math.atan2(rotationOrigin.y - pointerY, pointerX - rotationOrigin.x) * _RAD2DEG;
dif = self.y - y;
if (dif > 180) {
startElementY -= 360;
self.y = y;
} else if (dif < -180) {
startElementY += 360;
self.y = y;
}
if (self.x !== startElementX || Math.abs(startElementY - y) > minimumMovement) {
self.y = y;
x = startElementX + (startElementY - y) * dragTolerance;
} else {
x = startElementX;
}
} else {
if (matrix) {
temp = pointerX * matrix[0] + pointerY * matrix[2] + matrix[4];
pointerY = pointerX * matrix[1] + pointerY * matrix[3] + matrix[5];
pointerX = temp;
}
yChange = (pointerY - startPointerY);
xChange = (pointerX - startPointerX);
if (yChange < minimumMovement && yChange > -minimumMovement) {
yChange = 0;
}
if (xChange < minimumMovement && xChange > -minimumMovement) {
xChange = 0;
}
if ((self.lockAxis || self.lockedAxis) && (xChange || yChange)) {
temp = self.lockedAxis;
if (!temp) {
self.lockedAxis = temp = (allowX && Math.abs(xChange) > Math.abs(yChange)) ? "y" : allowY ? "x" : null;
if (temp && typeof(self.vars.onLockAxis) === "function") {
self.vars.onLockAxis.call(self, self.pointerEvent);
}
}
if (temp === "y") {
yChange = 0;
} else if (temp === "x") {
xChange = 0;
}
}
x = startElementX + xChange * dragTolerance;
y = startElementY + yChange * dragTolerance;
}
if ((snapX || snapY || snapXY) && (self.x !== x || (self.y !== y && !rotationMode))) {
if (snapXY) {
_temp1.x = x;
_temp1.y = y;
temp = snapXY(_temp1);
x = temp.x;
y = temp.y;
}
if (snapX) {
x = snapX(x);
}
if (snapY) {
y = snapY(y);
}
} else if (hasBounds) {
if (x > maxX) {
x = maxX + (x - maxX) * edgeTolerance;
} else if (x < minX) {
x = minX + (x - minX) * edgeTolerance;
}
if (!rotationMode) {
if (y > maxY) {
y = maxY + (y - maxY) * edgeTolerance;
} else if (y < minY) {
y = minY + (y - minY) * edgeTolerance;
}
}
}
if (!rotationMode && !matrix) {
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)
return;
}
self.isPressed = false;
var originalEvent = e,
wasDragging = self.isDragging,
isContextMenuRelease = (self.vars.allowContextMenu && e && (e.ctrlKey || e.which > 2)),
placeholderDelayedCall = TweenLite.delayedCall(0.001, removePlaceholder),
touches, i, syntheticEvent, eventTarget, syntheticClick;
if (touchEventTarget) {
_removeListener(touchEventTarget, "touchend", onRelease);
_removeListener(touchEventTarget, "touchmove", onMove);
_removeListener(touchEventTarget, "touchcancel", onRelease);
_removeListener(_doc, "touchstart", _onMultiTouchDocument);
} else {
_removeListener(_doc, "mousemove", onMove);
}
_removeListener(_doc, "mouseup", onRelease);
if (e && e.target) {
_removeListener(e.target, "mouseup", onRelease);
}
dirty = false;
if (isClicking && !isContextMenuRelease) {
if (e) {
_removeListener(e.target, "change", onRelease);
self.pointerEvent = originalEvent;
}
_setSelectable(triggers, false);
_dispatchEvent(self, "release", "onRelease");
_dispatchEvent(self, "click", "onClick");
isClicking = false;
return;
}
_removeFromRenderQueue(render);
if (!rotationMode) {
i = triggers.length;
while (--i > -1) {
_setStyle(triggers[i], "cursor", vars.cursor || (vars.cursor !== false ? "move" : null));
}
}
if (wasDragging) {
dragEndTime = _lastDragTime = _getTime();
self.isDragging = false;
}
_dragCount--;
if (e) {
if (_isOldIE) {
e = _populateIEEvent(e, false);
}
touches = e.changedTouches;
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)
i = touches.length;
while (--i > -1 && (e = touches[i]).identifier !== touchID) {}
if (i < 0) {
return;
}
}
}
self.pointerEvent = originalEvent;
self.pointerX = e.pageX;
self.pointerY = e.pageY;
}
if (isContextMenuRelease && originalEvent) {
originalEvent.preventDefault();
if (originalEvent.preventManipulation) {
originalEvent.preventManipulation(); //for some Microsoft browsers
}
_dispatchEvent(self, "release", "onRelease");
} else if (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".
_dispatchEvent(self, "click", "onClick");
if (_getTime() - clickTime < 300) {
_dispatchEvent(self, "doubleclick", "onDoubleClick");
}
eventTarget = originalEvent.target || originalEvent.srcElement || target; //old IE uses srcElement
clickTime = _getTime();
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 (clickTime !== clickDispatch && self.enabled() && !self.isPressed) {
if (eventTarget.click) { //some browsers (like mobile Safari) don't properly trigger the click event
eventTarget.click();
} else if (_doc.createEvent) {
syntheticEvent = _doc.createEvent("MouseEvents");
syntheticEvent.initMouseEvent("click", true, true, window, 1, self.pointerEvent.screenX, self.pointerEvent.screenY, self.pointerX, self.pointerY, false, false, false, false, 0, null);
eventTarget.dispatchEvent(syntheticEvent);
}
}
};
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.
if (!_isOldIE && !self.allowEventDefault && originalEvent && (vars.dragClickables !== false || !isClickable.call(self, originalEvent.target)) && wasDragging && (!allowNativeTouchScrolling || (touchDragAxis && allowNativeTouchScrolling === touchDragAxis)) && originalEvent.cancelable !== false) {
originalEvent.preventDefault();
if (originalEvent.preventManipulation) {
originalEvent.preventManipulation(); //for some Microsoft browsers
}
}
_dispatchEvent(self, "release", "onRelease");
}
if (isTweening()) {
placeholderDelayedCall.duration( self.tween.duration() ); //sync the timing so that the placeholder DIV gets
}
if (wasDragging) {
_dispatchEvent(self, "dragend", "onDragEnd");
}
return true;
},
updateScroll = function(e) {
if (e && self.isDragging && !scrollProxy) {
var parent = e.target || e.srcElement || target.parentNode,
deltaX = parent.scrollLeft - parent._gsScrollX,
deltaY = parent.scrollTop - parent._gsScrollY;
if (deltaX || deltaY) {
if (matrix) {
startPointerX -= deltaX * matrix[0] + deltaY * matrix[2];
startPointerY -= deltaY * matrix[3] + deltaX * matrix[1];
} else {
startPointerX -= deltaX;
startPointerY -= deltaY;
}
parent._gsScrollX += deltaX;
parent._gsScrollY += deltaY;
setPointerPosition(self.pointerX, self.pointerY);
}
}
},
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.
var time = _getTime(),
recentlyClicked = (time - clickTime < 40),
recentlyDragged = (time - dragEndTime < 40),
alreadyDispatched = (recentlyClicked && clickDispatch === clickTime),
isModern = !!e.preventDefault,
defaultPrevented = (self.pointerEvent && self.pointerEvent.defaultPrevented),
alreadyDispatchedTrusted = (recentlyClicked && trustedClickDispatch === clickTime),
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 (isModern && (alreadyDispatched || (recentlyDragged && self.vars.suppressClickOnDrag !== false) )) {
e.stopImmediatePropagation();
}
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)
if (trusted && alreadyDispatched) {
trustedClickDispatch = clickTime;
}
clickDispatch = clickTime;
return;
}
if (self.isPressed || recentlyDragged || recentlyClicked) {
if (!isModern) {
e.returnValue = false;
} else if (!trusted || !e.detail || !recentlyClicked || defaultPrevented) {
e.preventDefault();
if (e.preventManipulation) {
e.preventManipulation(); //for some Microsoft browsers
}
}
}
},
localizePoint = function(p) {
return matrix ? {x:p.x * matrix[0] + p.y * matrix[2] + matrix[4], y:p.x * matrix[1] + p.y * matrix[3] + matrix[5]} : {x:p.x, y:p.y};
};
old = Draggable.get(this.target);
if (old) {
old.kill(); // avoids duplicates (an element can only be controlled by one Draggable)
}
//give the user access to start/stop dragging...
this.startDrag = function(e, align) {
var r1, r2, p1, p2;
onPress(e || self.pointerEvent, true);
//if the pointer isn't on top of the element, adjust things accordingly
if (align && !self.hitTest(e || self.pointerEvent)) {
r1 = _parseRect(e || self.pointerEvent);
r2 = _parseRect(target);
p1 = localizePoint({x:r1.left + r1.width / 2, y:r1.top + r1.height / 2});
p2 = localizePoint({x:r2.left + r2.width / 2, y:r2.top + r2.height / 2});
startPointerX -= p1.x - p2.x;
startPointerY -= p1.y - p2.y;
}
if (!self.isDragging) {
self.isDragging = true;
_dispatchEvent(self, "dragstart", "onDragStart");
}
};
this.drag = onMove;
this.endDrag = function(e) {
onRelease(e || self.pointerEvent, true);
};
this.timeSinceDrag = function() {
return self.isDragging ? 0 : (_getTime() - dragEndTime) / 1000;
};
this.timeSinceClick = function() {
return (_getTime() - clickTime) / 1000;
};
this.hitTest = function(target, threshold) {
return Draggable.hitTest(self.target, target, threshold);
};
this.getDirection = function(from, diagonalThreshold) { //from can be "start" (default), "velocity", or an element
var mode = (from === "velocity" && ThrowPropsPlugin) ? from : (typeof(from) === "object" && !rotationMode) ? "element" : "start",
xChange, yChange, ratio, direction, r1, r2;
if (mode === "element") {
r1 = _parseRect(self.target);
r2 = _parseRect(from);
}
xChange = (mode === "start") ? self.x - startElementX : (mode === "velocity") ? ThrowPropsPlugin.getVelocity(this.target, xProp) : (r1.left + r1.width / 2) - (r2.left + r2.width / 2);
if (rotationMode) {
return xChange < 0 ? "counter-clockwise" : "clockwise";
} else {
diagonalThreshold = diagonalThreshold || 2;
yChange = (mode === "start") ? self.y - startElementY : (mode === "velocity") ? ThrowPropsPlugin.getVelocity(this.target, yProp) : (r1.top + r1.height / 2) - (r2.top + r2.height / 2);
ratio = Math.abs(xChange / yChange);
direction = (ratio < 1 / diagonalThreshold) ? "" : (xChange < 0) ? "left" : "right";
if (ratio < diagonalThreshold) {
if (direction !== "") {
direction += "-";
}
direction += (yChange < 0) ? "up" : "down";
}
}
return direction;
};
this.applyBounds = function(newBounds) {
var x, y, forceZeroVelocity, e, parent, isRoot;
if (newBounds && vars.bounds !== newBounds) {
vars.bounds = newBounds;
return self.update(true);
}
syncXY(true);
calculateBounds();
if (hasBounds) {
x = self.x;
y = self.y;
if (x > maxX) {
x = maxX;
} else if (x < minX) {
x = minX;
}
if (y > maxY) {
y = maxY;
} else if (y < minY) {
y = minY;
}
if (self.x !== x || self.y !== y) {
forceZeroVelocity = true;
self.x = self.endX = x;
if (rotationMode) {
self.endRotation = x;
} else {
self.y = self.endY = y;
}
dirty = true;
render(true);
if (self.autoScroll && !self.isDragging) {
_recordMaxScrolls(target.parentNode);
e = target;
_windowProxy.scrollTop = ((window.pageYOffset != null) ? window.pageYOffset : (_docElement.scrollTop != null) ? _docElement.scrollTop : _doc.body.scrollTop);
_windowProxy.scrollLeft = ((window.pageXOffset != null) ? window.pageXOffset : (_docElement.scrollLeft != null) ? _docElement.scrollLeft : _doc.body.scrollLeft);
while (e && !isRoot) { //walk up the chain and sense wherever the scrollTop/scrollLeft exceeds the maximum.
isRoot = _isRoot(e.parentNode);
parent = isRoot ? _windowProxy : e.parentNode;
if (allowY && parent.scrollTop > parent._gsMaxScrollY) {
parent.scrollTop = parent._gsMaxScrollY;
}
if (allowX && parent.scrollLeft > parent._gsMaxScrollX) {
parent.scrollLeft = parent._gsMaxScrollX;
}
e = parent;
}
}
}
if (self.isThrowing && (forceZeroVelocity || self.endX > maxX || self.endX < minX || self.endY > maxY || self.endY < minY)) {
animate(vars.throwProps, forceZeroVelocity);
}
}
return self;
};
this.update = function(applyBounds, sticky, ignoreExternalChanges) {
var x = self.x,
y = self.y;
updateMatrix(!sticky);
if (applyBounds) {
self.applyBounds();
} else {
if (dirty && ignoreExternalChanges) {
render(true);
}
syncXY(true);
}
if (sticky) {
setPointerPosition(self.pointerX, self.pointerY);
if (dirty) {
render(true);
}
}
if (self.isPressed && !sticky && ((allowX && Math.abs(x - self.x) > 0.01) || (allowY && (Math.abs(y - self.y) > 0.01 && !rotationMode)))) {
recordStartPositions();
}
if (self.autoScroll) {
_recordMaxScrolls(target.parentNode);
checkAutoScrollBounds = self.isDragging;
render(true);
}
if (self.autoScroll) { //in case reparenting occurred.
_removeScrollListener(target, updateScroll);
_addScrollListener(target, updateScroll);
}
return self;
};
this.enable = function(type) {
var id, i, trigger;
if (type !== "soft") {
i = triggers.length;
while (--i > -1) {
trigger = triggers[i];
_addListener(trigger, "mousedown", onPress);
_addListener(trigger, "touchstart", onPress);
_addListener(trigger, "click", onClick, true); //note: used to pass true for capture but it prevented click-to-play-video functionality in Firefox.
if (!rotationMode && vars.cursor !== false) {
_setStyle(trigger, "cursor", vars.cursor || "move");
}
_setStyle(trigger, "touchCallout", "none");
_setStyle(trigger, "touchAction", (allowX === allowY) ? "none" : allowX ? "pan-y" : "pan-x");
if (_isSVG(trigger)) { // a bug in chrome doesn't respect touch-action on SVG elements - it only works if we set it on the parent SVG.
_setStyle(trigger.ownerSVGElement || trigger, "touchAction", (allowX === allowY) ? "none" : allowX ? "pan-y" : "pan-x");
}
if (!this.vars.allowContextMenu) {
_addListener(trigger, "contextmenu", onContextMenu);
}
}
_setSelectable(triggers, false);
}
_addScrollListener(target, updateScroll);
enabled = true;
if (ThrowPropsPlugin && type !== "soft") {
ThrowPropsPlugin.track(scrollProxy || target, (xyMode ? "x,y" : rotationMode ? "rotation" : "top,left"));
}
if (scrollProxy) {
scrollProxy.enable();
}
target._gsDragID = id = "d" + (_lookupCount++);
_lookup[id] = this;
if (scrollProxy) {
scrollProxy.element._gsDragID = id;
}
TweenLite.set(target, {x:"+=0", overwrite:false, data:"_draggable"}); //simply ensures that there's a _gsTransform on the element.
applyObj = {
t:target,
data:_isOldIE ? cssVars : target._gsTransform,
tween:{},
setRatio:(_isOldIE ? function() { TweenLite.set(target, tempVars); } : CSSPlugin._internals.setTransformRatio || CSSPlugin._internals.set3DTransformRatio)
};
recordStartPositions();
self.update(true);
return self;
};
this.disable = function(type) {
var dragging = self.isDragging,
i, trigger;
if (!rotationMode) {
i = triggers.length;
while (--i > -1) {
_setStyle(triggers[i], "cursor", null);
}
}
if (type !== "soft") {
i = triggers.length;
while (--i > -1) {
trigger = triggers[i];
_setStyle(trigger, "touchCallout", null);
_setStyle(trigger, "touchAction", null);
_removeListener(trigger, "mousedown", onPress);
_removeListener(trigger, "touchstart", onPress);
_removeListener(trigger, "click", onClick);
_removeListener(trigger, "contextmenu", onContextMenu);
}
_setSelectable(triggers, true);
if (touchEventTarget) {
_removeListener(touchEventTarget, "touchcancel", onRelease);
_removeListener(touchEventTarget, "touchend", onRelease);
_removeListener(touchEventTarget, "touchmove", onMove);
}
_removeListener(_doc, "mouseup", onRelease);
_removeListener(_doc, "mousemove", onMove);
}
_removeScrollListener(target, updateScroll);
enabled = false;
if (ThrowPropsPlugin && type !== "soft") {
ThrowPropsPlugin.untrack(scrollProxy || target, (xyMode ? "x,y" : rotationMode ? "rotation" : "top,left"));
}
if (scrollProxy) {
scrollProxy.disable();
}
_removeFromRenderQueue(render);
self.isDragging = self.isPressed = isClicking = false;
if (dragging) {
_dispatchEvent(self, "dragend", "onDragEnd");
}
return self;
};
this.enabled = function(value, type) {
return arguments.length ? (value ? self.enable(type) : self.disable(type)) : enabled;
};
this.kill = function() {
self.isThrowing = false;
TweenLite.killTweensOf(scrollProxy || target, true, killProps);
self.disable();
TweenLite.set(triggers, {clearProps:"userSelect"});
delete _lookup[target._gsDragID];
return self;
};
if (type.indexOf("scroll") !== -1) {
scrollProxy = this.scrollProxy = new ScrollProxy(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)
target.style.overflowY = (allowY && !_isTouchDevice) ? "auto" : "hidden";
target.style.overflowX = (allowX && !_isTouchDevice) ? "auto" : "hidden";
target = scrollProxy.content;
}
if (vars.force3D !== false) {
TweenLite.set(target, {force3D:true}); //improve performance by forcing a GPU layer when possible
}
if (rotationMode) {
killProps.rotation = 1;
} else {
if (allowX) {
killProps[xProp] = 1;
}
if (allowY) {
killProps[yProp] = 1;
}
}
if (rotationMode) {
tempVars = _tempVarsRotation;
cssVars = tempVars.css;
tempVars.overwrite = false;
} else if (xyMode) {
tempVars = (allowX && allowY) ? _tempVarsXY : allowX ? _tempVarsX : _tempVarsY;
cssVars = tempVars.css;
tempVars.overwrite = false;
}
this.enable();
},
p = Draggable.prototype = new EventDispatcher();
p.constructor = Draggable;
p.pointerX = p.pointerY = p.startX = p.startY = p.deltaX = p.deltaY = 0;
p.isDragging = p.isPressed = false;
Draggable.version = "0.17.0";
Draggable.zIndex = 1000;
_addListener(_doc, "touchcancel", function() {
//some older Android devices intermittently stop dispatching "touchmove" events if we don't listen for "touchcancel" on the document. Very strange indeed.
});
_addListener(_doc, "contextmenu", function(e) {
var p;
for (p in _lookup) {
if (_lookup[p].isPressed) {
_lookup[p].endDrag();
}
}
});
Draggable.create = function(targets, vars) {
if (typeof(targets) === "string") {
targets = TweenLite.selector(targets);
}
var a = (!targets || targets.length === 0) ? [] : _isArrayLike(targets) ? _flattenArray(targets) : [targets],
i = a.length;
while (--i > -1) {
a[i] = new Draggable(a[i], vars);
}
return a;
};
Draggable.get = function(target) {
return _lookup[(_unwrapElement(target) || {})._gsDragID];
};
Draggable.timeSinceDrag = function() {
return (_getTime() - _lastDragTime) / 1000;
};
var _tempRect = {}, //reuse to reduce garbage collection tasks
_oldIERect = function(e) { //IE8 doesn't support getBoundingClientRect(), so we use this as a backup.
var top = 0,
left = 0,
width, height;
e = _unwrapElement(e);
width = e.offsetWidth;
height = e.offsetHeight;
while(e) {
top += e.offsetTop;
left += e.offsetLeft;
e = e.offsetParent;
}
return {top: top, left: left, width: width, height: height};
},
_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
if (e === window) {
_tempRect.left = _tempRect.top = 0;
_tempRect.width = _tempRect.right = _docElement.clientWidth || e.innerWidth || _doc.body.clientWidth || 0;
_tempRect.height = _tempRect.bottom = ((e.innerHeight || 0) - 20 < _docElement.clientHeight) ? _docElement.clientHeight : e.innerHeight || _doc.body.clientHeight || 0;
return _tempRect;
}
var r = (e.pageX !== undefined) ? {left:e.pageX - _getDocScrollLeft(), top:e.pageY - _getDocScrollTop(), right:e.pageX - _getDocScrollLeft() + 1, bottom:e.pageY - _getDocScrollTop() + 1} : (!e.nodeType && e.left !== undefined && e.top !== undefined) ? e : _isOldIE ? _oldIERect(e) : _unwrapElement(e).getBoundingClientRect();
if (r.right === undefined && r.width !== undefined) {
r.right = r.left + r.width;
r.bottom = r.top + r.height;
} else if (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.
r = {width: r.right - r.left, height: r.bottom - r.top, right: r.right, left: r.left, bottom: r.bottom, top: r.top};
}
return r;
};
Draggable.hitTest = function(obj1, obj2, threshold) {
if (obj1 === obj2) {
return false;
}
var r1 = _parseRect(obj1),
r2 = _parseRect(obj2),
isOutside = (r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top),
overlap, area, isRatio;
if (isOutside || !threshold) {
return !isOutside;
}
isRatio = ((threshold + "").indexOf("%") !== -1);
threshold = parseFloat(threshold) || 0;
overlap = {left:Math.max(r1.left, r2.left), top:Math.max(r1.top, r2.top)};
overlap.width = Math.min(r1.right, r2.right) - overlap.left;
overlap.height = Math.min(r1.bottom, r2.bottom) - overlap.top;
if (overlap.width < 0 || overlap.height < 0) {
return false;
}
if (isRatio) {
threshold *= 0.01;
area = overlap.width * overlap.height;
return (area >= r1.width * r1.height * threshold || area >= r2.width * r2.height * threshold);
}
return (overlap.width > threshold && overlap.height > threshold);
};
_placeholderDiv.style.cssText = "visibility:hidden;height:1px;top:-1px;pointer-events:none;position:relative;clear:both;";
return Draggable;
}, true);
}); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); }
//export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date)
(function(name) {
"use strict";
var getGlobal = function() {
return (_gsScope.GreenSockGlobals || _gsScope)[name];
};
if (typeof(module) !== "undefined" && module.exports) { //node
require("gsap/umd/TweenLite");
require("gsap/umd/TimelineLite");
require("gsap/umd/CSSPlugin");
module.exports = getGlobal();
} else if (typeof(define) === "function" && define.amd) { //AMD
define(["gsap/umd/TweenLite", "gsap/umd/TimelineLite", "gsap/umd/CSSPlugin"], getGlobal);
}
}("GSDevTools"));