motion 13.4.3 → 13.4.5
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 +28 -10
- package/dist/motion.dev.js +245 -247
- package/dist/motion.js +1 -1
- package/package.json +3 -3
package/dist/motion.dev.js
CHANGED
|
@@ -1118,7 +1118,6 @@
|
|
|
1118
1118
|
stiffness: 100,
|
|
1119
1119
|
damping: 10,
|
|
1120
1120
|
mass: 1.0,
|
|
1121
|
-
velocity: 0.0,
|
|
1122
1121
|
// Default duration/bounce-based options
|
|
1123
1122
|
duration: 800, // in ms
|
|
1124
1123
|
bounce: 0.3,
|
|
@@ -1153,7 +1152,12 @@
|
|
|
1153
1152
|
* This is ported from the Framer implementation of duration-based spring resolution.
|
|
1154
1153
|
*/
|
|
1155
1154
|
const safeMin = 0.001;
|
|
1156
|
-
|
|
1155
|
+
/**
|
|
1156
|
+
* Assumes zero initial velocity and the default mass: time-defined springs
|
|
1157
|
+
* ignore inherited velocity and only resolve without valid physics. Returns
|
|
1158
|
+
* NaN physics when the root search doesn't converge.
|
|
1159
|
+
*/
|
|
1160
|
+
function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, }) {
|
|
1157
1161
|
let envelope;
|
|
1158
1162
|
let derivative;
|
|
1159
1163
|
exports.warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
|
|
@@ -1170,15 +1174,13 @@
|
|
|
1170
1174
|
envelope = (undampedFreq) => {
|
|
1171
1175
|
const exponentialDecay = undampedFreq * dampingRatio;
|
|
1172
1176
|
const delta = exponentialDecay * duration;
|
|
1173
|
-
const a = exponentialDecay - velocity;
|
|
1174
1177
|
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
1175
1178
|
const c = Math.exp(-delta);
|
|
1176
|
-
return safeMin - (
|
|
1179
|
+
return safeMin - (exponentialDecay / b) * c;
|
|
1177
1180
|
};
|
|
1178
1181
|
derivative = (undampedFreq) => {
|
|
1179
1182
|
const exponentialDecay = undampedFreq * dampingRatio;
|
|
1180
1183
|
const delta = exponentialDecay * duration;
|
|
1181
|
-
const d = delta * velocity + velocity;
|
|
1182
1184
|
const e = dampingRatio *
|
|
1183
1185
|
dampingRatio *
|
|
1184
1186
|
undampedFreq *
|
|
@@ -1187,7 +1189,7 @@
|
|
|
1187
1189
|
const f = Math.exp(-delta);
|
|
1188
1190
|
const g = calcAngularFreq(undampedFreq * undampedFreq, dampingRatio);
|
|
1189
1191
|
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
1190
|
-
return (factor *
|
|
1192
|
+
return (factor * -e * f) / g;
|
|
1191
1193
|
};
|
|
1192
1194
|
}
|
|
1193
1195
|
else {
|
|
@@ -1196,79 +1198,89 @@
|
|
|
1196
1198
|
*/
|
|
1197
1199
|
envelope = (undampedFreq) => {
|
|
1198
1200
|
const a = Math.exp(-undampedFreq * duration);
|
|
1199
|
-
const b =
|
|
1201
|
+
const b = undampedFreq * duration + 1;
|
|
1200
1202
|
return -safeMin + a * b;
|
|
1201
1203
|
};
|
|
1202
1204
|
derivative = (undampedFreq) => {
|
|
1203
1205
|
const a = Math.exp(-undampedFreq * duration);
|
|
1204
|
-
const b =
|
|
1206
|
+
const b = -undampedFreq * (duration * duration);
|
|
1205
1207
|
return a * b;
|
|
1206
1208
|
};
|
|
1207
1209
|
}
|
|
1208
1210
|
const initialGuess = 5 / duration;
|
|
1209
1211
|
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
else {
|
|
1219
|
-
const stiffness = undampedFreq * undampedFreq * mass;
|
|
1220
|
-
return {
|
|
1221
|
-
stiffness,
|
|
1222
|
-
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
1223
|
-
duration,
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1212
|
+
const stiffness = undampedFreq * undampedFreq;
|
|
1213
|
+
return {
|
|
1214
|
+
stiffness,
|
|
1215
|
+
damping: dampingRatio * 2 * Math.sqrt(stiffness),
|
|
1216
|
+
duration: secondsToMilliseconds(duration),
|
|
1217
|
+
};
|
|
1226
1218
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1219
|
+
/**
|
|
1220
|
+
* Spring physics must be finite. stiffness and mass are also divisors so must
|
|
1221
|
+
* be positive, whereas a damping of 0 is a valid, perpetually oscillating
|
|
1222
|
+
* spring. Relational rather than Number.isFinite so numeric strings still
|
|
1223
|
+
* coerce.
|
|
1224
|
+
*/
|
|
1225
|
+
const isValidPhysics = (value, canBeZero) => (canBeZero ? value >= 0 : value > 0) && value < Infinity;
|
|
1226
|
+
/**
|
|
1227
|
+
* Returns value if it's usable spring physics, otherwise undefined so callers
|
|
1228
|
+
* fall back to the default. An explicit `undefined`, e.g. from a forwarded
|
|
1229
|
+
* optional prop, must fall back too. Invalid physics would otherwise resolve
|
|
1230
|
+
* to NaN spring values that never report done.
|
|
1231
|
+
*/
|
|
1232
|
+
function resolvePhysics(value, canBeZero) {
|
|
1233
|
+
if (isValidPhysics(value, canBeZero))
|
|
1234
|
+
return value;
|
|
1235
|
+
{
|
|
1236
|
+
exports.warning(value === undefined, "Spring stiffness and mass must be positive, damping 0 or greater", "spring-invalid-physics");
|
|
1237
|
+
}
|
|
1238
|
+
return undefined;
|
|
1231
1239
|
}
|
|
1232
1240
|
function getSpringOptions(options) {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1241
|
+
/**
|
|
1242
|
+
* Resolve physics before choosing between physics- and duration-based
|
|
1243
|
+
* resolution, so an invalid stiffness doesn't also silently discard a
|
|
1244
|
+
* valid duration/bounce.
|
|
1245
|
+
*/
|
|
1246
|
+
const validStiffness = resolvePhysics(options.stiffness);
|
|
1247
|
+
const validDamping = resolvePhysics(options.damping, true);
|
|
1248
|
+
const validMass = resolvePhysics(options.mass);
|
|
1249
|
+
const springOptions = {
|
|
1239
1250
|
...options,
|
|
1251
|
+
stiffness: validStiffness ?? springDefaults.stiffness,
|
|
1252
|
+
damping: validDamping ?? springDefaults.damping,
|
|
1253
|
+
mass: validMass ?? springDefaults.mass,
|
|
1254
|
+
isResolvedFromDuration: false,
|
|
1255
|
+
// stiffness/damping/mass overrides duration/bounce
|
|
1256
|
+
isTimeDefined: (validStiffness ?? validDamping ?? validMass) === undefined &&
|
|
1257
|
+
(options.duration !== undefined || options.bounce !== undefined),
|
|
1240
1258
|
};
|
|
1241
|
-
|
|
1242
|
-
if (!isSpringType(options, physicsKeys) &&
|
|
1243
|
-
isSpringType(options, durationKeys)) {
|
|
1244
|
-
// Time-defined springs should ignore inherited velocity.
|
|
1245
|
-
// Velocity from interrupted animations can cause findSpring()
|
|
1246
|
-
// to compute wildly different spring parameters, leading to
|
|
1247
|
-
// massive oscillation on small-range animations.
|
|
1248
|
-
springOptions.velocity = 0;
|
|
1259
|
+
if (springOptions.isTimeDefined) {
|
|
1249
1260
|
if (options.visualDuration) {
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
springOptions = {
|
|
1257
|
-
...springOptions,
|
|
1258
|
-
mass: springDefaults.mass,
|
|
1259
|
-
stiffness,
|
|
1260
|
-
damping,
|
|
1261
|
-
};
|
|
1261
|
+
const root = (2 * Math.PI) / (options.visualDuration * 1.2);
|
|
1262
|
+
springOptions.stiffness = root * root;
|
|
1263
|
+
springOptions.damping =
|
|
1264
|
+
2 *
|
|
1265
|
+
clamp(0.05, 1, 1 - (options.bounce || 0)) *
|
|
1266
|
+
Math.sqrt(springOptions.stiffness);
|
|
1262
1267
|
}
|
|
1263
1268
|
else {
|
|
1264
|
-
|
|
1265
|
-
springOptions = {
|
|
1266
|
-
...springOptions,
|
|
1267
|
-
...derived,
|
|
1268
|
-
mass: springDefaults.mass,
|
|
1269
|
-
};
|
|
1269
|
+
Object.assign(springOptions, findSpring(springOptions));
|
|
1270
1270
|
springOptions.isResolvedFromDuration = true;
|
|
1271
1271
|
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Time options can degenerate: a NaN bounce gives a NaN damping, an
|
|
1274
|
+
* infinite visualDuration a 0 stiffness, and findSpring NaN for both
|
|
1275
|
+
* when it doesn't converge. Replace the two together, so the
|
|
1276
|
+
* relationship duration resolution establishes between them is never
|
|
1277
|
+
* left half-overwritten.
|
|
1278
|
+
*/
|
|
1279
|
+
if (!isValidPhysics(springOptions.stiffness) ||
|
|
1280
|
+
!isValidPhysics(springOptions.damping, true)) {
|
|
1281
|
+
springOptions.stiffness = springDefaults.stiffness;
|
|
1282
|
+
springOptions.damping = springDefaults.damping;
|
|
1283
|
+
}
|
|
1272
1284
|
}
|
|
1273
1285
|
return springOptions;
|
|
1274
1286
|
}
|
|
@@ -1287,10 +1299,13 @@
|
|
|
1287
1299
|
* to reduce GC during animation.
|
|
1288
1300
|
*/
|
|
1289
1301
|
const state = { done: false, value: origin };
|
|
1290
|
-
const { stiffness, damping, mass, duration,
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1302
|
+
const { stiffness, damping, mass, duration, isResolvedFromDuration, isTimeDefined, } = getSpringOptions({ ...options });
|
|
1303
|
+
/**
|
|
1304
|
+
* Time-defined springs ignore inherited velocity. Velocity from
|
|
1305
|
+
* interrupted animations causes massive oscillation on small-range
|
|
1306
|
+
* animations.
|
|
1307
|
+
*/
|
|
1308
|
+
const inheritVelocity = (velocity) => isTimeDefined ? 0 : -millisecondsToSeconds(velocity);
|
|
1294
1309
|
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
1295
1310
|
const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
|
|
1296
1311
|
const decay = dampingRatio * undampedAngularFreq;
|
|
@@ -1304,7 +1319,7 @@
|
|
|
1304
1319
|
const s = {
|
|
1305
1320
|
target,
|
|
1306
1321
|
delta: target - origin,
|
|
1307
|
-
velocity: velocity || 0
|
|
1322
|
+
velocity: inheritVelocity(options.velocity || 0) || 0,
|
|
1308
1323
|
restSpeed: 0,
|
|
1309
1324
|
restDelta: 0,
|
|
1310
1325
|
};
|
|
@@ -1312,7 +1327,7 @@
|
|
|
1312
1327
|
* If we're working on a granular scale, use smaller defaults for determining
|
|
1313
1328
|
* when the spring is finished.
|
|
1314
1329
|
*
|
|
1315
|
-
* These defaults have been selected
|
|
1330
|
+
* These defaults have been selected empirically based on what strikes a good
|
|
1316
1331
|
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
|
1317
1332
|
*/
|
|
1318
1333
|
const setRestThresholds = () => {
|
|
@@ -1410,11 +1425,6 @@
|
|
|
1410
1425
|
};
|
|
1411
1426
|
}
|
|
1412
1427
|
update();
|
|
1413
|
-
/**
|
|
1414
|
-
* Time-defined springs ignore inherited velocity, see getSpringOptions.
|
|
1415
|
-
*/
|
|
1416
|
-
const ignoreVelocity = !isSpringType(options, physicsKeys) &&
|
|
1417
|
-
isSpringType(options, durationKeys);
|
|
1418
1428
|
const calculatedDuration = isResolvedFromDuration ? duration || null : null;
|
|
1419
1429
|
const generator = {
|
|
1420
1430
|
calculatedDuration,
|
|
@@ -1425,9 +1435,7 @@
|
|
|
1425
1435
|
retarget: (keyframes, newVelocity) => {
|
|
1426
1436
|
s.target = keyframes[keyframes.length - 1];
|
|
1427
1437
|
s.delta = s.target - keyframes[0];
|
|
1428
|
-
s.velocity =
|
|
1429
|
-
? 0
|
|
1430
|
-
: -millisecondsToSeconds(newVelocity);
|
|
1438
|
+
s.velocity = inheritVelocity(newVelocity);
|
|
1431
1439
|
// Default thresholds depend on the scale of the new delta
|
|
1432
1440
|
if (!(options.restSpeed && options.restDelta))
|
|
1433
1441
|
setRestThresholds();
|
|
@@ -3445,7 +3453,7 @@
|
|
|
3445
3453
|
|
|
3446
3454
|
class GroupAnimation {
|
|
3447
3455
|
constructor(animations) {
|
|
3448
|
-
// Bound to
|
|
3456
|
+
// Bound to accommodate common `return animation.stop` pattern
|
|
3449
3457
|
this.stop = () => this.runAll("stop");
|
|
3450
3458
|
this.animations = animations.filter(Boolean);
|
|
3451
3459
|
}
|
|
@@ -5777,7 +5785,7 @@
|
|
|
5777
5785
|
translateAxis(box.y, -node.scroll.offset.y);
|
|
5778
5786
|
}
|
|
5779
5787
|
if (delta) {
|
|
5780
|
-
//
|
|
5788
|
+
// Incorporate each ancestor's scale into a cumulative treeScale for this component
|
|
5781
5789
|
treeScale.x *= delta.x.scale;
|
|
5782
5790
|
treeScale.y *= delta.y.scale;
|
|
5783
5791
|
// Apply each ancestor's calculated delta into this component's recorded layout box
|
|
@@ -5985,7 +5993,7 @@
|
|
|
5985
5993
|
/**
|
|
5986
5994
|
* Create a hover gesture. hover() is different to .addEventListener("pointerenter")
|
|
5987
5995
|
* in that it has an easier syntax, filters out polyfilled touch events, interoperates
|
|
5988
|
-
* with drag gestures, and automatically removes the "
|
|
5996
|
+
* with drag gestures, and automatically removes the "pointerleave" event listener when the hover ends.
|
|
5989
5997
|
*
|
|
5990
5998
|
* @public
|
|
5991
5999
|
*/
|
|
@@ -7721,7 +7729,7 @@
|
|
|
7721
7729
|
/**
|
|
7722
7730
|
* Iterate backwards over the builders array. We can ignore the
|
|
7723
7731
|
* "wait" animations. If we have an interrupting animation in the
|
|
7724
|
-
* queue then we need to batch all
|
|
7732
|
+
* queue then we need to batch all preceding animations into it.
|
|
7725
7733
|
* Currently this only batches the update functions but will also
|
|
7726
7734
|
* need to batch the targets.
|
|
7727
7735
|
*/
|
|
@@ -8601,7 +8609,7 @@
|
|
|
8601
8609
|
/**
|
|
8602
8610
|
* compareDocumentPosition returns a bitmask, by using the bitwise &
|
|
8603
8611
|
* we're returning true if 2 in that bitmask is set to true. 2 is set
|
|
8604
|
-
* to true if b
|
|
8612
|
+
* to true if b precedes a.
|
|
8605
8613
|
*/
|
|
8606
8614
|
return a.compareDocumentPosition(b) & 2 ? 1 : -1;
|
|
8607
8615
|
}
|
|
@@ -9093,7 +9101,7 @@
|
|
|
9093
9101
|
}
|
|
9094
9102
|
/**
|
|
9095
9103
|
* Set all encountered keys so far as the protected keys for this type. This will
|
|
9096
|
-
* be any key that has been animated or otherwise handled by active, higher-
|
|
9104
|
+
* be any key that has been animated or otherwise handled by active, higher-priority types.
|
|
9097
9105
|
*/
|
|
9098
9106
|
typeState.protectedKeys = { ...encounteredKeys };
|
|
9099
9107
|
// Check if we can skip analysing this prop early
|
|
@@ -9918,8 +9926,8 @@
|
|
|
9918
9926
|
this.hasCheckedOptimisedAppear = false;
|
|
9919
9927
|
/**
|
|
9920
9928
|
* An object representing the calculated contextual/accumulated/tree scale.
|
|
9921
|
-
* This will be used to scale
|
|
9922
|
-
* calculated in screen-space but need to be scaled for elements to
|
|
9929
|
+
* This will be used to scale calculated projection transforms, as these are
|
|
9930
|
+
* calculated in screen-space but need to be scaled for elements to visually
|
|
9923
9931
|
* make it to their calculated destinations.
|
|
9924
9932
|
*
|
|
9925
9933
|
* TODO: Lazy-init
|
|
@@ -9930,7 +9938,6 @@
|
|
|
9930
9938
|
*/
|
|
9931
9939
|
this.eventHandlers = new Map();
|
|
9932
9940
|
this.hasTreeAnimated = false;
|
|
9933
|
-
this.layoutVersion = 0;
|
|
9934
9941
|
// Note: Currently only running on root node
|
|
9935
9942
|
this.updateScheduled = false;
|
|
9936
9943
|
this.scheduleUpdate = () => this.update();
|
|
@@ -9970,7 +9977,6 @@
|
|
|
9970
9977
|
* Frame calculations
|
|
9971
9978
|
*/
|
|
9972
9979
|
this.resolvedRelativeTargetAt = 0.0;
|
|
9973
|
-
this.linkedParentVersion = 0;
|
|
9974
9980
|
this.hasProjected = false;
|
|
9975
9981
|
this.isVisible = true;
|
|
9976
9982
|
this.animationProgress = 0;
|
|
@@ -10251,6 +10257,7 @@
|
|
|
10251
10257
|
* get measured even when memoized (willUpdate skipped).
|
|
10252
10258
|
*/
|
|
10253
10259
|
this.nodes.forEach(ensureDraggedNodesSnapshotted);
|
|
10260
|
+
this.nodes.forEach(syncRelativeLayout);
|
|
10254
10261
|
/**
|
|
10255
10262
|
* Write
|
|
10256
10263
|
*/
|
|
@@ -10348,7 +10355,6 @@
|
|
|
10348
10355
|
}
|
|
10349
10356
|
const prevLayout = this.layout;
|
|
10350
10357
|
this.layout = this.measure(false);
|
|
10351
|
-
this.layoutVersion++;
|
|
10352
10358
|
if (!this.layoutCorrected)
|
|
10353
10359
|
this.layoutCorrected = createBox();
|
|
10354
10360
|
this.isLayoutDirty = false;
|
|
@@ -10568,11 +10574,6 @@
|
|
|
10568
10574
|
return;
|
|
10569
10575
|
this.resolvedRelativeTargetAt = frameData.timestamp;
|
|
10570
10576
|
const relativeParent = this.getClosestProjectingParent();
|
|
10571
|
-
if (relativeParent &&
|
|
10572
|
-
this.linkedParentVersion !== relativeParent.layoutVersion &&
|
|
10573
|
-
!relativeParent.options.layoutRoot) {
|
|
10574
|
-
this.removeRelativeTarget();
|
|
10575
|
-
}
|
|
10576
10577
|
/**
|
|
10577
10578
|
* If we don't have a targetDelta but do have a layout, we can attempt to resolve
|
|
10578
10579
|
* a relativeParent. This will allow a component to perform scale correction
|
|
@@ -10675,7 +10676,6 @@
|
|
|
10675
10676
|
}
|
|
10676
10677
|
createRelativeTarget(relativeParent, layout, parentLayout) {
|
|
10677
10678
|
this.relativeParent = relativeParent;
|
|
10678
|
-
this.linkedParentVersion = relativeParent.layoutVersion;
|
|
10679
10679
|
this.forceRelativeParentToResolveTarget();
|
|
10680
10680
|
this.relativeTarget = createBox();
|
|
10681
10681
|
this.relativeTargetOrigin = createBox();
|
|
@@ -11385,6 +11385,26 @@
|
|
|
11385
11385
|
node.isLayoutDirty = true;
|
|
11386
11386
|
}
|
|
11387
11387
|
}
|
|
11388
|
+
/**
|
|
11389
|
+
* If a node's relative parent is about to be re-measured but the node isn't
|
|
11390
|
+
* (e.g. it's in another LayoutGroup), its relative target is out of date.
|
|
11391
|
+
* If it's layout animating, re-measure it too, so its relative target keeps
|
|
11392
|
+
* resolving against current layouts. Otherwise its layout is still valid
|
|
11393
|
+
* relative to its parent, so drop the relative target, and any targetDelta
|
|
11394
|
+
* left over from a finished animation while an ancestor still animates, to be
|
|
11395
|
+
* rebuilt from its layout.
|
|
11396
|
+
*/
|
|
11397
|
+
function syncRelativeLayout(node) {
|
|
11398
|
+
if (node.relativeTarget && node.relativeParent?.isLayoutDirty) {
|
|
11399
|
+
if (node.currentAnimation) {
|
|
11400
|
+
node.isLayoutDirty = true;
|
|
11401
|
+
}
|
|
11402
|
+
else {
|
|
11403
|
+
node.targetDelta = undefined;
|
|
11404
|
+
node.removeRelativeTarget();
|
|
11405
|
+
}
|
|
11406
|
+
}
|
|
11407
|
+
}
|
|
11388
11408
|
function resetTransformStyle(node) {
|
|
11389
11409
|
const { visualElement } = node.options;
|
|
11390
11410
|
if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
|
|
@@ -12561,7 +12581,10 @@
|
|
|
12561
12581
|
x: createAxisInfo(),
|
|
12562
12582
|
y: createAxisInfo(),
|
|
12563
12583
|
});
|
|
12564
|
-
|
|
12584
|
+
/**
|
|
12585
|
+
* Also iterated with for...in as the list of axes.
|
|
12586
|
+
*/
|
|
12587
|
+
const axisKeys = {
|
|
12565
12588
|
x: {
|
|
12566
12589
|
length: "Width",
|
|
12567
12590
|
position: "Left",
|
|
@@ -12573,11 +12596,13 @@
|
|
|
12573
12596
|
};
|
|
12574
12597
|
function updateAxisInfo(element, axisName, info, time) {
|
|
12575
12598
|
const axis = info[axisName];
|
|
12576
|
-
const { length, position } =
|
|
12599
|
+
const { length, position } = axisKeys[axisName];
|
|
12577
12600
|
const prev = axis.current;
|
|
12578
12601
|
const prevTime = info.time;
|
|
12579
12602
|
axis.current = Math.abs(element[`scroll${position}`]);
|
|
12580
|
-
axis.
|
|
12603
|
+
axis.containerLength = element[`client${length}`];
|
|
12604
|
+
axis.targetLength = element[`scroll${length}`];
|
|
12605
|
+
axis.scrollLength = axis.targetLength - axis.containerLength;
|
|
12581
12606
|
axis.offset.length = 0;
|
|
12582
12607
|
axis.offset[0] = 0;
|
|
12583
12608
|
axis.offset[1] = axis.scrollLength;
|
|
@@ -12588,54 +12613,15 @@
|
|
|
12588
12613
|
? 0
|
|
12589
12614
|
: velocityPerSecond(axis.current - prev, elapsed);
|
|
12590
12615
|
}
|
|
12616
|
+
/**
|
|
12617
|
+
* Measures a scroll container. Runs once per container per frame; every
|
|
12618
|
+
* handler on that container derives its info from the result.
|
|
12619
|
+
*/
|
|
12591
12620
|
function updateScrollInfo(element, info, time) {
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
info.time = time;
|
|
12595
|
-
}
|
|
12596
|
-
|
|
12597
|
-
function calcInset(element, container) {
|
|
12598
|
-
const inset = { x: 0, y: 0 };
|
|
12599
|
-
let current = element;
|
|
12600
|
-
while (current && current !== container) {
|
|
12601
|
-
if (isHTMLElement(current)) {
|
|
12602
|
-
inset.x += current.offsetLeft;
|
|
12603
|
-
inset.y += current.offsetTop;
|
|
12604
|
-
current = current.offsetParent;
|
|
12605
|
-
}
|
|
12606
|
-
else if (current.tagName === "svg") {
|
|
12607
|
-
/**
|
|
12608
|
-
* This isn't an ideal approach to measuring the offset of <svg /> tags.
|
|
12609
|
-
* It would be preferable, given they behave like HTMLElements in most ways
|
|
12610
|
-
* to use offsetLeft/Top. But these don't exist on <svg />. Likewise we
|
|
12611
|
-
* can't use .getBBox() like most SVG elements as these provide the offset
|
|
12612
|
-
* relative to the SVG itself, which for <svg /> is usually 0x0.
|
|
12613
|
-
*/
|
|
12614
|
-
const svgBoundingBox = current.getBoundingClientRect();
|
|
12615
|
-
current = current.parentElement;
|
|
12616
|
-
const parentBoundingBox = current.getBoundingClientRect();
|
|
12617
|
-
inset.x += svgBoundingBox.left - parentBoundingBox.left;
|
|
12618
|
-
inset.y += svgBoundingBox.top - parentBoundingBox.top;
|
|
12619
|
-
}
|
|
12620
|
-
else if (current instanceof SVGGraphicsElement) {
|
|
12621
|
-
const { x, y } = current.getBBox();
|
|
12622
|
-
inset.x += x;
|
|
12623
|
-
inset.y += y;
|
|
12624
|
-
let svg = null;
|
|
12625
|
-
let parent = current.parentNode;
|
|
12626
|
-
while (!svg) {
|
|
12627
|
-
if (parent.tagName === "svg") {
|
|
12628
|
-
svg = parent;
|
|
12629
|
-
}
|
|
12630
|
-
parent = current.parentNode;
|
|
12631
|
-
}
|
|
12632
|
-
current = svg;
|
|
12633
|
-
}
|
|
12634
|
-
else {
|
|
12635
|
-
break;
|
|
12636
|
-
}
|
|
12621
|
+
for (const axis in axisKeys) {
|
|
12622
|
+
updateAxisInfo(element, axis, info, time);
|
|
12637
12623
|
}
|
|
12638
|
-
|
|
12624
|
+
info.time = time;
|
|
12639
12625
|
}
|
|
12640
12626
|
|
|
12641
12627
|
const namedEdges = {
|
|
@@ -12733,86 +12719,104 @@
|
|
|
12733
12719
|
],
|
|
12734
12720
|
};
|
|
12735
12721
|
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12722
|
+
/**
|
|
12723
|
+
* Resolved offsets map to evenly spaced progress values, so progress is
|
|
12724
|
+
* derived from the segment index rather than building an interpolator.
|
|
12725
|
+
*/
|
|
12726
|
+
function offsetsToProgress(offsets, v) {
|
|
12727
|
+
const n = offsets.length - 1;
|
|
12728
|
+
if (n < 1)
|
|
12729
|
+
return 0;
|
|
12730
|
+
const reverse = offsets[0] > offsets[n];
|
|
12731
|
+
const at = (i) => offsets[reverse ? n - i : i];
|
|
12732
|
+
/**
|
|
12733
|
+
* Matches interpolate(), which checks for a zero-length first range
|
|
12734
|
+
* before reversing descending offsets.
|
|
12735
|
+
*/
|
|
12736
|
+
if (offsets[0] === offsets[1] && v < at(0))
|
|
12737
|
+
return reverse ? 1 : 0;
|
|
12738
|
+
let i = 0;
|
|
12739
|
+
while (i < n - 1 && v >= at(i + 1))
|
|
12740
|
+
i++;
|
|
12741
|
+
const p = (i + progress(at(i), at(i + 1), v)) / n;
|
|
12742
|
+
return reverse ? 1 - p : p;
|
|
12741
12743
|
}
|
|
12742
|
-
function resolveOffsets(
|
|
12743
|
-
const { offset: offsetDefinition = ScrollOffset.All } = options;
|
|
12744
|
-
const
|
|
12745
|
-
const lengthLabel = axis === "y" ? "height" : "width";
|
|
12746
|
-
const inset = target !== container ? calcInset(target, container) : point;
|
|
12747
|
-
/**
|
|
12748
|
-
* Measure the target and container. If they're the same thing then we
|
|
12749
|
-
* use the container's scrollWidth/Height as the target, from there
|
|
12750
|
-
* all other calculations can remain the same.
|
|
12751
|
-
*/
|
|
12752
|
-
const targetSize = target === container
|
|
12753
|
-
? { width: container.scrollWidth, height: container.scrollHeight }
|
|
12754
|
-
: getTargetSize(target);
|
|
12755
|
-
const containerSize = {
|
|
12756
|
-
width: container.clientWidth,
|
|
12757
|
-
height: container.clientHeight,
|
|
12758
|
-
};
|
|
12744
|
+
function resolveOffsets(info, options) {
|
|
12745
|
+
const { offset: offsetDefinition = ScrollOffset.All, axis = "y" } = options;
|
|
12746
|
+
const axisInfo = info[axis];
|
|
12759
12747
|
/**
|
|
12760
12748
|
* Reset the length of the resolved offset array rather than creating a new one.
|
|
12761
|
-
* TODO: More reusable data structures for targetSize/containerSize would also be good.
|
|
12762
12749
|
*/
|
|
12763
|
-
|
|
12750
|
+
axisInfo.offset.length = 0;
|
|
12764
12751
|
/**
|
|
12765
12752
|
* Populate the offset array by resolving the user's offset definition into
|
|
12766
|
-
* a list of pixel scroll
|
|
12753
|
+
* a list of pixel scroll offsets.
|
|
12767
12754
|
*/
|
|
12768
|
-
let hasChanged = !info[axis].interpolate;
|
|
12769
12755
|
const numOffsets = offsetDefinition.length;
|
|
12770
12756
|
for (let i = 0; i < numOffsets; i++) {
|
|
12771
|
-
|
|
12772
|
-
if (!hasChanged && offset !== info[axis].interpolatorOffsets[i]) {
|
|
12773
|
-
hasChanged = true;
|
|
12774
|
-
}
|
|
12775
|
-
info[axis].offset[i] = offset;
|
|
12757
|
+
axisInfo.offset[i] = resolveOffset(offsetDefinition[i], axisInfo.containerLength, axisInfo.targetLength, axisInfo.targetOffset);
|
|
12776
12758
|
}
|
|
12777
|
-
|
|
12778
|
-
* If the pixel scroll offsets have changed, create a new interpolator function
|
|
12779
|
-
* to map scroll value into a progress.
|
|
12780
|
-
*/
|
|
12781
|
-
if (hasChanged) {
|
|
12782
|
-
info[axis].interpolate = interpolate(info[axis].offset, defaultOffset$1(offsetDefinition), { clamp: false });
|
|
12783
|
-
info[axis].interpolatorOffsets = [...info[axis].offset];
|
|
12784
|
-
}
|
|
12785
|
-
info[axis].progress = clamp(0, 1, info[axis].interpolate(info[axis].current));
|
|
12759
|
+
axisInfo.progress = clamp(0, 1, offsetsToProgress(axisInfo.offset, axisInfo.current));
|
|
12786
12760
|
}
|
|
12787
12761
|
|
|
12788
|
-
function
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12762
|
+
function calcInset(element, container) {
|
|
12763
|
+
const inset = { x: 0, y: 0 };
|
|
12764
|
+
let current = element;
|
|
12765
|
+
while (current && current !== container) {
|
|
12766
|
+
if (isHTMLElement(current)) {
|
|
12767
|
+
inset.x += current.offsetLeft;
|
|
12768
|
+
inset.y += current.offsetTop;
|
|
12769
|
+
current = current.offsetParent;
|
|
12770
|
+
}
|
|
12771
|
+
else if (current.tagName === "svg") {
|
|
12772
|
+
/**
|
|
12773
|
+
* This isn't an ideal approach to measuring the offset of <svg /> tags.
|
|
12774
|
+
* It would be preferable, given they behave like HTMLElements in most ways
|
|
12775
|
+
* to use offsetLeft/Top. But these don't exist on <svg />. Likewise we
|
|
12776
|
+
* can't use .getBBox() like most SVG elements as these provide the offset
|
|
12777
|
+
* relative to the SVG itself, which for <svg /> is usually 0x0.
|
|
12778
|
+
*/
|
|
12779
|
+
const svgBoundingBox = current.getBoundingClientRect();
|
|
12780
|
+
current = current.parentElement;
|
|
12781
|
+
const parentBoundingBox = current.getBoundingClientRect();
|
|
12782
|
+
inset.x += svgBoundingBox.left - parentBoundingBox.left;
|
|
12783
|
+
inset.y += svgBoundingBox.top - parentBoundingBox.top;
|
|
12784
|
+
}
|
|
12785
|
+
else if (current instanceof SVGGraphicsElement) {
|
|
12786
|
+
const { x, y } = current.getBBox();
|
|
12787
|
+
inset.x += x;
|
|
12788
|
+
inset.y += y;
|
|
12789
|
+
let svg = null;
|
|
12790
|
+
let parent = current.parentNode;
|
|
12791
|
+
while (!svg) {
|
|
12792
|
+
if (parent.tagName === "svg") {
|
|
12793
|
+
svg = parent;
|
|
12794
|
+
}
|
|
12795
|
+
parent = current.parentNode;
|
|
12796
|
+
}
|
|
12797
|
+
current = svg;
|
|
12798
|
+
}
|
|
12799
|
+
else {
|
|
12800
|
+
break;
|
|
12800
12801
|
}
|
|
12801
12802
|
}
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12803
|
+
return inset;
|
|
12804
|
+
}
|
|
12805
|
+
|
|
12806
|
+
function getTargetSize(target) {
|
|
12807
|
+
return "getBBox" in target && target.tagName !== "svg"
|
|
12808
|
+
? target.getBBox()
|
|
12809
|
+
: { width: target.clientWidth, height: target.clientHeight };
|
|
12810
|
+
}
|
|
12811
|
+
function createOnScrollHandler(container, onScroll, info, options = {}) {
|
|
12812
|
+
const { target } = options;
|
|
12808
12813
|
/**
|
|
12809
12814
|
* In development mode ensure scroll containers aren't position: static as this makes
|
|
12810
12815
|
* it difficult to measure their relative positions. The document scrolling element
|
|
12811
12816
|
* is exempt: offsetParent measurements naturally resolve relative to the document.
|
|
12812
12817
|
*/
|
|
12813
12818
|
{
|
|
12814
|
-
if (
|
|
12815
|
-
target &&
|
|
12819
|
+
if (target &&
|
|
12816
12820
|
target !== container &&
|
|
12817
12821
|
container !== document.documentElement &&
|
|
12818
12822
|
container !== document.scrollingElement &&
|
|
@@ -12820,24 +12824,39 @@
|
|
|
12820
12824
|
warnOnce(getComputedStyle(container).position !== "static", "Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly.");
|
|
12821
12825
|
}
|
|
12822
12826
|
}
|
|
12823
|
-
|
|
12824
|
-
|
|
12827
|
+
/**
|
|
12828
|
+
* Handlers without a target or offset are notified with the container's
|
|
12829
|
+
* shared info object, so they measure nothing themselves.
|
|
12830
|
+
*/
|
|
12831
|
+
const needsOwnInfo = target || options.offset;
|
|
12825
12832
|
return {
|
|
12826
|
-
measure: (
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12833
|
+
measure: (containerInfo) => {
|
|
12834
|
+
if (!needsOwnInfo)
|
|
12835
|
+
return;
|
|
12836
|
+
info.time = containerInfo.time;
|
|
12837
|
+
for (const key in axisKeys) {
|
|
12838
|
+
const axis = key;
|
|
12839
|
+
const { offset } = info[axis];
|
|
12840
|
+
Object.assign(info[axis], containerInfo[axis]);
|
|
12841
|
+
info[axis].offset = offset;
|
|
12842
|
+
}
|
|
12843
|
+
if (target && target !== container) {
|
|
12844
|
+
const inset = calcInset(target, container);
|
|
12845
|
+
const size = getTargetSize(target);
|
|
12846
|
+
info.x.targetOffset = inset.x;
|
|
12847
|
+
info.y.targetOffset = inset.y;
|
|
12848
|
+
info.x.targetLength = size.width;
|
|
12849
|
+
info.y.targetLength = size.height;
|
|
12850
|
+
}
|
|
12851
|
+
resolveOffsets(info, options);
|
|
12832
12852
|
},
|
|
12833
|
-
notify: () => onScroll(info),
|
|
12853
|
+
notify: (containerInfo) => onScroll(needsOwnInfo ? info : containerInfo),
|
|
12834
12854
|
};
|
|
12835
12855
|
}
|
|
12836
12856
|
|
|
12837
12857
|
const scrollListeners = new WeakMap();
|
|
12838
12858
|
const resizeListeners = new WeakMap();
|
|
12839
12859
|
const onScrollHandlers = new WeakMap();
|
|
12840
|
-
const scrollSize = new WeakMap();
|
|
12841
12860
|
const dimensionCheckProcesses = new WeakMap();
|
|
12842
12861
|
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
|
|
12843
12862
|
function scrollInfo(onScroll, { container = document.scrollingElement, trackContentSize = false, ...options } = {}) {
|
|
@@ -12863,15 +12882,17 @@
|
|
|
12863
12882
|
* If not, create one.
|
|
12864
12883
|
*/
|
|
12865
12884
|
if (!scrollListeners.has(container)) {
|
|
12885
|
+
const containerInfo = createScrollInfo();
|
|
12866
12886
|
const measureAll = () => {
|
|
12887
|
+
updateScrollInfo(container, containerInfo, frameData.timestamp);
|
|
12867
12888
|
for (const handler of containerHandlers) {
|
|
12868
|
-
handler.measure(
|
|
12889
|
+
handler.measure(containerInfo);
|
|
12869
12890
|
}
|
|
12870
12891
|
frame.preUpdate(notifyAll);
|
|
12871
12892
|
};
|
|
12872
12893
|
const notifyAll = () => {
|
|
12873
12894
|
for (const handler of containerHandlers) {
|
|
12874
|
-
handler.notify();
|
|
12895
|
+
handler.notify(containerInfo);
|
|
12875
12896
|
}
|
|
12876
12897
|
};
|
|
12877
12898
|
const listener = () => frame.read(measureAll);
|
|
@@ -12894,7 +12915,6 @@
|
|
|
12894
12915
|
width: container.scrollWidth,
|
|
12895
12916
|
height: container.scrollHeight,
|
|
12896
12917
|
};
|
|
12897
|
-
scrollSize.set(container, size);
|
|
12898
12918
|
// Add frame-based scroll dimension checking to detect content changes
|
|
12899
12919
|
const checkScrollDimensions = () => {
|
|
12900
12920
|
const newWidth = container.scrollWidth;
|
|
@@ -12923,8 +12943,11 @@
|
|
|
12923
12943
|
if (currentHandlers.size)
|
|
12924
12944
|
return;
|
|
12925
12945
|
/**
|
|
12926
|
-
* If no more handlers, remove the scroll listener too.
|
|
12946
|
+
* If no more handlers, remove the scroll listener too. The handler
|
|
12947
|
+
* set goes with it, as a measure still queued from this listener
|
|
12948
|
+
* would otherwise notify handlers added by a later scrollInfo call.
|
|
12927
12949
|
*/
|
|
12950
|
+
onScrollHandlers.delete(container);
|
|
12928
12951
|
const scrollListener = scrollListeners.get(container);
|
|
12929
12952
|
scrollListeners.delete(container);
|
|
12930
12953
|
if (scrollListener) {
|
|
@@ -12938,7 +12961,6 @@
|
|
|
12938
12961
|
cancelFrame(dimensionCheckProcess);
|
|
12939
12962
|
dimensionCheckProcesses.delete(container);
|
|
12940
12963
|
}
|
|
12941
|
-
scrollSize.delete(container);
|
|
12942
12964
|
};
|
|
12943
12965
|
}
|
|
12944
12966
|
|
|
@@ -13019,10 +13041,8 @@
|
|
|
13019
13041
|
}, options);
|
|
13020
13042
|
return { currentTime, cancel };
|
|
13021
13043
|
}
|
|
13022
|
-
function getTimeline({
|
|
13044
|
+
function getTimeline({ container, ...options }) {
|
|
13023
13045
|
const { axis } = options;
|
|
13024
|
-
if (source)
|
|
13025
|
-
container = source;
|
|
13026
13046
|
let containerCache = timelineCache.get(container);
|
|
13027
13047
|
if (!containerCache) {
|
|
13028
13048
|
containerCache = new Map();
|
|
@@ -13098,38 +13118,16 @@
|
|
|
13098
13118
|
});
|
|
13099
13119
|
}
|
|
13100
13120
|
|
|
13101
|
-
|
|
13102
|
-
* Currently, we only support element tracking with `scrollInfo`, though in
|
|
13103
|
-
* the future we can also offer ViewTimeline support.
|
|
13104
|
-
*/
|
|
13105
|
-
function isElementTracking(options) {
|
|
13106
|
-
return options && (options.target || options.offset);
|
|
13107
|
-
}
|
|
13108
|
-
|
|
13109
|
-
/**
|
|
13110
|
-
* If the onScroll function has two arguments, it's expecting
|
|
13111
|
-
* more specific information about the scroll from scrollInfo.
|
|
13112
|
-
*/
|
|
13113
|
-
function isOnScrollWithInfo(onScroll) {
|
|
13114
|
-
return onScroll.length === 2;
|
|
13115
|
-
}
|
|
13116
|
-
function attachToFunction(onScroll, options) {
|
|
13117
|
-
if (isOnScrollWithInfo(onScroll) || isElementTracking(options)) {
|
|
13118
|
-
return scrollInfo((info) => {
|
|
13119
|
-
onScroll(info[options.axis].progress, info);
|
|
13120
|
-
}, options);
|
|
13121
|
-
}
|
|
13122
|
-
else {
|
|
13123
|
-
return observeTimeline(onScroll, getTimeline(options));
|
|
13124
|
-
}
|
|
13125
|
-
}
|
|
13126
|
-
|
|
13127
|
-
function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
|
|
13121
|
+
function scroll(onScroll, { axis = "y", source, container = document.scrollingElement, ...options } = {}) {
|
|
13128
13122
|
if (!container)
|
|
13129
13123
|
return noop;
|
|
13130
|
-
const optionsWithDefaults = {
|
|
13124
|
+
const optionsWithDefaults = {
|
|
13125
|
+
axis,
|
|
13126
|
+
container: source || container,
|
|
13127
|
+
...options,
|
|
13128
|
+
};
|
|
13131
13129
|
return typeof onScroll === "function"
|
|
13132
|
-
?
|
|
13130
|
+
? scrollInfo((info) => onScroll(info[axis].progress, info), optionsWithDefaults)
|
|
13133
13131
|
: attachToAnimation(onScroll, optionsWithDefaults);
|
|
13134
13132
|
}
|
|
13135
13133
|
|