Skip to content

Commit d769460

Browse files
committed
fix: preserve update starts for channelled updates
VChart drives update state through applyAnimationState after setting finalAttrs and diffAttrs. When built-in update also declared a channel that omitted layout keys, AnimateExecutor pre-committed attrOutChannel before Update captured from props. That collapsed layout from and to values to final and skipped the visible position animation. Constraint: D3 static truth keeps animation frames transient and base/final as final layout Rejected: Make VChart pre-snapshot update starts | leaks Update ownership to consumers Rejected: Disable attrOutChannel globally | breaks non-update channel semantics Confidence: high Scope-risk: narrow Directive: Do not pre-commit context.diffAttrs before built-in Update captures starts Tested: vrender-animate animation-runtime-attribute targeted and full file Tested: packages/vrender-animate rushx test Tested: rush compile -t @visactor/vrender-animate -t @visactor/vrender-core Tested: packages/vrender-animate rushx eslint, 0 errors with existing warnings Tested: git diff --check
1 parent 1af67d5 commit d769460

2 files changed

Lines changed: 122 additions & 8 deletions

File tree

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

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,112 @@ describe('D3 pre-handoff animation runtime', () => {
11251125
expect(rect.getFinalAttribute().y).toBe(finalY);
11261126
});
11271127

1128+
test.each([
1129+
['style-only channel', ['fillOpacity']],
1130+
['empty channel', []]
1131+
])('animation state update keeps diffAttrs animated with %s', (_name, channel) => {
1132+
const { group, ticker, graphicService } = createStageHarness('state-update-channel-out-position');
1133+
const oldAttrs = {
1134+
x: 0,
1135+
y: 46.90909090909094,
1136+
width: 10,
1137+
height: 34,
1138+
fillOpacity: 1,
1139+
visible: true
1140+
};
1141+
const diffAttrs = {
1142+
x: 974.1100285714286,
1143+
y: 3,
1144+
height: 34
1145+
};
1146+
const finalAttrs = {
1147+
...oldAttrs,
1148+
...diffAttrs
1149+
};
1150+
const rect = createRect(oldAttrs);
1151+
bindGraphicService(rect as any, graphicService);
1152+
rect.setFinalAttributes(oldAttrs);
1153+
(rect as any).context = {
1154+
diffState: 'update',
1155+
animationState: 'update',
1156+
data: [{ id: 'China_share_global_co2' }],
1157+
diffAttrs,
1158+
finalAttrs
1159+
};
1160+
group.appendChild(rect);
1161+
1162+
rect.setFinalAttributes(finalAttrs);
1163+
(rect as any).applyAnimationState(
1164+
['update'],
1165+
[
1166+
{
1167+
name: 'update_0',
1168+
animation: {
1169+
type: 'update',
1170+
duration: 300,
1171+
easing: 'linear',
1172+
channel
1173+
}
1174+
}
1175+
]
1176+
);
1177+
1178+
tick(ticker, 1);
1179+
1180+
const animate = (rect as any)._animationStateManager.stateList[0].executor._animates[0];
1181+
const step = (animate as any)._firstStep;
1182+
expect(step.getFromProps().y).toBeCloseTo(oldAttrs.y, 6);
1183+
expect(step.getEndProps().y).toBeCloseTo(diffAttrs.y, 6);
1184+
expect(rect.attribute.y).toBeGreaterThan(diffAttrs.y);
1185+
expect(rect.attribute.y).toBeLessThan(oldAttrs.y);
1186+
1187+
tick(ticker, 299);
1188+
1189+
expect(rect.attribute.y).toBe(diffAttrs.y);
1190+
expect((rect as any).baseAttributes.y).toBe(diffAttrs.y);
1191+
expect(rect.getFinalAttribute().y).toBe(diffAttrs.y);
1192+
});
1193+
1194+
test('non-animation update commits diffAttrs without going through animation executor', () => {
1195+
const oldAttrs = {
1196+
x: 0,
1197+
y: 46.90909090909094,
1198+
width: 10,
1199+
height: 34,
1200+
fillOpacity: 1,
1201+
visible: true
1202+
};
1203+
const diffAttrs = {
1204+
x: 974.1100285714286,
1205+
y: 3,
1206+
height: 34
1207+
};
1208+
const finalAttrs = {
1209+
...oldAttrs,
1210+
...diffAttrs
1211+
};
1212+
const rect = createRect(oldAttrs);
1213+
rect.setFinalAttributes(oldAttrs);
1214+
(rect as any).context = {
1215+
diffState: 'update',
1216+
animationState: 'update',
1217+
data: [{ id: 'China_share_global_co2' }],
1218+
diffAttrs,
1219+
finalAttrs
1220+
};
1221+
1222+
rect.setAttributes(diffAttrs);
1223+
rect.setFinalAttributes(finalAttrs);
1224+
1225+
expect(rect.attribute.y).toBe(diffAttrs.y);
1226+
expect((rect as any).baseAttributes.y).toBe(diffAttrs.y);
1227+
expect(rect.getFinalAttribute().y).toBe(diffAttrs.y);
1228+
expect(rect.attribute.x).toBe(diffAttrs.x);
1229+
expect((rect as any).baseAttributes.x).toBe(diffAttrs.x);
1230+
expect(rect.getFinalAttribute().x).toBe(diffAttrs.x);
1231+
expect((rect as any)._animationStateManager).toBeUndefined();
1232+
});
1233+
11281234
test('superseded executor update cannot commit stale layout when it ends late', () => {
11291235
const { group, ticker, graphicService } = createStageHarness('executor-update-stale-end');
11301236
const threeItemLayout = {

packages/vrender-animate/src/executor/animate-executor.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,20 +341,20 @@ export class AnimateExecutor implements IAnimateExecutor {
341341
// animate.wait(delayValue);
342342
// }
343343

344-
// 根据 channel 配置创建属性对象
345344
// 根据 channel 配置创建属性对象
346345
let parsedFromProps = null;
347346
let props = params.to;
348347
let from = params.from;
348+
const commitAttrOutChannel = this.shouldCommitAttrOutChannel(type);
349349
if (!props) {
350350
if (!parsedFromProps) {
351-
parsedFromProps = this.createPropsFromChannel(channel, graphic);
351+
parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel);
352352
}
353353
props = parsedFromProps.props;
354354
}
355355
if (!from) {
356356
if (!parsedFromProps) {
357-
parsedFromProps = this.createPropsFromChannel(channel, graphic);
357+
parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel);
358358
}
359359
from = parsedFromProps.from;
360360
}
@@ -552,15 +552,16 @@ export class AnimateExecutor implements IAnimateExecutor {
552552
let parsedFromProps = null;
553553
let props = effect.to;
554554
let from = effect.from;
555+
const commitAttrOutChannel = this.shouldCommitAttrOutChannel(type);
555556
if (!props) {
556557
if (!parsedFromProps) {
557-
parsedFromProps = this.createPropsFromChannel(channel, graphic);
558+
parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel);
558559
}
559560
props = parsedFromProps.props;
560561
}
561562
if (!from) {
562563
if (!parsedFromProps) {
563-
parsedFromProps = this.createPropsFromChannel(channel, graphic);
564+
parsedFromProps = this.createPropsFromChannel(channel, graphic, commitAttrOutChannel);
564565
}
565566
from = parsedFromProps.from;
566567
}
@@ -620,6 +621,12 @@ export class AnimateExecutor implements IAnimateExecutor {
620621
animate.to(props, duration, easing);
621622
}
622623

624+
private shouldCommitAttrOutChannel(type: string): boolean {
625+
// Update owns context.diffAttrs itself. Its channel config must not pre-commit
626+
// omitted diff keys before Update captures their start frame.
627+
return type !== 'update';
628+
}
629+
623630
/**
624631
* 创建自定义动画类
625632
*/
@@ -654,7 +661,8 @@ export class AnimateExecutor implements IAnimateExecutor {
654661
*/
655662
private createPropsFromChannel(
656663
channel: IAnimationChannelAttrs | IAnimationChannelAttributes | undefined,
657-
graphic: IGraphic
664+
graphic: IGraphic,
665+
includeAttrOutChannel: boolean = true
658666
): {
659667
from: Record<string, any> | null;
660668
props: Record<string, any>;
@@ -670,7 +678,7 @@ export class AnimateExecutor implements IAnimateExecutor {
670678
};
671679
}
672680

673-
const attrOutChannel: Record<string, any> | null = {};
681+
const attrOutChannel: Record<string, any> | null = includeAttrOutChannel ? {} : null;
674682
let hasAttrs = false;
675683
const diffAttrs = graphic.context?.diffAttrs;
676684
if (Array.isArray(channel)) {
@@ -705,7 +713,7 @@ export class AnimateExecutor implements IAnimateExecutor {
705713
}
706714
});
707715

708-
if (diffAttrs) {
716+
if (diffAttrs && attrOutChannel) {
709717
for (const key in diffAttrs) {
710718
const value = (diffAttrs as any)[key];
711719
if (value === undefined) {

0 commit comments

Comments
 (0)