motion 13.4.2 → 13.4.4
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 +195 -184
- package/dist/motion.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,10 +24,12 @@ npm install motion-v
|
|
|
24
24
|
1. [Why Motion?](#why-motion)
|
|
25
25
|
2. [🍦 Platforms](#-platforms)
|
|
26
26
|
3. [🎓 Examples](#-examples)
|
|
27
|
-
4. [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
4. [🎨 Motion Studio](#-motion-studio)
|
|
28
|
+
4. [🤖 Using Motion with AI](#-using-motion-with-ai)
|
|
29
|
+
5. [⚡️ Motion+](#-motion)
|
|
30
|
+
6. [👩🏻⚖️ License](#-license)
|
|
31
|
+
7. [💎 Contribute](#-contribute)
|
|
32
|
+
8. [✨ Sponsors](#-sponsors)
|
|
31
33
|
|
|
32
34
|
## Why Motion?
|
|
33
35
|
|
|
@@ -106,19 +108,35 @@ Get started with [Motion for Vue](https://motion.dev/docs/vue).
|
|
|
106
108
|
|
|
107
109
|
## 🎓 Examples & tutorials
|
|
108
110
|
|
|
109
|
-
Browse
|
|
111
|
+
Browse 450+ [official examples](https://motion.dev/examples), with copy-paste code that'll level-up your animations whether you're a beginner or an expert.
|
|
110
112
|
|
|
111
|
-
Over
|
|
113
|
+
Over 110 examples come with a full step-by-step tutorial on [their example page](https://motion.dev/examples).
|
|
114
|
+
|
|
115
|
+
## 🤖 Using Motion with AI
|
|
116
|
+
|
|
117
|
+
Give your coding agent current Motion docs:
|
|
118
|
+
|
|
119
|
+
- **Agent skill:** `npx motion-ai` installs the free, MIT-licensed `/motion` skill and sets up Motion's MCP servers for Claude Code, Cursor, Amp, OpenCode, Gemini CLI and Copilot. [Source on GitHub](https://github.com/motiondivision/ai-kit).
|
|
120
|
+
- **MCP server:** `https://mcp.motion.dev` searches the Motion docs and example metadata. It is free and needs no account.
|
|
121
|
+
- **llms.txt:** [motion.dev/llms.txt](https://motion.dev/llms.txt?utm_source=npm-readme) indexes every docs page. Motion+ pages are labelled as paid, with their install and import.
|
|
122
|
+
|
|
123
|
+
[Motion+](https://motion.dev/plus?utm_source=npm-readme) adds example and Motion UI source, MotionScore performance audits and CSS spring generation for your agent.
|
|
124
|
+
|
|
125
|
+
## 🎨 Motion Studio
|
|
126
|
+
|
|
127
|
+
A visual animation editor for your website. Edit keyframes, easing curves and springs on a live timeline, or describe changes to the Ultramotion agent, then apply the result straight to your code with Cursor, Codex or Claude.
|
|
128
|
+
|
|
129
|
+
[Explore Motion Studio](https://motion.dev/studio)
|
|
112
130
|
|
|
113
131
|
## ⚡️ Motion+
|
|
114
132
|
|
|
115
|
-
A one-time
|
|
133
|
+
A one-time Personal licence with lifetime updates, or an annual per-seat Business plan for teams:
|
|
116
134
|
|
|
117
|
-
- **
|
|
118
|
-
- **
|
|
135
|
+
- **450+ examples**
|
|
136
|
+
- **110+ tutorials**
|
|
119
137
|
- **Premium APIs** like [Cursor](https://motion.dev/docs/cursor) and [Ticker](https://motion.dev/docs/react-ticker)
|
|
120
138
|
- **Transition editor** for Cursor and VS Code
|
|
121
|
-
- **AI
|
|
139
|
+
- **AI Kit:** example and Motion UI source, MotionScore audits and CSS springs for your agent
|
|
122
140
|
- **Private Discord**
|
|
123
141
|
- **Early access content**
|
|
124
142
|
|
package/dist/motion.dev.js
CHANGED
|
@@ -1225,22 +1225,50 @@
|
|
|
1225
1225
|
}
|
|
1226
1226
|
}
|
|
1227
1227
|
const durationKeys = ["duration", "bounce"];
|
|
1228
|
-
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
1229
1228
|
function isSpringType(options, keys) {
|
|
1230
1229
|
return keys.some((key) => options[key] !== undefined);
|
|
1231
1230
|
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Spring physics must be finite. stiffness and mass are also divisors so must
|
|
1233
|
+
* be positive, whereas a damping of 0 is a valid, perpetually oscillating
|
|
1234
|
+
* spring. Relational rather than Number.isFinite so numeric strings still
|
|
1235
|
+
* coerce.
|
|
1236
|
+
*/
|
|
1237
|
+
const isValidPhysics = (value, canBeZero) => (canBeZero ? value >= 0 : value > 0) && value < Infinity;
|
|
1238
|
+
/**
|
|
1239
|
+
* Returns value if it's usable spring physics, otherwise undefined so callers
|
|
1240
|
+
* fall back to the default. An explicit `undefined`, e.g. from a forwarded
|
|
1241
|
+
* optional prop, must fall back too. Invalid physics would otherwise resolve
|
|
1242
|
+
* to NaN spring values that never report done.
|
|
1243
|
+
*/
|
|
1244
|
+
function resolvePhysics(value, canBeZero) {
|
|
1245
|
+
if (isValidPhysics(value, canBeZero))
|
|
1246
|
+
return value;
|
|
1247
|
+
{
|
|
1248
|
+
exports.warning(value === undefined, "Spring stiffness and mass must be positive, damping 0 or greater", "spring-invalid-physics");
|
|
1249
|
+
}
|
|
1250
|
+
return undefined;
|
|
1251
|
+
}
|
|
1232
1252
|
function getSpringOptions(options) {
|
|
1253
|
+
/**
|
|
1254
|
+
* Resolve physics before choosing between physics- and duration-based
|
|
1255
|
+
* resolution, so an invalid stiffness doesn't also silently discard a
|
|
1256
|
+
* valid duration/bounce.
|
|
1257
|
+
*/
|
|
1258
|
+
const validStiffness = resolvePhysics(options.stiffness);
|
|
1259
|
+
const validDamping = resolvePhysics(options.damping, true);
|
|
1260
|
+
const validMass = resolvePhysics(options.mass);
|
|
1233
1261
|
let springOptions = {
|
|
1234
|
-
velocity: springDefaults.velocity,
|
|
1235
|
-
stiffness: springDefaults.stiffness,
|
|
1236
|
-
damping: springDefaults.damping,
|
|
1237
|
-
mass: springDefaults.mass,
|
|
1238
|
-
isResolvedFromDuration: false,
|
|
1239
1262
|
...options,
|
|
1263
|
+
stiffness: validStiffness ?? springDefaults.stiffness,
|
|
1264
|
+
damping: validDamping ?? springDefaults.damping,
|
|
1265
|
+
mass: validMass ?? springDefaults.mass,
|
|
1266
|
+
isResolvedFromDuration: false,
|
|
1267
|
+
// stiffness/damping/mass overrides duration/bounce
|
|
1268
|
+
isTimeDefined: (validStiffness ?? validDamping ?? validMass) === undefined &&
|
|
1269
|
+
isSpringType(options, durationKeys),
|
|
1240
1270
|
};
|
|
1241
|
-
|
|
1242
|
-
if (!isSpringType(options, physicsKeys) &&
|
|
1243
|
-
isSpringType(options, durationKeys)) {
|
|
1271
|
+
if (springOptions.isTimeDefined) {
|
|
1244
1272
|
// Time-defined springs should ignore inherited velocity.
|
|
1245
1273
|
// Velocity from interrupted animations can cause findSpring()
|
|
1246
1274
|
// to compute wildly different spring parameters, leading to
|
|
@@ -1261,7 +1289,7 @@
|
|
|
1261
1289
|
};
|
|
1262
1290
|
}
|
|
1263
1291
|
else {
|
|
1264
|
-
const derived = findSpring(
|
|
1292
|
+
const derived = findSpring(springOptions);
|
|
1265
1293
|
springOptions = {
|
|
1266
1294
|
...springOptions,
|
|
1267
1295
|
...derived,
|
|
@@ -1269,6 +1297,17 @@
|
|
|
1269
1297
|
};
|
|
1270
1298
|
springOptions.isResolvedFromDuration = true;
|
|
1271
1299
|
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Non-finite time options degenerate: a NaN bounce gives a NaN
|
|
1302
|
+
* damping, an infinite visualDuration a 0 stiffness. Replace the two
|
|
1303
|
+
* together, so the relationship duration resolution establishes
|
|
1304
|
+
* between them is never left half-overwritten.
|
|
1305
|
+
*/
|
|
1306
|
+
if (!isValidPhysics(springOptions.stiffness) ||
|
|
1307
|
+
!isValidPhysics(springOptions.damping, true)) {
|
|
1308
|
+
springOptions.stiffness = springDefaults.stiffness;
|
|
1309
|
+
springOptions.damping = springDefaults.damping;
|
|
1310
|
+
}
|
|
1272
1311
|
}
|
|
1273
1312
|
return springOptions;
|
|
1274
1313
|
}
|
|
@@ -1287,7 +1326,7 @@
|
|
|
1287
1326
|
* to reduce GC during animation.
|
|
1288
1327
|
*/
|
|
1289
1328
|
const state = { done: false, value: origin };
|
|
1290
|
-
const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
|
|
1329
|
+
const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, isTimeDefined, } = getSpringOptions({
|
|
1291
1330
|
...options,
|
|
1292
1331
|
velocity: -millisecondsToSeconds(options.velocity || 0),
|
|
1293
1332
|
});
|
|
@@ -1312,7 +1351,7 @@
|
|
|
1312
1351
|
* If we're working on a granular scale, use smaller defaults for determining
|
|
1313
1352
|
* when the spring is finished.
|
|
1314
1353
|
*
|
|
1315
|
-
* These defaults have been selected
|
|
1354
|
+
* These defaults have been selected empirically based on what strikes a good
|
|
1316
1355
|
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
|
1317
1356
|
*/
|
|
1318
1357
|
const setRestThresholds = () => {
|
|
@@ -1410,11 +1449,6 @@
|
|
|
1410
1449
|
};
|
|
1411
1450
|
}
|
|
1412
1451
|
update();
|
|
1413
|
-
/**
|
|
1414
|
-
* Time-defined springs ignore inherited velocity, see getSpringOptions.
|
|
1415
|
-
*/
|
|
1416
|
-
const ignoreVelocity = !isSpringType(options, physicsKeys) &&
|
|
1417
|
-
isSpringType(options, durationKeys);
|
|
1418
1452
|
const calculatedDuration = isResolvedFromDuration ? duration || null : null;
|
|
1419
1453
|
const generator = {
|
|
1420
1454
|
calculatedDuration,
|
|
@@ -1425,9 +1459,8 @@
|
|
|
1425
1459
|
retarget: (keyframes, newVelocity) => {
|
|
1426
1460
|
s.target = keyframes[keyframes.length - 1];
|
|
1427
1461
|
s.delta = s.target - keyframes[0];
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
: -millisecondsToSeconds(newVelocity);
|
|
1462
|
+
// Time-defined springs ignore inherited velocity, see getSpringOptions
|
|
1463
|
+
s.velocity = isTimeDefined ? 0 : -millisecondsToSeconds(newVelocity);
|
|
1431
1464
|
// Default thresholds depend on the scale of the new delta
|
|
1432
1465
|
if (!(options.restSpeed && options.restDelta))
|
|
1433
1466
|
setRestThresholds();
|
|
@@ -3445,7 +3478,7 @@
|
|
|
3445
3478
|
|
|
3446
3479
|
class GroupAnimation {
|
|
3447
3480
|
constructor(animations) {
|
|
3448
|
-
// Bound to
|
|
3481
|
+
// Bound to accommodate common `return animation.stop` pattern
|
|
3449
3482
|
this.stop = () => this.runAll("stop");
|
|
3450
3483
|
this.animations = animations.filter(Boolean);
|
|
3451
3484
|
}
|
|
@@ -5777,7 +5810,7 @@
|
|
|
5777
5810
|
translateAxis(box.y, -node.scroll.offset.y);
|
|
5778
5811
|
}
|
|
5779
5812
|
if (delta) {
|
|
5780
|
-
//
|
|
5813
|
+
// Incorporate each ancestor's scale into a cumulative treeScale for this component
|
|
5781
5814
|
treeScale.x *= delta.x.scale;
|
|
5782
5815
|
treeScale.y *= delta.y.scale;
|
|
5783
5816
|
// Apply each ancestor's calculated delta into this component's recorded layout box
|
|
@@ -5985,7 +6018,7 @@
|
|
|
5985
6018
|
/**
|
|
5986
6019
|
* Create a hover gesture. hover() is different to .addEventListener("pointerenter")
|
|
5987
6020
|
* in that it has an easier syntax, filters out polyfilled touch events, interoperates
|
|
5988
|
-
* with drag gestures, and automatically removes the "
|
|
6021
|
+
* with drag gestures, and automatically removes the "pointerleave" event listener when the hover ends.
|
|
5989
6022
|
*
|
|
5990
6023
|
* @public
|
|
5991
6024
|
*/
|
|
@@ -7721,7 +7754,7 @@
|
|
|
7721
7754
|
/**
|
|
7722
7755
|
* Iterate backwards over the builders array. We can ignore the
|
|
7723
7756
|
* "wait" animations. If we have an interrupting animation in the
|
|
7724
|
-
* queue then we need to batch all
|
|
7757
|
+
* queue then we need to batch all preceding animations into it.
|
|
7725
7758
|
* Currently this only batches the update functions but will also
|
|
7726
7759
|
* need to batch the targets.
|
|
7727
7760
|
*/
|
|
@@ -8601,7 +8634,7 @@
|
|
|
8601
8634
|
/**
|
|
8602
8635
|
* compareDocumentPosition returns a bitmask, by using the bitwise &
|
|
8603
8636
|
* we're returning true if 2 in that bitmask is set to true. 2 is set
|
|
8604
|
-
* to true if b
|
|
8637
|
+
* to true if b precedes a.
|
|
8605
8638
|
*/
|
|
8606
8639
|
return a.compareDocumentPosition(b) & 2 ? 1 : -1;
|
|
8607
8640
|
}
|
|
@@ -9093,7 +9126,7 @@
|
|
|
9093
9126
|
}
|
|
9094
9127
|
/**
|
|
9095
9128
|
* 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-
|
|
9129
|
+
* be any key that has been animated or otherwise handled by active, higher-priority types.
|
|
9097
9130
|
*/
|
|
9098
9131
|
typeState.protectedKeys = { ...encounteredKeys };
|
|
9099
9132
|
// Check if we can skip analysing this prop early
|
|
@@ -9918,8 +9951,8 @@
|
|
|
9918
9951
|
this.hasCheckedOptimisedAppear = false;
|
|
9919
9952
|
/**
|
|
9920
9953
|
* 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
|
|
9954
|
+
* This will be used to scale calculated projection transforms, as these are
|
|
9955
|
+
* calculated in screen-space but need to be scaled for elements to visually
|
|
9923
9956
|
* make it to their calculated destinations.
|
|
9924
9957
|
*
|
|
9925
9958
|
* TODO: Lazy-init
|
|
@@ -12561,7 +12594,10 @@
|
|
|
12561
12594
|
x: createAxisInfo(),
|
|
12562
12595
|
y: createAxisInfo(),
|
|
12563
12596
|
});
|
|
12564
|
-
|
|
12597
|
+
/**
|
|
12598
|
+
* Also iterated with for...in as the list of axes.
|
|
12599
|
+
*/
|
|
12600
|
+
const axisKeys = {
|
|
12565
12601
|
x: {
|
|
12566
12602
|
length: "Width",
|
|
12567
12603
|
position: "Left",
|
|
@@ -12573,11 +12609,13 @@
|
|
|
12573
12609
|
};
|
|
12574
12610
|
function updateAxisInfo(element, axisName, info, time) {
|
|
12575
12611
|
const axis = info[axisName];
|
|
12576
|
-
const { length, position } =
|
|
12612
|
+
const { length, position } = axisKeys[axisName];
|
|
12577
12613
|
const prev = axis.current;
|
|
12578
12614
|
const prevTime = info.time;
|
|
12579
12615
|
axis.current = Math.abs(element[`scroll${position}`]);
|
|
12580
|
-
axis.
|
|
12616
|
+
axis.containerLength = element[`client${length}`];
|
|
12617
|
+
axis.targetLength = element[`scroll${length}`];
|
|
12618
|
+
axis.scrollLength = axis.targetLength - axis.containerLength;
|
|
12581
12619
|
axis.offset.length = 0;
|
|
12582
12620
|
axis.offset[0] = 0;
|
|
12583
12621
|
axis.offset[1] = axis.scrollLength;
|
|
@@ -12588,54 +12626,15 @@
|
|
|
12588
12626
|
? 0
|
|
12589
12627
|
: velocityPerSecond(axis.current - prev, elapsed);
|
|
12590
12628
|
}
|
|
12629
|
+
/**
|
|
12630
|
+
* Measures a scroll container. Runs once per container per frame; every
|
|
12631
|
+
* handler on that container derives its info from the result.
|
|
12632
|
+
*/
|
|
12591
12633
|
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
|
-
}
|
|
12634
|
+
for (const axis in axisKeys) {
|
|
12635
|
+
updateAxisInfo(element, axis, info, time);
|
|
12637
12636
|
}
|
|
12638
|
-
|
|
12637
|
+
info.time = time;
|
|
12639
12638
|
}
|
|
12640
12639
|
|
|
12641
12640
|
const namedEdges = {
|
|
@@ -12733,86 +12732,104 @@
|
|
|
12733
12732
|
],
|
|
12734
12733
|
};
|
|
12735
12734
|
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12735
|
+
/**
|
|
12736
|
+
* Resolved offsets map to evenly spaced progress values, so progress is
|
|
12737
|
+
* derived from the segment index rather than building an interpolator.
|
|
12738
|
+
*/
|
|
12739
|
+
function offsetsToProgress(offsets, v) {
|
|
12740
|
+
const n = offsets.length - 1;
|
|
12741
|
+
if (n < 1)
|
|
12742
|
+
return 0;
|
|
12743
|
+
const reverse = offsets[0] > offsets[n];
|
|
12744
|
+
const at = (i) => offsets[reverse ? n - i : i];
|
|
12745
|
+
/**
|
|
12746
|
+
* Matches interpolate(), which checks for a zero-length first range
|
|
12747
|
+
* before reversing descending offsets.
|
|
12748
|
+
*/
|
|
12749
|
+
if (offsets[0] === offsets[1] && v < at(0))
|
|
12750
|
+
return reverse ? 1 : 0;
|
|
12751
|
+
let i = 0;
|
|
12752
|
+
while (i < n - 1 && v >= at(i + 1))
|
|
12753
|
+
i++;
|
|
12754
|
+
const p = (i + progress(at(i), at(i + 1), v)) / n;
|
|
12755
|
+
return reverse ? 1 - p : p;
|
|
12741
12756
|
}
|
|
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
|
-
};
|
|
12757
|
+
function resolveOffsets(info, options) {
|
|
12758
|
+
const { offset: offsetDefinition = ScrollOffset.All, axis = "y" } = options;
|
|
12759
|
+
const axisInfo = info[axis];
|
|
12759
12760
|
/**
|
|
12760
12761
|
* 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
12762
|
*/
|
|
12763
|
-
|
|
12763
|
+
axisInfo.offset.length = 0;
|
|
12764
12764
|
/**
|
|
12765
12765
|
* Populate the offset array by resolving the user's offset definition into
|
|
12766
|
-
* a list of pixel scroll
|
|
12766
|
+
* a list of pixel scroll offsets.
|
|
12767
12767
|
*/
|
|
12768
|
-
let hasChanged = !info[axis].interpolate;
|
|
12769
12768
|
const numOffsets = offsetDefinition.length;
|
|
12770
12769
|
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;
|
|
12776
|
-
}
|
|
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];
|
|
12770
|
+
axisInfo.offset[i] = resolveOffset(offsetDefinition[i], axisInfo.containerLength, axisInfo.targetLength, axisInfo.targetOffset);
|
|
12784
12771
|
}
|
|
12785
|
-
|
|
12772
|
+
axisInfo.progress = clamp(0, 1, offsetsToProgress(axisInfo.offset, axisInfo.current));
|
|
12786
12773
|
}
|
|
12787
12774
|
|
|
12788
|
-
function
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12775
|
+
function calcInset(element, container) {
|
|
12776
|
+
const inset = { x: 0, y: 0 };
|
|
12777
|
+
let current = element;
|
|
12778
|
+
while (current && current !== container) {
|
|
12779
|
+
if (isHTMLElement(current)) {
|
|
12780
|
+
inset.x += current.offsetLeft;
|
|
12781
|
+
inset.y += current.offsetTop;
|
|
12782
|
+
current = current.offsetParent;
|
|
12783
|
+
}
|
|
12784
|
+
else if (current.tagName === "svg") {
|
|
12785
|
+
/**
|
|
12786
|
+
* This isn't an ideal approach to measuring the offset of <svg /> tags.
|
|
12787
|
+
* It would be preferable, given they behave like HTMLElements in most ways
|
|
12788
|
+
* to use offsetLeft/Top. But these don't exist on <svg />. Likewise we
|
|
12789
|
+
* can't use .getBBox() like most SVG elements as these provide the offset
|
|
12790
|
+
* relative to the SVG itself, which for <svg /> is usually 0x0.
|
|
12791
|
+
*/
|
|
12792
|
+
const svgBoundingBox = current.getBoundingClientRect();
|
|
12793
|
+
current = current.parentElement;
|
|
12794
|
+
const parentBoundingBox = current.getBoundingClientRect();
|
|
12795
|
+
inset.x += svgBoundingBox.left - parentBoundingBox.left;
|
|
12796
|
+
inset.y += svgBoundingBox.top - parentBoundingBox.top;
|
|
12797
|
+
}
|
|
12798
|
+
else if (current instanceof SVGGraphicsElement) {
|
|
12799
|
+
const { x, y } = current.getBBox();
|
|
12800
|
+
inset.x += x;
|
|
12801
|
+
inset.y += y;
|
|
12802
|
+
let svg = null;
|
|
12803
|
+
let parent = current.parentNode;
|
|
12804
|
+
while (!svg) {
|
|
12805
|
+
if (parent.tagName === "svg") {
|
|
12806
|
+
svg = parent;
|
|
12807
|
+
}
|
|
12808
|
+
parent = current.parentNode;
|
|
12809
|
+
}
|
|
12810
|
+
current = svg;
|
|
12811
|
+
}
|
|
12812
|
+
else {
|
|
12813
|
+
break;
|
|
12800
12814
|
}
|
|
12801
12815
|
}
|
|
12802
|
-
|
|
12803
|
-
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12816
|
+
return inset;
|
|
12817
|
+
}
|
|
12818
|
+
|
|
12819
|
+
function getTargetSize(target) {
|
|
12820
|
+
return "getBBox" in target && target.tagName !== "svg"
|
|
12821
|
+
? target.getBBox()
|
|
12822
|
+
: { width: target.clientWidth, height: target.clientHeight };
|
|
12823
|
+
}
|
|
12824
|
+
function createOnScrollHandler(container, onScroll, info, options = {}) {
|
|
12825
|
+
const { target } = options;
|
|
12808
12826
|
/**
|
|
12809
12827
|
* In development mode ensure scroll containers aren't position: static as this makes
|
|
12810
12828
|
* it difficult to measure their relative positions. The document scrolling element
|
|
12811
12829
|
* is exempt: offsetParent measurements naturally resolve relative to the document.
|
|
12812
12830
|
*/
|
|
12813
12831
|
{
|
|
12814
|
-
if (
|
|
12815
|
-
target &&
|
|
12832
|
+
if (target &&
|
|
12816
12833
|
target !== container &&
|
|
12817
12834
|
container !== document.documentElement &&
|
|
12818
12835
|
container !== document.scrollingElement &&
|
|
@@ -12820,24 +12837,39 @@
|
|
|
12820
12837
|
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
12838
|
}
|
|
12822
12839
|
}
|
|
12823
|
-
|
|
12824
|
-
|
|
12840
|
+
/**
|
|
12841
|
+
* Handlers without a target or offset are notified with the container's
|
|
12842
|
+
* shared info object, so they measure nothing themselves.
|
|
12843
|
+
*/
|
|
12844
|
+
const needsOwnInfo = target || options.offset;
|
|
12825
12845
|
return {
|
|
12826
|
-
measure: (
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12846
|
+
measure: (containerInfo) => {
|
|
12847
|
+
if (!needsOwnInfo)
|
|
12848
|
+
return;
|
|
12849
|
+
info.time = containerInfo.time;
|
|
12850
|
+
for (const key in axisKeys) {
|
|
12851
|
+
const axis = key;
|
|
12852
|
+
const { offset } = info[axis];
|
|
12853
|
+
Object.assign(info[axis], containerInfo[axis]);
|
|
12854
|
+
info[axis].offset = offset;
|
|
12855
|
+
}
|
|
12856
|
+
if (target && target !== container) {
|
|
12857
|
+
const inset = calcInset(target, container);
|
|
12858
|
+
const size = getTargetSize(target);
|
|
12859
|
+
info.x.targetOffset = inset.x;
|
|
12860
|
+
info.y.targetOffset = inset.y;
|
|
12861
|
+
info.x.targetLength = size.width;
|
|
12862
|
+
info.y.targetLength = size.height;
|
|
12863
|
+
}
|
|
12864
|
+
resolveOffsets(info, options);
|
|
12832
12865
|
},
|
|
12833
|
-
notify: () => onScroll(info),
|
|
12866
|
+
notify: (containerInfo) => onScroll(needsOwnInfo ? info : containerInfo),
|
|
12834
12867
|
};
|
|
12835
12868
|
}
|
|
12836
12869
|
|
|
12837
12870
|
const scrollListeners = new WeakMap();
|
|
12838
12871
|
const resizeListeners = new WeakMap();
|
|
12839
12872
|
const onScrollHandlers = new WeakMap();
|
|
12840
|
-
const scrollSize = new WeakMap();
|
|
12841
12873
|
const dimensionCheckProcesses = new WeakMap();
|
|
12842
12874
|
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
|
|
12843
12875
|
function scrollInfo(onScroll, { container = document.scrollingElement, trackContentSize = false, ...options } = {}) {
|
|
@@ -12863,15 +12895,17 @@
|
|
|
12863
12895
|
* If not, create one.
|
|
12864
12896
|
*/
|
|
12865
12897
|
if (!scrollListeners.has(container)) {
|
|
12898
|
+
const containerInfo = createScrollInfo();
|
|
12866
12899
|
const measureAll = () => {
|
|
12900
|
+
updateScrollInfo(container, containerInfo, frameData.timestamp);
|
|
12867
12901
|
for (const handler of containerHandlers) {
|
|
12868
|
-
handler.measure(
|
|
12902
|
+
handler.measure(containerInfo);
|
|
12869
12903
|
}
|
|
12870
12904
|
frame.preUpdate(notifyAll);
|
|
12871
12905
|
};
|
|
12872
12906
|
const notifyAll = () => {
|
|
12873
12907
|
for (const handler of containerHandlers) {
|
|
12874
|
-
handler.notify();
|
|
12908
|
+
handler.notify(containerInfo);
|
|
12875
12909
|
}
|
|
12876
12910
|
};
|
|
12877
12911
|
const listener = () => frame.read(measureAll);
|
|
@@ -12894,7 +12928,6 @@
|
|
|
12894
12928
|
width: container.scrollWidth,
|
|
12895
12929
|
height: container.scrollHeight,
|
|
12896
12930
|
};
|
|
12897
|
-
scrollSize.set(container, size);
|
|
12898
12931
|
// Add frame-based scroll dimension checking to detect content changes
|
|
12899
12932
|
const checkScrollDimensions = () => {
|
|
12900
12933
|
const newWidth = container.scrollWidth;
|
|
@@ -12923,8 +12956,11 @@
|
|
|
12923
12956
|
if (currentHandlers.size)
|
|
12924
12957
|
return;
|
|
12925
12958
|
/**
|
|
12926
|
-
* If no more handlers, remove the scroll listener too.
|
|
12959
|
+
* If no more handlers, remove the scroll listener too. The handler
|
|
12960
|
+
* set goes with it, as a measure still queued from this listener
|
|
12961
|
+
* would otherwise notify handlers added by a later scrollInfo call.
|
|
12927
12962
|
*/
|
|
12963
|
+
onScrollHandlers.delete(container);
|
|
12928
12964
|
const scrollListener = scrollListeners.get(container);
|
|
12929
12965
|
scrollListeners.delete(container);
|
|
12930
12966
|
if (scrollListener) {
|
|
@@ -12938,7 +12974,6 @@
|
|
|
12938
12974
|
cancelFrame(dimensionCheckProcess);
|
|
12939
12975
|
dimensionCheckProcesses.delete(container);
|
|
12940
12976
|
}
|
|
12941
|
-
scrollSize.delete(container);
|
|
12942
12977
|
};
|
|
12943
12978
|
}
|
|
12944
12979
|
|
|
@@ -13019,10 +13054,8 @@
|
|
|
13019
13054
|
}, options);
|
|
13020
13055
|
return { currentTime, cancel };
|
|
13021
13056
|
}
|
|
13022
|
-
function getTimeline({
|
|
13057
|
+
function getTimeline({ container, ...options }) {
|
|
13023
13058
|
const { axis } = options;
|
|
13024
|
-
if (source)
|
|
13025
|
-
container = source;
|
|
13026
13059
|
let containerCache = timelineCache.get(container);
|
|
13027
13060
|
if (!containerCache) {
|
|
13028
13061
|
containerCache = new Map();
|
|
@@ -13098,38 +13131,16 @@
|
|
|
13098
13131
|
});
|
|
13099
13132
|
}
|
|
13100
13133
|
|
|
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 } = {}) {
|
|
13134
|
+
function scroll(onScroll, { axis = "y", source, container = document.scrollingElement, ...options } = {}) {
|
|
13128
13135
|
if (!container)
|
|
13129
13136
|
return noop;
|
|
13130
|
-
const optionsWithDefaults = {
|
|
13137
|
+
const optionsWithDefaults = {
|
|
13138
|
+
axis,
|
|
13139
|
+
container: source || container,
|
|
13140
|
+
...options,
|
|
13141
|
+
};
|
|
13131
13142
|
return typeof onScroll === "function"
|
|
13132
|
-
?
|
|
13143
|
+
? scrollInfo((info) => onScroll(info[axis].progress, info), optionsWithDefaults)
|
|
13133
13144
|
: attachToAnimation(onScroll, optionsWithDefaults);
|
|
13134
13145
|
}
|
|
13135
13146
|
|