Skip to content

Commit 9a94cd9

Browse files
committed
fix(studio): include all animated properties in every keyframe commit
Position, resize, and rotation intercepts now read ALL animated property values from gsap.getProperty() at commit time and include them in the keyframe. Prevents other properties from jumping to interpolated values between surrounding keyframes when only one property (e.g., width) was explicitly changed.
1 parent 40ffaae commit 9a94cd9

1 file changed

Lines changed: 81 additions & 11 deletions

File tree

packages/studio/src/hooks/gsapRuntimeBridge.ts

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ export async function tryGsapDragIntercept(
146146
const gsapPos = readGsapPositionFromIframe(iframe, selector);
147147
if (!gsapPos) return false;
148148

149-
await commitGsapPositionFromDrag(selection, posAnim, offset, gsapPos, { commitMutation });
149+
await commitGsapPositionFromDrag(selection, posAnim, offset, gsapPos, iframe, selector, {
150+
commitMutation,
151+
});
150152
return true;
151153
}
152154

@@ -171,6 +173,8 @@ async function commitGsapPositionFromDrag(
171173
anim: GsapAnimation,
172174
studioOffset: { x: number; y: number },
173175
gsapPos: { x: number; y: number },
176+
iframe: HTMLIFrameElement | null,
177+
selector: string,
174178
callbacks: GsapDragCommitCallbacks,
175179
): Promise<void> {
176180
// CSS composition: translate → rotate → transform. The studioOffset is in
@@ -188,24 +192,37 @@ async function commitGsapPositionFromDrag(
188192
const clearOffset = () => clearStudioPathOffset(selection.element);
189193

190194
if (anim.keyframes) {
191-
await commitKeyframedPosition(selection, anim, newX, newY, callbacks, clearOffset);
195+
const runtimeProps = readAllAnimatedProperties(iframe, selector, anim);
196+
await commitKeyframedPosition(
197+
selection,
198+
anim,
199+
{ ...runtimeProps, x: newX, y: newY },
200+
callbacks,
201+
clearOffset,
202+
);
192203
} else if (anim.method === "from") {
193204
await commitFromPosition(selection, anim, studioOffset, callbacks, clearOffset);
194205
} else if (anim.method === "fromTo") {
195206
await commitFromToPosition(selection, anim, studioOffset, callbacks, clearOffset);
196207
} else {
197208
// Flat to()/set() — convert to keyframes first so the drag position
198209
// is captured at the current seek time, not just the tween endpoint.
199-
await commitFlatViaKeyframes(selection, anim, newX, newY, callbacks, clearOffset);
210+
const runtimeProps = readAllAnimatedProperties(iframe, selector, anim);
211+
await commitFlatViaKeyframes(
212+
selection,
213+
anim,
214+
{ ...runtimeProps, x: newX, y: newY },
215+
callbacks,
216+
clearOffset,
217+
);
200218
}
201219
}
202220

203221
// fallow-ignore-next-line complexity
204222
async function commitKeyframedPosition(
205223
selection: DomEditSelection,
206224
anim: GsapAnimation,
207-
newX: number,
208-
newY: number,
225+
properties: Record<string, number>,
209226
callbacks: GsapDragCommitCallbacks,
210227
beforeReload: () => void,
211228
): Promise<void> {
@@ -217,7 +234,7 @@ async function commitKeyframedPosition(
217234
type: "add-keyframe",
218235
animationId: anim.id,
219236
percentage: pct,
220-
properties: { x: newX, y: newY },
237+
properties,
221238
},
222239
{ label: `Move layer (keyframe ${pct}%)`, softReload: true, beforeReload },
223240
);
@@ -232,8 +249,7 @@ async function commitKeyframedPosition(
232249
async function commitFlatViaKeyframes(
233250
selection: DomEditSelection,
234251
anim: GsapAnimation,
235-
newX: number,
236-
newY: number,
252+
properties: Record<string, number>,
237253
callbacks: GsapDragCommitCallbacks,
238254
beforeReload: () => void,
239255
): Promise<void> {
@@ -251,7 +267,7 @@ async function commitFlatViaKeyframes(
251267
type: "add-keyframe",
252268
animationId: anim.id,
253269
percentage: pct,
254-
properties: { x: newX, y: newY },
270+
properties,
255271
},
256272
{ label: `Move layer (keyframe ${pct}%)`, softReload: true, beforeReload },
257273
);
@@ -316,6 +332,49 @@ async function commitFromToPosition(
316332
);
317333
}
318334

335+
// ── Runtime property reader ───────────────────────────────────────────────
336+
337+
function readAllAnimatedProperties(
338+
iframe: HTMLIFrameElement | null,
339+
selector: string,
340+
anim: GsapAnimation,
341+
): Record<string, number> {
342+
const result: Record<string, number> = {};
343+
if (!iframe?.contentWindow) return result;
344+
let gsap: IframeGsap | undefined;
345+
try {
346+
gsap = (iframe.contentWindow as unknown as { gsap?: IframeGsap }).gsap;
347+
} catch {
348+
return result;
349+
}
350+
if (!gsap?.getProperty) return result;
351+
let doc: Document | null = null;
352+
try {
353+
doc = iframe.contentDocument;
354+
} catch {
355+
return result;
356+
}
357+
const el = doc?.querySelector(selector);
358+
if (!el) return result;
359+
360+
const propKeys = new Set<string>();
361+
if (anim.keyframes) {
362+
for (const kf of anim.keyframes.keyframes) {
363+
for (const p of Object.keys(kf.properties)) {
364+
if (typeof kf.properties[p] === "number") propKeys.add(p);
365+
}
366+
}
367+
} else {
368+
for (const p of Object.keys(anim.properties)) propKeys.add(p);
369+
}
370+
371+
for (const prop of propKeys) {
372+
const val = Number(gsap.getProperty(el, prop));
373+
if (Number.isFinite(val)) result[prop] = Math.round(val);
374+
}
375+
return result;
376+
}
377+
319378
// ── Resize intercept ──────────────────────────────────────────────────────
320379

321380
export async function tryGsapResizeIntercept(
@@ -345,13 +404,21 @@ export async function tryGsapResizeIntercept(
345404
);
346405
}
347406

407+
const selector = selectorForSelection(selection);
408+
const runtimeProps = selector ? readAllAnimatedProperties(iframe, selector, anim) : {};
409+
const properties = {
410+
...runtimeProps,
411+
width: Math.round(size.width),
412+
height: Math.round(size.height),
413+
};
414+
348415
await commitMutation(
349416
selection,
350417
{
351418
type: "add-keyframe",
352419
animationId: anim.id,
353420
percentage: pct,
354-
properties: { width: Math.round(size.width), height: Math.round(size.height) },
421+
properties,
355422
},
356423
{ label: `Resize (keyframe ${pct}%)`, softReload: true },
357424
);
@@ -407,13 +474,16 @@ export async function tryGsapRotationIntercept(
407474
);
408475
}
409476

477+
const runtimeProps = readAllAnimatedProperties(iframe, selector, anim);
478+
const properties = { ...runtimeProps, rotation: newRotation };
479+
410480
await commitMutation(
411481
selection,
412482
{
413483
type: "add-keyframe",
414484
animationId: anim.id,
415485
percentage: pct,
416-
properties: { rotation: newRotation },
486+
properties,
417487
},
418488
{ label: `Rotate (keyframe ${pct}%)`, softReload: true },
419489
);

0 commit comments

Comments
 (0)