Desktop UI

Анімації у WPF: Storyboard та Easing Functions

Створення плавних анімацій для інтерактивних інтерфейсів. Storyboard, DoubleAnimation, ColorAnimation, Easing Functions, Event Triggers та code-behind анімації.

Анімації у WPF: Storyboard та Easing Functions

Анімації — це те, що робить інтерфейс живим. Плавне відкриття бічної панелі, fade-in при завантаженні сторінки, пульсуючий індикатор завантаження, зміна кольору кнопки при наведенні — все це анімації. Вони покращують user experience, роблять інтерфейс зрозумілішим та приємнішим у використанні.

WPF має потужну систему анімацій через Storyboard — контейнер для анімацій, що може змінювати будь-які властивості контролів у часі. Можна анімувати числа (Width, Opacity), кольори (Background), товщину (Margin), та навіть складні трансформації (обертання, масштабування).

У цій статті ми детально розберемо всі аспекти анімацій у WPF: від базового синтаксису до складних easing functions та code-behind контролю. Ви навчитесь створювати професійні анімації, що роблять ваш інтерфейс сучасним та інтерактивним.

Словник теми:Storyboard — контейнер для анімацій, що координує їх виконання. Timeline — базовий клас для всіх анімацій (DoubleAnimation, ColorAnimation). From/To/By — початкове, кінцеве значення та зміна. Duration — тривалість анімації. Easing Function — функція для нелінійної зміни значення (прискорення, уповільнення). Event Trigger — тригер для запуску анімації при події. Property Path — шлях до властивості для анімації. RepeatBehavior — поведінка повторення анімації. AutoReverse — автоматичне повернення до початкового стану.

Storyboard: контейнер для анімацій

Storyboard — це клас WPF, що координує виконання кількох анімацій одночасно або послідовно. Він визначає, які властивості яких елементів анімувати та як.

Базовий синтаксис

<Window.Resources>
    <Storyboard x:Key="FadeInStoryboard">
        <DoubleAnimation Storyboard.TargetName="MyButton"
                         Storyboard.TargetProperty="Opacity"
                         From="0"
                         To="1"
                         Duration="0:0:0.5"/>
    </Storyboard>
</Window.Resources>

<Button x:Name="MyButton" Content="Fade In" Opacity="0"/>

Ключові властивості:

  • Storyboard.TargetName — ім'я елемента для анімації (x:Name)
  • Storyboard.TargetProperty — властивість для анімації
  • From — початкове значення
  • To — кінцеве значення
  • Duration — тривалість у форматі hours:minutes:seconds.milliseconds

Запуск анімації через EventTrigger

<Button Content="Fade In" Opacity="0">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                     From="0"
                                     To="1"
                                     Duration="0:0:0.5"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Доступні події для EventTrigger:

  • Loaded — елемент завантажено
  • MouseEnter — курсор над елементом
  • MouseLeave — курсор покинув елемент
  • GotFocus — елемент отримав фокус
  • LostFocus — елемент втратив фокус

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...


Типи анімацій

WPF підтримує різні типи анімацій для різних типів властивостей.

DoubleAnimation: анімація чисел

Для властивостей типу double: Width, Height, Opacity, FontSize, Margin (окремі значення).

<Storyboard x:Key="ExpandWidth">
    <DoubleAnimation Storyboard.TargetName="Sidebar"
                     Storyboard.TargetProperty="Width"
                     From="0"
                     To="250"
                     Duration="0:0:0.3"/>
</Storyboard>

From/To/By варіанти:

<!-- From + To: з 0 до 250 -->
<DoubleAnimation From="0" To="250" Duration="0:0:0.3"/>

<!-- Лише To: з поточного значення до 250 -->
<DoubleAnimation To="250" Duration="0:0:0.3"/>

<!-- By: збільшити на 50 від поточного -->
<DoubleAnimation By="50" Duration="0:0:0.3"/>

<!-- From + By: з 0 збільшити на 250 -->
<DoubleAnimation From="0" By="250" Duration="0:0:0.3"/>

ColorAnimation: анімація кольорів

Для властивостей типу Color (не Brush!). Потрібен Property Path до Color всередині Brush.

<Button x:Name="MyButton" Content="Hover Me">
    <Button.Background>
        <SolidColorBrush Color="#3b82f6"/>
    </Button.Background>
    <Button.Triggers>
        <EventTrigger RoutedEvent="MouseEnter">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)"
                                    To="#2563eb"
                                    Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
        <EventTrigger RoutedEvent="MouseLeave">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)"
                                    To="#3b82f6"
                                    Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Важливо: (Button.Background).(SolidColorBrush.Color) — це Property Path до Color всередині SolidColorBrush.

ThicknessAnimation: анімація Margin/Padding

Для властивостей типу Thickness: Margin, Padding, BorderThickness.

<Storyboard x:Key="SlideIn">
    <ThicknessAnimation Storyboard.TargetName="Panel"
                        Storyboard.TargetProperty="Margin"
                        From="-250,0,0,0"
                        To="0,0,0,0"
                        Duration="0:0:0.3"/>
</Storyboard>

ObjectAnimationUsingKeyFrames: дискретні значення

Для властивостей будь-якого типу, що не підтримують інтерполяцію (наприклад, Visibility, string).

<Storyboard x:Key="BlinkAnimation">
    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Indicator"
                                   Storyboard.TargetProperty="Visibility"
                                   RepeatBehavior="Forever">
        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
        <DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="{x:Static Visibility.Collapsed}"/>
        <DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{x:Static Visibility.Visible}"/>
    </ObjectAnimationUsingKeyFrames>
</Storyboard>

Порівняльна таблиця

Тип анімаціїДля властивостейПриклад
DoubleAnimationdouble (Width, Opacity)Fade-in, розширення
ColorAnimationColor (всередині Brush)Зміна кольору фону
ThicknessAnimationThickness (Margin, Padding)Slide-in панель
PointAnimationPointПереміщення по координатах
ObjectAnimationUsingKeyFramesБудь-який типМигання, дискретні зміни

Duration та RepeatBehavior

Duration: тривалість анімації

Формат: hours:minutes:seconds.milliseconds

<!-- 0.5 секунди -->
<DoubleAnimation Duration="0:0:0.5"/>

<!-- 300 мілісекунд -->
<DoubleAnimation Duration="0:0:0.3"/>

<!-- 1 секунда -->
<DoubleAnimation Duration="0:0:1"/>

<!-- 2 секунди -->
<DoubleAnimation Duration="0:0:2"/>

RepeatBehavior: повторення

<!-- Виконати 1 раз (за замовчуванням) -->
<DoubleAnimation RepeatBehavior="1x"/>

<!-- Виконати 3 рази -->
<DoubleAnimation RepeatBehavior="3x"/>

<!-- Повторювати нескінченно -->
<DoubleAnimation RepeatBehavior="Forever"/>

<!-- Повторювати протягом 5 секунд -->
<DoubleAnimation RepeatBehavior="0:0:5"/>

AutoReverse: повернення назад

<!-- Анімація виконується туди-назад -->
<DoubleAnimation From="0" To="1" Duration="0:0:0.5" AutoReverse="True"/>

Приклад пульсуючого індикатора:

<Ellipse x:Name="Indicator" Width="20" Height="20" Fill="#3b82f6">
    <Ellipse.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                     From="1"
                                     To="0.3"
                                     Duration="0:0:0.8"
                                     AutoReverse="True"
                                     RepeatBehavior="Forever"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Ellipse.Triggers>
</Ellipse>

Easing Functions: нелінійна зміна значень

Easing Functions роблять анімації природнішими, додаючи прискорення та уповільнення. Замість лінійної зміни (постійна швидкість), значення змінюється за кривою.

Базові Easing Functions

QuadraticEase — квадратична крива (найпопулярніша):

<DoubleAnimation From="0" To="250" Duration="0:0:0.3">
    <DoubleAnimation.EasingFunction>
        <QuadraticEase EasingMode="EaseOut"/>
    </DoubleAnimation.EasingFunction>
</DoubleAnimation>

EasingMode:

  • EaseIn — прискорення на початку
  • EaseOut — уповільнення в кінці (найприродніше)
  • EaseInOut — прискорення на початку + уповільнення в кінці

Типи Easing Functions

ФункціяОписВикористання
QuadraticEaseКвадратична криваУніверсальна, природна
CubicEaseКубічна криваБільш виражена
QuarticEaseКрива 4-го степеняДуже виражена
QuinticEaseКрива 5-го степеняЕкстремальна
SineEaseСинусоїдальнаПлавна, м'яка
ExponentialEaseЕкспоненційнаРізке прискорення
CircleEaseКруговаПлавна, природна
BackEaseЗ "відскоком" назадЕфект пружини
ElasticEaseЕластичнаЕфект гумки
BounceEaseЗ відскокомЕфект м'яча

Приклади з різними Easing Functions

BackEase — відскок назад:

<DoubleAnimation From="0" To="250" Duration="0:0:0.5">
    <DoubleAnimation.EasingFunction>
        <BackEase EasingMode="EaseOut" Amplitude="0.5"/>
    </DoubleAnimation.EasingFunction>
</DoubleAnimation>

ElasticEase — еластичний ефект:

<DoubleAnimation From="0" To="250" Duration="0:0:0.8">
    <DoubleAnimation.EasingFunction>
        <ElasticEase EasingMode="EaseOut" Oscillations="3" Springiness="5"/>
    </DoubleAnimation.EasingFunction>
</DoubleAnimation>

BounceEase — відскок:

<DoubleAnimation From="0" To="250" Duration="0:0:0.6">
    <DoubleAnimation.EasingFunction>
        <BounceEase EasingMode="EaseOut" Bounces="3" Bounciness="2"/>
    </DoubleAnimation.EasingFunction>
</DoubleAnimation>

Візуальне порівняння EasingMode

Щоб зрозуміти різницю між EasingMode, уявіть автомобіль, що рухається від точки A до точки B:

  • EaseIn — автомобіль починає повільно, потім прискорюється. Використовується рідко, бо виглядає неприродньо для UI.
  • EaseOut — автомобіль починає швидко, потім уповільнюється перед зупинкою. Найприродніший варіант для UI-анімацій.
  • EaseInOut — автомобіль прискорюється на початку та уповільнюється в кінці. Підходить для циклічних анімацій.
<StackPanel>
    <!-- EaseIn — прискорення на початку -->
    <Border x:Name="EaseInBox" Width="50" Height="50" Background="#3b82f6" Margin="0,0,0,8">
        <Border.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
                                         From="0" To="300" Duration="0:0:1"
                                         RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimation.EasingFunction>
                                <QuadraticEase EasingMode="EaseIn"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Border.Triggers>
    </Border>
    
    <!-- EaseOut — уповільнення в кінці -->
    <Border Width="50" Height="50" Background="#22c55e" Margin="0,0,0,8">
        <Border.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
                                         From="0" To="300" Duration="0:0:1"
                                         RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimation.EasingFunction>
                                <QuadraticEase EasingMode="EaseOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Border.Triggers>
    </Border>
    
    <!-- EaseInOut — прискорення + уповільнення -->
    <Border Width="50" Height="50" Background="#f59e0b">
        <Border.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
                                         From="0" To="300" Duration="0:0:1"
                                         RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimation.EasingFunction>
                                <QuadraticEase EasingMode="EaseInOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Border.Triggers>
    </Border>
</StackPanel>
Рекомендація: Для більшості UI-анімацій використовуйте QuadraticEase або CubicEase з EasingMode="EaseOut". Це найприродніший вигляд для відкриття панелей, fade-in/out та інших стандартних анімацій. Дослідження показують, що користувачі сприймають EaseOut як найбільш "природний" рух, схожий на фізичні об'єкти у реальному світі.

Порівняння різних Easing Functions

Кожна Easing Function створює унікальну криву руху. Ось як вони відрізняються:

QuadraticEase vs CubicEase vs QuarticEase:

Ці функції відрізняються "силою" прискорення/уповільнення:

  • QuadraticEase (x²) — м'яке, ледь помітне прискорення. Підходить для тонких анімацій.
  • CubicEase (x³) — середнє прискорення. Універсальний вибір для більшості UI.
  • QuarticEase (x⁴) — сильне прискорення. Для драматичних ефектів.
<StackPanel Spacing="16">
    <TextBlock Text="Порівняння сили Easing Functions" FontWeight="Bold"/>
    
    <!-- QuadraticEase — м'яке -->
    <Border Width="60" Height="40" Background="#3b82f6" CornerRadius="4">
        <Border.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
                                         From="0" To="300" Duration="0:0:1"
                                         RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimation.EasingFunction>
                                <QuadraticEase EasingMode="EaseOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Border.Triggers>
        <TextBlock Text="Quadratic" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Border>
    
    <!-- CubicEase — середнє -->
    <Border Width="60" Height="40" Background="#22c55e" CornerRadius="4">
        <Border.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
                                         From="0" To="300" Duration="0:0:1"
                                         RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimation.EasingFunction>
                                <CubicEase EasingMode="EaseOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Border.Triggers>
        <TextBlock Text="Cubic" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Border>
    
    <!-- QuarticEase — сильне -->
    <Border Width="60" Height="40" Background="#f59e0b" CornerRadius="4">
        <Border.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
                                         From="0" To="300" Duration="0:0:1"
                                         RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimation.EasingFunction>
                                <QuarticEase EasingMode="EaseOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Border.Triggers>
        <TextBlock Text="Quartic" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Border>
</StackPanel>

BackEase — ефект "відскоку":

BackEase створює ефект, коли анімація "відскакує" назад перед рухом вперед (EaseIn) або після досягнення цілі (EaseOut). Це нагадує натягування пружини перед стрибком.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

ElasticEase — еластичний ефект:

ElasticEase створює ефект гумки або пружини — елемент "коливається" навколо кінцевої позиції перед зупинкою. Параметри Oscillations (кількість коливань) та Springiness (жорсткість пружини) контролюють поведінку.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

BounceEase — ефект відскоку:

BounceEase імітує фізичний відскок м'яча від поверхні. Параметри Bounces (кількість відскоків) та Bounciness (висота відскоку) контролюють поведінку.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...


Property Path: анімація вкладених властивостей

Для анімації властивостей всередині інших об'єктів використовується Property Path.

Синтаксис Property Path

Формат: (Type.Property).(NestedType.NestedProperty)

Приклади:

<!-- Анімація Color всередині SolidColorBrush у Background -->
<ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)"
                To="#2563eb" Duration="0:0:0.2"/>

<!-- Анімація Color всередині BorderBrush -->
<ColorAnimation Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"
                To="#3b82f6" Duration="0:0:0.2"/>

<!-- Анімація Foreground -->
<ColorAnimation Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)"
                To="#1e293b" Duration="0:0:0.2"/>

Анімація Transform

<Button Content="Rotate Me">
    <Button.RenderTransform>
        <RotateTransform Angle="0"/>
    </Button.RenderTransform>
    <Button.Triggers>
        <EventTrigger RoutedEvent="Click">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Button.RenderTransform).(RotateTransform.Angle)"
                                     From="0" To="360" Duration="0:0:0.5"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Анімація окремих значень Thickness

<!-- Анімація лише Left у Margin -->
<ThicknessAnimation Storyboard.TargetProperty="Margin"
                    From="0,0,0,0" To="20,0,0,0" Duration="0:0:0.3"/>

<!-- Або через Property Path до конкретного значення -->
<DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Margin).(Thickness.Left)"
                 From="0" To="20" Duration="0:0:0.3"/>

Transform анімації: обертання, масштабування, переміщення

Transform — це потужний механізм WPF для зміни візуального представлення елементів без зміни їх логічного розташування. Анімація Transform дозволяє створювати ефекти обертання, масштабування, нахилу та переміщення.

RenderTransform vs LayoutTransform

WPF має два типи трансформацій:

  • RenderTransform — застосовується після layout. Не впливає на розташування інших елементів. Використовується для анімацій.
  • LayoutTransform — застосовується під час layout. Впливає на розташування інших елементів. Рідко використовується для анімацій.

Для анімацій завжди використовуйте RenderTransform!

TranslateTransform: переміщення

TranslateTransform переміщує елемент по осях X та Y без зміни його логічного розташування.

<Button Content="Slide In">
    <Button.RenderTransform>
        <TranslateTransform X="-200" Y="0"/>
    </Button.RenderTransform>
    <Button.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Button.RenderTransform).(TranslateTransform.X)"
                                     From="-200" To="0" Duration="0:0:0.5">
                        <DoubleAnimation.EasingFunction>
                            <CubicEase EasingMode="EaseOut"/>
                        </DoubleAnimation.EasingFunction>
                    </DoubleAnimation>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Приклад: Slide-in панель зліва:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

ScaleTransform: масштабування

ScaleTransform змінює розмір елемента. Властивості ScaleX та ScaleY контролюють масштаб по осях.

<Button Content="Zoom In">
    <Button.RenderTransform>
        <ScaleTransform ScaleX="1" ScaleY="1"/>
    </Button.RenderTransform>
    <Button.RenderTransformOrigin>0.5,0.5</Button.RenderTransformOrigin>
    <Button.Triggers>
        <EventTrigger RoutedEvent="MouseEnter">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Button.RenderTransform).(ScaleTransform.ScaleX)"
                                     To="1.1" Duration="0:0:0.2"/>
                    <DoubleAnimation Storyboard.TargetProperty="(Button.RenderTransform).(ScaleTransform.ScaleY)"
                                     To="1.1" Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
        <EventTrigger RoutedEvent="MouseLeave">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Button.RenderTransform).(ScaleTransform.ScaleX)"
                                     To="1" Duration="0:0:0.2"/>
                    <DoubleAnimation Storyboard.TargetProperty="(Button.RenderTransform).(ScaleTransform.ScaleY)"
                                     To="1" Duration="0:0:0.2"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Важливо: RenderTransformOrigin="0.5,0.5" встановлює центр трансформації в центр елемента. За замовчуванням це 0,0 (верхній лівий кут).

Приклад: Hover ефект для карток:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

RotateTransform: обертання

RotateTransform обертає елемент навколо точки. Властивість Angle контролює кут обертання в градусах.

<Button Content="Rotate">
    <Button.RenderTransform>
        <RotateTransform Angle="0"/>
    </Button.RenderTransform>
    <Button.RenderTransformOrigin>0.5,0.5</Button.RenderTransformOrigin>
    <Button.Triggers>
        <EventTrigger RoutedEvent="Click">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Button.RenderTransform).(RotateTransform.Angle)"
                                     From="0" To="360" Duration="0:0:0.6">
                        <DoubleAnimation.EasingFunction>
                            <CubicEase EasingMode="EaseInOut"/>
                        </DoubleAnimation.EasingFunction>
                    </DoubleAnimation>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Приклад: Обертання іконки завантаження:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

SkewTransform: нахил

SkewTransform нахиляє елемент по осях X та Y. Рідко використовується, але корисний для креативних ефектів.

<Border Background="#3b82f6" Width="100" Height="100">
    <Border.RenderTransform>
        <SkewTransform AngleX="0" AngleY="0"/>
    </Border.RenderTransform>
    <Border.Triggers>
        <EventTrigger RoutedEvent="MouseEnter">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Border.RenderTransform).(SkewTransform.AngleX)"
                                     To="10" Duration="0:0:0.3"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
        <EventTrigger RoutedEvent="MouseLeave">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Border.RenderTransform).(SkewTransform.AngleX)"
                                     To="0" Duration="0:0:0.3"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>
</Border>

TransformGroup: комбінування трансформацій

Для одночасного застосування кількох трансформацій використовується TransformGroup.

<Border Background="#3b82f6" Width="100" Height="100">
    <Border.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
            <RotateTransform Angle="0"/>
            <TranslateTransform X="0" Y="0"/>
        </TransformGroup>
    </Border.RenderTransform>
    <Border.RenderTransformOrigin>0.5,0.5</Border.RenderTransformOrigin>
    <Border.Triggers>
        <EventTrigger RoutedEvent="MouseEnter">
            <BeginStoryboard>
                <Storyboard>
                    <!-- Масштабування -->
                    <DoubleAnimation Storyboard.TargetProperty="(Border.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
                                     To="1.2" Duration="0:0:0.3"/>
                    <DoubleAnimation Storyboard.TargetProperty="(Border.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
                                     To="1.2" Duration="0:0:0.3"/>
                    <!-- Обертання -->
                    <DoubleAnimation Storyboard.TargetProperty="(Border.RenderTransform).(TransformGroup.Children)[1].(RotateTransform.Angle)"
                                     To="15" Duration="0:0:0.3"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>
</Border>

Приклад: Card flip ефект:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Важливо про Property Path для TransformGroup: Коли використовуєте TransformGroup, Property Path стає складнішим: (Border.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX). Індекс [0], [1], [2] відповідає порядку трансформацій у TransformGroup.

KeyFrame анімації: складні траєкторії руху

KeyFrame анімації дозволяють створювати складні анімації з кількома проміжними точками. Замість простого руху від A до B, ви можете визначити точки C, D, E з різними швидкостями та easing functions.

DoubleAnimationUsingKeyFrames

Найпопулярніший тип KeyFrame анімації — для числових значень.

<Border x:Name="AnimatedBox" Width="60" Height="60" Background="#3b82f6">
    <Border.RenderTransform>
        <TranslateTransform X="0" Y="0"/>
    </Border.RenderTransform>
    <Border.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard RepeatBehavior="Forever">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.RenderTransform).(TranslateTransform.X)">
                        <!-- Початок: X = 0 -->
                        <LinearDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                        <!-- Через 1 секунду: X = 200 -->
                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="200">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <QuadraticEase EasingMode="EaseOut"/>
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                        <!-- Через 2 секунди: X = 200 (пауза) -->
                        <LinearDoubleKeyFrame KeyTime="0:0:2" Value="200"/>
                        <!-- Через 3 секунди: X = 0 (повернення) -->
                        <EasingDoubleKeyFrame KeyTime="0:0:3" Value="0">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <QuadraticEase EasingMode="EaseIn"/>
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>
</Border>

Типи KeyFrame

WPF підтримує три типи KeyFrame:

ТипОписВикористання
LinearDoubleKeyFrameЛінійна інтерполяціяПостійна швидкість між точками
EasingDoubleKeyFrameЗ Easing FunctionПриродні анімації з прискоренням/уповільненням
DiscreteDoubleKeyFrameМиттєва змінаСтрибок без інтерполяції
SplineDoubleKeyFrameКубічна крива БезьєМаксимальний контроль над кривою

SplineDoubleKeyFrame: повний контроль

SplineDoubleKeyFrame використовує кубічну криву Безьє для контролю швидкості. Властивість KeySpline визначає дві контрольні точки кривої.

<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
    <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0" KeySpline="0.1,0.9 0.2,1"/>
    <SplineDoubleKeyFrame KeyTime="0:0:1" Value="1" KeySpline="0.3,0.1 0.9,0.2"/>
</DoubleAnimationUsingKeyFrames>

KeySpline формат: "x1,y1 x2,y2" — дві контрольні точки кривої Безьє.

ColorAnimationUsingKeyFrames

Для анімації кольорів з кількома проміжними точками:

<Border Background="#3b82f6">
    <Border.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard RepeatBehavior="Forever" AutoReverse="True">
                    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)">
                        <!-- Синій -->
                        <LinearColorKeyFrame KeyTime="0:0:0" Value="#3b82f6"/>
                        <!-- Зелений -->
                        <LinearColorKeyFrame KeyTime="0:0:2" Value="#22c55e"/>
                        <!-- Помаранчевий -->
                        <LinearColorKeyFrame KeyTime="0:0:4" Value="#f59e0b"/>
                        <!-- Червоний -->
                        <LinearColorKeyFrame KeyTime="0:0:6" Value="#ef4444"/>
                    </ColorAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>
</Border>

Приклад: Складна траєкторія руху:

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

ObjectAnimationUsingKeyFrames: дискретні зміни

Для властивостей, що не підтримують інтерполяцію (Visibility, string, enum), використовується ObjectAnimationUsingKeyFrames.

<TextBlock x:Name="StatusText" Text="Idle" FontSize="24">
    <TextBlock.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard RepeatBehavior="Forever">
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Text">
                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="Loading"/>
                        <DiscreteObjectKeyFrame KeyTime="0:0:1" Value="Loading."/>
                        <DiscreteObjectKeyFrame KeyTime="0:0:2" Value="Loading.."/>
                        <DiscreteObjectKeyFrame KeyTime="0:0:3" Value="Loading..."/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </TextBlock.Triggers>
</TextBlock>

Приклад: Мигаючий індикатор через Visibility:

<Ellipse Width="20" Height="20" Fill="#ef4444">
    <Ellipse.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard RepeatBehavior="Forever">
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility">
                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
                        <DiscreteObjectKeyFrame KeyTime="0:0:0.5" Value="{x:Static Visibility.Collapsed}"/>
                        <DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{x:Static Visibility.Visible}"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Ellipse.Triggers>
</Ellipse>
Коли використовувати KeyFrame анімації:
  • Складні траєкторії руху (не просто A → B)
  • Анімації з паузами між етапами
  • Зміна кольору через кілька проміжних кольорів
  • Комбінування різних Easing Functions в одній анімації
  • Синхронізація кількох властивостей з різними таймінгами

Реальні приклади анімацій

Notification Toast: slide-in + fade-out

Типова анімація для сповіщень — з'являється зверху, затримується, зникає.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Типова анімація для модальних вікон — затемнення фону + масштабування діалогу.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Progress Bar: плавне заповнення

Анімація прогрес-бару з плавним заповненням.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...

Skeleton Loading: пульсуючі плейсхолдери

Сучасний підхід до індикації завантаження — skeleton screens.

Loading Avalonia WebAssembly...

Downloading .NET runtime (10MB)...


Code-behind анімації: програмний контроль

Іноді потрібно запускати анімації з коду, а не через EventTrigger.

Створення та запуск Storyboard у коді

private void AnimateButton_Click(object sender, RoutedEventArgs e)
{
    // Створюємо анімацію
    var animation = new DoubleAnimation
    {
        From = 0,
        To = 1,
        Duration = TimeSpan.FromMilliseconds(500),
        EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
    };
    
    // Створюємо Storyboard
    var storyboard = new Storyboard();
    storyboard.Children.Add(animation);
    
    // Встановлюємо Target та Property
    Storyboard.SetTarget(animation, MyButton);
    Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity"));
    
    // Запускаємо
    storyboard.Begin();
}

Контроль виконання Storyboard

private Storyboard? _currentStoryboard;

private void StartAnimation_Click(object sender, RoutedEventArgs e)
{
    _currentStoryboard = CreateFadeInStoryboard();
    _currentStoryboard.Begin();
}

private void PauseAnimation_Click(object sender, RoutedEventArgs e)
{
    _currentStoryboard?.Pause();
}

private void ResumeAnimation_Click(object sender, RoutedEventArgs e)
{
    _currentStoryboard?.Resume();
}

private void StopAnimation_Click(object sender, RoutedEventArgs e)
{
    _currentStoryboard?.Stop();
}

private Storyboard CreateFadeInStoryboard()
{
    var animation = new DoubleAnimation
    {
        From = 0,
        To = 1,
        Duration = TimeSpan.FromSeconds(2)
    };
    
    var storyboard = new Storyboard();
    storyboard.Children.Add(animation);
    Storyboard.SetTarget(animation, MyButton);
    Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity"));
    
    return storyboard;
}

Події Storyboard

private void StartAnimation_Click(object sender, RoutedEventArgs e)
{
    var storyboard = CreateStoryboard();
    
    // Підписка на події
    storyboard.Completed += (s, args) =>
    {
        MessageBox.Show("Анімація завершена!");
    };
    
    storyboard.CurrentTimeInvalidated += (s, args) =>
    {
        // Викликається при кожному кадрі
        var progress = storyboard.GetCurrentTime().TotalSeconds / storyboard.Duration.TimeSpan.TotalSeconds;
        ProgressTextBlock.Text = $"Прогрес: {progress:P0}";
    };
    
    storyboard.Begin();
}

Продуктивність анімацій: оптимізація та best practices

Анімації можуть суттєво вплинути на продуктивність додатку, особливо на слабких пристроях. Розуміння того, як WPF рендерить анімації, допоможе створювати плавні та ефективні інтерфейси.

Hardware Acceleration: GPU vs CPU

WPF підтримує апаратне прискорення через DirectX. Деякі анімації виконуються на GPU (швидко), інші на CPU (повільно).

Анімації на GPU (швидкі):

  • Opacity — найшвидша анімація
  • RenderTransform (TranslateTransform, ScaleTransform, RotateTransform, SkewTransform)
  • Clip (RectangleGeometry)

Анімації на CPU (повільні):

  • Width, Height — викликають перерахунок layout
  • Margin, Padding — викликають перерахунок layout
  • LayoutTransform — викликає перерахунок layout
  • Складні Path геометрії
  • Анімації з великою кількістю елементів
Золоте правило: Завжди використовуйте RenderTransform замість Width/Height/Margin для анімацій переміщення та масштабування. RenderTransform виконується на GPU і не викликає перерахунок layout.

Приклад: правильна vs неправильна анімація

❌ Неправильно (повільно):

<!-- Анімація Width викликає перерахунок layout на кожному кадрі -->
<Border x:Name="Sidebar" Width="0">
    <Border.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="Width"
                                     From="0" To="250" Duration="0:0:0.3"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>
</Border>

✅ Правильно (швидко):

<!-- Анімація ScaleTransform виконується на GPU -->
<Border Width="250">
    <Border.RenderTransform>
        <ScaleTransform ScaleX="0" ScaleY="1"/>
    </Border.RenderTransform>
    <Border.RenderTransformOrigin>0,0.5</Border.RenderTransformOrigin>
    <Border.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(Border.RenderTransform).(ScaleTransform.ScaleX)"
                                     From="0" To="1" Duration="0:0:0.3"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>
</Border>

CacheMode: кешування візуального дерева

Для складних елементів з багатьма дочірніми елементами використовуйте CacheMode="BitmapCache". Це кешує візуальне представлення як bitmap і анімує його на GPU.

<Border CacheMode="BitmapCache">
    <Border.RenderTransform>
        <TranslateTransform X="0" Y="0"/>
    </Border.RenderTransform>
    <!-- Складний вміст з багатьма елементами -->
    <StackPanel>
        <TextBlock Text="Title"/>
        <TextBlock Text="Subtitle"/>
        <!-- ... багато інших елементів ... -->
    </StackPanel>
</Border>

Коли використовувати BitmapCache:

  • Анімація складних елементів з багатьма дочірніми елементами
  • Анімація елементів з градієнтами або складними Brush
  • Анімація елементів з ефектами (DropShadowEffect, BlurEffect)

Коли НЕ використовувати BitmapCache:

  • Прості елементи (один Border, один TextBlock)
  • Елементи, що часто змінюють вміст під час анімації
  • Статичні елементи без анімацій

Timeline.DesiredFrameRate: контроль FPS

За замовчуванням WPF намагається досягти 60 FPS. Для деяких анімацій це надмірно і можна знизити FPS для економії ресурсів.

<Storyboard Timeline.DesiredFrameRate="30">
    <DoubleAnimation Storyboard.TargetProperty="Opacity"
                     From="0" To="1" Duration="0:0:2"/>
</Storyboard>

Рекомендації:

  • 60 FPS — для швидких, критичних анімацій (hover ефекти, transitions)
  • 30 FPS — для повільних анімацій (fade-in при завантаженні)
  • 15 FPS — для фонових анімацій (пульсуючі індикатори)

Freeze: заморожування об'єктів

Об'єкти, що не змінюються, можна "заморозити" через Freeze(). Це дозволяє WPF оптимізувати їх використання.

// Створюємо Brush і заморожуємо його
var brush = new SolidColorBrush(Colors.Blue);
brush.Freeze(); // Тепер brush незмінний і оптимізований

// Використовуємо в анімації
var animation = new ColorAnimation
{
    To = Colors.Red,
    Duration = TimeSpan.FromSeconds(1)
};
animation.Freeze(); // Заморожуємо анімацію

// Frozen об'єкти можна використовувати в кількох потоках

Переваги Freeze:

  • Об'єкт стає thread-safe (можна використовувати в кількох потоках)
  • WPF оптимізує використання пам'яті
  • Швидший рендеринг

Профілювання анімацій

Для виявлення проблем з продуктивністю використовуйте Visual Studio Profiler або WPF Performance Suite.

Включення FPS counter в WPF:

// В App.xaml.cs або MainWindow.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    
    // Показати FPS counter
    System.Diagnostics.PresentationTraceSources.SetTraceLevel(
        this, 
        System.Diagnostics.PresentationTraceLevel.High
    );
}

Best Practices: чек-лист

✅ Використовуйте RenderTransform

Замість Width/Height/Margin для переміщення та масштабування. Виконується на GPU.

✅ Opacity для fade-in/out

Найшвидша анімація в WPF. Завжди виконується на GPU.

✅ BitmapCache для складних елементів

Кешує візуальне дерево як bitmap для швидшої анімації.

✅ Freeze незмінні об'єкти

Brush, Animation, Geometry — заморожуйте для оптимізації.

❌ Уникайте Width/Height анімацій

Викликають перерахунок layout на кожному кадрі. Повільно.

❌ Уникайте LayoutTransform

Використовуйте RenderTransform для анімацій. LayoutTransform повільний.

⚡ Знижуйте FPS для фонових анімацій

30 FPS або 15 FPS для некритичних анімацій економить ресурси.

🔍 Профілюйте продуктивність

Використовуйте Visual Studio Profiler для виявлення bottlenecks.

Порівняльна таблиця продуктивності

АнімаціяПродуктивністьGPU/CPULayout RecalcРекомендація
Opacity⚡⚡⚡ ВідміннаGPUНіЗавжди використовуйте
RenderTransform⚡⚡⚡ ВідміннаGPUНіЗавжди використовуйте
Clip⚡⚡ ДобраGPUНіМожна використовувати
Width/Height⚠️ ПоганаCPUТакУникайте
Margin/Padding⚠️ ПоганаCPUТакУникайте
LayoutTransform⚠️ ПоганаCPUТакУникайте
Background Color⚡ СередняGPUНіМожна використовувати
FontSize⚠️ ПоганаCPUТакУникайте

Практичні завдання

Рівень 1: Fade-in при завантаженні

Мета: Навчитися створювати базову анімацію через EventTrigger.

Завдання:

Створіть вікно з елементами, що плавно з'являються при завантаженні:

  1. UI:
    • Заголовок (TextBlock)
    • Підзаголовок (TextBlock)
    • Кнопка
  2. Анімація:
    • Всі елементи починають з Opacity="0"
    • При Loaded → Opacity змінюється до 1
    • Тривалість: 0.5 секунди
    • Easing: QuadraticEase EaseOut
  3. Додатково:
    • Затримка між елементами (BeginTime)
    • Заголовок з'являється першим
    • Підзаголовок через 0.2 секунди
    • Кнопка через 0.4 секунди

Критерії успіху:

  • Fade-in працює при завантаженні
  • Використано EventTrigger з Loaded
  • Easing Function застосовано
  • Затримки між елементами працюють

Підказка:

<StackPanel>
    <TextBlock Text="Заголовок" Opacity="0">
        <TextBlock.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         From="0" To="1" Duration="0:0:0.5"
                                         BeginTime="0:0:0">
                            <DoubleAnimation.EasingFunction>
                                <QuadraticEase EasingMode="EaseOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </TextBlock.Triggers>
    </TextBlock>
    
    <TextBlock Text="Підзаголовок" Opacity="0">
        <TextBlock.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         From="0" To="1" Duration="0:0:0.5"
                                         BeginTime="0:0:0.2">
                            <DoubleAnimation.EasingFunction>
                                <QuadraticEase EasingMode="EaseOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </TextBlock.Triggers>
    </TextBlock>
</StackPanel>

Рівень 2: Анімована sidebar панель

Мета: Навчитися створювати складнішу анімацію з code-behind контролем.

Завдання:

Створіть бічну панель, що виїжджає зліва:

  1. UI:
    • Кнопка "Відкрити меню"
    • Sidebar (Border) з Width="0" за замовчуванням
    • Вміст sidebar (список пунктів меню)
  2. Анімація:
    • При натисканні кнопки → Width змінюється з 0 до 250
    • Тривалість: 0.3 секунди
    • Easing: CubicEase EaseOut
    • Повторне натискання → закриває sidebar (Width до 0)
  3. Додатково:
    • Напівпрозорий overlay при відкритій sidebar
    • Клік на overlay закриває sidebar
    • Fade-in/out для overlay

Критерії успіху:

  • Sidebar відкривається/закривається плавно
  • Використано code-behind для контролю
  • Easing Function застосовано
  • Overlay працює правильно

Підказка:

private bool _isSidebarOpen = false;

private void ToggleSidebar_Click(object sender, RoutedEventArgs e)
{
    _isSidebarOpen = !_isSidebarOpen;
    
    var widthAnimation = new DoubleAnimation
    {
        To = _isSidebarOpen ? 250 : 0,
        Duration = TimeSpan.FromMilliseconds(300),
        EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
    };
    
    var overlayAnimation = new DoubleAnimation
    {
        To = _isSidebarOpen ? 0.5 : 0,
        Duration = TimeSpan.FromMilliseconds(300)
    };
    
    Sidebar.BeginAnimation(WidthProperty, widthAnimation);
    Overlay.BeginAnimation(OpacityProperty, overlayAnimation);
    
    Overlay.IsHitTestVisible = _isSidebarOpen;
}

Рівень 3: Пульсуючий індикатор завантаження

Мета: Навчитися створювати нескінченні анімації з RepeatBehavior та AutoReverse.

Завдання:

Створіть індикатор завантаження з пульсуючими точками:

  1. UI:
    • 3 кола (Ellipse)
    • Розташовані горизонтально
  2. Анімація:
    • Кожне коло пульсує (Opacity 1 → 0.3 → 1)
    • Тривалість: 0.8 секунди
    • AutoReverse="True"
    • RepeatBehavior="Forever"
    • Затримка між колами (BeginTime)
  3. Додатково:
    • Анімація ScaleTransform (збільшення/зменшення)
    • Зміна кольору через ColorAnimation
    • Кнопка Start/Stop для контролю

Критерії успіху:

  • Пульсація працює нескінченно
  • Затримки між колами створюють ефект хвилі
  • AutoReverse працює правильно
  • Можна зупинити/запустити анімацію

Підказка:

<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="8">
    <Ellipse x:Name="Dot1" Width="20" Height="20" Fill="#3b82f6">
        <Ellipse.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard x:Name="Dot1Storyboard">
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         From="1" To="0.3" Duration="0:0:0.8"
                                         AutoReverse="True"
                                         RepeatBehavior="Forever"
                                         BeginTime="0:0:0"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Ellipse.Triggers>
    </Ellipse>
    
    <Ellipse Width="20" Height="20" Fill="#3b82f6">
        <Ellipse.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         From="1" To="0.3" Duration="0:0:0.8"
                                         AutoReverse="True"
                                         RepeatBehavior="Forever"
                                         BeginTime="0:0:0.2"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Ellipse.Triggers>
    </Ellipse>
    
    <Ellipse Width="20" Height="20" Fill="#3b82f6">
        <Ellipse.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         From="1" To="0.3" Duration="0:0:0.8"
                                         AutoReverse="True"
                                         RepeatBehavior="Forever"
                                         BeginTime="0:0:0.4"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Ellipse.Triggers>
    </Ellipse>
</StackPanel>

Підсумок

Анімації у WPF через Storyboard — потужний інструмент для створення інтерактивних та професійних інтерфейсів. Ми детально розглянули всі аспекти анімацій: від базового синтаксису до складних KeyFrame анімацій та оптимізації продуктивності.

Ключові висновки:

📦 Storyboard

Контейнер для координації кількох анімацій. TargetName та TargetProperty для вказівки цілі. EventTrigger для автоматичного запуску.

🎬 Типи анімацій

DoubleAnimation для чисел, ColorAnimation для кольорів, ThicknessAnimation для Margin/Padding, ObjectAnimationUsingKeyFrames для дискретних значень.

⏱️ Duration & Repeat

Duration для тривалості, RepeatBehavior для повторення (1x, 3x, Forever), AutoReverse для повернення, BeginTime для затримки.

📈 Easing Functions

QuadraticEase, CubicEase для природних анімацій. BackEase для відскоку, ElasticEase для пружини, BounceEase для м'яча. EaseOut найпопулярніший.

🔄 Transform анімації

TranslateTransform для переміщення, ScaleTransform для масштабування, RotateTransform для обертання. Завжди використовуйте RenderTransform, не LayoutTransform.

🎯 KeyFrame анімації

Складні траєкторії з кількома проміжними точками. LinearKeyFrame, EasingKeyFrame, DiscreteKeyFrame, SplineKeyFrame для максимального контролю.

💻 Code-behind

Програмний контроль через Begin(), Pause(), Resume(), Stop(). Події Completed та CurrentTimeInvalidated для відстеження прогресу.

⚡ Продуктивність

Використовуйте RenderTransform та Opacity (GPU). Уникайте Width/Height/Margin (CPU). BitmapCache для складних елементів. Freeze для оптимізації.

Що далі?

Наступна стаття — Avalonia Animations покаже простіший підхід через Transitions та CSS-подібні анімації.


Словник термінів

Storyboard — контейнер для координації виконання кількох анімацій одночасно або послідовно.Timeline — базовий клас для всіх анімацій (DoubleAnimation, ColorAnimation, тощо).From/To/By — початкове значення, кінцеве значення та зміна для анімації.Duration — тривалість анімації у форматі hours:minutes:seconds.milliseconds.Easing Function — функція для нелінійної зміни значення (прискорення, уповільнення).EasingMode — режим застосування easing: EaseIn (прискорення на початку), EaseOut (уповільнення в кінці), EaseInOut (обидва).RepeatBehavior — поведінка повторення анімації (кількість разів або Forever).AutoReverse — автоматичне повернення до початкового стану після завершення.BeginTime — затримка перед початком анімації.Property Path — шлях до властивості для анімації, включаючи вкладені об'єкти.EventTrigger — тригер для запуску анімації при події (Loaded, MouseEnter, тощо).BeginStoryboard — дія для запуску Storyboard у EventTrigger.RenderTransform — трансформація, що застосовується після layout. Не впливає на інші елементи. Виконується на GPU.LayoutTransform — трансформація, що застосовується під час layout. Впливає на інші елементи. Виконується на CPU.TranslateTransform — переміщення елемента по осях X та Y.ScaleTransform — масштабування елемента по осях X та Y.RotateTransform — обертання елемента навколо точки.SkewTransform — нахил елемента по осях X та Y.TransformGroup — контейнер для комбінування кількох трансформацій.RenderTransformOrigin — точка, навколо якої застосовується трансформація (0,0 = верхній лівий кут, 0.5,0.5 = центр).KeyFrame Animation — анімація з кількома проміжними точками (keyframes).LinearKeyFrame — лінійна інтерполяція між keyframes (постійна швидкість).EasingKeyFrame — keyframe з Easing Function для природної анімації.DiscreteKeyFrame — миттєва зміна без інтерполяції.SplineKeyFrame — keyframe з кубічною кривою Безьє для максимального контролю.KeyTime — час, коли досягається значення keyframe.BitmapCache — кешування візуального дерева як bitmap для швидшої анімації на GPU.Hardware Acceleration — використання GPU для рендерингу анімацій замість CPU.Freeze — заморожування об'єкта для оптимізації (стає незмінним та thread-safe).DesiredFrameRate — бажана кількість кадрів на секунду для анімації (FPS).

Додаткові ресурси

📖 WPF Animation Docs

Офіційна документація про анімації у WPF.

📈 Easing Functions Guide

Повний гайд з Easing Functions та візуальні приклади.

🎬 Storyboard Tutorial

Детальна стаття про Storyboard та Timeline.

📚 Попередня стаття: Avalonia TemplatedControl

Повернутися до Avalonia TemplatedControl.

📚 Наступна стаття: Avalonia Animations

Дізнатися про Transitions у Avalonia.