gsap
Version:
GSAP is a JavaScript library for building high-performance animations that work in **every** major browser. Animate CSS, SVG, canvas, React, Vue, WebGL, colors, strings, motion paths, generic objects...anything JavaScript can touch! The ScrollTrigger plug
1,172 lines (1,083 loc) • 120 kB
JavaScript
/*!
* GSAP 3.3.4
* https://greensock.com
*
* @license Copyright 2008-2020, GreenSock. All rights reserved.
* Subject to the terms at https://greensock.com/standard-license or for
* Club GreenSock members, the agreement issued with that membership.
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let _config = {
autoSleep: 120,
force3D: "auto",
nullTargetWarn: 1,
units: {lineHeight:""}
},
_defaults = {
duration: .5,
overwrite: false,
delay: 0
},
_bigNum = 1e8,
_tinyNum = 1 / _bigNum,
_2PI = Math.PI * 2,
_HALF_PI = _2PI / 4,
_gsID = 0,
_sqrt = Math.sqrt,
_cos = Math.cos,
_sin = Math.sin,
_isString = value => typeof(value) === "string",
_isFunction = value => typeof(value) === "function",
_isNumber = value => typeof(value) === "number",
_isUndefined = value => typeof(value) === "undefined",
_isObject = value => typeof(value) === "object",
_isNotFalse = value => value !== false,
_windowExists = () => typeof(window) !== "undefined",
_isFuncOrString = value => _isFunction(value) || _isString(value),
_isArray = Array.isArray,
_strictNumExp = /(?:-?\.?\d|\.)+/gi, //only numbers (including negatives and decimals) but NOT relative values.
_numExp = /[-+=.]*\d+[.e\-+]*\d*[e\-\+]*\d*/g, //finds any numbers, including ones that start with += or -=, negative numbers, and ones in scientific notation like 1e-8.
_numWithUnitExp = /[-+=.]*\d+[.e-]*\d*[a-z%]*/g,
_complexStringNumExp = /[-+=.]*\d+(?:\.|e-|e)*\d*/gi, //duplicate so that while we're looping through matches from exec(), it doesn't contaminate the lastIndex of _numExp which we use to search for colors too.
_parenthesesExp = /\(([^()]+)\)/i, //finds the string between parentheses.
_relExp = /[+-]=-?[\.\d]+/,
_delimitedValueExp = /[#\-+.]*\b[a-z\d-=+%.]+/gi,
_globalTimeline, _win, _coreInitted, _doc,
_globals = {},
_installScope = {},
_coreReady,
_install = scope => (_installScope = _merge(scope, _globals)) && gsap,
_missingPlugin = (property, value) => console.warn("Invalid property", property, "set to", value, "Missing plugin? gsap.registerPlugin()"),
_warn = (message, suppress) => !suppress && console.warn(message),
_addGlobal = (name, obj) => (name && (_globals[name] = obj) && (_installScope && (_installScope[name] = obj))) || _globals,
_emptyFunc = () => 0,
_reservedProps = {},
_lazyTweens = [],
_lazyLookup = {},
_lastRenderedFrame,
_plugins = {},
_effects = {},
_nextGCFrame = 30,
_harnessPlugins = [],
_callbackNames = "",
_harness = targets => {
let target = targets[0],
harnessPlugin, i;
if (!_isObject(target) && !_isFunction(target)) {
targets = [targets];
}
if (!(harnessPlugin = (target._gsap || {}).harness)) {
i = _harnessPlugins.length;
while (i-- && !_harnessPlugins[i].targetTest(target)) { }
harnessPlugin = _harnessPlugins[i];
}
i = targets.length;
while (i--) {
(targets[i] && (targets[i]._gsap || (targets[i]._gsap = new GSCache(targets[i], harnessPlugin)))) || targets.splice(i, 1);
}
return targets;
},
_getCache = target => target._gsap || _harness(toArray(target))[0]._gsap,
_getProperty = (target, property) => {
let currentValue = target[property];
return _isFunction(currentValue) ? target[property]() : (_isUndefined(currentValue) && target.getAttribute(property)) || currentValue;
},
_forEachName = (names, func) => ((names = names.split(",")).forEach(func)) || names, //split a comma-delimited list of names into an array, then run a forEach() function and return the split array (this is just a way to consolidate/shorten some code).
_round = value => Math.round(value * 100000) / 100000 || 0,
_arrayContainsAny = (toSearch, toFind) => { //searches one array to find matches for any of the items in the toFind array. As soon as one is found, it returns true. It does NOT return all the matches; it's simply a boolean search.
let l = toFind.length,
i = 0;
for (; toSearch.indexOf(toFind[i]) < 0 && ++i < l;) { }
return (i < l);
},
_parseVars = (params, type, parent) => { //reads the arguments passed to one of the key methods and figures out if the user is defining things with the OLD/legacy syntax where the duration is the 2nd parameter, and then it adjusts things accordingly and spits back the corrected vars object (with the duration added if necessary, as well as runBackwards or startAt or immediateRender). type 0 = to()/staggerTo(), 1 = from()/staggerFrom(), 2 = fromTo()/staggerFromTo()
let isLegacy = _isNumber(params[1]),
varsIndex = (isLegacy ? 2 : 1) + (type < 2 ? 0 : 1),
vars = params[varsIndex],
irVars;
if (isLegacy) {
vars.duration = params[1];
}
vars.parent = parent;
if (type) {
irVars = vars;
while (parent && !("immediateRender" in irVars)) { // inheritance hasn't happened yet, but someone may have set a default in an ancestor timeline. We could do vars.immediateRender = _isNotFalse(_inheritDefaults(vars).immediateRender) but that'd exact a slight performance penalty because _inheritDefaults() also runs in the Tween constructor. We're paying a small kb price here to gain speed.
irVars = parent.vars.defaults || {};
parent = _isNotFalse(parent.vars.inherit) && parent.parent;
}
vars.immediateRender = _isNotFalse(irVars.immediateRender);
if (type < 2) {
vars.runBackwards = 1;
} else {
vars.startAt = params[varsIndex - 1]; // "from" vars
}
}
return vars;
},
_lazyRender = () => {
let l = _lazyTweens.length,
a = _lazyTweens.slice(0),
i, tween;
_lazyLookup = {};
_lazyTweens.length = 0;
for (i = 0; i < l; i++) {
tween = a[i];
tween && tween._lazy && (tween.render(tween._lazy[0], tween._lazy[1], true)._lazy = 0);
}
},
_lazySafeRender = (animation, time, suppressEvents, force) => {
_lazyTweens.length && _lazyRender();
animation.render(time, suppressEvents, force);
_lazyTweens.length && _lazyRender(); //in case rendering caused any tweens to lazy-init, we should render them because typically when someone calls seek() or time() or progress(), they expect an immediate render.
},
_numericIfPossible = value => {
let n = parseFloat(value);
return (n || n === 0) && (value + "").match(_delimitedValueExp).length < 2 ? n : value;
},
_passThrough = p => p,
_setDefaults = (obj, defaults) => {
for (let p in defaults) {
(p in obj) || (obj[p] = defaults[p]);
}
return obj;
},
_setKeyframeDefaults = (obj, defaults) => {
for (let p in defaults) {
if (!(p in obj) && p !== "duration" && p !== "ease") {
obj[p] = defaults[p];
}
}
},
_merge = (base, toMerge) => {
for (let p in toMerge) {
base[p] = toMerge[p];
}
return base;
},
_mergeDeep = (base, toMerge) => {
for (let p in toMerge) {
base[p] = _isObject(toMerge[p]) ? _mergeDeep(base[p] || (base[p] = {}), toMerge[p]) : toMerge[p];
}
return base;
},
_copyExcluding = (obj, excluding) => {
let copy = {},
p;
for (p in obj) {
(p in excluding) || (copy[p] = obj[p]);
}
return copy;
},
_inheritDefaults = vars => {
let parent = vars.parent || _globalTimeline,
func = vars.keyframes ? _setKeyframeDefaults : _setDefaults;
if (_isNotFalse(vars.inherit)) {
while (parent) {
func(vars, parent.vars.defaults);
parent = parent.parent || parent._dp;
}
}
return vars;
},
_arraysMatch = (a1, a2) => {
let i = a1.length,
match = i === a2.length;
while (match && i-- && a1[i] === a2[i]) { }
return i < 0;
},
_addLinkedListItem = (parent, child, firstProp = "_first", lastProp = "_last", sortBy) => {
let prev = parent[lastProp],
t;
if (sortBy) {
t = child[sortBy];
while (prev && prev[sortBy] > t) {
prev = prev._prev;
}
}
if (prev) {
child._next = prev._next;
prev._next = child;
} else {
child._next = parent[firstProp];
parent[firstProp] = child;
}
if (child._next) {
child._next._prev = child;
} else {
parent[lastProp] = child;
}
child._prev = prev;
child.parent = child._dp = parent;
return child;
},
_removeLinkedListItem = (parent, child, firstProp = "_first", lastProp = "_last") => {
let prev = child._prev,
next = child._next;
if (prev) {
prev._next = next;
} else if (parent[firstProp] === child) {
parent[firstProp] = next;
}
if (next) {
next._prev = prev;
} else if (parent[lastProp] === child) {
parent[lastProp] = prev;
}
child._next = child._prev = child.parent = null; // don't delete the _dp just so we can revert if necessary. But parent should be null to indicate the item isn't in a linked list.
},
_removeFromParent = (child, onlyIfParentHasAutoRemove) => {
if (child.parent && (!onlyIfParentHasAutoRemove || child.parent.autoRemoveChildren)) {
child.parent.remove(child);
}
child._act = 0;
},
_uncache = animation => {
let a = animation;
while (a) {
a._dirty = 1;
a = a.parent;
}
return animation;
},
_recacheAncestors = animation => {
let parent = animation.parent;
while (parent && parent.parent) { //sometimes we must force a re-sort of all children and update the duration/totalDuration of all ancestor timelines immediately in case, for example, in the middle of a render loop, one tween alters another tween's timeScale which shoves its startTime before 0, forcing the parent timeline to shift around and shiftChildren() which could affect that next tween's render (startTime). Doesn't matter for the root timeline though.
parent._dirty = 1;
parent.totalDuration();
parent = parent.parent;
}
return animation;
},
_hasNoPausedAncestors = animation => !animation || (animation._ts && _hasNoPausedAncestors(animation.parent)),
_elapsedCycleDuration = animation => animation._repeat ? _animationCycle(animation._tTime, (animation = animation.duration() + animation._rDelay)) * animation : 0,
// feed in the totalTime and cycleDuration and it'll return the cycle (iteration minus 1) and if the playhead is exactly at the very END, it will NOT bump up to the next cycle.
_animationCycle = (tTime, cycleDuration) => (tTime /= cycleDuration) && (~~tTime === tTime) ? ~~tTime - 1 : ~~tTime,
_parentToChildTotalTime = (parentTime, child) => (parentTime - child._start) * child._ts + (child._ts >= 0 ? 0 : (child._dirty ? child.totalDuration() : child._tDur)),
_setEnd = animation => (animation._end = _round(animation._start + ((animation._tDur / Math.abs(animation._ts || animation._rts || _tinyNum)) || 0))),
/*
_totalTimeToTime = (clampedTotalTime, duration, repeat, repeatDelay, yoyo) => {
let cycleDuration = duration + repeatDelay,
time = _round(clampedTotalTime % cycleDuration);
if (time > duration) {
time = duration;
}
return (yoyo && (~~(clampedTotalTime / cycleDuration) & 1)) ? duration - time : time;
},
*/
_postAddChecks = (timeline, child) => {
let t;
if (child._time || (child._initted && !child._dur)) { //in case, for example, the _start is moved on a tween that has already rendered. Imagine it's at its end state, then the startTime is moved WAY later (after the end of this timeline), it should render at its beginning.
t = _parentToChildTotalTime(timeline.rawTime(), child);
if (!child._dur || _clamp(0, child.totalDuration(), t) - child._tTime > _tinyNum) {
child.render(t, true);
}
}
//if the timeline has already ended but the inserted tween/timeline extends the duration, we should enable this timeline again so that it renders properly. We should also align the playhead with the parent timeline's when appropriate.
if (_uncache(timeline)._dp && timeline._initted && timeline._time >= timeline._dur && timeline._ts) {
//in case any of the ancestors had completed but should now be enabled...
if (timeline._dur < timeline.duration()) {
t = timeline;
while (t._dp) {
(t.rawTime() >= 0) && t.totalTime(t._tTime); //moves the timeline (shifts its startTime) if necessary, and also enables it. If it's currently zero, though, it may not be scheduled to render until later so there's no need to force it to align with the current playhead position. Only move to catch up with the playhead.
t = t._dp;
}
}
timeline._zTime = -_tinyNum; // helps ensure that the next render() will be forced (crossingStart = true in render()), even if the duration hasn't changed (we're adding a child which would need to get rendered). Definitely an edge case. Note: we MUST do this AFTER the loop above where the totalTime() might trigger a render() because this _addToTimeline() method gets called from the Animation constructor, BEFORE tweens even record their targets, etc. so we wouldn't want things to get triggered in the wrong order.
}
},
_addToTimeline = (timeline, child, position, skipChecks) => {
child.parent && _removeFromParent(child);
child._start = _round(position + child._delay);
child._end = _round(child._start + ((child.totalDuration() / Math.abs(child.timeScale())) || 0));
_addLinkedListItem(timeline, child, "_first", "_last", timeline._sort ? "_start" : 0);
timeline._recent = child;
skipChecks || _postAddChecks(timeline, child);
return timeline;
},
_scrollTrigger = (animation, trigger) => (_globals.ScrollTrigger || _missingPlugin("scrollTrigger", trigger)) && _globals.ScrollTrigger.create(trigger, animation),
_attemptInitTween = (tween, totalTime, force, suppressEvents) => {
_initTween(tween, totalTime);
if (!tween._initted) {
return 1;
}
if (!force && tween._pt && ((tween._dur && tween.vars.lazy !== false) || (!tween._dur && tween.vars.lazy)) && _lastRenderedFrame !== _ticker.frame) {
_lazyTweens.push(tween);
tween._lazy = [totalTime, suppressEvents];
return 1;
}
},
_renderZeroDurationTween = (tween, totalTime, suppressEvents, force) => {
let prevRatio = tween.ratio,
ratio = totalTime < 0 || (!totalTime && (prevRatio && !tween._start && tween._zTime > _tinyNum && !tween._dp._lock) || (tween._ts < 0 || tween._dp._ts < 0)) ? 0 : 1, // check parent's _lock because when a timeline repeats/yoyos and does its artificial wrapping, we shouldn't force the ratio back to 0. Also, if the tween or its parent is reversed and the totalTime is 0, we should go to a ratio of 0.
repeatDelay = tween._rDelay,
tTime = 0,
pt, iteration, prevIteration;
if (repeatDelay && tween._repeat) { // in case there's a zero-duration tween that has a repeat with a repeatDelay
tTime = _clamp(0, tween._tDur, totalTime);
iteration = _animationCycle(tTime, repeatDelay);
prevIteration = _animationCycle(tween._tTime, repeatDelay);
if (iteration !== prevIteration) {
prevRatio = 1 - ratio;
tween.vars.repeatRefresh && tween._initted && tween.invalidate();
}
}
if (!tween._initted && _attemptInitTween(tween, totalTime, force, suppressEvents)) { // if we render the very beginning (time == 0) of a fromTo(), we must force the render (normal tweens wouldn't need to render at a time of 0 when the prevTime was also 0). This is also mandatory to make sure overwriting kicks in immediately.
return;
}
if (ratio !== prevRatio || force || tween._zTime === _tinyNum || (!totalTime && tween._zTime)) {
prevIteration = tween._zTime;
tween._zTime = totalTime || (suppressEvents ? _tinyNum : 0); // when the playhead arrives at EXACTLY time 0 (right on top) of a zero-duration tween, we need to discern if events are suppressed so that when the playhead moves again (next time), it'll trigger the callback. If events are NOT suppressed, obviously the callback would be triggered in this render. Basically, the callback should fire either when the playhead ARRIVES or LEAVES this exact spot, not both. Imagine doing a timeline.seek(0) and there's a callback that sits at 0. Since events are suppressed on that seek() by default, nothing will fire, but when the playhead moves off of that position, the callback should fire. This behavior is what people intuitively expect.
suppressEvents || (suppressEvents = totalTime && !prevIteration); // if it was rendered previously at exactly 0 (_zTime) and now the playhead is moving away, DON'T fire callbacks otherwise they'll seem like duplicates.
tween.ratio = ratio;
tween._from && (ratio = 1 - ratio);
tween._time = 0;
tween._tTime = tTime;
suppressEvents || _callback(tween, "onStart");
pt = tween._pt;
while (pt) {
pt.r(ratio, pt.d);
pt = pt._next;
}
tween._startAt && totalTime < 0 && tween._startAt.render(totalTime, true, true);
tween._onUpdate && !suppressEvents && _callback(tween, "onUpdate");
tTime && tween._repeat && !suppressEvents && tween.parent && _callback(tween, "onRepeat");
if ((totalTime >= tween._tDur || totalTime < 0) && tween.ratio === ratio) {
ratio && _removeFromParent(tween, 1);
if (!suppressEvents) {
_callback(tween, (ratio ? "onComplete" : "onReverseComplete"), true);
tween._prom && tween._prom();
}
}
} else if (!tween._zTime) {
tween._zTime = totalTime;
}
},
_findNextPauseTween = (animation, prevTime, time) => {
let child;
if (time > prevTime) {
child = animation._first;
while (child && child._start <= time) {
if (!child._dur && child.data === "isPause" && child._start > prevTime) {
return child;
}
child = child._next;
}
} else {
child = animation._last;
while (child && child._start >= time) {
if (!child._dur && child.data === "isPause" && child._start < prevTime) {
return child;
}
child = child._prev;
}
}
},
_setDuration = (animation, duration, skipUncache) => {
let repeat = animation._repeat,
dur = _round(duration) || 0;
animation._dur = dur;
animation._tDur = !repeat ? dur : repeat < 0 ? 1e10 : _round(dur * (repeat + 1) + (animation._rDelay * repeat));
if (animation._time > dur) {
animation._time = dur;
animation._tTime = Math.min(animation._tTime, animation._tDur);
}
!skipUncache && _uncache(animation.parent);
animation.parent && _setEnd(animation);
return animation;
},
_onUpdateTotalDuration = animation => (animation instanceof Timeline) ? _uncache(animation) : _setDuration(animation, animation._dur),
_zeroPosition = {_start:0, endTime:_emptyFunc},
_parsePosition = (animation, position) => {
let labels = animation.labels,
recent = animation._recent || _zeroPosition,
clippedDuration = animation.duration() >= _bigNum ? recent.endTime(false) : animation._dur, //in case there's a child that infinitely repeats, users almost never intend for the insertion point of a new child to be based on a SUPER long value like that so we clip it and assume the most recently-added child's endTime should be used instead.
i, offset;
if (_isString(position) && (isNaN(position) || (position in labels))) { //if the string is a number like "1", check to see if there's a label with that name, otherwise interpret it as a number (absolute value).
i = position.charAt(0);
if (i === "<" || i === ">") {
return (i === "<" ? recent._start : recent.endTime(recent._repeat >= 0)) + (parseFloat(position.substr(1)) || 0);
}
i = position.indexOf("=");
if (i < 0) {
(position in labels) || (labels[position] = clippedDuration);
return labels[position];
}
offset = +(position.charAt(i-1) + position.substr(i+1));
return (i > 1) ? _parsePosition(animation, position.substr(0, i-1)) + offset : clippedDuration + offset;
}
return (position == null) ? clippedDuration : +position;
},
_conditionalReturn = (value, func) => value || value === 0 ? func(value) : func,
_clamp = (min, max, value) => value < min ? min : value > max ? max : value,
getUnit = value => (value + "").substr((parseFloat(value) + "").length),
clamp = (min, max, value) => _conditionalReturn(value, v => _clamp(min, max, v)),
_slice = [].slice,
_isArrayLike = (value, nonEmpty) => value && (_isObject(value) && "length" in value && ((!nonEmpty && !value.length) || ((value.length - 1) in value && _isObject(value[0]))) && !value.nodeType && value !== _win),
_flatten = (ar, leaveStrings, accumulator = []) => ar.forEach(value => (_isString(value) && !leaveStrings) || _isArrayLike(value, 1) ? accumulator.push(...toArray(value)) : accumulator.push(value)) || accumulator,
//takes any value and returns an array. If it's a string (and leaveStrings isn't true), it'll use document.querySelectorAll() and convert that to an array. It'll also accept iterables like jQuery objects.
toArray = (value, leaveStrings) => _isString(value) && !leaveStrings && (_coreInitted || !_wake()) ? _slice.call(_doc.querySelectorAll(value), 0) : _isArray(value) ? _flatten(value, leaveStrings) : _isArrayLike(value) ? _slice.call(value, 0) : value ? [value] : [],
shuffle = a => a.sort(() => .5 - Math.random()), // alternative that's a bit faster and more reliably diverse but bigger: for (let j, v, i = a.length; i; j = Math.floor(Math.random() * i), v = a[--i], a[i] = a[j], a[j] = v); return a;
//for distributing values across an array. Can accept a number, a function or (most commonly) a function which can contain the following properties: {base, amount, from, ease, grid, axis, length, each}. Returns a function that expects the following parameters: index, target, array. Recognizes the following
distribute = v => {
if (_isFunction(v)) {
return v;
}
let vars = _isObject(v) ? v : {each:v}, //n:1 is just to indicate v was a number; we leverage that later to set v according to the length we get. If a number is passed in, we treat it like the old stagger value where 0.1, for example, would mean that things would be distributed with 0.1 between each element in the array rather than a total "amount" that's chunked out among them all.
ease = _parseEase(vars.ease),
from = vars.from || 0,
base = parseFloat(vars.base) || 0,
cache = {},
isDecimal = (from > 0 && from < 1),
ratios = isNaN(from) || isDecimal,
axis = vars.axis,
ratioX = from,
ratioY = from;
if (_isString(from)) {
ratioX = ratioY = {center:.5, edges:.5, end:1}[from] || 0;
} else if (!isDecimal && ratios) {
ratioX = from[0];
ratioY = from[1];
}
return (i, target, a) => {
let l = (a || vars).length,
distances = cache[l],
originX, originY, x, y, d, j, max, min, wrapAt;
if (!distances) {
wrapAt = (vars.grid === "auto") ? 0 : (vars.grid || [1, _bigNum])[1];
if (!wrapAt) {
max = -_bigNum;
while (max < (max = a[wrapAt++].getBoundingClientRect().left) && wrapAt < l) { }
wrapAt--;
}
distances = cache[l] = [];
originX = ratios ? (Math.min(wrapAt, l) * ratioX) - .5 : from % wrapAt;
originY = ratios ? l * ratioY / wrapAt - .5 : (from / wrapAt) | 0;
max = 0;
min = _bigNum;
for (j = 0; j < l; j++) {
x = (j % wrapAt) - originX;
y = originY - ((j / wrapAt) | 0);
distances[j] = d = !axis ? _sqrt(x * x + y * y) : Math.abs((axis === "y") ? y : x);
(d > max) && (max = d);
(d < min) && (min = d);
}
(from === "random") && shuffle(distances);
distances.max = max - min;
distances.min = min;
distances.v = l = (parseFloat(vars.amount) || (parseFloat(vars.each) * (wrapAt > l ? l - 1 : !axis ? Math.max(wrapAt, l / wrapAt) : axis === "y" ? l / wrapAt : wrapAt)) || 0) * (from === "edges" ? -1 : 1);
distances.b = (l < 0) ? base - l : base;
distances.u = getUnit(vars.amount || vars.each) || 0; //unit
ease = (ease && l < 0) ? _invertEase(ease) : ease;
}
l = ((distances[i] - distances.min) / distances.max) || 0;
return _round(distances.b + (ease ? ease(l) : l) * distances.v) + distances.u; //round in order to work around floating point errors
};
},
_roundModifier = v => { //pass in 0.1 get a function that'll round to the nearest tenth, or 5 to round to the closest 5, or 0.001 to the closest 1000th, etc.
let p = v < 1 ? Math.pow(10, (v + "").length - 2) : 1; //to avoid floating point math errors (like 24 * 0.1 == 2.4000000000000004), we chop off at a specific number of decimal places (much faster than toFixed()
return raw => Math.floor(Math.round(parseFloat(raw) / v) * v * p) / p + (_isNumber(raw) ? 0 : getUnit(raw));
},
snap = (snapTo, value) => {
let isArray = _isArray(snapTo),
radius, is2D;
if (!isArray && _isObject(snapTo)) {
radius = isArray = snapTo.radius || _bigNum;
if (snapTo.values) {
snapTo = toArray(snapTo.values);
if ((is2D = !_isNumber(snapTo[0]))) {
radius *= radius; //performance optimization so we don't have to Math.sqrt() in the loop.
}
} else {
snapTo = _roundModifier(snapTo.increment);
}
}
return _conditionalReturn(value, !isArray ? _roundModifier(snapTo) : _isFunction(snapTo) ? raw => {is2D = snapTo(raw); return Math.abs(is2D - raw) <= radius ? is2D : raw; } : raw => {
let x = parseFloat(is2D ? raw.x : raw),
y = parseFloat(is2D ? raw.y : 0),
min = _bigNum,
closest = 0,
i = snapTo.length,
dx, dy;
while (i--) {
if (is2D) {
dx = snapTo[i].x - x;
dy = snapTo[i].y - y;
dx = dx * dx + dy * dy;
} else {
dx = Math.abs(snapTo[i] - x);
}
if (dx < min) {
min = dx;
closest = i;
}
}
closest = (!radius || min <= radius) ? snapTo[closest] : raw;
return (is2D || closest === raw || _isNumber(raw)) ? closest : closest + getUnit(raw);
});
},
random = (min, max, roundingIncrement, returnFunction) => _conditionalReturn(_isArray(min) ? !max : roundingIncrement === true ? !!(roundingIncrement = 0) : !returnFunction, () => _isArray(min) ? min[~~(Math.random() * min.length)] : (roundingIncrement = roundingIncrement || 1e-5) && (returnFunction = roundingIncrement < 1 ? 10 ** ((roundingIncrement + "").length - 2) : 1) && (Math.floor(Math.round((min + Math.random() * (max - min)) / roundingIncrement) * roundingIncrement * returnFunction) / returnFunction)),
pipe = (...functions) => value => functions.reduce((v, f) => f(v), value),
unitize = (func, unit) => value => func(parseFloat(value)) + (unit || getUnit(value)),
normalize = (min, max, value) => mapRange(min, max, 0, 1, value),
_wrapArray = (a, wrapper, value) => _conditionalReturn(value, index => a[~~wrapper(index)]),
wrap = function(min, max, value) { // NOTE: wrap() CANNOT be an arrow function! A very odd compiling bug causes problems (unrelated to GSAP).
let range = max - min;
return _isArray(min) ? _wrapArray(min, wrap(0, min.length), max) : _conditionalReturn(value, value => ((range + (value - min) % range) % range) + min);
},
wrapYoyo = (min, max, value) => {
let range = max - min,
total = range * 2;
return _isArray(min) ? _wrapArray(min, wrapYoyo(0, min.length - 1), max) : _conditionalReturn(value, value => {
value = (total + (value - min) % total) % total || 0;
return min + ((value > range) ? (total - value) : value);
});
},
_replaceRandom = value => { //replaces all occurrences of random(...) in a string with the calculated random value. can be a range like random(-100, 100, 5) or an array like random([0, 100, 500])
let prev = 0,
s = "",
i, nums, end, isArray;
while (~(i = value.indexOf("random(", prev))) {
end = value.indexOf(")", i);
isArray = value.charAt(i + 7) === "[";
nums = value.substr(i + 7, end - i - 7).match(isArray ? _delimitedValueExp : _strictNumExp);
s += value.substr(prev, i - prev) + random(isArray ? nums : +nums[0], +nums[1], +nums[2] || 1e-5);
prev = end + 1;
}
return s + value.substr(prev, value.length - prev);
},
mapRange = (inMin, inMax, outMin, outMax, value) => {
let inRange = inMax - inMin,
outRange = outMax - outMin;
return _conditionalReturn(value, value => outMin + ((((value - inMin) / inRange) * outRange) || 0));
},
interpolate = (start, end, progress, mutate) => {
let func = isNaN(start + end) ? 0 : p => (1 - p) * start + p * end;
if (!func) {
let isString = _isString(start),
master = {},
p, i, interpolators, l, il;
progress === true && (mutate = 1) && (progress = null);
if (isString) {
start = {p: start};
end = {p: end};
} else if (_isArray(start) && !_isArray(end)) {
interpolators = [];
l = start.length;
il = l - 2;
for (i = 1; i < l; i++) {
interpolators.push(interpolate(start[i-1], start[i])); //build the interpolators up front as a performance optimization so that when the function is called many times, it can just reuse them.
}
l--;
func = p => {
p *= l;
let i = Math.min(il, ~~p);
return interpolators[i](p - i);
};
progress = end;
} else if (!mutate) {
start = _merge(_isArray(start) ? [] : {}, start);
}
if (!interpolators) {
for (p in end) {
_addPropTween.call(master, start, p, "get", end[p]);
}
func = p => _renderPropTweens(p, master) || (isString ? start.p : start);
}
}
return _conditionalReturn(progress, func);
},
_getLabelInDirection = (timeline, fromTime, backward) => { //used for nextLabel() and previousLabel()
let labels = timeline.labels,
min = _bigNum,
p, distance, label;
for (p in labels) {
distance = labels[p] - fromTime;
if ((distance < 0) === !!backward && distance && min > (distance = Math.abs(distance))) {
label = p;
min = distance;
}
}
return label;
},
_callback = (animation, type, executeLazyFirst) => {
let v = animation.vars,
callback = v[type],
params, scope;
if (!callback) {
return;
}
params = v[type + "Params"];
scope = v.callbackScope || animation;
executeLazyFirst && _lazyTweens.length && _lazyRender(); //in case rendering caused any tweens to lazy-init, we should render them because typically when a timeline finishes, users expect things to have rendered fully. Imagine an onUpdate on a timeline that reports/checks tweened values.
return params ? callback.apply(scope, params) : callback.call(scope);
},
_interrupt = animation => {
_removeFromParent(animation);
if (animation.progress() < 1) {
_callback(animation, "onInterrupt");
}
return animation;
},
_quickTween,
_createPlugin = config => {
config = !config.name && config.default || config; //UMD packaging wraps things oddly, so for example MotionPathHelper becomes {MotionPathHelper:MotionPathHelper, default:MotionPathHelper}.
let name = config.name,
isFunc = _isFunction(config),
Plugin = (name && !isFunc && config.init) ? function() { this._props = []; } : config, //in case someone passes in an object that's not a plugin, like CustomEase
instanceDefaults = {init:_emptyFunc, render:_renderPropTweens, add:_addPropTween, kill:_killPropTweensOf, modifier:_addPluginModifier, rawVars:0},
statics = {targetTest:0, get:0, getSetter:_getSetter, aliases:{}, register:0};
_wake();
if (config !== Plugin) {
if (_plugins[name]) {
return;
}
_setDefaults(Plugin, _setDefaults(_copyExcluding(config, instanceDefaults), statics)); //static methods
_merge(Plugin.prototype, _merge(instanceDefaults, _copyExcluding(config, statics))); //instance methods
_plugins[(Plugin.prop = name)] = Plugin;
if (config.targetTest) {
_harnessPlugins.push(Plugin);
_reservedProps[name] = 1;
}
name = (name === "css" ? "CSS" : name.charAt(0).toUpperCase() + name.substr(1)) + "Plugin"; //for the global name. "motionPath" should become MotionPathPlugin
}
_addGlobal(name, Plugin);
if (config.register) {
config.register(gsap, Plugin, PropTween);
}
},
/*
* --------------------------------------------------------------------------------------
* COLORS
* --------------------------------------------------------------------------------------
*/
_255 = 255,
_colorLookup = {
aqua:[0,_255,_255],
lime:[0,_255,0],
silver:[192,192,192],
black:[0,0,0],
maroon:[128,0,0],
teal:[0,128,128],
blue:[0,0,_255],
navy:[0,0,128],
white:[_255,_255,_255],
olive:[128,128,0],
yellow:[_255,_255,0],
orange:[_255,165,0],
gray:[128,128,128],
purple:[128,0,128],
green:[0,128,0],
red:[_255,0,0],
pink:[_255,192,203],
cyan:[0,_255,_255],
transparent:[_255,_255,_255,0]
},
_hue = (h, m1, m2) => {
h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h;
return ((((h * 6 < 1) ? m1 + (m2 - m1) * h * 6 : (h < .5) ? m2 : (h * 3 < 2) ? m1 + (m2 - m1) * (2 / 3 - h) * 6 : m1) * _255) + .5) | 0;
},
splitColor = (v, toHSL, forceAlpha) => {
let a = !v ? _colorLookup.black : _isNumber(v) ? [v >> 16, (v >> 8) & _255, v & _255] : 0,
r, g, b, h, s, l, max, min, d, wasHSL;
if (!a) {
if (v.substr(-1) === ",") { //sometimes a trailing comma is included and we should chop it off (typically from a comma-delimited list of values like a textShadow:"2px 2px 2px blue, 5px 5px 5px rgb(255,0,0)" - in this example "blue," has a trailing comma. We could strip it out inside parseComplex() but we'd need to do it to the beginning and ending values plus it wouldn't provide protection from other potential scenarios like if the user passes in a similar value.
v = v.substr(0, v.length - 1);
}
if (_colorLookup[v]) {
a = _colorLookup[v];
} else if (v.charAt(0) === "#") {
if (v.length === 4) { //for shorthand like #9F0
r = v.charAt(1);
g = v.charAt(2);
b = v.charAt(3);
v = "#" + r + r + g + g + b + b;
}
v = parseInt(v.substr(1), 16);
a = [v >> 16, (v >> 8) & _255, v & _255];
} else if (v.substr(0, 3) === "hsl") {
a = wasHSL = v.match(_strictNumExp);
if (!toHSL) {
h = (+a[0] % 360) / 360;
s = +a[1] / 100;
l = +a[2] / 100;
g = (l <= .5) ? l * (s + 1) : l + s - l * s;
r = l * 2 - g;
if (a.length > 3) {
a[3] *= 1; //cast as number
}
a[0] = _hue(h + 1 / 3, r, g);
a[1] = _hue(h, r, g);
a[2] = _hue(h - 1 / 3, r, g);
} else if (~v.indexOf("=")) { //if relative values are found, just return the raw strings with the relative prefixes in place.
a = v.match(_numExp);
forceAlpha && a.length < 4 && (a[3] = 1);
return a;
}
} else {
a = v.match(_strictNumExp) || _colorLookup.transparent;
}
a = a.map(Number);
}
if (toHSL && !wasHSL) {
r = a[0] / _255;
g = a[1] / _255;
b = a[2] / _255;
max = Math.max(r, g, b);
min = Math.min(r, g, b);
l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
h = (max === r) ? (g - b) / d + (g < b ? 6 : 0) : (max === g) ? (b - r) / d + 2 : (r - g) / d + 4;
h *= 60;
}
a[0] = ~~(h + .5);
a[1] = ~~(s * 100 + .5);
a[2] = ~~(l * 100 + .5);
}
forceAlpha && a.length < 4 && (a[3] = 1);
return a;
},
_colorOrderData = v => { // strips out the colors from the string, finds all the numeric slots (with units) and returns an array of those. The Array also has a "c" property which is an Array of the index values where the colors belong. This is to help work around issues where there's a mis-matched order of color/numeric data like drop-shadow(#f00 0px 1px 2px) and drop-shadow(0x 1px 2px #f00). This is basically a helper function used in _formatColors()
let values = [],
c = [],
i = -1;
v.split(_colorExp).forEach(v => {
let a = v.match(_numWithUnitExp) || [];
values.push(...a);
c.push(i += a.length + 1);
});
values.c = c;
return values;
},
_formatColors = (s, toHSL, orderMatchData) => {
let result = "",
colors = (s + result).match(_colorExp),
type = toHSL ? "hsla(" : "rgba(",
i = 0,
c, shell, d, l;
if (!colors) {
return s;
}
colors = colors.map(color => (color = splitColor(color, toHSL, 1)) && type + (toHSL ? color[0] + "," + color[1] + "%," + color[2] + "%," + color[3] : color.join(",")) + ")");
if (orderMatchData) {
d = _colorOrderData(s);
c = orderMatchData.c;
if (c.join(result) !== d.c.join(result)) {
shell = s.replace(_colorExp, "1").split(_numWithUnitExp);
l = shell.length - 1;
for (; i < l; i++) {
result += shell[i] + (~c.indexOf(i) ? colors.shift() || type + "0,0,0,0)" : (d.length ? d : colors.length ? colors : orderMatchData).shift());
}
}
}
if (!shell) {
shell = s.split(_colorExp);
l = shell.length - 1;
for (; i < l; i++) {
result += shell[i] + colors[i];
}
}
return result + shell[l];
},
_colorExp = (function() {
let s = "(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#(?:[0-9a-f]{3}){1,2}\\b", //we'll dynamically build this Regular Expression to conserve file size. After building it, it will be able to find rgb(), rgba(), # (hexadecimal), and named color values like red, blue, purple, etc.,
p;
for (p in _colorLookup) {
s += "|" + p + "\\b";
}
return new RegExp(s + ")", "gi");
})(),
_hslExp = /hsl[a]?\(/,
_colorStringFilter = a => {
let combined = a.join(" "),
toHSL;
_colorExp.lastIndex = 0;
if (_colorExp.test(combined)) {
toHSL = _hslExp.test(combined);
a[1] = _formatColors(a[1], toHSL);
a[0] = _formatColors(a[0], toHSL, _colorOrderData(a[1])); // make sure the order of numbers/colors match with the END value.
return true;
}
},
/*
* --------------------------------------------------------------------------------------
* TICKER
* --------------------------------------------------------------------------------------
*/
_tickerActive,
_ticker = (function() {
let _getTime = Date.now,
_lagThreshold = 500,
_adjustedLag = 33,
_startTime = _getTime(),
_lastUpdate = _startTime,
_gap = 1 / 240,
_nextTime = _gap,
_listeners = [],
_id, _req, _raf, _self,
_tick = v => {
let elapsed = _getTime() - _lastUpdate,
manual = (v === true),
overlap, dispatch;
if (elapsed > _lagThreshold) {
_startTime += elapsed - _adjustedLag;
}
_lastUpdate += elapsed;
_self.time = (_lastUpdate - _startTime) / 1000;
overlap = _self.time - _nextTime;
if (overlap > 0 || manual) {
_self.frame++;
_nextTime += overlap + (overlap >= _gap ? 0.004 : _gap - overlap);
dispatch = 1;
}
manual || (_id = _req(_tick)); //make sure the request is made before we dispatch the "tick" event so that timing is maintained. Otherwise, if processing the "tick" requires a bunch of time (like 15ms) and we're using a setTimeout() that's based on 16.7ms, it'd technically take 31.7ms between frames otherwise.
dispatch && _listeners.forEach(l => l(_self.time, elapsed, _self.frame, v));
};
_self = {
time:0,
frame:0,
tick() {
_tick(true);
},
wake() {
if (_coreReady) {
if (!_coreInitted && _windowExists()) {
_win = _coreInitted = window;
_doc = _win.document || {};
_globals.gsap = gsap;
(_win.gsapVersions || (_win.gsapVersions = [])).push(gsap.version);
_install(_installScope || _win.GreenSockGlobals || (!_win.gsap && _win) || {});
_raf = _win.requestAnimationFrame;
}
_id && _self.sleep();
_req = _raf || (f => setTimeout(f, ((_nextTime - _self.time) * 1000 + 1) | 0));
_tickerActive = 1;
_tick(2);
}
},
sleep() {
(_raf ? _win.cancelAnimationFrame : clearTimeout)(_id);
_tickerActive = 0;
_req = _emptyFunc;
},
lagSmoothing(threshold, adjustedLag) {
_lagThreshold = threshold || (1 / _tinyNum); //zero should be interpreted as basically unlimited
_adjustedLag = Math.min(adjustedLag, _lagThreshold, 0);
},
fps(fps) {
_gap = 1 / (fps || 240);
_nextTime = _self.time + _gap;
},
add(callback) {
_listeners.indexOf(callback) < 0 && _listeners.push(callback);
_wake();
},
remove(callback) {
let i;
~(i = _listeners.indexOf(callback)) && _listeners.splice(i, 1);
},
_listeners:_listeners
};
return _self;
})(),
_wake = () => !_tickerActive && _ticker.wake(), //also ensures the core classes are initialized.
/*
* -------------------------------------------------
* EASING
* -------------------------------------------------
*/
_easeMap = {},
_customEaseExp = /^[\d.\-M][\d.\-,\s]/,
_quotesExp = /["']/g,
_parseObjectInString = value => { //takes a string like "{wiggles:10, type:anticipate})" and turns it into a real object. Notice it ends in ")" and includes the {} wrappers. This is because we only use this function for parsing ease configs and prioritized optimization rather than reusability.
let obj = {},
split = value.substr(1, value.length-3).split(":"),
key = split[0],
i = 1,
l = split.length,
index, val, parsedVal;
for (; i < l; i++) {
val = split[i];
index = i !== l-1 ? val.lastIndexOf(",") : val.length;
parsedVal = val.substr(0, index);
obj[key] = isNaN(parsedVal) ? parsedVal.replace(_quotesExp, "").trim() : +parsedVal;
key = val.substr(index+1).trim();
}
return obj;
},
_configEaseFromString = name => { //name can be a string like "elastic.out(1,0.5)", and pass in _easeMap as obj and it'll parse it out and call the actual function like _easeMap.Elastic.easeOut.config(1,0.5). It will also parse custom ease strings as long as CustomEase is loaded and registered (internally as _easeMap._CE).
let split = (name + "").split("("),
ease = _easeMap[split[0]];
return (ease && split.length > 1 && ease.config) ? ease.config.apply(null, ~name.indexOf("{") ? [_parseObjectInString(split[1])] : _parenthesesExp.exec(name)[1].split(",").map(_numericIfPossible)) : (_easeMap._CE && _customEaseExp.test(name)) ? _easeMap._CE("", name) : ease;
},
_invertEase = ease => p => 1 - ease(1 - p),
// allow yoyoEase to be set in children and have those affected when the parent/ancestor timeline yoyos.
_propagateYoyoEase = (timeline, isYoyo) => {
let child = timeline._first, ease;
while (child) {
if (child instanceof Timeline) {
_propagateYoyoEase(child, isYoyo);
} else if (child.vars.yoyoEase && (!child._yoyo || !child._repeat) && child._yoyo !== isYoyo) {
if (child.timeline) {
_propagateYoyoEase(child.timeline, isYoyo);
} else {
ease = child._ease;
child._ease = child._yEase;
child._yEase = ease;
child._yoyo = isYoyo;
}
}
child = child._next;
}
},
_parseEase = (ease, defaultEase) => !ease ? defaultEase : (_isFunction(ease) ? ease : _easeMap[ease] || _configEaseFromString(ease)) || defaultEase,
_insertEase = (names, easeIn, easeOut = p => 1 - easeIn(1 - p), easeInOut = (p => p < .5 ? easeIn(p * 2) / 2 : 1 - easeIn((1 - p) * 2) / 2)) => {
let ease = {easeIn, easeOut, easeInOut},
lowercaseName;
_forEachName(names, name => {
_easeMap[name] = _globals[name] = ease;
_easeMap[(lowercaseName = name.toLowerCase())] = easeOut;
for (let p in ease) {
_easeMap[lowercaseName + (p === "easeIn" ? ".in" : p === "easeOut" ? ".out" : ".inOut")] = _easeMap[name + "." + p] = ease[p];
}
});
return ease;
},
_easeInOutFromOut = easeOut => (p => p < .5 ? (1 - easeOut(1 - (p * 2))) / 2 : .5 + easeOut((p - .5) * 2) / 2),
_configElastic = (type, amplitude, period) => {
let p1 = (amplitude >= 1) ? amplitude : 1, //note: if amplitude is < 1, we simply adjust the period for a more natural feel. Otherwise the math doesn't work right and the curve starts at 1.
p2 = (period || (type ? .3 : .45)) / (amplitude < 1 ? amplitude : 1),
p3 = p2 / _2PI * (Math.asin(1 / p1) || 0),
easeOut = p => p === 1 ? 1 : p1 * (2 ** (-10 * p)) * _sin((p - p3) * p2) + 1,
ease = (type === "out") ? easeOut : (type === "in") ? p => 1 - easeOut(1 - p) : _easeInOutFromOut(easeOut);
p2 = _2PI / p2; //precalculate to optimize
ease.config = (amplitude, period) => _configElastic(type, amplitude, period);
return ease;
},
_configBack = (type, overshoot = 1.70158) => {
let easeOut = p => p ? ((--p) * p * ((overshoot + 1) * p + overshoot) + 1) : 0,
ease = (type === "out") ? easeOut : (type === "in") ? p => 1 - easeOut(1 - p) : _easeInOutFromOut(easeOut);
ease.config = overshoot => _configBack(type, overshoot);
return ease;
};
// a cheaper (kb and cpu) but more mild way to get a parameterized weighted ease by feeding in a value between -1 (easeIn) and 1 (easeOut) where 0 is linear.
// _weightedEase = ratio => {
// let y = 0.5 + ratio / 2;
// return p => (2 * (1 - p) * p * y + p * p);
// },
// a stronger (but more expensive kb/cpu) parameterized weighted ease that lets you feed in a value between -1 (easeIn) and 1 (easeOut) where 0 is linear.
// _weightedEaseStrong = ratio => {
// ratio = .5 + ratio / 2;
// let o = 1 / 3 * (ratio < .5 ? ratio : 1 - ratio),
// b = ratio - o,
// c = ratio + o;
// return p => p === 1 ? p : 3 * b * (1 - p) * (1 - p) * p + 3 * c * (1 - p) * p * p + p * p * p;
// };
_forEachName("Linear,Quad,Cubic,Quart,Quint,Strong", (name, i) => {
let power = i < 5 ? i + 1 : i;
_insertEase(name + ",Power" + (power - 1), i ? p => p ** power : p => p, p => 1 - (1 - p) ** power, p => p < .5 ? (p * 2) ** power / 2 : 1 - ((1 - p) * 2) ** power / 2);
});
_easeMap.Linear.easeNone = _easeMap.none = _easeMap.Linear.easeIn;
_insertEase("Elastic", _configElastic("in"), _configElastic("out"), _configElastic());
((n, c) => {
let n1 = 1 / c,
n2 = 2 * n1,
n3 = 2.5 * n1,
easeOut = p => (p < n1) ? n * p * p : (p < n2) ? n * (p - 1.5 / c) ** 2 + .75 : (p < n3) ? n * (p -= 2.25 / c) * p + .9375 : n * (p - 2.625 / c) ** 2 + .984375;
_insertEase("Bounce", p => 1 - easeOut(1 - p), easeOut);
})(7.5625, 2.75);
_insertEase("Expo", p => p ? 2 ** (10 * (p - 1)) : 0);
_insertEase("Circ", p => -(_sqrt(1 - (p * p)) - 1));
_insertEase("Sine", p => p === 1 ? 1 : -_cos(p * _HALF_PI) + 1);
_insertEase("Back", _configBack("in"), _configBack("out"), _configBack());
_easeMap.SteppedEase = _easeMap.steps = _globals.SteppedEase = {
config(steps = 1, immediateStart) {
let p1 = 1 / steps,
p2 = steps + (immediateStart ? 0 : 1),
p3 = immediateStart ? 1 : 0,
max = 1 - _tinyNum;
return p => (((p2 * _clamp(0, max, p)) | 0) + p3) * p1;
}
};
_defaults.ease = _easeMap["quad.out"];
_forEachName("onComplete,onUpdate,onStart,onRepeat,onReverseComplete,onInterrupt", name => _callbackNames += name + "," + name + "Params,");
/*
* --------------------------------------------------------------------------------------
* CACHE
* --------------------------------------------------------------------------------------
*/
export class GSCache {
constructor(target, harness) {
this.id = _gsID++;
target._gsap = this;
this.target = target;
this.harness = harness;
this.get = harness ? harness.get : _getProperty;
this.set = harness ? harness.getSetter : _getSetter;
}
}
/*
* --------------------------------------------------------------------------------------
* ANIMATION
* --------------------------------------------------------------------------------------
*/
export class Animation {
constructor(vars, time) {
let parent = vars.parent || _globalTimeline;
this.vars = vars;
this._delay = +vars.delay || 0;
if ((this._repeat = vars.repeat || 0)) {
this._rDelay = vars.repeatDelay || 0;
this._yoyo = !!vars.yoyo || !!vars.yoyoEase;
}
this._ts = 1;
_setDuration(this, +vars.duration, 1);
this.data = vars.data;
_tickerActive || _ticker.wake();
parent && _addToTimeline(parent, this, (time || time === 0) ? time : parent._time, 1);
vars.reversed && this.reverse();
vars.paused && this.paused(true);
}
delay(value) {
if (value || value === 0) {
this.parent && this.parent.smoothChildTiming && (this.startTime(this._start + value - this._delay));
this._delay = value;
return this;
}
return this._delay;
}
duration(value) {
return arguments.length ? this.totalDuration(this._repeat > 0 ? value + (value + this._rDelay) * this._repeat : value) : this.totalDuration() && this._dur;
}
totalDuration(value) {
if (!arguments.length) {
return this._tDur;
}
this._dirty = 0;
return _setDuration(this, this._repeat < 0 ? value : (value - (this._repeat * this._rDelay)) / (this._repeat + 1));
}
totalTime(totalTime, suppressEvents) {
_wake();
if (!arguments.length) {
return this._tTime;
}
let parent = this.parent || this._dp;
if (parent && parent.smoothChildTiming && this._ts) {
// if (!parent._dp && parent._time === parent._dur) { // if a root timeline completes...and then a while later one of its children resumes, we must shoot the playhead forward to where it should be raw-wise, otherwise the child will jump to the end. Down side: this assumes it's using the _ticker.time as a reference.
// parent._time = _ticker.time - parent._start;
// }
this._start = _round(parent._time - (this._ts > 0 ? totalTime / this._ts : ((this._dirty ? this.totalDuration() : this._tDur) - totalTime) / -this._ts));
_setEnd(this);
parent._dirty || _uncache(parent); //for performance improvement. If the parent's cache is already dirty, it already took care of marking the ancestors as dirty too, so skip the function call here.
//in case any of the ancestor timelines had completed but should now be enabled, we should reset their totalTime() which will also ensure that they're lined up properly and enabled. Skip for animations that are on the root (wasteful). Example: a TimelineLite.exportRoot() is performed when there's a paused tween on the root, the export will not complete until that tween is unpaused, but imagine a child gets restarted later, after all [unpaused] tweens have completed. The start of that child would get pushed out, but one of the ancestors may have completed.
while (parent.parent) {
if (parent.parent._time !== parent._start + (parent._ts >= 0 ? parent._tTime / parent._ts : (parent.totalDuration() - parent._tTime) / -parent._ts)) {
parent.totalTime(parent._tTime, true);
}
parent = parent.parent;
}
if (!this.parent && this._dp.autoRemoveChildren && ((this._ts > 0 && totalTime < this._tDur) || (this._ts < 0 && totalTime > 0) || (!this._tDur && !totalTime) )) { //if the animation doesn't have a parent, put it back into its last parent (recorded as _dp for exactly cases like this). Limit to parents with autoRemoveChildren (like globalTimeline) so that if the user manually removes an animation from a timeline and then alters its playhead, it