TransitionToggleRepeat is a utility class that repeatedly toggles a USS class on a VisualElement, creating looping CSS transition animations. It's useful for breathing effects, pulsing buttons, or any repeating visual state change.

Features

  • CSS-Driven Animations: Uses USS transitions for smooth animations
  • Configurable Timing: Control delay between toggles
  • Start Delay: Optional initial delay before animation begins
  • Pause/Resume: Start and pause animations as needed

Important: The USS class you toggle must define a transition on at least one property. Otherwise, the TransitionEndEvent will not fire and the animation loop won't continue.

Basic Usage

1. Define USS Transitions

First, create USS styles with transitions:

css
.my-element { opacity: 1; transition: opacity 0.5s ease-in-out; } .my-element.fade-anim { opacity: 0.5; }

2. Create and Start the Animation

csharp
using UnityEngine.UIElements; using CupkekGames.Luna; public class PulsingButton : MonoBehaviour { private TransitionToggleRepeat _pulseAnimation; private void Start() { var button = _uiDocument.rootVisualElement.Q<Button>("PulseButton"); // Create repeating animation that toggles "fade-anim" class // with 200ms delay between transitions _pulseAnimation = new TransitionToggleRepeat( button, "fade-anim", delayMsInterval: 200 ); } private void OnEnable() { // Start animation with 100ms initial delay _pulseAnimation.Start(delayMs: 100); } private void OnDisable() { _pulseAnimation.Pause(); } }

Constructor

csharp
public TransitionToggleRepeat( VisualElement ve, string ussClassName, int delayMsInterval )
ParameterTypeDescription
veVisualElementThe element to animate
ussClassNamestringUSS class to toggle
delayMsIntervalintDelay in milliseconds between each toggle

Methods

Start

Starts the repeating animation.

csharp
public void Start(long delayMs)
ParameterTypeDescription
delayMslongInitial delay before first toggle

Pause

Stops the animation and unregisters the transition event callback.

csharp
public void Pause()

How It Works

  1. When Start() is called, it schedules the first class toggle after delayMs
  2. When a CSS transition ends, it listens for TransitionEndEvent
  3. After the transition completes, it waits delayMsInterval milliseconds
  4. Then it toggles the class again, repeating the cycle

This creates a seamless loop where the element transitions between two states defined by the presence or absence of the USS class.

Animation Examples

Breathing Effect

css
.character-portrait { scale: 1; transition: scale 1s ease-in-out; } .character-portrait.breathing { scale: 1.05; }
csharp
var portrait = root.Q<VisualElement>("Portrait"); var breathing = new TransitionToggleRepeat(portrait, "breathing", 100); breathing.Start(0);

Pulsing Glow

css
.notification-icon { --glow-opacity: 0; transition: --glow-opacity 0.5s ease-in-out; } .notification-icon.glow-pulse { --glow-opacity: 1; }
csharp
var icon = root.Q<VisualElement>("NotificationIcon"); var pulse = new TransitionToggleRepeat(icon, "glow-pulse", 300); pulse.Start(0);

Bouncing Arrow

css
.continue-arrow { translate: 0 0; transition: translate 0.3s ease-in-out; } .continue-arrow.bounce { translate: 0 -10px; }
csharp
var arrow = root.Q<VisualElement>("ContinueArrow"); var bounce = new TransitionToggleRepeat(arrow, "bounce", 100); bounce.Start(500); // Start after 500ms

Example: Animated Avatars Manager

A complete example that manages multiple rotating avatar animations:

csharp
using System.Collections.Generic; using System.Linq; using CupkekGames.Luna; using UnityEngine; using UnityEngine.UIElements; public class AnimatedAvatars { private readonly List<Sprite> _avatars; private readonly List<VisualElement> _avatarElements; private readonly List<TransitionToggleRepeat> _transitions = new(); public AnimatedAvatars(List<Sprite> avatars, List<VisualElement> avatarElements) { _avatars = avatars; _avatarElements = avatarElements; foreach (VisualElement avatar in _avatarElements) { _transitions.Add(new TransitionToggleRepeat(avatar, "avatar_anim_rotate", 200)); } } public void StartAnimation() { RandomizeAvatars(); foreach (TransitionToggleRepeat transition in _transitions) { transition.Start(0); } } public void StopAnimation() { foreach (TransitionToggleRepeat transition in _transitions) { transition.Pause(); } } private void RandomizeAvatars() { List<int> shuffledIndices = Enumerable.Range(0, _avatars.Count) .OrderBy(_ => Random.value).ToList(); for (int i = 0; i < _avatarElements.Count; i++) { if (i >= shuffledIndices.Count) { _avatarElements[i].parent.style.display = DisplayStyle.None; continue; } _avatarElements[i].style.backgroundImage = new StyleBackground(_avatars[shuffledIndices[i]]); } } }

Real-World Usage: Visual Novel

The Visual Novel controller uses TransitionToggleRepeat for avatar breathing animations and next icon indicators:

csharp
// Animate avatar with breathing effect _avatarLeftSchedule = new TransitionToggleRepeat(_avatarLeft, "vn_avatar_anim", 200); _avatarRightSchedule = new TransitionToggleRepeat(_avatarRight, "vn_avatar_anim", 200); // Animate "next" icon _nextIconSchedule = new TransitionToggleRepeat(_nextIcon, "vn_next_icon_anim", 100); // Show avatar with animation public void ShowAvatarLeft(Sprite sprite) { _avatarLeft.style.backgroundImage = new StyleBackground(sprite); _avatarLeftContainer.style.display = DisplayStyle.Flex; _avatarLeftSchedule.Start(1); } // Hide avatar and stop animation public void HideAvatarLeft() { _avatarLeftSchedule.Pause(); _avatarLeftContainer.style.display = DisplayStyle.None; }

Tips

  • Ensure your USS transitions have appropriate durations
  • The total animation cycle is: transition duration + delayMsInterval
  • Use reasonable delay intervals (100-500ms) to prevent visual jittering
  • Call Pause() before the VisualElement is removed from the hierarchy
  • Multiple transitions on the same element will each trigger TransitionEndEvent, but the class handles this with a flag to prevent multiple toggles

Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL