Écosystème étendu
Animations (ancien système)

Transitions et déclencheurs d'animation

Important : Le package @angular/animations est désormais déprécié. L'équipe Angular recommande d'utiliser le CSS natif avec animate.enter et animate.leave pour les animations dans tout nouveau code. Pour en savoir plus, consultez le nouveau guide des animations d'entrée et de sortie. Consultez aussi Abandonner le package Animations d'Angular pour savoir comment commencer à migrer vers des animations en CSS pur dans vos applications.

Ce guide approfondit les états de transition spéciaux, comme le wildcard * et void. Il montre aussi comment ces états sont utilisés pour les éléments qui entrent dans une vue ou en sortent. Cette section explore également les déclencheurs d'animation multiples, les callbacks d'animation et les animations séquentielles à base de keyframes.

États prédéfinis et correspondance wildcard

Dans Angular, les états de transition peuvent être définis explicitement avec la fonction state(), ou à l'aide des états prédéfinis * (wildcard) et void.

État wildcard

Un astérisque *, ou wildcard, correspond à n'importe quel état d'animation. C'est utile pour définir des transitions qui s'appliquent quel que soit l'état de départ ou d'arrivée de l'élément HTML.

Par exemple, une transition open => * s'applique quand l'état de l'élément passe de open à n'importe quel autre état.

expressions d'état wildcard

Voici un autre exemple de code qui utilise l'état wildcard avec l'exemple précédent basé sur les états open et closed. Au lieu de définir chaque paire de transitions d'un état à l'autre, toute transition vers closed dure 1 seconde, et toute transition vers open dure 0,5 seconde.

Cela permet d'ajouter de nouveaux états sans avoir à inclure des transitions distinctes pour chacun d'eux.

open-close.ts

animations: [
  trigger('openClose', [
    // ...
    state(
      'open',
      style({
        height: '200px',
        opacity: 1,
        backgroundColor: 'yellow',
      }),
    ),
    state(
      'closed',
      style({
        height: '100px',
        opacity: 0.8,
        backgroundColor: 'blue',
      }),
    ),
    transition('* => closed', [animate('1s')]),
    transition('* => open', [animate('0.5s')]),
  ]),
],

Utilisez la syntaxe à double flèche pour spécifier des transitions d'un état à l'autre dans les deux sens.

open-close.ts

transition('open <=> closed', [animate('0.5s')]),

Utiliser l'état wildcard avec plusieurs états de transition

Dans l'exemple du bouton à deux états, le wildcard n'est pas très utile, car il n'existe que deux états possibles, open et closed. En général, utilisez les états wildcard quand un élément peut passer à plusieurs états potentiels. Si le bouton peut passer de open à closed ou à un état comme inProgress, un état wildcard peut réduire la quantité de code nécessaire.

état wildcard avec 3 états

open-close.ts

animations: [
  trigger('openClose', [
    // ...
    state(
      'open',
      style({
        height: '200px',
        opacity: 1,
        backgroundColor: 'yellow',
      }),
    ),
    state(
      'closed',
      style({
        height: '100px',
        opacity: 0.8,
        backgroundColor: 'blue',
      }),
    ),
    transition('open => closed', [animate('1s')]),
    transition('closed => open', [animate('0.5s')]),
    transition('* => closed', [animate('1s')]),
    transition('* => open', [animate('0.5s')]),
    transition('open <=> closed', [animate('0.5s')]),
    transition('* => open', [animate('1s', style({opacity: '*'}))]),
    transition('* => *', [animate('1s')]),

La transition * => * s'applique à tout changement entre deux états.

Les transitions sont mises en correspondance dans l'ordre où elles sont définies. Vous pouvez donc appliquer d'autres transitions en plus de la transition * => *. Par exemple, définissez des changements de style ou des animations qui s'appliquent uniquement à open => closed, puis utilisez * => * comme solution de repli pour les paires d'états qui ne sont pas explicitement traitées.

Pour cela, listez les transitions les plus spécifiques avant * => *.

Utiliser les wildcards avec des styles

Utilisez le wildcard * avec un style pour indiquer à l'animation d'utiliser la valeur actuelle de ce style, quelle qu'elle soit, et d'animer à partir de celle-ci. Le wildcard est une valeur de repli utilisée si l'état animé n'est pas déclaré dans le déclencheur.

open-close.ts

transition('* => open', [animate('1s', style({opacity: '*'}))]),

État void

Utilisez l'état void pour configurer les transitions d'un élément qui entre dans une page ou en sort. Consultez Animer l'entrée et la sortie d'une vue.

Combiner les états wildcard et void

Combinez les états wildcard et void dans une transition pour déclencher des animations à l'entrée et à la sortie de la page :

  • Une transition * => void s'applique quand l'élément quitte une vue, quel que soit l'état dans lequel il se trouvait avant de la quitter
  • Une transition void => * s'applique quand l'élément entre dans une vue, quel que soit l'état qu'il prend en entrant
  • L'état wildcard * correspond à n'importe quel état, y compris void

Animer l'entrée et la sortie d'une vue

Cette section montre comment animer des éléments qui entrent dans une page ou en sortent.

Ajoutez un nouveau comportement :

  • Quand vous ajoutez un héros à la liste des héros, il semble arriver sur la page en volant depuis la gauche
  • Quand vous retirez un héros de la liste, il semble s'envoler vers la droite

hero-list-enter-leave.ts

animations: [
  trigger('flyInOut', [
    state('in', style({transform: 'translateX(0)'})),
    transition('void => *', [style({transform: 'translateX(-100%)'}), animate(100)]),
    transition('* => void', [animate(100, style({transform: 'translateX(100%)'}))]),
  ]),
],

Dans le code précédent, vous avez appliqué l'état void lorsque l'élément HTML n'est pas attaché à une vue.

Alias :enter et :leave

:enter et :leave sont des alias des transitions void => * et * => void. Ces alias sont utilisés par plusieurs fonctions d'animation.

transition ( ':enter', [ … ] ); // alias for void => _
transition ( ':leave', [ … ] ); // alias for _ => void

Il est plus difficile de cibler un élément qui entre dans une vue, car il n'est pas encore dans le DOM. Utilisez les alias :enter et :leave pour cibler les éléments HTML insérés dans une vue ou retirés d'une vue.

Utiliser *ngIf et *ngFor avec :enter et :leave

La transition :enter s'exécute quand des vues *ngIf ou *ngFor sont placées sur la page, et :leave s'exécute quand ces vues sont retirées de la page.

Important : Les comportements d'entrée et de sortie peuvent parfois prêter à confusion. En règle générale, considérez que tout élément ajouté au DOM par Angular passe par la transition :enter. Seuls les éléments retirés directement du DOM par Angular passent par la transition :leave. Par exemple, la vue d'un élément est retirée du DOM parce que son parent est retiré du DOM.

Cet exemple comporte un déclencheur spécial pour l'animation d'entrée et de sortie, appelé myInsertRemoveTrigger. Le template HTML contient le code suivant.

insert-remove.html

@if (isShown) {
  <div @myInsertRemoveTrigger class="insert-remove-container">
    <p>The box is inserted</p>
  </div>
}

Dans le fichier du composant, la transition :enter définit une opacité initiale de 0. Elle anime ensuite cette opacité jusqu'à 1 à mesure que l'élément est inséré dans la vue.

insert-remove.ts

trigger('myInsertRemoveTrigger', [
  transition(':enter', [style({opacity: 0}), animate('100ms', style({opacity: 1}))]),
  transition(':leave', [animate('100ms', style({opacity: 0}))]),
]),

Notez que cet exemple n'a pas besoin d'utiliser state().

Transitions :increment et :decrement

La fonction transition() accepte d'autres valeurs de sélecteur, :increment et :decrement. Utilisez-les pour lancer une transition quand une valeur numérique a augmenté ou diminué.

Utile : L'exemple suivant utilise les méthodes query() et stagger(). Pour en savoir plus sur ces méthodes, consultez la page sur les séquences complexes.

hero-list-page.ts

trigger('filterAnimation', [
  transition(':enter, * => 0, * => -1', []),
  transition(':increment', [
    query(
      ':enter',
      [
        style({opacity: 0, width: 0}),
        stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]),
      ],
      {optional: true},
    ),
  ]),
  transition(':decrement', [
    query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]),
  ]),
]),

Valeurs booléennes dans les transitions

Si un déclencheur a pour valeur de binding un booléen, cette valeur peut être mise en correspondance à l'aide d'une expression transition() qui compare true et false, ou 1 et 0.

open-close.html

<div [@openClose]="isOpen ? true : false" class="open-close-container">
</div>

Dans l'extrait de code ci-dessus, le template HTML lie un élément <div> à un déclencheur nommé openClose avec une expression d'état isOpen, dont les valeurs possibles sont true et false. Ce pattern est une alternative à la pratique qui consiste à créer deux états nommés comme open et close.

Dans les métadonnées de @Component, sous la propriété animations:, quand l'état s'évalue à true, la hauteur de l'élément HTML associé est un style wildcard, ou par défaut. Dans ce cas, l'animation utilise la hauteur que l'élément avait déjà avant le début de l'animation. Quand l'élément est closed, il est animé jusqu'à une hauteur de 0, ce qui le rend invisible.

open-close.ts

animations: [
  trigger('openClose', [
    state('true', style({height: '*'})),
    state('false', style({height: '0px'})),
    transition('false <=> true', animate(500)),
  ]),
],

Déclencheurs d'animation multiples

Vous pouvez définir plusieurs déclencheurs d'animation pour un composant. Attachez les déclencheurs d'animation à différents éléments : les relations parent-enfant entre ces éléments influencent la manière dont les animations s'exécutent et le moment où elles le font.

Animations parent-enfant

Chaque fois qu'une animation est déclenchée dans Angular, l'animation parente a toujours la priorité et les animations enfants sont bloquées. Pour qu'une animation enfant s'exécute, l'animation parente doit cibler par une query chacun des éléments qui contiennent des animations enfants. Elle laisse ensuite les animations s'exécuter à l'aide de la fonction animateChild().

Désactiver une animation sur un élément HTML

Un binding spécial de contrôle des animations, appelé @.disabled, peut être placé sur un élément HTML pour désactiver les animations sur cet élément ainsi que sur tous les éléments imbriqués. Quand il vaut true, le binding @.disabled empêche le rendu de toutes les animations.

L'exemple de code suivant montre comment utiliser cette fonctionnalité.

Quand le binding @.disabled vaut true, le déclencheur @childAnimation ne se lance pas.

Quand les animations d'un élément d'un template HTML sont désactivées à l'aide du host binding @.disabled, elles sont aussi désactivées sur tous les éléments internes. Vous ne pouvez pas désactiver sélectivement plusieurs animations sur un même élément.

Certaines animations enfants peuvent néanmoins s'exécuter sur un parent désactivé, de l'une des façons suivantes :

  • Une animation parente peut utiliser la fonction query() pour collecter les éléments internes situés dans des zones désactivées du template HTML. Ces éléments peuvent quand même être animés.
  • Une animation enfant peut être ciblée par une query du parent, puis animée ensuite avec la fonction animateChild()

Désactiver toutes les animations

Pour désactiver toutes les animations d'une application Angular, placez le host binding @.disabled sur le composant Angular de plus haut niveau.

app.ts

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrls: ['app.css'],
  imports: [RouterLink, RouterOutlet],
  animations: [
    slideInAnimation,
  ],
})
export class AppComponent {
  @HostBinding('@.disabled')
  public animationsDisabled = false;
}

Utile : Désactiver les animations dans toute l'application est utile pendant les tests de bout en bout (E2E).

Callbacks d'animation

La fonction d'animation trigger() émet des callbacks quand elle démarre et quand elle se termine. L'exemple suivant présente un composant qui contient un déclencheur openClose.

open-close.ts

@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      // ...
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  onAnimationEvent(event: AnimationEvent) {
  }
}

Dans le template HTML, l'événement d'animation est renvoyé via $event, sous la forme @triggerName.start et @triggerName.done, où triggerName est le nom du déclencheur utilisé. Dans cet exemple, le déclencheur openClose se présente ainsi.

open-close.html

<div
  [@openClose]="isOpen ? 'open' : 'closed'"
  (@openClose.start)="onAnimationEvent($event)"
  (@openClose.done)="onAnimationEvent($event)"
  class="open-close-container"
>
</div>

Les callbacks d'animation peuvent par exemple servir à masquer la lenteur d'un appel d'API, comme une recherche en base de données. Par exemple, un bouton InProgress peut être configuré pour avoir sa propre animation en boucle pendant que l'opération du système backend se termine.

Une autre animation peut être appelée quand l'animation en cours se termine. Par exemple, le bouton passe de l'état inProgress à l'état closed quand l'appel d'API est terminé.

Une animation peut amener l'utilisateur final à percevoir l'opération comme plus rapide, même quand elle ne l'est pas.

Les callbacks peuvent servir d'outil de débogage, par exemple avec console.warn() pour suivre la progression de l'application dans la console JavaScript des outils de développement du navigateur. L'extrait de code suivant produit une sortie dans la console pour l'exemple d'origine, un bouton avec les deux états open et closed.

open-close.ts

export class OpenClose {
  onAnimationEvent(event: AnimationEvent) {
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
  }
}

Keyframes

Pour créer une animation composée de plusieurs étapes exécutées en séquence, utilisez des keyframes.

La fonction keyframe() d'Angular permet plusieurs changements de style au sein d'un même segment temporel. Par exemple, au lieu de s'estomper, le bouton pourrait changer de couleur plusieurs fois sur une même durée de 2 secondes.

keyframes

Le code de ce changement de couleur pourrait ressembler à ceci.

status-slider.ts

transition('* => active', [
  animate(
    '2s',
    keyframes([
      style({backgroundColor: 'blue'}),
      style({backgroundColor: 'red'}),
      style({backgroundColor: 'orange'}),
    ]),
  ),

Offset

Les keyframes incluent un offset qui définit le moment de l'animation où chaque changement de style se produit. Les offsets sont des mesures relatives allant de zéro à un, qui marquent le début et la fin de l'animation. Si vous en utilisez au moins un, ils doivent être appliqués à chacune des étapes de keyframes.

La définition des offsets des keyframes est facultative. Si vous les omettez, des offsets régulièrement espacés sont attribués automatiquement. Par exemple, trois keyframes sans offsets prédéfinis reçoivent les offsets 0, 0,5 et 1. Spécifier un offset de 0,8 pour la transition du milieu dans l'exemple précédent pourrait ressembler à ceci.

keyframes avec offset

Le code avec les offsets spécifiés serait le suivant.

status-slider.ts

transition('* => active', [
  animate(
    '2s',
    keyframes([
      style({backgroundColor: 'blue', offset: 0}),
      style({backgroundColor: 'red', offset: 0.8}),
      style({backgroundColor: '#754600', offset: 1.0}),
    ]),
  ),
]),
transition('* => inactive', [
  animate(
    '2s',
    keyframes([
      style({backgroundColor: '#754600', offset: 0}),
      style({backgroundColor: 'red', offset: 0.2}),
      style({backgroundColor: 'blue', offset: 1.0}),
    ]),
  ),
]),

Vous pouvez combiner des keyframes avec duration, delay et easing dans une même animation.

Keyframes avec une pulsation

Utilisez des keyframes pour créer un effet de pulsation dans vos animations en définissant des styles à des offsets précis tout au long de l'animation.

Voici un exemple d'utilisation des keyframes pour créer un effet de pulsation :

  • Les états d'origine open et closed, avec les changements d'origine de hauteur, de couleur et d'opacité, qui se produisent sur une durée de 1 seconde
  • Une séquence de keyframes insérée au milieu, qui fait pulser le bouton de manière irrégulière au cours de cette même seconde
keyframes avec pulsation irrégulière

L'extrait de code de cette animation pourrait ressembler à ceci.

open-close.ts

trigger('openClose', [
  state(
    'open',
    style({
      height: '200px',
      opacity: 1,
      backgroundColor: 'yellow',
    }),
  ),
  state(
    'close',
    style({
      height: '100px',
      opacity: 0.5,
      backgroundColor: 'green',
    }),
  ),
  // ...
  transition('* => *', [
    animate(
      '1s',
      keyframes([
        style({opacity: 0.1, offset: 0.1}),
        style({opacity: 0.6, offset: 0.2}),
        style({opacity: 1, offset: 0.5}),
        style({opacity: 0.2, offset: 0.7}),
      ]),
    ),
  ]),
]),

Propriétés animables et unités

Les animations Angular reposent sur les web animations, vous pouvez donc animer toute propriété que le navigateur considère comme animable. Cela inclut les positions, les tailles, les transformations, les couleurs, les bordures, etc. Le W3C tient à jour une liste des propriétés animables sur sa page CSS Transitions.

Pour les propriétés à valeur numérique, définissez une unité en fournissant la valeur sous forme de chaîne, entre guillemets, avec le suffixe approprié :

  • 50 pixels : '50px'

  • Taille de police relative : '3em'

  • Pourcentage : '100%'

Vous pouvez aussi fournir la valeur sous forme de nombre. Dans ce cas, Angular suppose une unité par défaut en pixels, soit px. Exprimer 50 pixels par 50 revient à écrire '50px'.

Utile : La chaîne "50", en revanche, ne serait pas considérée comme valide.

Calcul automatique des propriétés avec les wildcards

Parfois, la valeur d'une propriété de style dimensionnelle n'est connue qu'à l'exécution. Par exemple, les éléments ont souvent une largeur et une hauteur qui dépendent de leur contenu ou de la taille de l'écran. Ces propriétés sont souvent difficiles à animer en CSS.

Dans ces cas, vous pouvez utiliser la valeur de propriété spéciale wildcard * dans style(). La valeur de cette propriété de style est calculée à l'exécution, puis injectée dans l'animation.

L'exemple suivant comporte un déclencheur appelé shrinkOut, utilisé quand un élément HTML quitte la page. L'animation prend la hauteur qu'a l'élément avant de partir, et l'anime de cette hauteur jusqu'à zéro.

hero-list-auto.ts

animations: [
  trigger('shrinkOut', [
    state('in', style({height: '*'})),
    transition('* => void', [style({height: '*'}), animate(250, style({height: 0}))]),
  ]),
],

Résumé des keyframes

La fonction keyframes() d'Angular permet de spécifier plusieurs styles intermédiaires au sein d'une même transition. Un offset facultatif peut servir à définir le moment de l'animation où chaque changement de style doit se produire.

En savoir plus sur les animations Angular

Les pages suivantes pourraient aussi vous intéresser :