gsap
Version:
GSAP is a framework-agnostic JavaScript animation library that turns developers into animation superheroes. Build high-performance animations that work in **every** major browser. Animate CSS, SVG, canvas, React, Vue, WebGL, colors, strings, motion paths,
1,062 lines (1,013 loc) • 151 kB
JavaScript
/*!
* GSAP 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @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
},
_suppressOverwrites,
_reverting, _context,
_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),
_isTypedArray = (typeof ArrayBuffer === "function" && ArrayBuffer.isView) || function() {}, // note: IE10 has ArrayBuffer, but NOT ArrayBuffer.isView().
_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+\.?\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.
_relExp = /[+-]=-?[.\d]+/,
_delimitedValueExp = /[^,'"\[\]\s]+/gi, // previously /[#\-+.]*\b[a-z\d\-=+%.]+/gi but didn't catch special characters.
_unitExp = /^[+\-=e\s\d]*\d+[.\d]*([a-z]*|%)\s*$/i,
_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,
_startAtRevertConfig = {suppressEvents: true, isStart: true, kill: false},
_revertConfigNoKill = {suppressEvents: true, kill: false},
_revertConfig = {suppressEvents: true},
_reservedProps = {},
_lazyTweens = [],
_lazyLookup = {},
_lastRenderedFrame,
_plugins = {},
_effects = {},
_nextGCFrame = 30,
_harnessPlugins = [],
_callbackNames = "",
_harness = targets => {
let target = targets[0],
harnessPlugin, i;
_isObject(target) || _isFunction(target) || (targets = [targets]);
if (!(harnessPlugin = (target._gsap || {}).harness)) { // find the first target with a harness. We assume targets passed into an animation will be of similar type, meaning the same kind of harness can be used for them all (performance optimization)
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, v) => (v = target[property]) && _isFunction(v) ? target[property]() : (_isUndefined(v) && target.getAttribute && target.getAttribute(property)) || v,
_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,
_roundPrecise = value => Math.round(value * 10000000) / 10000000 || 0, // increased precision mostly for timing values.
_parseRelative = (start, value) => {
let operator = value.charAt(0),
end = parseFloat(value.substr(2));
start = parseFloat(start);
return operator === "+" ? start + end : operator === "-" ? start - end : operator === "*" ? start * end : start / end;
},
_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);
},
_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);
}
},
_isRevertWorthy = (animation) => !!(animation._initted || animation._startAt || animation.add),
_lazySafeRender = (animation, time, suppressEvents, force) => {
_lazyTweens.length && !_reverting && _lazyRender();
animation.render(time, suppressEvents, force || !!(_reverting && time < 0 && _isRevertWorthy(animation)));
_lazyTweens.length && !_reverting && _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 : _isString(value) ? value.trim() : value;
},
_passThrough = p => p,
_setDefaults = (obj, defaults) => {
for (let p in defaults) {
(p in obj) || (obj[p] = defaults[p]);
}
return obj;
},
_setKeyframeDefaults = excludeDuration => (obj, defaults) => {
for (let p in defaults) {
(p in obj) || (p === "duration" && excludeDuration) || 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) {
p !== "__proto__" && p !== "constructor" && p !== "prototype" && (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(_isArray(vars.keyframes)) : _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) => {
child.parent && (!onlyIfParentHasAutoRemove || child.parent.autoRemoveChildren) && child.parent.remove && child.parent.remove(child);
child._act = 0;
},
_uncache = (animation, child) => {
if (animation && (!child || child._end > animation._dur || child._start < 0)) { // performance optimization: if a child animation is passed in we should only uncache if that child EXTENDS the animation (its end time is beyond the end)
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;
},
_rewindStartAt = (tween, totalTime, suppressEvents, force) => tween._startAt && (_reverting ? tween._startAt.revert(_revertConfigNoKill) : (tween.vars.immediateRender && !tween.vars.autoRevert) || tween._startAt.render(totalTime, true, force)),
_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) => {
let whole = Math.floor(tTime = _roundPrecise(tTime / cycleDuration));
return tTime && (whole === tTime) ? whole - 1 : whole;
},
_parentToChildTotalTime = (parentTime, child) => (parentTime - child._start) * child._ts + (child._ts >= 0 ? 0 : (child._dirty ? child.totalDuration() : child._tDur)),
_setEnd = animation => (animation._end = _roundPrecise(animation._start + ((animation._tDur / Math.abs(animation._ts || animation._rts || _tinyNum)) || 0))),
_alignPlayhead = (animation, totalTime) => { // adjusts the animation's _start and _end according to the provided totalTime (only if the parent's smoothChildTiming is true and the animation isn't paused). It doesn't do any rendering or forcing things back into parent timelines, etc. - that's what totalTime() is for.
let parent = animation._dp;
if (parent && parent.smoothChildTiming && animation._ts) {
animation._start = _roundPrecise(parent._time - (animation._ts > 0 ? totalTime / animation._ts : ((animation._dirty ? animation.totalDuration() : animation._tDur) - totalTime) / -animation._ts));
_setEnd(animation);
parent._dirty || _uncache(parent, animation); //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.
}
return animation;
},
/*
_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._dur && child._initted) || (child._start < timeline._time && (child._dur || !child.add))) { // in case, for example, the _start is moved on a tween that has already rendered, or if it's being inserted into a timeline BEFORE where the playhead is currently. 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. Special case: if it's a timeline (has .add() method) and no duration, we can skip rendering because the user may be populating it AFTER adding it to a parent timeline (unconventional, but possible, and we wouldn't want it to get removed if the parent's autoRemoveChildren is true).
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, child)._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 = _roundPrecise((_isNumber(position) ? position : position || timeline !== _globalTimeline ? _parsePosition(timeline, position, child) : timeline._time) + child._delay);
child._end = _roundPrecise(child._start + ((child.totalDuration() / Math.abs(child.timeScale())) || 0));
_addLinkedListItem(timeline, child, "_first", "_last", timeline._sort ? "_start" : 0);
_isFromOrFromStart(child) || (timeline._recent = child);
skipChecks || _postAddChecks(timeline, child);
timeline._ts < 0 && _alignPlayhead(timeline, timeline._tTime); // if the timeline is reversed and the new child makes it longer, we may need to adjust the parent's _start (push it back)
return timeline;
},
_scrollTrigger = (animation, trigger) => (_globals.ScrollTrigger || _missingPlugin("scrollTrigger", trigger)) && _globals.ScrollTrigger.create(trigger, animation),
_attemptInitTween = (tween, time, force, suppressEvents, tTime) => {
_initTween(tween, time, tTime);
if (!tween._initted) {
return 1;
}
if (!force && tween._pt && !_reverting && ((tween._dur && tween.vars.lazy !== false) || (!tween._dur && tween.vars.lazy)) && _lastRenderedFrame !== _ticker.frame) {
_lazyTweens.push(tween);
tween._lazy = [tTime, suppressEvents];
return 1;
}
},
_parentPlayheadIsBeforeStart = ({parent}) => parent && parent._ts && parent._initted && !parent._lock && (parent.rawTime() < 0 || _parentPlayheadIsBeforeStart(parent)), // check parent's _lock because when a timeline repeats/yoyos and does its artificial wrapping, we shouldn't force the ratio back to 0
_isFromOrFromStart = ({data}) => data === "isFromStart" || data === "isStart",
_renderZeroDurationTween = (tween, totalTime, suppressEvents, force) => {
let prevRatio = tween.ratio,
ratio = totalTime < 0 || (!totalTime && ((!tween._start && _parentPlayheadIsBeforeStart(tween) && !(!tween._initted && _isFromOrFromStart(tween))) || ((tween._ts < 0 || tween._dp._ts < 0) && !_isFromOrFromStart(tween)))) ? 0 : 1, // if the tween or its parent is reversed and the totalTime is 0, we should go to a ratio of 0. Edge case: if a from() or fromTo() stagger tween is placed later in a timeline, the "startAt" zero-duration tween could initially render at a time when the parent timeline's playhead is technically BEFORE where this tween is, so make sure that any "from" and "fromTo" startAt tweens are rendered the first time at a ratio of 1.
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);
tween._yoyo && (iteration & 1) && (ratio = 1 - ratio);
if (iteration !== _animationCycle(tween._tTime, repeatDelay)) { // if iteration changed
prevRatio = 1 - ratio;
tween.vars.repeatRefresh && tween._initted && tween.invalidate();
}
}
if (ratio !== prevRatio || _reverting || force || tween._zTime === _tinyNum || (!totalTime && tween._zTime)) {
if (!tween._initted && _attemptInitTween(tween, totalTime, force, suppressEvents, tTime)) { // 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;
}
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;
pt = tween._pt;
while (pt) {
pt.r(ratio, pt.d);
pt = pt._next;
}
totalTime < 0 && _rewindStartAt(tween, totalTime, suppressEvents, 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 && !_reverting) {
_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.data === "isPause" && child._start > prevTime) {
return child;
}
child = child._next;
}
} else {
child = animation._last;
while (child && child._start >= time) {
if (child.data === "isPause" && child._start < prevTime) {
return child;
}
child = child._prev;
}
}
},
_setDuration = (animation, duration, skipUncache, leavePlayhead) => {
let repeat = animation._repeat,
dur = _roundPrecise(duration) || 0,
totalProgress = animation._tTime / animation._tDur;
totalProgress && !leavePlayhead && (animation._time *= dur / animation._dur);
animation._dur = dur;
animation._tDur = !repeat ? dur : repeat < 0 ? 1e10 : _roundPrecise(dur * (repeat + 1) + (animation._rDelay * repeat));
totalProgress > 0 && !leavePlayhead && _alignPlayhead(animation, (animation._tTime = animation._tDur * totalProgress));
animation.parent && _setEnd(animation);
skipUncache || _uncache(animation.parent, animation);
return animation;
},
_onUpdateTotalDuration = animation => (animation instanceof Timeline) ? _uncache(animation) : _setDuration(animation, animation._dur),
_zeroPosition = {_start:0, endTime:_emptyFunc, totalDuration:_emptyFunc},
_parsePosition = (animation, position, percentAnimation) => {
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, isPercent;
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).
offset = position.charAt(0);
isPercent = position.substr(-1) === "%";
i = position.indexOf("=");
if (offset === "<" || offset === ">") {
i >= 0 && (position = position.replace(/=/, ""));
return (offset === "<" ? recent._start : recent.endTime(recent._repeat >= 0)) + (parseFloat(position.substr(1)) || 0) * (isPercent ? (i < 0 ? recent : percentAnimation).totalDuration() / 100 : 1);
}
if (i < 0) {
(position in labels) || (labels[position] = clippedDuration);
return labels[position];
}
offset = parseFloat(position.charAt(i-1) + position.substr(i+1));
if (isPercent && percentAnimation) {
offset = offset / 100 * (_isArray(percentAnimation) ? percentAnimation[0] : percentAnimation).totalDuration();
}
return (i > 1) ? _parsePosition(animation, position.substr(0, i-1), percentAnimation) + offset : clippedDuration + offset;
}
return (position == null) ? clippedDuration : +position;
},
_createTweenType = (type, params, timeline) => {
let isLegacy = _isNumber(params[1]),
varsIndex = (isLegacy ? 2 : 1) + (type < 2 ? 0 : 1),
vars = params[varsIndex],
irVars, parent;
isLegacy && (vars.duration = params[1]);
vars.parent = timeline;
if (type) {
irVars = vars;
parent = timeline;
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);
type < 2 ? (vars.runBackwards = 1) : (vars.startAt = params[varsIndex - 1]); // "from" vars
}
return new Tween(params[0], vars, params[varsIndex + 1]);
},
_conditionalReturn = (value, func) => value || value === 0 ? func(value) : func,
_clamp = (min, max, value) => value < min ? min : value > max ? max : value,
getUnit = (value, v) => !_isString(value) || !(v = _unitExp.exec(value)) ? "" : v[1], // note: protect against padded numbers as strings, like "100.100". That shouldn't return "00" as the unit. If it's numeric, return no unit.
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, scope, leaveStrings) => _context && !scope && _context.selector ? _context.selector(value) : _isString(value) && !leaveStrings && (_coreInitted || !_wake()) ? _slice.call((scope || _doc).querySelectorAll(value), 0) : _isArray(value) ? _flatten(value, leaveStrings) : _isArrayLike(value) ? _slice.call(value, 0) : value ? [value] : [],
selector = value => {
value = toArray(value)[0] || _warn("Invalid scope") || {};
return v => {
let el = value.current || value.nativeElement || value;
return toArray(v, el.querySelectorAll ? el : el === value ? _warn("Invalid scope") || _doc.createElement("div") : 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.random() * i) | 0, 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 < l && wrapAt--;
}
distances = cache[l] = [];
originX = ratios ? (Math.min(wrapAt, l) * ratioX) - .5 : from % wrapAt;
originY = wrapAt === _bigNum ? 0 : 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 _roundPrecise(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 = Math.pow(10, ((v + "").split(".")[1] || "").length); //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 => {
let n = _roundPrecise(Math.round(parseFloat(raw) / v) * v * p);
return (n - n % 1) / p + (_isNumber(raw) ? 0 : getUnit(raw)); // n - n % 1 replaces Math.floor() in order to handle negative values properly. For example, Math.floor(-150.00000000000003) is 151!
};
},
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 - roundingIncrement / 2 + Math.random() * (max - min + roundingIncrement * .99)) / 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], isArray ? 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],
prevContext = _context,
context = animation._ctx,
params, scope, result;
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.
context && (_context = context);
result = params ? callback.apply(scope, params) : callback.call(scope);
_context = prevContext;
return result;
},
_interrupt = animation => {
_removeFromParent(animation);
animation.scrollTrigger && animation.scrollTrigger.kill(!!_reverting);
animation.progress() < 1 && _callback(animation, "onInterrupt");
return animation;
},
_quickTween,
_registerPluginQueue = [],
_createPlugin = config => {
if (!config) return;
config = (!config.name && config.default) || config; // UMD packaging wraps things oddly, so for example MotionPathHelper becomes {MotionPathHelper:MotionPathHelper, default:MotionPathHelper}.
if (_windowExists() || config.headless) { // edge case: some build tools may pass in a null/undefined value
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);
config.register && config.register(gsap, Plugin, PropTween);
} else {
_registerPluginQueue.push(config);
}
},
/*
* --------------------------------------------------------------------------------------
* 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]
},
// possible future idea to replace the hard-coded color name values - put this in the ticker.wake() where we set the _doc:
// let ctx = _doc.createElement("canvas").getContext("2d");
// _forEachName("aqua,lime,silver,black,maroon,teal,blue,navy,white,olive,yellow,orange,gray,purple,green,red,pink,cyan", color => {ctx.fillStyle = color; _colorLookup[color] = splitColor(ctx.fillStyle)});
_hue = (h, m1, m2) => {
h += h < 0 ? 1 : h > 1 ? -1 : 0;
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 < 6) { //for shorthand like #9F0 or #9F0F (could have alpha)
r = v.charAt(1);
g = v.charAt(2);
b = v.charAt(3);
v = "#" + r + r + g + g + b + b + (v.length === 5 ? v.charAt(4) + v.charAt(4) : "");
}
if (v.length === 9) { // hex with alpha, like #fd5e53ff
a = parseInt(v.substr(1, 6), 16);
return [a >> 16, (a >> 8) & _255, a & _255, parseInt(v.substr(7), 16) / 255];
}
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;
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,4}){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 = 1000 / 240,
_nextTime = _gap,
_listeners = [],
_id, _req, _raf, _self, _delta, _i,
_tick = v => {
let elapsed = _getTime() - _lastUpdate,
manual = v === true,
overlap, dispatch, time, frame;
(elapsed > _lagThreshold || elapsed < 0) && (_startTime += elapsed - _adjustedLag);
_lastUpdate += elapsed;
time = _lastUpdate - _startTime;
overlap = time - _nextTime;
if (overlap > 0 || manual) {
frame = ++_self.frame;
_delta = time - _self.time * 1000;
_self.time = time = time / 1000;
_nextTime += overlap + (overlap >= _gap ? 4 : _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.
if (dispatch) {
for (_i = 0; _i < _listeners.length; _i++) { // use _i and check _listeners.length instead of a variable because a listener could get removed during the loop, and if that happens to an element less than the current index, it'd throw things off in the loop.
_listeners[_i](time, _delta, frame, v);
}
}
};
_self = {
time:0,
frame:0,
tick() {
_tick(true);
},
deltaRatio(fps) {
return _delta / (1000 / (fps || 60));
},
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) || {});
_registerPluginQueue.forEach(_createPlugin);
}
_raf = typeof(requestAnimationFrame) !== "undefined" && requestAnimationFrame;
_id && _self.sleep();
_req = _raf || (f => setTimeout(f, (_nextTime - _self.time * 1000 + 1) | 0));
_tickerActive = 1;
_tick(2);
}
},
sleep() {
(_raf ? cancelAnimationFrame : clearTimeout)(_id);
_tickerActive = 0;
_req = _emptyFunc;
},
lagSmoothing(threshold, adjustedLag) {
_lagThreshold = threshold || Infinity; // zero should be interpreted as basically unlimited
_adjustedLag = Math.min(adjustedLag || 33, _lagThreshold);
},
fps(fps) {
_gap = 1000 / (fps || 240);
_nextTime = _self.time * 1000 + _gap;
},
add(callback, once, prioritize) {
let func = once ? (t, d, f, v) => {callback(t, d, f, v); _self.remove(func);} : callback;
_self.remove(callback);
_listeners[prioritize ? "unshift" : "push"](func);
_wake();
return func;
},
remove(callback, i) {
~(i = _listeners.indexOf(callback)) && _listeners.splice(i, 1) && _i >= i && _i--;
},
_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;
},
_valueInParentheses = value => {
let open = value.indexOf("(") + 1,
close = value.indexOf(")"),
nested = value.indexOf("(", open);
return value.substring(open, ~nested && nested < close ? value.indexOf(")", close + 1) : close);
},
_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])] : _valueInParentheses(name).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