You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FuncAnimation动画末尾淡出影响开头淡入问题排查

问题描述

尝试用Matplotlib的FuncAnimation实现LaTeX文本的淡入淡出效果,编写了fade_in和fade_out方法,单独测试均正常,但组合执行以下步骤时出现异常:

  1. 对符号$x$执行淡入
  2. 对符号$y$执行淡入
  3. 对符号$y$执行淡出

异常现象:动画前三分之一阶段(即$x$淡入的阶段),$y$会显示在屏幕上,而预期该阶段$y$应该不可见,仅在自身淡入阶段出现。但如果只执行前两步($x$淡入、$y$淡入),$y$在$x$淡入阶段是正常不可见的。

代码如下:

import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
from matplotlib.animation import FuncAnimation, FFMpegWriter

def chain_animations(animations):
    # Extract all update functions and frame counts
    update_funcs = [a._func for a in animations]
    frame_counts = [a.frame_seq.__length_hint__() for a in animations]
    intervals = [a._interval for a in animations]
    figs = [a._fig for a in animations]

    assert all(interval == intervals[0] for interval in intervals), "All animations must have the same interval"
    assert all(fig is figs[0] for fig in figs), "All animations must use the same figure"

    total_frames = sum(frame_counts)
    cumulative = [0]
    for count in frame_counts:
        cumulative.append(cumulative[-1] + count)

    def update(frame):
        for i in range(len(frame_counts)):
            if cumulative[i] <= frame < cumulative[i + 1]:
                local_frame = frame - cumulative[i]
                return update_funcs[i](local_frame)
        return []

    return FuncAnimation(figs[0], update, frames=total_frames, interval=intervals[0], blit=True)


class Symbol:
    def __init__(self, ax, letter, size=20, color='black', position=(0, 0), alpha=1.0, zorder=1, row = -1, col = -1):
        self.ax = ax
        self.letter = letter
        self.size = size
        self.color = color
        self.position = position
        self.alpha = alpha
        self.zorder = zorder
        self.font = FontProperties(family='cmr10')
        self.row = row
        self.col = col

        self.add_to_axes(ax)

    def add_to_axes(self, ax):
        self.ax = ax
        fontsize = self.size
        x, y = self.position
        self.patch = ax.text(
            x, y,
            self.letter,
            fontsize=fontsize,
            color=self.color,
            alpha=self.alpha,
            zorder=self.zorder,
            ha='left',
            va='baseline',
            math_fontfamily='cm',
            usetex=False
        )

    def update_position(self, new_pos):
        self.position = new_pos

    def fade_in(self, ax, time=1.0, fps=30):
        total_frames = max(1, int(time * fps))
        target_alpha = 1.0
        self.alpha = 0.0
        self.patch.set_alpha(0.0)

        def animate(frame):
            t = frame / (total_frames - 1) if total_frames > 1 else 1.0
            current_alpha = t * target_alpha
            self.alpha = current_alpha
            self.patch.set_alpha(current_alpha)
            return [self.patch]

        return FuncAnimation(ax.figure, animate, frames=total_frames, interval=1000/fps, blit=True)
    
    def fade_out(self, ax, time=1.0, fps=30):
        total_frames = max(1, int(time * fps))
        target_alpha = 1.0
        self.alpha = 1.0

        if self.patch is not None:
            self.patch.set_alpha(1.0)

        def animate(frame):
            t = frame / (total_frames - 1) if total_frames > 1 else 1.0
            current_alpha = (1 - t) * target_alpha
            self.alpha = current_alpha
            self.patch.set_alpha(current_alpha)
            return [self.patch]

        return FuncAnimation(ax.figure, animate, frames=total_frames, interval=1000/fps, blit=True)


# 测试代码
fig, ax = plt.subplots()
ax.set_xlim(0, 200)
ax.set_ylim(0, 200)

x = Symbol(ax, letter='$x$', size=30, color='blue', position=(50, 50))
y = Symbol(ax, letter='$y$', size=30, color='red', position=(55, 50))

anim1 = x.fade_in(ax, time=1.0, fps=30)
anim2 = y.fade_in(ax, time=1.0, fps=30)
anim3 = y.fade_out(ax, time=1.0, fps=30)
anim = chain_animations([anim1, anim2, anim3])
anim.save("fade.mp4", writer=FFMpegWriter(fps=30))
问题原因

问题出在创建anim3 = y.fade_out(ax, ...)这一步:fade_out方法在被调用时,会直接将self.alpha设为1.0,并调用self.patch.set_alpha(1.0),这会立即修改$y$的显示状态,而非等到动画执行时才生效。

当你创建三个动画对象并传入chain_animations时,anim3的初始化代码已经把$y$的alpha改成了1.0,所以在整个链式动画的第一阶段($x$淡入),$y$已经是可见状态。而如果只创建前两个动画,没有fade_out的初始化修改,$y$的alpha保持fade_in设置的0.0,因此第一阶段不可见。

解决方案

方案1:延迟fade_out的初始状态设置

将fade_out中直接修改alpha的逻辑移到动画的第一帧执行,避免方法调用时就修改状态:

def fade_out(self, ax, time=1.0, fps=30):
    total_frames = max(1, int(time * fps))
    target_alpha = 1.0

    def animate(frame):
        # 仅在第一帧设置初始alpha为1.0
        if frame == 0:
            self.alpha = 1.0
            self.patch.set_alpha(1.0)
        t = frame / (total_frames - 1) if total_frames > 1 else 1.0
        current_alpha = (1 - t) * target_alpha
        self.alpha = current_alpha
        self.patch.set_alpha(current_alpha)
        return [self.patch]

    return FuncAnimation(ax.figure, animate, frames=total_frames, interval=1000/fps, blit=True)

方案2:在链式动画中添加初始化步骤

修改chain_animations函数,让每个动画在开始执行前先运行一次初始帧的更新,确保状态正确:

def chain_animations(animations):
    update_funcs = [a._func for a in animations]
    frame_counts = [a.frame_seq.__length_hint__() for a in animations]
    intervals = [a._interval for a in animations]
    figs = [a._fig for a in animations]

    assert all(interval == intervals[0] for interval in intervals), "All animations must have the same interval"
    assert all(fig is figs[0] for fig in figs), "All animations must use the same figure"

    total_frames = sum(frame_counts)
    cumulative = [0]
    for count in frame_counts:
        cumulative.append(cumulative[-1] + count)
    
    # 预执行每个动画的第0帧,初始化状态
    for func in update_funcs:
        func(0)

    def update(frame):
        for i in range(len(frame_counts)):
            if cumulative[i] <= frame < cumulative[i + 1]:
                local_frame = frame - cumulative[i]
                return update_funcs[i](local_frame)
        return []

    return FuncAnimation(figs[0], update, frames=total_frames, interval=intervals[0], blit=True)

同时需要同步修改fade_in,将初始状态设置移到第一帧:

def fade_in(self, ax, time=1.0, fps=30):
    total_frames = max(1, int(time * fps))
    target_alpha = 1.0

    def animate(frame):
        if frame == 0:
            self.alpha = 0.0
            self.patch.set_alpha(0.0)
        t = frame / (total_frames - 1) if total_frames > 1 else 1.0
        current_alpha = t * target_alpha
        self.alpha = current_alpha
        self.patch.set_alpha(current_alpha)
        return [self.patch]

    return FuncAnimation(ax.figure, animate, frames=total_frames, interval=1000/fps, blit=True)

方案3:手动重置初始状态

在创建完所有动画后,手动将$y$的alpha重置为0.0,覆盖fade_out带来的修改:

anim1 = x.fade_in(ax, time=1.0, fps=30)
anim2 = y.fade_in(ax, time=1.0, fps=30)
anim3 = y.fade_out(ax, time=1.0, fps=30)
# 手动重置y的初始可见性
y.patch.set_alpha(0.0)
y.alpha = 0.0
anim = chain_animations([anim1, anim2, anim3])

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:37:07