修复序列帧播放淡入闪烁
序列帧图片不再复用普通图片加载淡入样式 补充画布序列帧无过渡播放回归测试 记录序列帧播放闪烁排障经验
This commit is contained in:
@@ -79,6 +79,14 @@
|
||||
- 验证:`ImageCanvasGenerationLayerModel` 应断言动作结果 `src` 为首帧且 `mediaType="image-sequence"`;画布集成测试应出现 `画布序列帧:角色动作` 图片播放器,不应出现角色动作 `<video>`;导出测试应断言角色动作下载和画布素材导出都包含序列帧 ZIP / frames 目录。
|
||||
- 关联:`src/components/image-editor/ImageCanvasGenerationLayerModel.ts`、`src/components/image-editor/ImageCanvasWorldView.tsx`、`src/components/image-editor/ImageCanvasExportModel.ts`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
|
||||
|
||||
## 图片画布序列帧播放不要复用普通图片淡入样式
|
||||
|
||||
- 现象:角色动作序列帧播放时看起来像每帧之间在渐变或闪烁。
|
||||
- 原因:序列帧播放器每帧切换可低至 40ms,默认约 125ms 一帧;如果帧 `<img>` 复用普通图片的 `image-canvas-editor__layer-image--loading/--loaded`,其中 `opacity 180ms ease` 会跨过下一帧切换,形成类似交叉淡入淡出的视觉。
|
||||
- 处理:`ImageCanvasImageSequenceFrame` 只使用序列帧专属 class,帧显隐用同步 `opacity` 硬切,并显式 `transition: none`;保留“下一帧未加载时继续显示上一帧”的 readiness gate。
|
||||
- 验证:`ImageCanvasWorldView.test.tsx` 应断言序列帧 `<img>` 不带普通图片 loading/loaded class,且 style 中 `transition` 为 `none`。
|
||||
- 关联:`src/components/image-editor/ImageCanvasWorldView.tsx`、`src/index.css`、`src/components/image-editor/ImageCanvasWorldView.test.tsx`。
|
||||
|
||||
## Vidu 文生音频线上网关可能要求 sound 字段
|
||||
|
||||
- 现象:画板点击 `生成游戏音效` 后,请求返回 `Failed to deserialize the JSON body into the target type: missing field sound`。
|
||||
|
||||
@@ -1087,6 +1087,14 @@ describe('ImageCanvasWorldView', () => {
|
||||
expect(frame.getAttribute('src')).toBe(
|
||||
'https://oss.example.com/frame01.png?signature=1',
|
||||
);
|
||||
expect(frame.className).toBe(
|
||||
'image-canvas-editor__layer-image-sequence-frame',
|
||||
);
|
||||
expect(frame.style.opacity).toBe('0');
|
||||
expect(frame.style.transition).toBe('none');
|
||||
fireEvent.load(frame);
|
||||
expect(frame.style.opacity).toBe('1');
|
||||
expect(frame.style.transition).toBe('none');
|
||||
expect(within(layerButton).getByText('1/2')).toBeTruthy();
|
||||
expect(within(layerButton).getByText('动作')).toBeTruthy();
|
||||
|
||||
|
||||
@@ -755,16 +755,14 @@ function ImageCanvasImageSequenceFrame({
|
||||
alt={isVisible ? `画布序列帧:${layer.title}` : ''}
|
||||
aria-hidden={isVisible ? undefined : true}
|
||||
decoding="async"
|
||||
className={`image-canvas-editor__layer-image-sequence-frame ${
|
||||
isVisible && isLoaded
|
||||
? 'image-canvas-editor__layer-image--loaded'
|
||||
: 'image-canvas-editor__layer-image--loading'
|
||||
}`}
|
||||
className="image-canvas-editor__layer-image-sequence-frame"
|
||||
style={{
|
||||
inset: 0,
|
||||
opacity: isVisible && isLoaded ? 1 : 0,
|
||||
position: 'absolute',
|
||||
transition: 'none',
|
||||
transform: mediaTransform,
|
||||
zIndex: 1,
|
||||
}}
|
||||
onLoad={() => onFrameReady(frameKey)}
|
||||
onError={() => onFrameReady(frameKey)}
|
||||
|
||||
Reference in New Issue
Block a user