feat: add animation progress bar for 3D nodes and viewer (#7839)

## Summary
- Add draggable progress bar to AnimationControls component
- Display current time / total duration
- Allow seeking through animations when paused or playing
- Add animation controls to 3D Viewer

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7830 and
https://github.com/Comfy-Org/ComfyUI_frontend/issues/7831

## Screenshots (if applicable)


https://github.com/user-attachments/assets/f6d0668c-c7a4-497e-8345-9ef6e47a41c6

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7839-feat-add-animation-progress-bar-for-3D-nodes-and-viewer-2de6d73d36508101ac98f673206b30d9)
by [Unito](https://www.unito.io)
This commit is contained in:
Terry Jia
2026-01-04 08:47:30 -05:00
committed by GitHub
parent 8d1f8edc5a
commit 7fcfa4c201
9 changed files with 280 additions and 27 deletions

View File

@@ -125,6 +125,13 @@ export class AnimationManager implements AnimationManagerInterface {
}
this.animationActions = [action]
// Emit initial progress to set duration
this.eventManager.emitEvent('animationProgressChange', {
progress: 0,
currentTime: 0,
duration: clip.duration
})
}
toggleAnimation(play?: boolean): void {
@@ -150,8 +157,58 @@ export class AnimationManager implements AnimationManagerInterface {
update(delta: number): void {
if (this.currentAnimation && this.isAnimationPlaying) {
this.currentAnimation.update(delta)
if (this.animationActions.length > 0) {
const action = this.animationActions[0]
const clip = action.getClip()
const progress = (action.time / clip.duration) * 100
this.eventManager.emitEvent('animationProgressChange', {
progress,
currentTime: action.time,
duration: clip.duration
})
}
}
}
getAnimationTime(): number {
if (this.animationActions.length === 0) return 0
return this.animationActions[0].time
}
getAnimationDuration(): number {
if (this.animationActions.length === 0) return 0
return this.animationActions[0].getClip().duration
}
setAnimationTime(time: number): void {
if (this.animationActions.length === 0) return
const duration = this.getAnimationDuration()
const clampedTime = Math.max(0, Math.min(time, duration))
// Temporarily unpause to allow time update, then restore
const wasPaused = this.animationActions.map((action) => action.paused)
this.animationActions.forEach((action) => {
action.paused = false
action.time = clampedTime
})
if (this.currentAnimation) {
this.currentAnimation.setTime(clampedTime)
this.currentAnimation.update(0)
}
// Restore paused state
this.animationActions.forEach((action, i) => {
action.paused = wasPaused[i]
})
this.eventManager.emitEvent('animationProgressChange', {
progress: (clampedTime / duration) * 100,
currentTime: clampedTime,
duration
})
}
reset(): void {}
}

View File

@@ -726,6 +726,19 @@ class Load3d {
return this.animationManager.animationClips.length > 0
}
public getAnimationTime(): number {
return this.animationManager.getAnimationTime()
}
public getAnimationDuration(): number {
return this.animationManager.getAnimationDuration()
}
public setAnimationTime(time: number): void {
this.animationManager.setAnimationTime(time)
this.forceRender()
}
public remove(): void {
if (this.contextMenuAbortController) {
this.contextMenuAbortController.abort()

View File

@@ -146,6 +146,9 @@ export interface AnimationManagerInterface extends BaseManager {
updateSelectedAnimation(index: number): void
toggleAnimation(play?: boolean): void
update(delta: number): void
getAnimationTime(): number
getAnimationDuration(): number
setAnimationTime(time: number): void
}
export interface ModelManagerInterface {