在 Python 中为演示文稿应用形状动画

概述

要使用效果中的各个行为或编辑运动路径段,请参阅自定义动画

Aspose.Slides for Python via .NET 将幻灯片动画表示为幻灯片时间轴中的效果。每个效果具有目标形状、动画类型和子类型、触发器、时间设置以及可选属性(例如声音或动画后行为)。

时间轴包含两类序列:

  • 主序列在幻灯片前进时播放。
  • 交互序列在其触发形状被单击时启动。

因为文本框、图片、图表、表格和其他幻灯片对象实现了IShape,所以对大多数幻灯片内容使用相同的Sequence.add_effect方法。可用的效果列在EffectType枚举中。

添加形状动画

要添加动画,获取幻灯片的主序列并调用Sequence.add_effect,传入目标形状、效果类型、子类型和触发器。要创建在单击另一个形状时启动的效果,请创建一个触发器为该形状的交互序列。

下面的示例创建了两种类型的动画并将结果保存为shape-animations.pptx

import aspose.slides as slides


with slides.Presentation() as presentation:
    slide = presentation.slides[0]

    target_shape = slide.shapes.add_auto_shape(slides.ShapeType.ROUND_CORNER_RECTANGLE, 120, 100, 320, 80)
    target_shape.text_frame.text = "Click to animate this shape"

    main_sequence = slide.timeline.main_sequence
    entrance_effect = main_sequence.add_effect(target_shape, slides.animation.EffectType.FADE, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)
    entrance_effect.timing.duration = 1.5

    trigger_shape = slide.shapes.add_auto_shape(slides.ShapeType.BEVEL, 20, 20, 100, 40)
    trigger_shape.text_frame.text = "Move"

    interactive_sequence = slide.timeline.interactive_sequences.add(trigger_shape)
    interactive_sequence.add_effect(target_shape, slides.animation.EffectType.PATH_FOOTBALL, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)

    presentation.save("shape-animations.pptx", slides.export.SaveFormat.PPTX)

触发器决定效果何时开始:

要为图片、图表或其他形状类型设置动画,请将该对象传递给Sequence.add_effect,而不是target_shape。有关图表特定的分组选项,请参阅动画图表

读取形状动画

当已知目标形状时,使用Sequence.get_effects_by_shape。若要检查每个效果,请遍历主序列和所有交互序列。遍历可避免假设序列在索引0处一定有效果。

下面的示例创建了一个具有主序列和交互效果的形状,获取针对该形状的效果,然后遍历幻灯片上的每个序列。

import aspose.slides as slides


def print_sequence(label, sequence):
    print(f"  {label}: {sequence.count} effect(s)")

    for effect in sequence:
        target_name = "unknown" if effect.target_shape is None else effect.target_shape.name
        effect_description = f"{effect.type.name} {effect.subtype.name}; target: {target_name}; trigger: {effect.timing.trigger_type.name}"
        print(f"    {effect_description}")


with slides.Presentation() as presentation:
    slide = presentation.slides[0]
    target_shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 120, 100, 320, 80)
    target_shape.text_frame.text = "Animated shape"

    main_sequence = slide.timeline.main_sequence
    main_sequence.add_effect(target_shape, slides.animation.EffectType.FADE, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)

    trigger_shape = slide.shapes.add_auto_shape(slides.ShapeType.BEVEL, 20, 20, 100, 40)
    trigger_shape.text_frame.text = "Move"

    interactive_sequence = slide.timeline.interactive_sequences.add(trigger_shape)
    interactive_sequence.add_effect(target_shape, slides.animation.EffectType.PATH_FOOTBALL, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)

    target_effects = main_sequence.get_effects_by_shape(target_shape)
    print(f"The main sequence contains {len(target_effects)} effect(s) for {target_shape.name}.")

    print_sequence("Main sequence", main_sequence)

    for interactive_index, sequence in enumerate(slide.timeline.interactive_sequences, start=1):
        trigger_name = "unknown" if sequence.trigger_shape is None else sequence.trigger_shape.name
        sequence_label = f"Interactive sequence {interactive_index}, trigger: {trigger_name}"
        print_sequence(sequence_label, sequence)

如果只需要一个形状的效果,请先通过名称、占位符类型或其他稳定属性确定该形状;然后调用Sequence.get_effects_by_shape。不要假设索引0处的形状一定是目标对象。

处理继承占位符效果

普通幻灯片上的占位符可以继承其版式幻灯片和母版幻灯片上对应占位符的动画行为。Shape.get_base_placeholder返回该父占位符;如果不存在父占位符则返回None

在下面的示例演示文稿中,页脚在普通幻灯片上使用随机条形,在版式幻灯片上使用分割,在母版幻灯片上使用飞入

普通幻灯片上的页脚动画效果

版式幻灯片上的页脚占位符动画效果

母版幻灯片上的页脚占位符动画效果

下面的示例自行构建占位符层次结构。它向母版占位符、版式占位符以及普通幻灯片上的对应占位符添加效果。每次调用Shape.get_base_placeholder前都进行检查,以确保返回的形状可安全使用。

import aspose.slides as slides


def find_placeholder_with_base(slide):
    for shape in slide.shapes:
        if shape.get_base_placeholder() is not None:
            return shape

    return None


def print_effects(source, effects):
    print(f"{source}: {len(effects)} effect(s)")

    for effect in effects:
        print(f"  {effect.type.name} {effect.subtype.name}")


with slides.Presentation() as presentation:
    layout_slide = presentation.layout_slides.get_by_type(slides.SlideLayoutType.BLANK)
    layout_placeholder = layout_slide.placeholder_manager.add_text_placeholder(100, 100, 400, 80)
    layout_slide.timeline.main_sequence.add_effect(layout_placeholder, slides.animation.EffectType.SPLIT, slides.animation.EffectSubtype.VERTICAL_IN, slides.animation.EffectTriggerType.ON_CLICK)

    master_placeholder = layout_placeholder.get_base_placeholder()
    if master_placeholder is not None:
        master_sequence = layout_slide.master_slide.timeline.main_sequence
        master_sequence.add_effect(master_placeholder, slides.animation.EffectType.FLY, slides.animation.EffectSubtype.BOTTOM, slides.animation.EffectTriggerType.ON_CLICK)

    slide = presentation.slides.add_empty_slide(layout_slide)
    slide_placeholder = find_placeholder_with_base(slide)

    if slide_placeholder is None:
        raise RuntimeError("The slide does not contain a placeholder linked to its layout slide.")

    slide.timeline.main_sequence.add_effect(slide_placeholder, slides.animation.EffectType.RANDOM_BARS, slides.animation.EffectSubtype.HORIZONTAL, slides.animation.EffectTriggerType.ON_CLICK)
    print_effects("Normal slide", slide.timeline.main_sequence.get_effects_by_shape(slide_placeholder))

    base_layout_placeholder = slide_placeholder.get_base_placeholder()
    if base_layout_placeholder is not None:
        print_effects("Layout slide", layout_slide.timeline.main_sequence.get_effects_by_shape(base_layout_placeholder))

        base_master_placeholder = base_layout_placeholder.get_base_placeholder()
        if base_master_placeholder is not None:
            print_effects("Master slide", layout_slide.master_slide.timeline.main_sequence.get_effects_by_shape(base_master_placeholder))

    presentation.save("placeholder-animations.pptx", slides.export.SaveFormat.PPTX)

更改动画时间设置

PowerPoint 的时间对话框映射到Timing的属性。

PowerPoint 动画效果的时间对话框

下面的独立示例添加一个效果,通过Sequence.add_effect返回的对象修改其时间设置,并保存结果。保留返回的Effect引用可避免不必要的集合索引。

import aspose.slides as slides


with slides.Presentation() as presentation:
    slide = presentation.slides[0]
    shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 120, 100, 320, 80)
    shape.text_frame.text = "Timed animation"

    effect = slide.timeline.main_sequence.add_effect(shape, slides.animation.EffectType.FADE, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)
    effect.timing.trigger_type = slides.animation.EffectTriggerType.ON_CLICK
    effect.timing.duration = 2.0
    effect.timing.trigger_delay_time = 0.5
    effect.timing.repeat_until_next_click = False
    effect.timing.repeat_until_end_slide = False
    effect.timing.repeat_count = 2.0
    effect.timing.rewind = True

    presentation.save("shape-animation-timing.pptx", slides.export.SaveFormat.PPTX)

请仅有意使用一种重复模式。将重复计数与“直到”标志组合可能在不同的查看器中产生混乱的结果。更改重复模式时,应先设置Timing.repeat_until_next_clickTiming.repeat_until_end_slide,再设置Timing.repeat_count,因为设置任一标志会同时更改活动的重复模式。

添加和提取动画声音

动画效果可以通过Effect.sound引用嵌入的音频。Effect.stop_previous_sound指示效果停止先前效果启动的音频。

向效果添加声音

下面的示例假设本地存在名为animation-sound.wav的音频文件。它创建两个效果,将该文件嵌入为第一个效果的声音,并配置第二个效果停止该声音。它使用Sequence.add_effect返回的对象,因此不需要序列索引。

import aspose.slides as slides


with slides.Presentation() as presentation:
    slide = presentation.slides[0]
    first_shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 80, 100, 240, 80)
    second_shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 400, 100, 240, 80)
    first_shape.text_frame.text = "Starts sound"
    second_shape.text_frame.text = "Stops sound"

    sequence = slide.timeline.main_sequence
    first_effect = sequence.add_effect(first_shape, slides.animation.EffectType.FADE, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)
    second_effect = sequence.add_effect(second_shape, slides.animation.EffectType.FADE, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)

    with open("animation-sound.wav", "rb") as audio_file:
        effect_sound = presentation.audios.add_audio(audio_file.read())

    first_effect.sound = effect_sound
    second_effect.stop_previous_sound = True

    presentation.save("shape-animation-sound.pptx", slides.export.SaveFormat.PPTX)

提取嵌入的效果声音

下面的示例假设本地存在名为presentation-with-animation-sounds.pptx的演示文稿。它扫描主序列和交互序列,并将每个嵌入的效果声音写入extracted-animation-sounds目录。扩展名根据Audio.content_type返回的音频 MIME 类型选择。

import os

import aspose.slides as slides


def get_audio_extension(content_type):
    normalized_type = "" if content_type is None else content_type.lower()

    if normalized_type == "audio/mpeg":
        return ".mp3"
    if normalized_type == "audio/mp4":
        return ".m4a"
    if normalized_type == "audio/ogg":
        return ".ogg"
    if normalized_type in ("audio/wav", "audio/x-wav"):
        return ".wav"

    return ".bin"


def save_sounds(sequence, output_directory, sound_index):
    for effect in sequence:
        if effect.sound is None:
            continue

        extension = get_audio_extension(effect.sound.content_type)
        output_path = os.path.join(output_directory, f"effect-sound-{sound_index}{extension}")
        with open(output_path, "wb") as output_file:
            output_file.write(bytes(effect.sound.binary_data))
        sound_index += 1

    return sound_index


input_path = "presentation-with-animation-sounds.pptx"
output_directory = "extracted-animation-sounds"

os.makedirs(output_directory, exist_ok=True)

with slides.Presentation(input_path) as presentation:
    sound_index = 1

    for slide in presentation.slides:
        sound_index = save_sounds(slide.timeline.main_sequence, output_directory, sound_index)

        for sequence in slide.timeline.interactive_sequences:
            sound_index = save_sounds(sequence, output_directory, sound_index)

print(f"Extracted {sound_index - 1} sound file(s) to {os.path.abspath(output_directory)}.")

对于大型音频对象,使用Audio.get_stream并将流复制到文件,而不是将整个对象加载到字节数组中。

设置动画后行为

动画后选项控制形状在效果结束后会发生什么。

PowerPoint 效果选项对话框显示“动画后”设置

AfterAnimationType枚举支持保持形状不变、改变颜色、在动画后隐藏或在下一次单击时隐藏。当类型为AfterAnimationType.COLOR时,还需设置Effect.after_animation_color

下面的独立示例创建一个效果,通过返回的效果对象设置其动画后行为,并保存结果。

import aspose.pydrawing as draw
import aspose.slides as slides


with slides.Presentation() as presentation:
    slide = presentation.slides[0]
    shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 120, 100, 320, 80)
    shape.text_frame.text = "Dim after animation"

    effect = slide.timeline.main_sequence.add_effect(shape, slides.animation.EffectType.FADE, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)
    effect.after_animation_type = slides.animation.AfterAnimationType.COLOR
    effect.after_animation_color.color = draw.Color.light_gray

    presentation.save("shape-animation-after-effect.pptx", slides.export.SaveFormat.PPTX)

将类型从AfterAnimationType.COLOR更改为其他值时,会清除动画后颜色设置。

动画文本

文本动画有两个相关控制:

下面的独立示例为文本框中的单词添加动画。BuildType.AS_ONE_OBJECT关闭段落逐层构建,使单词设置适用于整个文本框。

import aspose.slides as slides


with slides.Presentation() as presentation:
    slide = presentation.slides[0]
    text_box = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 80, 80, 560, 100)
    text_box.text_frame.text = "Aspose.Slides animates this sentence word by word."

    effect = slide.timeline.main_sequence.add_effect(text_box, slides.animation.EffectType.FADE, slides.animation.EffectSubtype.NONE, slides.animation.EffectTriggerType.ON_CLICK)
    effect.text_animation.build_type = slides.animation.BuildType.AS_ONE_OBJECT
    effect.animate_text_type = slides.animation.AnimateTextType.BY_WORD
    effect.delay_between_text_parts = 20.0

    presentation.save("animated-text.pptx", slides.export.SaveFormat.PPTX)

要按段落构建文本框,请将BuildType.BY_LEVEL_PARAGRAPHS1(或其他段落层级)设为构建类型。要对单个段落单独设置效果,请使用接受IParagraphSequence.add_effect重载。有关段落级示例,请参阅动画文本

导出和兼容性说明

  • 保存为 PPT 或 PPTX 会保留动画模型,但最终播放由演示文稿查看器控制。
  • PDF 和静态图像不播放动画。需要显示运动时请使用HTML5 导出、动画 GIF 或视频转换
  • 对于 HTML5,请启用Html5Options.animate_shapes,必要时还要启用Html5Options.animate_transitions
  • 视频渲染支持许多常见的进入、强调、退出和运动路径效果,但并非所有 PowerPoint 效果都受支持。请检查当前的支持的动画和效果,并使用目标 Aspose.Slides 版本对关键演示文稿进行测试。
  • 高级自定义效果以及从其他演示文稿格式导入的效果可能会在文件中保留,但在 PowerPoint、HTML5 或视频中呈现方式不同。请验证导出结果,而不仅仅依赖效果名称。

常见问题解答

为什么动画在 PowerPoint 中可以看到,但在 PDF 中看不到?

PDF 是静态格式,动画和幻灯片切换不会播放。需要保留运动时请导出为 HTML5、动画 GIF 或视频。

为什么同一效果在视频中表现不同?

视频导出会渲染动画,而不是保存原始 PowerPoint 行为。一些高级效果不受支持或被近似。请查看支持的效果表,并在生产使用前对实际演示文稿进行测试。

移动形状的前后层会改变其动画顺序吗?

不会。形状的 Z 顺序仅控制重叠,序列顺序和触发器控制动画播放。如果需要不同的播放顺序,请修改时间轴。