FuncAnimation动画末尾淡出影响开头淡入问题排查
问题描述
尝试用Matplotlib的FuncAnimation实现LaTeX文本的淡入淡出效果,编写了fade_in和fade_out方法,单独测试均正常,但组合执行以下步骤时出现异常:
- 对符号$x$执行淡入
- 对符号$y$执行淡入
- 对符号$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
相关产品推荐
相关产品推荐

