1226 lines
74 KiB
JavaScript
1226 lines
74 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 */
|
||
|
|
||
|
import { TweenLite, _gsScope, globals, Animation, SimpleTimeline, TweenPlugin, Power2, Power3, Linear } from "gsap/TweenLite.js";
|
||
|
import TimelineLite from "gsap/TimelineLite.js";
|
||
|
import Draggable from "gsap/Draggable.js";
|
||
|
import CSSPlugin from "gsap/CSSPlugin.js";
|
||
|
import AttrPlugin from "gsap/AttrPlugin.js";
|
||
|
|
||
|
TweenPlugin.activate([CSSPlugin, AttrPlugin]); // to ensure treeshaking doesn't dump it.
|
||
|
|
||
|
_gsScope._gsDefine("GSDevTools", ["TweenLite", "core.Animation", "core.SimpleTimeline", "TimelineLite", "utils.Draggable", "plugins.CSSPlugin"], function() {
|
||
|
|
||
|
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.m
|
||
|
_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.
|
||
|
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);
|
||
|
|
||
|
export var GSDevTools = globals.GSDevTools;
|
||
|
export { GSDevTools as default };
|