animejs 4.0.2 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -47
- package/lib/anime.cjs +2 -2
- package/lib/anime.esm.js +861 -233
- package/lib/anime.esm.min.js +2 -2
- package/lib/anime.iife.js +2 -2
- package/lib/anime.iife.min.js +2 -2
- package/lib/anime.min.cjs +2 -2
- package/lib/anime.umd.js +2 -2
- package/lib/anime.umd.min.js +2 -2
- package/lib/gui/index.js +6341 -0
- package/package.json +1 -1
- package/types/index.d.ts +176 -117
- package/types/index.js +692 -158
package/types/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* anime.js - ESM
|
|
3
|
-
* @version v4.
|
|
3
|
+
* @version v4.1.1
|
|
4
4
|
* @author Julian Garnier
|
|
5
5
|
* @license MIT
|
|
6
6
|
* @copyright (c) 2025 Julian Garnier
|
|
@@ -56,10 +56,13 @@ const K = 1e3;
|
|
|
56
56
|
const maxFps = 120;
|
|
57
57
|
// Strings
|
|
58
58
|
const emptyString = '';
|
|
59
|
-
const shortTransforms =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const shortTransforms = /*#__PURE__*/ (() => {
|
|
60
|
+
const map = new Map();
|
|
61
|
+
map.set('x', 'translateX');
|
|
62
|
+
map.set('y', 'translateY');
|
|
63
|
+
map.set('z', 'translateZ');
|
|
64
|
+
return map;
|
|
65
|
+
})();
|
|
63
66
|
const validTransforms = [
|
|
64
67
|
'translateX',
|
|
65
68
|
'translateY',
|
|
@@ -79,7 +82,7 @@ const validTransforms = [
|
|
|
79
82
|
'matrix',
|
|
80
83
|
'matrix3d',
|
|
81
84
|
];
|
|
82
|
-
const transformsFragmentStrings = validTransforms.reduce((a, v) => ({ ...a, [v]: v + '(' }), {});
|
|
85
|
+
const transformsFragmentStrings = /*#__PURE__*/ validTransforms.reduce((a, v) => ({ ...a, [v]: v + '(' }), {});
|
|
83
86
|
// Functions
|
|
84
87
|
/** @return {void} */
|
|
85
88
|
const noop = () => { };
|
|
@@ -123,13 +126,15 @@ const defaults = {
|
|
|
123
126
|
onComplete: noop,
|
|
124
127
|
onRender: noop,
|
|
125
128
|
};
|
|
129
|
+
const scope = {
|
|
130
|
+
/** @type {Scope} */
|
|
131
|
+
current: null,
|
|
132
|
+
/** @type {Document|DOMTarget} */
|
|
133
|
+
root: doc,
|
|
134
|
+
};
|
|
126
135
|
const globals = {
|
|
127
136
|
/** @type {DefaultsParams} */
|
|
128
137
|
defaults,
|
|
129
|
-
/** @type {Document|DOMTarget} */
|
|
130
|
-
root: doc,
|
|
131
|
-
/** @type {Scope} */
|
|
132
|
-
scope: null,
|
|
133
138
|
/** @type {Number} */
|
|
134
139
|
precision: 4,
|
|
135
140
|
/** @type {Number} */
|
|
@@ -137,7 +142,7 @@ const globals = {
|
|
|
137
142
|
/** @type {Number} */
|
|
138
143
|
tickThreshold: 200,
|
|
139
144
|
};
|
|
140
|
-
const globalVersions = { version: '4.
|
|
145
|
+
const globalVersions = { version: '4.1.1', engine: null };
|
|
141
146
|
if (isBrowser) {
|
|
142
147
|
if (!win.AnimeJS)
|
|
143
148
|
win.AnimeJS = [];
|
|
@@ -244,6 +249,28 @@ const snap = (v, increment) => isArr(increment) ? increment.reduce((closest, cv)
|
|
|
244
249
|
* @return {Number}
|
|
245
250
|
*/
|
|
246
251
|
const interpolate = (start, end, progress) => start + (end - start) * progress;
|
|
252
|
+
/**
|
|
253
|
+
* @param {Number} min
|
|
254
|
+
* @param {Number} max
|
|
255
|
+
* @param {Number} [decimalLength]
|
|
256
|
+
* @return {Number}
|
|
257
|
+
*/
|
|
258
|
+
const random = (min, max, decimalLength) => { const m = 10 ** (decimalLength || 0); return floor((Math.random() * (max - min + (1 / m)) + min) * m) / m; };
|
|
259
|
+
/**
|
|
260
|
+
* Adapted from https://bost.ocks.org/mike/shuffle/
|
|
261
|
+
* @param {Array} items
|
|
262
|
+
* @return {Array}
|
|
263
|
+
*/
|
|
264
|
+
const shuffle = items => {
|
|
265
|
+
let m = items.length, t, i;
|
|
266
|
+
while (m) {
|
|
267
|
+
i = random(0, --m);
|
|
268
|
+
t = items[m];
|
|
269
|
+
items[m] = items[i];
|
|
270
|
+
items[i] = t;
|
|
271
|
+
}
|
|
272
|
+
return items;
|
|
273
|
+
};
|
|
247
274
|
/**
|
|
248
275
|
* @param {Number} v
|
|
249
276
|
* @return {Number}
|
|
@@ -332,6 +359,32 @@ const addChild = (parent, child, sortMethod, prevProp = '_prev', nextProp = '_ne
|
|
|
332
359
|
child[prevProp] = prev;
|
|
333
360
|
child[nextProp] = next;
|
|
334
361
|
};
|
|
362
|
+
/**
|
|
363
|
+
* @param {(...args: any[]) => Tickable | ((...args: any[]) => void)} constructor
|
|
364
|
+
* @return {(...args: any[]) => Tickable | ((...args: any[]) => void)}
|
|
365
|
+
*/
|
|
366
|
+
const createRefreshable = constructor => {
|
|
367
|
+
/** @type {Tickable} */
|
|
368
|
+
let tracked;
|
|
369
|
+
return (...args) => {
|
|
370
|
+
let currentIteration, currentIterationProgress, reversed, alternate;
|
|
371
|
+
if (tracked) {
|
|
372
|
+
currentIteration = tracked.currentIteration;
|
|
373
|
+
currentIterationProgress = tracked.iterationProgress;
|
|
374
|
+
reversed = tracked.reversed;
|
|
375
|
+
alternate = tracked._alternate;
|
|
376
|
+
tracked.revert();
|
|
377
|
+
}
|
|
378
|
+
const cleanup = constructor(...args);
|
|
379
|
+
if (cleanup && !isFnc(cleanup) && cleanup.revert)
|
|
380
|
+
tracked = cleanup;
|
|
381
|
+
if (!isUnd(currentIterationProgress)) {
|
|
382
|
+
/** @type {Tickable} */ (tracked).currentIteration = currentIteration;
|
|
383
|
+
/** @type {Tickable} */ (tracked).iterationProgress = (alternate ? !(currentIteration % 2) ? reversed : !reversed : reversed) ? 1 - currentIterationProgress : currentIterationProgress;
|
|
384
|
+
}
|
|
385
|
+
return cleanup || noop;
|
|
386
|
+
};
|
|
387
|
+
};
|
|
335
388
|
|
|
336
389
|
/*
|
|
337
390
|
* Base class to control framerate and playback rate.
|
|
@@ -801,8 +854,8 @@ const addAdditiveAnimation = lookups => {
|
|
|
801
854
|
return animation;
|
|
802
855
|
};
|
|
803
856
|
|
|
804
|
-
const engineTickMethod = isBrowser ? requestAnimationFrame : setImmediate;
|
|
805
|
-
const engineCancelMethod = isBrowser ? cancelAnimationFrame : clearImmediate;
|
|
857
|
+
const engineTickMethod = /*#__PURE__*/ (() => isBrowser ? requestAnimationFrame : setImmediate)();
|
|
858
|
+
const engineCancelMethod = /*#__PURE__*/ (() => isBrowser ? cancelAnimationFrame : clearImmediate)();
|
|
806
859
|
class Engine extends Clock {
|
|
807
860
|
/** @param {Number} [initTime] */
|
|
808
861
|
constructor(initTime) {
|
|
@@ -958,7 +1011,7 @@ const parseInlineTransforms = (target, propName, animationInlineStyles) => {
|
|
|
958
1011
|
* @return {NodeList|HTMLCollection}
|
|
959
1012
|
*/
|
|
960
1013
|
function getNodeList(v) {
|
|
961
|
-
const n = isStr(v) ?
|
|
1014
|
+
const n = isStr(v) ? scope.root.querySelectorAll(v) : v;
|
|
962
1015
|
if (n instanceof NodeList || n instanceof HTMLCollection)
|
|
963
1016
|
return n;
|
|
964
1017
|
}
|
|
@@ -1803,8 +1856,8 @@ class Timer extends Clock {
|
|
|
1803
1856
|
constructor(parameters = {}, parent = null, parentPosition = 0) {
|
|
1804
1857
|
super(0);
|
|
1805
1858
|
const { id, delay, duration, reversed, alternate, loop, loopDelay, autoplay, frameRate, playbackRate, onComplete, onLoop, onPause, onBegin, onBeforeUpdate, onUpdate, } = parameters;
|
|
1806
|
-
if (
|
|
1807
|
-
|
|
1859
|
+
if (scope.current)
|
|
1860
|
+
scope.current.register(this);
|
|
1808
1861
|
const timerInitTime = parent ? 0 : engine._elapsedTime;
|
|
1809
1862
|
const timerDefaults = parent ? parent.defaults : globals.defaults;
|
|
1810
1863
|
const timerDelay = /** @type {Number} */ (isFnc(delay) || isUnd(delay) ? timerDefaults.delay : +delay);
|
|
@@ -1924,14 +1977,14 @@ class Timer extends Clock {
|
|
|
1924
1977
|
this.currentTime = (this.iterationDuration * this._currentIteration) + time;
|
|
1925
1978
|
}
|
|
1926
1979
|
get progress() {
|
|
1927
|
-
return clamp(round(this._currentTime / this.duration,
|
|
1980
|
+
return clamp(round(this._currentTime / this.duration, 10), 0, 1);
|
|
1928
1981
|
}
|
|
1929
1982
|
/** @param {Number} progress */
|
|
1930
1983
|
set progress(progress) {
|
|
1931
1984
|
this.currentTime = this.duration * progress;
|
|
1932
1985
|
}
|
|
1933
1986
|
get iterationProgress() {
|
|
1934
|
-
return clamp(round(this._iterationTime / this.iterationDuration,
|
|
1987
|
+
return clamp(round(this._iterationTime / this.iterationDuration, 10), 0, 1);
|
|
1935
1988
|
}
|
|
1936
1989
|
/** @param {Number} progress */
|
|
1937
1990
|
set iterationProgress(progress) {
|
|
@@ -3090,12 +3143,13 @@ class JSAnimation extends Timer {
|
|
|
3090
3143
|
*/
|
|
3091
3144
|
refresh() {
|
|
3092
3145
|
forEachChildren(this, (/** @type {Tween} */ tween) => {
|
|
3093
|
-
const
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3146
|
+
const tweenFunc = tween._func;
|
|
3147
|
+
if (tweenFunc) {
|
|
3148
|
+
const ogValue = getOriginalAnimatableValue(tween.target, tween.property, tween._tweenType);
|
|
3149
|
+
decomposeRawValue(ogValue, decomposedOriginalValue);
|
|
3150
|
+
decomposeRawValue(tweenFunc(), toTargetObject);
|
|
3151
|
+
tween._fromNumbers = cloneArray(decomposedOriginalValue.d);
|
|
3152
|
+
tween._fromNumber = decomposedOriginalValue.n;
|
|
3099
3153
|
tween._toNumbers = cloneArray(toTargetObject.d);
|
|
3100
3154
|
tween._strings = cloneArray(toTargetObject.s);
|
|
3101
3155
|
tween._toNumber = toTargetObject.n;
|
|
@@ -3243,31 +3297,8 @@ const commonDefaultPXProperties = [
|
|
|
3243
3297
|
'borderRadius',
|
|
3244
3298
|
...transformsShorthands
|
|
3245
3299
|
];
|
|
3246
|
-
const validIndividualTransforms = [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))];
|
|
3247
|
-
|
|
3248
|
-
let transformsPropertiesRegistered = isBrowser && (isUnd(CSS) || !Object.hasOwnProperty.call(CSS, 'registerProperty'));
|
|
3249
|
-
const registerTransformsProperties = () => {
|
|
3250
|
-
if (transformsPropertiesRegistered)
|
|
3251
|
-
return;
|
|
3252
|
-
validTransforms.forEach(t => {
|
|
3253
|
-
const isSkew = stringStartsWith(t, 'skew');
|
|
3254
|
-
const isScale = stringStartsWith(t, 'scale');
|
|
3255
|
-
const isRotate = stringStartsWith(t, 'rotate');
|
|
3256
|
-
const isTranslate = stringStartsWith(t, 'translate');
|
|
3257
|
-
const isAngle = isRotate || isSkew;
|
|
3258
|
-
const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
|
|
3259
|
-
try {
|
|
3260
|
-
CSS.registerProperty({
|
|
3261
|
-
name: '--' + t,
|
|
3262
|
-
syntax,
|
|
3263
|
-
inherits: false,
|
|
3264
|
-
initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
|
|
3265
|
-
});
|
|
3266
|
-
}
|
|
3267
|
-
catch { }
|
|
3268
|
-
});
|
|
3269
|
-
transformsPropertiesRegistered = true;
|
|
3270
|
-
};
|
|
3300
|
+
const validIndividualTransforms = /*#__PURE__*/ (() => [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))])();
|
|
3301
|
+
let transformsPropertiesRegistered = null;
|
|
3271
3302
|
const WAAPIAnimationsLookups = {
|
|
3272
3303
|
_head: null,
|
|
3273
3304
|
_tail: null,
|
|
@@ -3380,9 +3411,34 @@ class WAAPIAnimation {
|
|
|
3380
3411
|
* @param {WAAPIAnimationParams} params
|
|
3381
3412
|
*/
|
|
3382
3413
|
constructor(targets, params) {
|
|
3383
|
-
if (
|
|
3384
|
-
|
|
3385
|
-
|
|
3414
|
+
if (scope.current)
|
|
3415
|
+
scope.current.register(this);
|
|
3416
|
+
// Skip the registration and fallback to no animation in case CSS.registerProperty is not supported
|
|
3417
|
+
if (isNil(transformsPropertiesRegistered)) {
|
|
3418
|
+
if (isBrowser && (isUnd(CSS) || !Object.hasOwnProperty.call(CSS, 'registerProperty'))) {
|
|
3419
|
+
transformsPropertiesRegistered = false;
|
|
3420
|
+
}
|
|
3421
|
+
else {
|
|
3422
|
+
validTransforms.forEach(t => {
|
|
3423
|
+
const isSkew = stringStartsWith(t, 'skew');
|
|
3424
|
+
const isScale = stringStartsWith(t, 'scale');
|
|
3425
|
+
const isRotate = stringStartsWith(t, 'rotate');
|
|
3426
|
+
const isTranslate = stringStartsWith(t, 'translate');
|
|
3427
|
+
const isAngle = isRotate || isSkew;
|
|
3428
|
+
const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*";
|
|
3429
|
+
try {
|
|
3430
|
+
CSS.registerProperty({
|
|
3431
|
+
name: '--' + t,
|
|
3432
|
+
syntax,
|
|
3433
|
+
inherits: false,
|
|
3434
|
+
initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0',
|
|
3435
|
+
});
|
|
3436
|
+
}
|
|
3437
|
+
catch { }
|
|
3438
|
+
});
|
|
3439
|
+
transformsPropertiesRegistered = true;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3386
3442
|
const parsedTargets = registerTargets(targets);
|
|
3387
3443
|
const targetsLength = parsedTargets.length;
|
|
3388
3444
|
if (!targetsLength) {
|
|
@@ -3815,32 +3871,15 @@ const remove = (targets, renderable, propertyName) => {
|
|
|
3815
3871
|
return targetsArray;
|
|
3816
3872
|
};
|
|
3817
3873
|
/**
|
|
3818
|
-
* @param {
|
|
3819
|
-
* @
|
|
3820
|
-
* @param {Number} [decimalLength]
|
|
3821
|
-
* @return {Number}
|
|
3874
|
+
* @param {(...args: any[]) => Tickable} constructor
|
|
3875
|
+
* @return {(...args: any[]) => Tickable}
|
|
3822
3876
|
*/
|
|
3823
|
-
const
|
|
3877
|
+
const keepTime = createRefreshable;
|
|
3824
3878
|
/**
|
|
3825
3879
|
* @param {String|Array} items
|
|
3826
3880
|
* @return {any}
|
|
3827
3881
|
*/
|
|
3828
3882
|
const randomPick = items => items[random(0, items.length - 1)];
|
|
3829
|
-
/**
|
|
3830
|
-
* Adapted from https://bost.ocks.org/mike/shuffle/
|
|
3831
|
-
* @param {Array} items
|
|
3832
|
-
* @return {Array}
|
|
3833
|
-
*/
|
|
3834
|
-
const shuffle = items => {
|
|
3835
|
-
let m = items.length, t, i;
|
|
3836
|
-
while (m) {
|
|
3837
|
-
i = random(0, --m);
|
|
3838
|
-
t = items[m];
|
|
3839
|
-
items[m] = items[i];
|
|
3840
|
-
items[i] = t;
|
|
3841
|
-
}
|
|
3842
|
-
return items;
|
|
3843
|
-
};
|
|
3844
3883
|
/**
|
|
3845
3884
|
* @param {Number|String} v
|
|
3846
3885
|
* @param {Number} decimalLength
|
|
@@ -4021,6 +4060,7 @@ const utils = {
|
|
|
4021
4060
|
shuffle,
|
|
4022
4061
|
lerp,
|
|
4023
4062
|
sync,
|
|
4063
|
+
keepTime,
|
|
4024
4064
|
clamp: /** @type {typeof clamp & ChainedClamp} */ (makeChainable(clamp)),
|
|
4025
4065
|
round: /** @type {typeof round & ChainedRound} */ (makeChainable(round)),
|
|
4026
4066
|
snap: /** @type {typeof snap & ChainedSnap} */ (makeChainable(snap)),
|
|
@@ -4340,8 +4380,8 @@ class Animatable {
|
|
|
4340
4380
|
* @param {AnimatableParams} parameters
|
|
4341
4381
|
*/
|
|
4342
4382
|
constructor(targets, parameters) {
|
|
4343
|
-
if (
|
|
4344
|
-
|
|
4383
|
+
if (scope.current)
|
|
4384
|
+
scope.current.register(this);
|
|
4345
4385
|
/** @type {AnimationParams} */
|
|
4346
4386
|
const globalParams = {};
|
|
4347
4387
|
const properties = {};
|
|
@@ -4665,8 +4705,8 @@ class Draggable {
|
|
|
4665
4705
|
constructor(target, parameters = {}) {
|
|
4666
4706
|
if (!target)
|
|
4667
4707
|
return;
|
|
4668
|
-
if (
|
|
4669
|
-
|
|
4708
|
+
if (scope.current)
|
|
4709
|
+
scope.current.register(this);
|
|
4670
4710
|
const paramX = parameters.x;
|
|
4671
4711
|
const paramY = parameters.y;
|
|
4672
4712
|
const trigger = parameters.trigger;
|
|
@@ -5696,39 +5736,11 @@ class Draggable {
|
|
|
5696
5736
|
const createDraggable = (target, parameters) => new Draggable(target, parameters);
|
|
5697
5737
|
|
|
5698
5738
|
|
|
5699
|
-
/**
|
|
5700
|
-
* @typedef {Object} ReactRef
|
|
5701
|
-
* @property {HTMLElement|SVGElement|null} [current]
|
|
5702
|
-
*/
|
|
5703
|
-
/**
|
|
5704
|
-
* @typedef {Object} AngularRef
|
|
5705
|
-
* @property {HTMLElement|SVGElement} [nativeElement]
|
|
5706
|
-
*/
|
|
5707
|
-
/**
|
|
5708
|
-
* @typedef {Object} ScopeParams
|
|
5709
|
-
* @property {DOMTargetSelector|ReactRef|AngularRef} [root]
|
|
5710
|
-
* @property {DefaultsParams} [defaults]
|
|
5711
|
-
* @property {Record<String, String>} [mediaQueries]
|
|
5712
|
-
*/
|
|
5713
|
-
/**
|
|
5714
|
-
* @callback ScopeCleanup
|
|
5715
|
-
* @param {Scope} [scope]
|
|
5716
|
-
*/
|
|
5717
|
-
/**
|
|
5718
|
-
* @callback ScopeConstructor
|
|
5719
|
-
* @param {Scope} [scope]
|
|
5720
|
-
* @return {ScopeCleanup|void}
|
|
5721
|
-
*/
|
|
5722
|
-
/**
|
|
5723
|
-
* @callback ScopeMethod
|
|
5724
|
-
* @param {...*} args
|
|
5725
|
-
* @return {ScopeCleanup|void}
|
|
5726
|
-
*/
|
|
5727
5739
|
class Scope {
|
|
5728
5740
|
/** @param {ScopeParams} [parameters] */
|
|
5729
5741
|
constructor(parameters = {}) {
|
|
5730
|
-
if (
|
|
5731
|
-
|
|
5742
|
+
if (scope.current)
|
|
5743
|
+
scope.current.register(this);
|
|
5732
5744
|
const rootParam = parameters.root;
|
|
5733
5745
|
/** @type {Document|DOMTarget} */
|
|
5734
5746
|
let root = doc;
|
|
@@ -5745,13 +5757,23 @@ class Scope {
|
|
|
5745
5757
|
this.defaults = scopeDefaults ? mergeObjects(scopeDefaults, globalDefault) : globalDefault;
|
|
5746
5758
|
/** @type {Document|DOMTarget} */
|
|
5747
5759
|
this.root = root;
|
|
5748
|
-
/** @type {Array<
|
|
5760
|
+
/** @type {Array<ScopeConstructorCallback>} */
|
|
5749
5761
|
this.constructors = [];
|
|
5750
|
-
/** @type {Array<
|
|
5762
|
+
/** @type {Array<ScopeCleanupCallback>} */
|
|
5751
5763
|
this.revertConstructors = [];
|
|
5752
5764
|
/** @type {Array<Revertible>} */
|
|
5753
5765
|
this.revertibles = [];
|
|
5754
|
-
/** @type {
|
|
5766
|
+
/** @type {Array<ScopeConstructorCallback | ((scope: this) => Tickable)>} */
|
|
5767
|
+
this.constructorsOnce = [];
|
|
5768
|
+
/** @type {Array<ScopeCleanupCallback>} */
|
|
5769
|
+
this.revertConstructorsOnce = [];
|
|
5770
|
+
/** @type {Array<Revertible>} */
|
|
5771
|
+
this.revertiblesOnce = [];
|
|
5772
|
+
/** @type {Boolean} */
|
|
5773
|
+
this.once = false;
|
|
5774
|
+
/** @type {Number} */
|
|
5775
|
+
this.onceIndex = 0;
|
|
5776
|
+
/** @type {Record<String, ScopeMethod>} */
|
|
5755
5777
|
this.methods = {};
|
|
5756
5778
|
/** @type {Record<String, Boolean>} */
|
|
5757
5779
|
this.matches = {};
|
|
@@ -5768,26 +5790,30 @@ class Scope {
|
|
|
5768
5790
|
}
|
|
5769
5791
|
}
|
|
5770
5792
|
/**
|
|
5771
|
-
* @
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5793
|
+
* @param {Revertible} revertible
|
|
5794
|
+
*/
|
|
5795
|
+
register(revertible) {
|
|
5796
|
+
const store = this.once ? this.revertiblesOnce : this.revertibles;
|
|
5797
|
+
store.push(revertible);
|
|
5798
|
+
}
|
|
5799
|
+
/**
|
|
5800
|
+
* @template T
|
|
5801
|
+
* @param {ScopedCallback<T>} cb
|
|
5802
|
+
* @return {T}
|
|
5777
5803
|
*/
|
|
5778
5804
|
execute(cb) {
|
|
5779
|
-
let activeScope =
|
|
5780
|
-
let activeRoot =
|
|
5805
|
+
let activeScope = scope.current;
|
|
5806
|
+
let activeRoot = scope.root;
|
|
5781
5807
|
let activeDefaults = globals.defaults;
|
|
5782
|
-
|
|
5783
|
-
|
|
5808
|
+
scope.current = this;
|
|
5809
|
+
scope.root = this.root;
|
|
5784
5810
|
globals.defaults = this.defaults;
|
|
5785
5811
|
const mqs = this.mediaQueryLists;
|
|
5786
5812
|
for (let mq in mqs)
|
|
5787
5813
|
this.matches[mq] = mqs[mq].matches;
|
|
5788
5814
|
const returned = cb(this);
|
|
5789
|
-
|
|
5790
|
-
|
|
5815
|
+
scope.current = activeScope;
|
|
5816
|
+
scope.root = activeRoot;
|
|
5791
5817
|
globals.defaults = activeDefaults;
|
|
5792
5818
|
return returned;
|
|
5793
5819
|
}
|
|
@@ -5795,6 +5821,7 @@ class Scope {
|
|
|
5795
5821
|
* @return {this}
|
|
5796
5822
|
*/
|
|
5797
5823
|
refresh() {
|
|
5824
|
+
this.onceIndex = 0;
|
|
5798
5825
|
this.execute(() => {
|
|
5799
5826
|
let i = this.revertibles.length;
|
|
5800
5827
|
let y = this.revertConstructors.length;
|
|
@@ -5804,9 +5831,9 @@ class Scope {
|
|
|
5804
5831
|
this.revertConstructors[y](this);
|
|
5805
5832
|
this.revertibles.length = 0;
|
|
5806
5833
|
this.revertConstructors.length = 0;
|
|
5807
|
-
this.constructors.forEach(constructor => {
|
|
5834
|
+
this.constructors.forEach((/** @type {ScopeConstructorCallback} */ constructor) => {
|
|
5808
5835
|
const revertConstructor = constructor(this);
|
|
5809
|
-
if (revertConstructor) {
|
|
5836
|
+
if (isFnc(revertConstructor)) {
|
|
5810
5837
|
this.revertConstructors.push(revertConstructor);
|
|
5811
5838
|
}
|
|
5812
5839
|
});
|
|
@@ -5814,28 +5841,26 @@ class Scope {
|
|
|
5814
5841
|
return this;
|
|
5815
5842
|
}
|
|
5816
5843
|
/**
|
|
5817
|
-
* @callback contructorCallback
|
|
5818
|
-
* @param {this} self
|
|
5819
|
-
*
|
|
5820
5844
|
* @overload
|
|
5821
5845
|
* @param {String} a1
|
|
5822
5846
|
* @param {ScopeMethod} a2
|
|
5823
5847
|
* @return {this}
|
|
5824
5848
|
*
|
|
5825
5849
|
* @overload
|
|
5826
|
-
* @param {
|
|
5850
|
+
* @param {ScopeConstructorCallback} a1
|
|
5827
5851
|
* @return {this}
|
|
5828
5852
|
*
|
|
5829
|
-
* @param {String|
|
|
5853
|
+
* @param {String|ScopeConstructorCallback} a1
|
|
5830
5854
|
* @param {ScopeMethod} [a2]
|
|
5831
5855
|
*/
|
|
5832
5856
|
add(a1, a2) {
|
|
5857
|
+
this.once = false;
|
|
5833
5858
|
if (isFnc(a1)) {
|
|
5834
|
-
const constructor = /** @type {
|
|
5859
|
+
const constructor = /** @type {ScopeConstructorCallback} */ (a1);
|
|
5835
5860
|
this.constructors.push(constructor);
|
|
5836
5861
|
this.execute(() => {
|
|
5837
5862
|
const revertConstructor = constructor(this);
|
|
5838
|
-
if (revertConstructor) {
|
|
5863
|
+
if (isFnc(revertConstructor)) {
|
|
5839
5864
|
this.revertConstructors.push(revertConstructor);
|
|
5840
5865
|
}
|
|
5841
5866
|
});
|
|
@@ -5845,6 +5870,46 @@ class Scope {
|
|
|
5845
5870
|
}
|
|
5846
5871
|
return this;
|
|
5847
5872
|
}
|
|
5873
|
+
/**
|
|
5874
|
+
* @param {ScopeConstructorCallback} scopeConstructorCallback
|
|
5875
|
+
* @return {this}
|
|
5876
|
+
*/
|
|
5877
|
+
addOnce(scopeConstructorCallback) {
|
|
5878
|
+
this.once = true;
|
|
5879
|
+
if (isFnc(scopeConstructorCallback)) {
|
|
5880
|
+
const currentIndex = this.onceIndex++;
|
|
5881
|
+
const tracked = this.constructorsOnce[currentIndex];
|
|
5882
|
+
if (tracked)
|
|
5883
|
+
return this;
|
|
5884
|
+
const constructor = /** @type {ScopeConstructorCallback} */ (scopeConstructorCallback);
|
|
5885
|
+
this.constructorsOnce[currentIndex] = constructor;
|
|
5886
|
+
this.execute(() => {
|
|
5887
|
+
const revertConstructor = constructor(this);
|
|
5888
|
+
if (isFnc(revertConstructor)) {
|
|
5889
|
+
this.revertConstructorsOnce.push(revertConstructor);
|
|
5890
|
+
}
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
return this;
|
|
5894
|
+
}
|
|
5895
|
+
/**
|
|
5896
|
+
* @param {(scope: this) => Tickable} cb
|
|
5897
|
+
* @return {Tickable}
|
|
5898
|
+
*/
|
|
5899
|
+
keepTime(cb) {
|
|
5900
|
+
this.once = true;
|
|
5901
|
+
const currentIndex = this.onceIndex++;
|
|
5902
|
+
const tracked = /** @type {(scope: this) => Tickable} */ (this.constructorsOnce[currentIndex]);
|
|
5903
|
+
if (isFnc(tracked))
|
|
5904
|
+
return tracked(this);
|
|
5905
|
+
const constructor = /** @type {(scope: this) => Tickable} */ (createRefreshable(cb));
|
|
5906
|
+
this.constructorsOnce[currentIndex] = constructor;
|
|
5907
|
+
let trackedTickable;
|
|
5908
|
+
this.execute(() => {
|
|
5909
|
+
trackedTickable = constructor(this);
|
|
5910
|
+
});
|
|
5911
|
+
return trackedTickable;
|
|
5912
|
+
}
|
|
5848
5913
|
/**
|
|
5849
5914
|
* @param {Event} e
|
|
5850
5915
|
*/
|
|
@@ -5858,18 +5923,30 @@ class Scope {
|
|
|
5858
5923
|
revert() {
|
|
5859
5924
|
const revertibles = this.revertibles;
|
|
5860
5925
|
const revertConstructors = this.revertConstructors;
|
|
5926
|
+
const revertiblesOnce = this.revertiblesOnce;
|
|
5927
|
+
const revertConstructorsOnce = this.revertConstructorsOnce;
|
|
5861
5928
|
const mqs = this.mediaQueryLists;
|
|
5862
5929
|
let i = revertibles.length;
|
|
5863
|
-
let
|
|
5930
|
+
let j = revertConstructors.length;
|
|
5931
|
+
let k = revertiblesOnce.length;
|
|
5932
|
+
let l = revertConstructorsOnce.length;
|
|
5864
5933
|
while (i--)
|
|
5865
5934
|
revertibles[i].revert();
|
|
5866
|
-
while (
|
|
5867
|
-
revertConstructors[
|
|
5935
|
+
while (j--)
|
|
5936
|
+
revertConstructors[j](this);
|
|
5937
|
+
while (k--)
|
|
5938
|
+
revertiblesOnce[k].revert();
|
|
5939
|
+
while (l--)
|
|
5940
|
+
revertConstructorsOnce[l](this);
|
|
5868
5941
|
for (let mq in mqs)
|
|
5869
5942
|
mqs[mq].removeEventListener('change', this);
|
|
5870
5943
|
revertibles.length = 0;
|
|
5871
5944
|
revertConstructors.length = 0;
|
|
5872
5945
|
this.constructors.length = 0;
|
|
5946
|
+
revertiblesOnce.length = 0;
|
|
5947
|
+
revertConstructorsOnce.length = 0;
|
|
5948
|
+
this.constructorsOnce.length = 0;
|
|
5949
|
+
this.onceIndex = 0;
|
|
5873
5950
|
this.matches = {};
|
|
5874
5951
|
this.methods = {};
|
|
5875
5952
|
this.mediaQueryLists = {};
|
|
@@ -6186,7 +6263,7 @@ const getAnimationDomTarget = linked => {
|
|
|
6186
6263
|
return $linkedTarget;
|
|
6187
6264
|
};
|
|
6188
6265
|
let scrollerIndex = 0;
|
|
6189
|
-
const debugColors = ['#FF4B4B', '#FF971B', '#FFC730', '#F9F640', '#7AFF5A', '#18FF74', '#17E09B', '#3CFFEC', '#05DBE9', '#33B3F1', '#638CF9', '#C563FE', '#FF4FCF', '#F93F8A'];
|
|
6266
|
+
const debugColors$1 = ['#FF4B4B', '#FF971B', '#FFC730', '#F9F640', '#7AFF5A', '#18FF74', '#17E09B', '#3CFFEC', '#05DBE9', '#33B3F1', '#638CF9', '#C563FE', '#FF4FCF', '#F93F8A'];
|
|
6190
6267
|
/**
|
|
6191
6268
|
* @typedef {Object} ScrollThresholdParam
|
|
6192
6269
|
* @property {ScrollThresholdValue} [target]
|
|
@@ -6227,8 +6304,8 @@ class ScrollObserver {
|
|
|
6227
6304
|
* @param {ScrollObserverParams} parameters
|
|
6228
6305
|
*/
|
|
6229
6306
|
constructor(parameters = {}) {
|
|
6230
|
-
if (
|
|
6231
|
-
|
|
6307
|
+
if (scope.current)
|
|
6308
|
+
scope.current.register(this);
|
|
6232
6309
|
const syncMode = setValue(parameters.sync, 'play pause');
|
|
6233
6310
|
const ease = syncMode ? parseEasings(/** @type {EasingParam} */ (syncMode)) : null;
|
|
6234
6311
|
const isLinear = syncMode && (syncMode === 'linear' || syncMode === none);
|
|
@@ -6418,7 +6495,7 @@ class ScrollObserver {
|
|
|
6418
6495
|
const $debug = doc.createElement('div');
|
|
6419
6496
|
const $thresholds = doc.createElement('div');
|
|
6420
6497
|
const $triggers = doc.createElement('div');
|
|
6421
|
-
const color = debugColors[this.index % debugColors.length];
|
|
6498
|
+
const color = debugColors$1[this.index % debugColors$1.length];
|
|
6422
6499
|
const useWin = container.useWin;
|
|
6423
6500
|
const containerWidth = useWin ? container.winWidth : container.width;
|
|
6424
6501
|
const containerHeight = useWin ? container.winHeight : container.height;
|
|
@@ -6771,27 +6848,475 @@ class ScrollObserver {
|
|
|
6771
6848
|
const onScroll = (parameters = {}) => new ScrollObserver(parameters);
|
|
6772
6849
|
|
|
6773
6850
|
|
|
6851
|
+
const segmenter = !isUnd(Intl) && Intl.Segmenter;
|
|
6852
|
+
const valueRgx = /\{value\}/g;
|
|
6853
|
+
const indexRgx = /\{i\}/g;
|
|
6854
|
+
const whiteSpaceGroupRgx = /(\s+)/;
|
|
6855
|
+
const whiteSpaceRgx = /^\s+$/;
|
|
6856
|
+
const lineType = 'line';
|
|
6857
|
+
const wordType = 'word';
|
|
6858
|
+
const charType = 'char';
|
|
6859
|
+
const dataLine = `data-line`;
|
|
6860
|
+
/**
|
|
6861
|
+
* @typedef {Object} Segment
|
|
6862
|
+
* @property {String} segment
|
|
6863
|
+
* @property {Boolean} [isWordLike]
|
|
6864
|
+
*/
|
|
6865
|
+
/**
|
|
6866
|
+
* @typedef {Object} Segmenter
|
|
6867
|
+
* @property {function(String): Iterable<Segment>} segment
|
|
6868
|
+
*/
|
|
6869
|
+
/** @type {Segmenter} */
|
|
6870
|
+
let wordSegmenter = null;
|
|
6871
|
+
/** @type {Segmenter} */
|
|
6872
|
+
let graphemeSegmenter = null;
|
|
6873
|
+
let $splitTemplate = null;
|
|
6874
|
+
/**
|
|
6875
|
+
* @param {Segment} seg
|
|
6876
|
+
* @return {Boolean}
|
|
6877
|
+
*/
|
|
6878
|
+
const isSegmentWordLike = seg => {
|
|
6879
|
+
return seg.isWordLike ||
|
|
6880
|
+
seg.segment === ' ' || // Consider spaces as words first, then handle them diffrently later
|
|
6881
|
+
isNum(+seg.segment); // Safari doesn't considers numbers as words
|
|
6882
|
+
};
|
|
6774
6883
|
/**
|
|
6775
|
-
* @
|
|
6776
|
-
* @property {Number|String} [start]
|
|
6777
|
-
* @property {Number|'first'|'center'|'last'} [from]
|
|
6778
|
-
* @property {Boolean} [reversed]
|
|
6779
|
-
* @property {Array.<Number>} [grid]
|
|
6780
|
-
* @property {('x'|'y')} [axis]
|
|
6781
|
-
* @property {EasingParam} [ease]
|
|
6782
|
-
* @property {TweenModifier} [modifier]
|
|
6884
|
+
* @param {HTMLElement} $el
|
|
6783
6885
|
*/
|
|
6886
|
+
const setAriaHidden = $el => $el.setAttribute('aria-hidden', 'true');
|
|
6784
6887
|
/**
|
|
6785
|
-
* @
|
|
6786
|
-
* @param {
|
|
6787
|
-
* @
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6888
|
+
* @param {DOMTarget} $el
|
|
6889
|
+
* @param {String} type
|
|
6890
|
+
* @return {Array<HTMLElement>}
|
|
6891
|
+
*/
|
|
6892
|
+
const getAllTopLevelElements = ($el, type) => [... /** @type {*} */($el.querySelectorAll(`[data-${type}]:not([data-${type}] [data-${type}])`))];
|
|
6893
|
+
const debugColors = { line: '#00D672', word: '#FF4B4B', char: '#5A87FF' };
|
|
6894
|
+
/**
|
|
6895
|
+
* @param {HTMLElement} $el
|
|
6896
|
+
*/
|
|
6897
|
+
const filterEmptyElements = $el => {
|
|
6898
|
+
if (!$el.childElementCount && !$el.textContent.trim()) {
|
|
6899
|
+
const $parent = $el.parentElement;
|
|
6900
|
+
$el.remove();
|
|
6901
|
+
if ($parent)
|
|
6902
|
+
filterEmptyElements($parent);
|
|
6903
|
+
}
|
|
6904
|
+
};
|
|
6905
|
+
/**
|
|
6906
|
+
* @param {HTMLElement} $el
|
|
6907
|
+
* @param {Number} lineIndex
|
|
6908
|
+
* @param {Set<HTMLElement>} bin
|
|
6909
|
+
* @returns {Set<HTMLElement>}
|
|
6910
|
+
*/
|
|
6911
|
+
const filterLineElements = ($el, lineIndex, bin) => {
|
|
6912
|
+
const dataLineAttr = $el.getAttribute(dataLine);
|
|
6913
|
+
if (dataLineAttr !== null && +dataLineAttr !== lineIndex || $el.tagName === 'BR')
|
|
6914
|
+
bin.add($el);
|
|
6915
|
+
let i = $el.childElementCount;
|
|
6916
|
+
while (i--)
|
|
6917
|
+
filterLineElements(/** @type {HTMLElement} */ ($el.children[i]), lineIndex, bin);
|
|
6918
|
+
return bin;
|
|
6919
|
+
};
|
|
6920
|
+
/**
|
|
6921
|
+
* @param {'line'|'word'|'char'} type
|
|
6922
|
+
* @param {splitTemplateParams} params
|
|
6923
|
+
* @return {String}
|
|
6924
|
+
*/
|
|
6925
|
+
const generateTemplate = (type, params = {}) => {
|
|
6926
|
+
let template = ``;
|
|
6927
|
+
const classString = isStr(params.class) ? ` class="${params.class}"` : '';
|
|
6928
|
+
const cloneType = setValue(params.clone, false);
|
|
6929
|
+
const wrapType = setValue(params.wrap, false);
|
|
6930
|
+
const overflow = wrapType ? wrapType === true ? 'clip' : wrapType : cloneType ? 'clip' : false;
|
|
6931
|
+
if (wrapType)
|
|
6932
|
+
template += `<span${overflow ? ` style="overflow:${overflow};"` : ''}>`;
|
|
6933
|
+
template += `<span${classString}${cloneType ? ` style="position:relative;"` : ''} data-${type}="{i}">`;
|
|
6934
|
+
if (cloneType) {
|
|
6935
|
+
const left = cloneType === 'left' ? '-100%' : cloneType === 'right' ? '100%' : '0';
|
|
6936
|
+
const top = cloneType === 'top' ? '-100%' : cloneType === 'bottom' ? '100%' : '0';
|
|
6937
|
+
template += `<span>{value}</span>`;
|
|
6938
|
+
template += `<span inert style="position:absolute;top:${top};left:${left};white-space:nowrap;">{value}</span>`;
|
|
6939
|
+
}
|
|
6940
|
+
else {
|
|
6941
|
+
template += `{value}`;
|
|
6942
|
+
}
|
|
6943
|
+
template += `</span>`;
|
|
6944
|
+
if (wrapType)
|
|
6945
|
+
template += `</span>`;
|
|
6946
|
+
return template;
|
|
6947
|
+
};
|
|
6948
|
+
/**
|
|
6949
|
+
* @param {String|SplitFunctionValue} htmlTemplate
|
|
6950
|
+
* @param {Array<HTMLElement>} store
|
|
6951
|
+
* @param {Node|HTMLElement} node
|
|
6952
|
+
* @param {DocumentFragment} $parentFragment
|
|
6953
|
+
* @param {'line'|'word'|'char'} type
|
|
6954
|
+
* @param {Boolean} debug
|
|
6955
|
+
* @param {Number} lineIndex
|
|
6956
|
+
* @param {Number} [wordIndex]
|
|
6957
|
+
* @param {Number} [charIndex]
|
|
6958
|
+
* @return {HTMLElement}
|
|
6959
|
+
*/
|
|
6960
|
+
const processHTMLTemplate = (htmlTemplate, store, node, $parentFragment, type, debug, lineIndex, wordIndex, charIndex) => {
|
|
6961
|
+
const isLine = type === lineType;
|
|
6962
|
+
const isChar = type === charType;
|
|
6963
|
+
const className = `_${type}_`;
|
|
6964
|
+
const template = isFnc(htmlTemplate) ? htmlTemplate(node) : htmlTemplate;
|
|
6965
|
+
const displayStyle = isLine ? 'block' : 'inline-block';
|
|
6966
|
+
$splitTemplate.innerHTML = template
|
|
6967
|
+
.replace(valueRgx, `<i class="${className}"></i>`)
|
|
6968
|
+
.replace(indexRgx, `${isChar ? charIndex : isLine ? lineIndex : wordIndex}`);
|
|
6969
|
+
const $content = $splitTemplate.content;
|
|
6970
|
+
const $highestParent = /** @type {HTMLElement} */ ($content.firstElementChild);
|
|
6971
|
+
const $split = /** @type {HTMLElement} */ ($content.querySelector(`[data-${type}]`)) || $highestParent;
|
|
6972
|
+
const $replacables = /** @type {NodeListOf<HTMLElement>} */ ($content.querySelectorAll(`i.${className}`));
|
|
6973
|
+
const replacablesLength = $replacables.length;
|
|
6974
|
+
if (replacablesLength) {
|
|
6975
|
+
$highestParent.style.display = displayStyle;
|
|
6976
|
+
$split.style.display = displayStyle;
|
|
6977
|
+
$split.setAttribute(dataLine, `${lineIndex}`);
|
|
6978
|
+
if (!isLine) {
|
|
6979
|
+
$split.setAttribute('data-word', `${wordIndex}`);
|
|
6980
|
+
if (isChar)
|
|
6981
|
+
$split.setAttribute('data-char', `${charIndex}`);
|
|
6982
|
+
}
|
|
6983
|
+
let i = replacablesLength;
|
|
6984
|
+
while (i--) {
|
|
6985
|
+
const $replace = $replacables[i];
|
|
6986
|
+
const $closestParent = $replace.parentElement;
|
|
6987
|
+
$closestParent.style.display = displayStyle;
|
|
6988
|
+
if (isLine) {
|
|
6989
|
+
$closestParent.innerHTML = /** @type {HTMLElement} */ (node).innerHTML;
|
|
6990
|
+
}
|
|
6991
|
+
else {
|
|
6992
|
+
$closestParent.replaceChild(node.cloneNode(true), $replace);
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
store.push($split);
|
|
6996
|
+
$parentFragment.appendChild($content);
|
|
6997
|
+
}
|
|
6998
|
+
else {
|
|
6999
|
+
console.warn(`The expression "{value}" is missing from the provided template.`);
|
|
7000
|
+
}
|
|
7001
|
+
if (debug)
|
|
7002
|
+
$highestParent.style.outline = `1px dotted ${debugColors[type]}`;
|
|
7003
|
+
return $highestParent;
|
|
7004
|
+
};
|
|
7005
|
+
/**
|
|
7006
|
+
* A class that splits text into words and wraps them in span elements while preserving the original HTML structure.
|
|
7007
|
+
* @class
|
|
7008
|
+
*/
|
|
7009
|
+
class TextSplitter {
|
|
7010
|
+
/**
|
|
7011
|
+
* @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
|
|
7012
|
+
* @param {TextSplitterParams} [parameters]
|
|
7013
|
+
*/
|
|
7014
|
+
constructor(target, parameters = {}) {
|
|
7015
|
+
// Only init segmenters when needed
|
|
7016
|
+
if (!wordSegmenter)
|
|
7017
|
+
wordSegmenter = segmenter ? new segmenter([], { granularity: wordType }) : {
|
|
7018
|
+
segment: (text) => {
|
|
7019
|
+
const segments = [];
|
|
7020
|
+
const words = text.split(whiteSpaceGroupRgx);
|
|
7021
|
+
for (let i = 0, l = words.length; i < l; i++) {
|
|
7022
|
+
const segment = words[i];
|
|
7023
|
+
segments.push({
|
|
7024
|
+
segment,
|
|
7025
|
+
isWordLike: !whiteSpaceRgx.test(segment), // Consider non-whitespace as word-like
|
|
7026
|
+
});
|
|
7027
|
+
}
|
|
7028
|
+
return segments;
|
|
7029
|
+
}
|
|
7030
|
+
};
|
|
7031
|
+
if (!graphemeSegmenter)
|
|
7032
|
+
graphemeSegmenter = segmenter ? new segmenter([], { granularity: 'grapheme' }) : {
|
|
7033
|
+
segment: text => [...text].map(char => ({ segment: char }))
|
|
7034
|
+
};
|
|
7035
|
+
if (!$splitTemplate && isBrowser)
|
|
7036
|
+
$splitTemplate = doc.createElement('template');
|
|
7037
|
+
if (scope.current)
|
|
7038
|
+
scope.current.register(this);
|
|
7039
|
+
const { words, chars, lines, accessible, includeSpaces, debug } = parameters;
|
|
7040
|
+
const $target = /** @type {HTMLElement} */ ((target = isArr(target) ? target[0] : target) && /** @type {Node} */ (target).nodeType ? target : (getNodeList(target) || [])[0]);
|
|
7041
|
+
const lineParams = lines === true ? {} : lines;
|
|
7042
|
+
const wordParams = words === true || isUnd(words) ? {} : words;
|
|
7043
|
+
const charParams = chars === true ? {} : chars;
|
|
7044
|
+
this.debug = setValue(debug, false);
|
|
7045
|
+
this.includeSpaces = setValue(includeSpaces, false);
|
|
7046
|
+
this.accessible = setValue(accessible, true);
|
|
7047
|
+
this.linesOnly = lineParams && (!wordParams && !charParams);
|
|
7048
|
+
/** @type {String|false|SplitFunctionValue} */
|
|
7049
|
+
this.lineTemplate = isObj(lineParams) ? generateTemplate(lineType, /** @type {splitTemplateParams} */ (lineParams)) : lineParams;
|
|
7050
|
+
/** @type {String|false|SplitFunctionValue} */
|
|
7051
|
+
this.wordTemplate = isObj(wordParams) || this.linesOnly ? generateTemplate(wordType, /** @type {splitTemplateParams} */ (wordParams)) : wordParams;
|
|
7052
|
+
/** @type {String|false|SplitFunctionValue} */
|
|
7053
|
+
this.charTemplate = isObj(charParams) ? generateTemplate(charType, /** @type {splitTemplateParams} */ (charParams)) : charParams;
|
|
7054
|
+
this.$target = $target;
|
|
7055
|
+
this.html = $target && $target.innerHTML;
|
|
7056
|
+
this.lines = [];
|
|
7057
|
+
this.words = [];
|
|
7058
|
+
this.chars = [];
|
|
7059
|
+
this.effects = [];
|
|
7060
|
+
this.effectsCleanups = [];
|
|
7061
|
+
this.cache = null;
|
|
7062
|
+
this.ready = false;
|
|
7063
|
+
this.width = 0;
|
|
7064
|
+
this.resizeTimeout = null;
|
|
7065
|
+
const handleSplit = () => this.html && (lineParams || wordParams || charParams) && this.split();
|
|
7066
|
+
// Make sure this is declared before calling handleSplit() in case revert() is called inside an effect callback
|
|
7067
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
7068
|
+
// Use a setTimeout instead of a Timer for better tree shaking
|
|
7069
|
+
clearTimeout(this.resizeTimeout);
|
|
7070
|
+
this.resizeTimeout = setTimeout(() => {
|
|
7071
|
+
const currentWidth = /** @type {HTMLElement} */ ($target).offsetWidth;
|
|
7072
|
+
if (currentWidth === this.width)
|
|
7073
|
+
return;
|
|
7074
|
+
this.width = currentWidth;
|
|
7075
|
+
handleSplit();
|
|
7076
|
+
}, 150);
|
|
7077
|
+
});
|
|
7078
|
+
// Only declare the font ready promise when splitting by lines and not alreay split
|
|
7079
|
+
if (this.lineTemplate && !this.ready) {
|
|
7080
|
+
doc.fonts.ready.then(handleSplit);
|
|
7081
|
+
}
|
|
7082
|
+
else {
|
|
7083
|
+
handleSplit();
|
|
7084
|
+
}
|
|
7085
|
+
$target ? this.resizeObserver.observe($target) : console.warn('No Text Splitter target found.');
|
|
7086
|
+
}
|
|
7087
|
+
/**
|
|
7088
|
+
* @param {(...args: any[]) => Tickable | (() => void)} effect
|
|
7089
|
+
* @return this
|
|
7090
|
+
*/
|
|
7091
|
+
addEffect(effect) {
|
|
7092
|
+
if (!isFnc(effect))
|
|
7093
|
+
return console.warn('Effect must return a function.');
|
|
7094
|
+
const refreshableEffect = createRefreshable(effect);
|
|
7095
|
+
this.effects.push(refreshableEffect);
|
|
7096
|
+
if (this.ready)
|
|
7097
|
+
this.effectsCleanups[this.effects.length - 1] = refreshableEffect(this);
|
|
7098
|
+
return this;
|
|
7099
|
+
}
|
|
7100
|
+
revert() {
|
|
7101
|
+
clearTimeout(this.resizeTimeout);
|
|
7102
|
+
this.lines.length = this.words.length = this.chars.length = 0;
|
|
7103
|
+
this.resizeObserver.disconnect();
|
|
7104
|
+
// Make sure to revert the effects after disconnecting the resizeObserver to avoid triggering it in the process
|
|
7105
|
+
this.effectsCleanups.forEach(cleanup => isFnc(cleanup) ? cleanup(this) : cleanup.revert && cleanup.revert());
|
|
7106
|
+
this.$target.innerHTML = this.html;
|
|
7107
|
+
return this;
|
|
7108
|
+
}
|
|
7109
|
+
/**
|
|
7110
|
+
* Recursively processes a node and its children
|
|
7111
|
+
* @param {Node} node
|
|
7112
|
+
*/
|
|
7113
|
+
splitNode(node) {
|
|
7114
|
+
const wordTemplate = this.wordTemplate;
|
|
7115
|
+
const charTemplate = this.charTemplate;
|
|
7116
|
+
const includeSpaces = this.includeSpaces;
|
|
7117
|
+
const debug = this.debug;
|
|
7118
|
+
const nodeType = node.nodeType;
|
|
7119
|
+
if (nodeType === 3) {
|
|
7120
|
+
const nodeText = node.nodeValue;
|
|
7121
|
+
// If the nodeText is only whitespace, leave it as is
|
|
7122
|
+
if (nodeText.trim()) {
|
|
7123
|
+
const tempWords = [];
|
|
7124
|
+
const words = this.words;
|
|
7125
|
+
const chars = this.chars;
|
|
7126
|
+
const wordSegments = wordSegmenter.segment(nodeText);
|
|
7127
|
+
const $wordsFragment = doc.createDocumentFragment();
|
|
7128
|
+
let prevSeg = null;
|
|
7129
|
+
for (const wordSegment of wordSegments) {
|
|
7130
|
+
const segment = wordSegment.segment;
|
|
7131
|
+
const isWordLike = isSegmentWordLike(wordSegment);
|
|
7132
|
+
// Determine if this segment should be a new word, first segment always becomes a new word
|
|
7133
|
+
if (!prevSeg || (isWordLike && (prevSeg && (isSegmentWordLike(prevSeg))))) {
|
|
7134
|
+
tempWords.push(segment);
|
|
7135
|
+
}
|
|
7136
|
+
else {
|
|
7137
|
+
// Only concatenate if both current and previous are non-word-like and don't contain spaces
|
|
7138
|
+
const lastWordIndex = tempWords.length - 1;
|
|
7139
|
+
const lastWord = tempWords[lastWordIndex];
|
|
7140
|
+
if (!lastWord.includes(' ') && !segment.includes(' ')) {
|
|
7141
|
+
tempWords[lastWordIndex] += segment;
|
|
7142
|
+
}
|
|
7143
|
+
else {
|
|
7144
|
+
tempWords.push(segment);
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
prevSeg = wordSegment;
|
|
7148
|
+
}
|
|
7149
|
+
for (let i = 0, l = tempWords.length; i < l; i++) {
|
|
7150
|
+
const word = tempWords[i];
|
|
7151
|
+
if (!word.trim()) {
|
|
7152
|
+
// Preserve whitespace only if includeSpaces is false and if the current space is not the first node
|
|
7153
|
+
if (i && includeSpaces)
|
|
7154
|
+
continue;
|
|
7155
|
+
$wordsFragment.appendChild(doc.createTextNode(word));
|
|
7156
|
+
}
|
|
7157
|
+
else {
|
|
7158
|
+
const nextWord = tempWords[i + 1];
|
|
7159
|
+
const hasWordFollowingSpace = includeSpaces && nextWord && !nextWord.trim();
|
|
7160
|
+
const wordToProcess = word;
|
|
7161
|
+
const charSegments = charTemplate ? graphemeSegmenter.segment(wordToProcess) : null;
|
|
7162
|
+
const $charsFragment = charTemplate ? doc.createDocumentFragment() : doc.createTextNode(hasWordFollowingSpace ? word + '\xa0' : word);
|
|
7163
|
+
if (charTemplate) {
|
|
7164
|
+
const charSegmentsArray = [...charSegments];
|
|
7165
|
+
for (let j = 0, jl = charSegmentsArray.length; j < jl; j++) {
|
|
7166
|
+
const charSegment = charSegmentsArray[j];
|
|
7167
|
+
const isLastChar = j === jl - 1;
|
|
7168
|
+
// If this is the last character and includeSpaces is true with a following space, append the space
|
|
7169
|
+
const charText = isLastChar && hasWordFollowingSpace ? charSegment.segment + '\xa0' : charSegment.segment;
|
|
7170
|
+
const $charNode = doc.createTextNode(charText);
|
|
7171
|
+
processHTMLTemplate(charTemplate, chars, $charNode, /** @type {DocumentFragment} */ ($charsFragment), charType, debug, -1, words.length, chars.length);
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
if (wordTemplate) {
|
|
7175
|
+
processHTMLTemplate(wordTemplate, words, $charsFragment, $wordsFragment, wordType, debug, -1, words.length, chars.length);
|
|
7176
|
+
// Chars elements must be re-parsed in the split() method if both words and chars are parsed
|
|
7177
|
+
}
|
|
7178
|
+
else if (charTemplate) {
|
|
7179
|
+
$wordsFragment.appendChild($charsFragment);
|
|
7180
|
+
}
|
|
7181
|
+
else {
|
|
7182
|
+
$wordsFragment.appendChild(doc.createTextNode(word));
|
|
7183
|
+
}
|
|
7184
|
+
// Skip the next iteration if we included a space
|
|
7185
|
+
if (hasWordFollowingSpace)
|
|
7186
|
+
i++;
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
node.parentNode.replaceChild($wordsFragment, node);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
else if (nodeType === 1) {
|
|
7193
|
+
// Converting to an array is necessary to work around childNodes pottential mutation
|
|
7194
|
+
const childNodes = /** @type {Array<Node>} */ ([... /** @type {*} */(node.childNodes)]);
|
|
7195
|
+
for (let i = 0, l = childNodes.length; i < l; i++)
|
|
7196
|
+
this.splitNode(childNodes[i]);
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
/**
|
|
7200
|
+
* @param {Boolean} clearCache
|
|
7201
|
+
* @return {this}
|
|
7202
|
+
*/
|
|
7203
|
+
split(clearCache = false) {
|
|
7204
|
+
const $el = this.$target;
|
|
7205
|
+
const isCached = !!this.cache && !clearCache;
|
|
7206
|
+
const lineTemplate = this.lineTemplate;
|
|
7207
|
+
const wordTemplate = this.wordTemplate;
|
|
7208
|
+
const charTemplate = this.charTemplate;
|
|
7209
|
+
const fontsReady = doc.fonts.status !== 'loading';
|
|
7210
|
+
const canSplitLines = lineTemplate && fontsReady;
|
|
7211
|
+
this.ready = !lineTemplate || fontsReady;
|
|
7212
|
+
if (canSplitLines || clearCache) {
|
|
7213
|
+
// No need to revert effects animations here since it's already taken care by the refreshable
|
|
7214
|
+
this.effectsCleanups.forEach(cleanup => isFnc(cleanup) && cleanup(this));
|
|
7215
|
+
}
|
|
7216
|
+
if (!isCached) {
|
|
7217
|
+
if (clearCache) {
|
|
7218
|
+
$el.innerHTML = this.html;
|
|
7219
|
+
this.words.length = this.chars.length = 0;
|
|
7220
|
+
}
|
|
7221
|
+
this.splitNode($el);
|
|
7222
|
+
this.cache = $el.innerHTML;
|
|
7223
|
+
}
|
|
7224
|
+
if (canSplitLines) {
|
|
7225
|
+
if (isCached)
|
|
7226
|
+
$el.innerHTML = this.cache;
|
|
7227
|
+
this.lines.length = 0;
|
|
7228
|
+
if (wordTemplate)
|
|
7229
|
+
this.words = getAllTopLevelElements($el, wordType);
|
|
7230
|
+
}
|
|
7231
|
+
// Always reparse characters after a line reset or if both words and chars are activated
|
|
7232
|
+
if (charTemplate && (canSplitLines || wordTemplate)) {
|
|
7233
|
+
this.chars = getAllTopLevelElements($el, charType);
|
|
7234
|
+
}
|
|
7235
|
+
// Words are used when lines only and prioritized over chars
|
|
7236
|
+
const elementsArray = this.words.length ? this.words : this.chars;
|
|
7237
|
+
let y, linesCount = 0;
|
|
7238
|
+
for (let i = 0, l = elementsArray.length; i < l; i++) {
|
|
7239
|
+
const $el = elementsArray[i];
|
|
7240
|
+
const { top, height } = $el.getBoundingClientRect();
|
|
7241
|
+
if (y && top - y > height * .5)
|
|
7242
|
+
linesCount++;
|
|
7243
|
+
$el.setAttribute(dataLine, `${linesCount}`);
|
|
7244
|
+
const nested = $el.querySelectorAll(`[${dataLine}]`);
|
|
7245
|
+
let c = nested.length;
|
|
7246
|
+
while (c--)
|
|
7247
|
+
nested[c].setAttribute(dataLine, `${linesCount}`);
|
|
7248
|
+
y = top;
|
|
7249
|
+
}
|
|
7250
|
+
if (canSplitLines) {
|
|
7251
|
+
const linesFragment = doc.createDocumentFragment();
|
|
7252
|
+
const parents = new Set();
|
|
7253
|
+
const clones = [];
|
|
7254
|
+
for (let lineIndex = 0; lineIndex < linesCount + 1; lineIndex++) {
|
|
7255
|
+
const $clone = /** @type {HTMLElement} */ ($el.cloneNode(true));
|
|
7256
|
+
filterLineElements($clone, lineIndex, new Set()).forEach($el => {
|
|
7257
|
+
const $parent = $el.parentElement;
|
|
7258
|
+
if ($parent)
|
|
7259
|
+
parents.add($parent);
|
|
7260
|
+
$el.remove();
|
|
7261
|
+
});
|
|
7262
|
+
clones.push($clone);
|
|
7263
|
+
}
|
|
7264
|
+
parents.forEach(filterEmptyElements);
|
|
7265
|
+
for (let cloneIndex = 0, clonesLength = clones.length; cloneIndex < clonesLength; cloneIndex++) {
|
|
7266
|
+
processHTMLTemplate(lineTemplate, this.lines, clones[cloneIndex], linesFragment, lineType, this.debug, cloneIndex);
|
|
7267
|
+
}
|
|
7268
|
+
$el.innerHTML = '';
|
|
7269
|
+
$el.appendChild(linesFragment);
|
|
7270
|
+
if (wordTemplate)
|
|
7271
|
+
this.words = getAllTopLevelElements($el, wordType);
|
|
7272
|
+
if (charTemplate)
|
|
7273
|
+
this.chars = getAllTopLevelElements($el, charType);
|
|
7274
|
+
}
|
|
7275
|
+
// Remove the word wrappers and clear the words array if lines split only
|
|
7276
|
+
if (this.linesOnly) {
|
|
7277
|
+
const words = this.words;
|
|
7278
|
+
let w = words.length;
|
|
7279
|
+
while (w--) {
|
|
7280
|
+
const $word = words[w];
|
|
7281
|
+
$word.replaceWith($word.textContent);
|
|
7282
|
+
}
|
|
7283
|
+
words.length = 0;
|
|
7284
|
+
}
|
|
7285
|
+
if (this.accessible && (canSplitLines || !isCached)) {
|
|
7286
|
+
const $accessible = doc.createElement('span');
|
|
7287
|
+
// Make the accessible element visually-hidden (https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html)
|
|
7288
|
+
$accessible.style.cssText = `position:absolute;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);width:1px;height:1px;white-space:nowrap;`;
|
|
7289
|
+
// $accessible.setAttribute('tabindex', '-1');
|
|
7290
|
+
$accessible.innerHTML = this.html;
|
|
7291
|
+
$el.insertBefore($accessible, $el.firstChild);
|
|
7292
|
+
this.lines.forEach(setAriaHidden);
|
|
7293
|
+
this.words.forEach(setAriaHidden);
|
|
7294
|
+
this.chars.forEach(setAriaHidden);
|
|
7295
|
+
}
|
|
7296
|
+
this.width = /** @type {HTMLElement} */ ($el).offsetWidth;
|
|
7297
|
+
if (canSplitLines || clearCache) {
|
|
7298
|
+
this.effects.forEach((effect, i) => this.effectsCleanups[i] = effect(this));
|
|
7299
|
+
}
|
|
7300
|
+
return this;
|
|
7301
|
+
}
|
|
7302
|
+
refresh() {
|
|
7303
|
+
this.split(true);
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
/**
|
|
7307
|
+
* @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
|
|
7308
|
+
* @param {TextSplitterParams} [parameters]
|
|
7309
|
+
* @return {TextSplitter}
|
|
6791
7310
|
*/
|
|
7311
|
+
const split = (target, parameters) => new TextSplitter(target, parameters);
|
|
7312
|
+
const text = {
|
|
7313
|
+
split,
|
|
7314
|
+
};
|
|
7315
|
+
|
|
7316
|
+
|
|
6792
7317
|
/**
|
|
6793
7318
|
* @param {Number|String|[Number|String,Number|String]} val
|
|
6794
|
-
* @param {
|
|
7319
|
+
* @param {StaggerParams} params
|
|
6795
7320
|
* @return {StaggerFunction}
|
|
6796
7321
|
*/
|
|
6797
7322
|
const stagger = (val, params = {}) => {
|
|
@@ -6805,22 +7330,29 @@ const stagger = (val, params = {}) => {
|
|
|
6805
7330
|
const staggerEase = hasSpring ? /** @type {Spring} */ (ease).ease : hasEasing ? parseEasings(ease) : null;
|
|
6806
7331
|
const grid = params.grid;
|
|
6807
7332
|
const axis = params.axis;
|
|
7333
|
+
const customTotal = params.total;
|
|
6808
7334
|
const fromFirst = isUnd(from) || from === 0 || from === 'first';
|
|
6809
7335
|
const fromCenter = from === 'center';
|
|
6810
7336
|
const fromLast = from === 'last';
|
|
7337
|
+
const fromRandom = from === 'random';
|
|
6811
7338
|
const isRange = isArr(val);
|
|
7339
|
+
const useProp = params.use;
|
|
6812
7340
|
const val1 = isRange ? parseNumber(val[0]) : parseNumber(val);
|
|
6813
7341
|
const val2 = isRange ? parseNumber(val[1]) : 0;
|
|
6814
7342
|
const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString);
|
|
6815
7343
|
const start = params.start || 0 + (isRange ? val1 : 0);
|
|
6816
7344
|
let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0;
|
|
6817
|
-
return (
|
|
7345
|
+
return (target, i, t, tl) => {
|
|
7346
|
+
const [registeredTarget] = registerTargets(target);
|
|
7347
|
+
const total = isUnd(customTotal) ? t : customTotal;
|
|
7348
|
+
const customIndex = !isUnd(useProp) ? isFnc(useProp) ? useProp(registeredTarget, i, total) : getOriginalAnimatableValue(registeredTarget, useProp) : false;
|
|
7349
|
+
const staggerIndex = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i;
|
|
6818
7350
|
if (fromCenter)
|
|
6819
|
-
fromIndex = (
|
|
7351
|
+
fromIndex = (total - 1) / 2;
|
|
6820
7352
|
if (fromLast)
|
|
6821
|
-
fromIndex =
|
|
7353
|
+
fromIndex = total - 1;
|
|
6822
7354
|
if (!values.length) {
|
|
6823
|
-
for (let index = 0; index <
|
|
7355
|
+
for (let index = 0; index < total; index++) {
|
|
6824
7356
|
if (!grid) {
|
|
6825
7357
|
values.push(abs(fromIndex - index));
|
|
6826
7358
|
}
|
|
@@ -6844,11 +7376,13 @@ const stagger = (val, params = {}) => {
|
|
|
6844
7376
|
values = values.map(val => staggerEase(val / maxValue) * maxValue);
|
|
6845
7377
|
if (reversed)
|
|
6846
7378
|
values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val));
|
|
7379
|
+
if (fromRandom)
|
|
7380
|
+
values = shuffle(values);
|
|
6847
7381
|
}
|
|
6848
7382
|
const spacing = isRange ? (val2 - val1) / maxValue : val1;
|
|
6849
7383
|
const offset = tl ? parseTimelinePosition(tl, isUnd(params.start) ? tl.iterationDuration : start) : /** @type {Number} */ (start);
|
|
6850
7384
|
/** @type {String|Number} */
|
|
6851
|
-
let output = offset + ((spacing * round(values[
|
|
7385
|
+
let output = offset + ((spacing * round(values[staggerIndex], 2)) || 0);
|
|
6852
7386
|
if (params.modifier)
|
|
6853
7387
|
output = params.modifier(output);
|
|
6854
7388
|
if (unitMatch)
|
|
@@ -6857,4 +7391,4 @@ const stagger = (val, params = {}) => {
|
|
|
6857
7391
|
};
|
|
6858
7392
|
};
|
|
6859
7393
|
|
|
6860
|
-
export { Animatable, Draggable, JSAnimation, Scope, ScrollObserver, Spring, Timeline, Timer, WAAPIAnimation, animate, createAnimatable, createDraggable, createScope, createSpring, createTimeline, createTimer, eases, engine, onScroll, scrollContainers, stagger, svg, utils, waapi };
|
|
7394
|
+
export { Animatable, Draggable, JSAnimation, Scope, ScrollObserver, Spring, TextSplitter, Timeline, Timer, WAAPIAnimation, animate, createAnimatable, createDraggable, createScope, createSpring, createTimeline, createTimer, eases, engine, onScroll, scrollContainers, stagger, svg, text, utils, waapi };
|