Skip to content

Commit 08d596d

Browse files
committed
fix(animate): commit update layout at animation end
VChart resize updates write the new layout into finalAttribute and pass the layout delta through context.diffAttrs for the built-in update animation. The animation frames interpolate correctly, but automatic close-out restores from baseAttributes. Before this change, baseAttributes still held the pre-resize layout, so restoreStaticAttribute pulled attribute and finalAttribute back to the old bar size after the update animation ended. The built-in update animation now treats its diffAttrs as an explicit end commit for upper-layer layout changes. It commits those attrs to static truth only when the update animation naturally ends, leaving in-flight frames transient and preserving ordinary self-driven animations that should restore to the old base. Constraint: D3 static truth remains baseAttributes + resolvedStatePatch -> attribute Constraint: Animation frames stay transient until an explicit end commit Rejected: Sync all setFinalAttributes calls to base | breaks custom target caches Rejected: Restore from finalAttribute directly | reintroduces finalAttribute as truth Confidence: high Scope-risk: narrow Directive: Keep update layout commits in Update.onEnd, not generic restore paths Tested: packages/vrender-animate rushx test --runInBand Tested: packages/vrender-core targeted static truth/state suites under Node 22.18.0 Tested: rush compile -t @visactor/vrender-animate Tested: rush build -t @visactor/vrender-animate Tested: rush build -t @visactor/vrender Tested: built aggregate CJS resize-update repro keeps resized y/y1/width after end Not-tested: Full VChart browser smoke against local packages
1 parent 74c676f commit 08d596d

2 files changed

Lines changed: 87 additions & 1 deletion

File tree

packages/vrender-animate/__tests__/unit/animation-runtime-attribute.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,80 @@ describe('D3 pre-handoff animation runtime', () => {
305305
expect(rect.getFinalAttribute().y1).toBe(260);
306306
});
307307

308+
test('executor update animation restores to resized final layout after completion', () => {
309+
const { group, ticker, graphicService } = createStageHarness('executor-update-resize-static-truth');
310+
const initial = {
311+
x: 78.75,
312+
y: 31.2,
313+
y1: 260,
314+
width: 202.5,
315+
visible: true
316+
};
317+
const resized = {
318+
x: 105,
319+
y: 38.4,
320+
y1: 320,
321+
width: 270,
322+
visible: true
323+
};
324+
const rect = createRect(initial);
325+
bindGraphicService(rect as any, graphicService);
326+
rect.setFinalAttributes(initial);
327+
group.appendChild(rect);
328+
329+
(rect as any).context = {
330+
animationState: 'update',
331+
data: [{ value: 10 }],
332+
diffAttrs: {
333+
x: resized.x,
334+
y: resized.y,
335+
y1: resized.y1,
336+
width: resized.width
337+
},
338+
finalAttrs: resized
339+
};
340+
rect.setFinalAttributes(resized);
341+
342+
new AnimateExecutor(rect).execute({
343+
type: 'update',
344+
duration: 100,
345+
easing: 'linear'
346+
});
347+
348+
expect((rect as any).baseAttributes.x).toBe(initial.x);
349+
expect((rect as any).baseAttributes.y).toBe(initial.y);
350+
expect((rect as any).baseAttributes.y1).toBe(initial.y1);
351+
expect((rect as any).baseAttributes.width).toBe(initial.width);
352+
expect(rect.getFinalAttribute().x).toBe(resized.x);
353+
expect(rect.getFinalAttribute().y).toBe(resized.y);
354+
expect(rect.getFinalAttribute().y1).toBe(resized.y1);
355+
expect(rect.getFinalAttribute().width).toBe(resized.width);
356+
357+
tick(ticker, 50);
358+
expect(rect.attribute.x).toBeCloseTo(91.875, 5);
359+
expect(rect.attribute.y).toBeCloseTo(34.8, 5);
360+
expect(rect.attribute.y1).toBeCloseTo(290, 5);
361+
expect(rect.attribute.width).toBeCloseTo(236.25, 5);
362+
expect((rect as any).baseAttributes.x).toBe(initial.x);
363+
expect((rect as any).baseAttributes.y).toBe(initial.y);
364+
expect((rect as any).baseAttributes.y1).toBe(initial.y1);
365+
expect((rect as any).baseAttributes.width).toBe(initial.width);
366+
367+
tick(ticker, 50);
368+
expect((rect as any).baseAttributes.x).toBe(resized.x);
369+
expect((rect as any).baseAttributes.y).toBe(resized.y);
370+
expect((rect as any).baseAttributes.y1).toBe(resized.y1);
371+
expect((rect as any).baseAttributes.width).toBe(resized.width);
372+
expect(rect.attribute.x).toBe(resized.x);
373+
expect(rect.attribute.y).toBe(resized.y);
374+
expect(rect.attribute.y1).toBe(resized.y1);
375+
expect(rect.attribute.width).toBe(resized.width);
376+
expect(rect.getFinalAttribute().x).toBe(resized.x);
377+
expect(rect.getFinalAttribute().y).toBe(resized.y);
378+
expect(rect.getFinalAttribute().y1).toBe(resized.y1);
379+
expect(rect.getFinalAttribute().width).toBe(resized.width);
380+
});
381+
308382
test('switching states mid-animation restores to the new static truth and blocks late writes', () => {
309383
const { group, ticker, graphicService } = createStageHarness('state-conflict');
310384
const rect = createAnimatedRect(graphicService);

packages/vrender-animate/src/custom/update.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EasingType } from '@visactor/vrender-core';
1+
import { AttributeUpdateType, type EasingType, type IAnimate, type IStep } from '@visactor/vrender-core';
22
import { ACustomAnimate } from './custom-animate';
33

44
export interface IUpdateAnimationOptions {
@@ -37,6 +37,18 @@ export class Update extends ACustomAnimate<Record<string, number>> {
3737
this.props = diffAttrs;
3838
}
3939

40+
onEnd(cb?: (animate: IAnimate, step: IStep) => void): void {
41+
if (cb) {
42+
super.onEnd(cb);
43+
return;
44+
}
45+
46+
if (this.props && Object.keys(this.props).length) {
47+
this.target.setAttributes(this.props, false, { type: AttributeUpdateType.ANIMATE_END });
48+
}
49+
super.onEnd();
50+
}
51+
4052
update(end: boolean, ratio: number, out: Record<string, any>): void {
4153
this.onStart();
4254
if (!this.props || !this.propKeys) {

0 commit comments

Comments
 (0)