CSSには、アプリケーション内で美しく魅力的なアニメーションを作成するための強力なツールが揃っています。
ネイティブCSSでアニメーションを書いたことがない場合は、入門に役立つ優れたガイドがいくつもあります。以下にいくつか紹介します。
MDNのCSSアニメーションガイド
W3SchoolsのCSS3アニメーションガイド
CSSアニメーションの完全チュートリアル
初心者向けCSSアニメーション
また、次の動画も参考にしてください。
9分でCSSアニメーションを学ぶ
Net NinjaのCSSアニメーションチュートリアル再生リスト
まずはこれらのガイドやチュートリアルに目を通し、その後に本ガイドに戻ってきてください。
@keyframesを使用すると、アプリケーション全体で共有できる再利用可能なアニメーションを作成できます。共通のCSSファイルにキーフレームアニメーションを定義しておけば、アプリケーション内の任意の場所で再利用できます。
@keyframes sharedAnimation {
to {
height: 0;
opacity: 1;
background-color: 'red';
}
}
.animated-class {
animation: sharedAnimation 1s;
}
要素にanimated-classクラスを追加すると、その要素でアニメーションが開始されます。
要素を開いたり閉じたりするときなど、2つの異なる状態の間をアニメーションで遷移させたいことがあります。CSSクラスを切り替えることで、キーフレームアニメーションやトランジションを使って実現できます。
.open {
height: '200px';
opacity: 1;
background-color: 'yellow';
transition: all 1s;
}
.closed {
height: '100px';
opacity: 0.8;
background-color: 'blue';
transition: all 1s;
}
openまたはclosed状態のトリガーは、コンポーネント内で要素のクラスを切り替えて行います。例は、テンプレートガイドで確認できます。
同様の例として、テンプレートガイドのスタイルを直接アニメーション化するも参照してください。
アニメーションでは、継続時間や遅延、イージングの挙動を調整することがよくあります。これは複数のCSSプロパティ(またはショートハンド)で指定できます。
キーフレームアニメーションでは、animation-duration、animation-delay、animation-timing-functionを指定するか、animationショートハンドプロパティを使用します。
.example-element {
animation-duration: 1s;
animation-delay: 500ms;
animation-timing-function: ease-in-out;
}
.example-shorthand {
animation: exampleAnimation 1s ease-in-out 500ms;
}
同様に、@keyframesを使用しないアニメーションでは、transition-duration、transition-delay、transition-timing-function、およびtransitionショートハンドプロパティを使用できます。
.example-element {
transition-duration: 1s;
transition-delay: 500ms;
transition-timing-function: ease-in-out;
transition-property: margin-right;
}
.example-shorthand {
transition: margin-right 1s ease-in-out 500ms;
}
アニメーションは、CSSのスタイルやクラスを切り替えることでトリガーできます。クラスが要素に追加されるとアニメーションが実行され、クラスを削除すると、その要素に定義されているCSSに戻ります。例を示します。
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-open-close',
templateUrl: 'open-close.html',
styleUrls: ['open-close.css'],
})
export class OpenClose {
isOpen = signal(true);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
<h2>Open / Close Example</h2>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>
<div class="open-close-container" [class.open]="isOpen()">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
:host {
display: block;
margin-top: 1rem;
}
.open-close-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
font-weight: bold;
font-size: 20px;
height: 100px;
opacity: 0.8;
background: #3b82f6;
color: #ebebeb;
transition-property: height, opacity, background-color, color;
transition-duration: 1s;
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
.open {
transition-duration: 0.5s;
height: 200px;
opacity: 1;
background: #475569;
color: #f9fafb;
}
CSS Gridを使用すると、height: autoへのアニメーションを実現できます。
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-auto-height',
templateUrl: 'auto-height.html',
styleUrls: ['auto-height.css'],
})
export class AutoHeight {
isOpen = signal(true);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
<h2>Auto Height Example</h2>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>
<div class="container" [class.open]="isOpen()">
<div class="content">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
</div>
.container {
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: grid-template-rows 1s;
}
.container.open {
grid-template-rows: 1fr;
}
.container .content {
min-height: 0;
transition: visibility 1s;
padding: 0 20px;
visibility: hidden;
margin-top: 1em;
font-weight: bold;
font-size: 20px;
background: #3b82f6;
color: #ebebeb;
overflow: hidden;
}
.container.open .content {
visibility: visible;
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
すべてのブラウザをサポートする必要がない場合は、height: autoをアニメーション化するためのより本質的な解決策であるcalc-size()も確認してください。詳しくは、MDNのドキュメントとこの項目に関連するチュートリアルを参照してください。
要素がビューに入るとき、またはビューから出るときのアニメーションを作成できます。まずは、ビューに入るときのアニメーションを見ていきましょう。animate.enterを使うと、要素がビューに入るときにアニメーション用のクラスが適用されます。
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-insert',
templateUrl: 'insert.html',
styleUrls: ['insert.css'],
})
export class Insert {
isShown = signal(false);
toggle() {
this.isShown.update((isShown) => !isShown);
}
}
<h2>Insert Element Example</h2>
<nav>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>
@if (isShown()) {
<div class="insert-container" animate.enter="enter-animation">
<p>The box is inserted</p>
</div>
}
:host {
display: block;
}
.insert-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
font-weight: bold;
font-size: 20px;
}
.enter-animation {
animation: slide-fade 1s;
}
@keyframes slide-fade {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
ビューから出るときのアニメーションも、ビューに入るときと同様です。animate.leaveで、要素がビューから出るときに適用するCSSクラスを指定します。
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-remove',
templateUrl: 'remove.html',
styleUrls: ['remove.css'],
})
export class Remove {
isShown = signal(false);
toggle() {
this.isShown.update((isShown) => !isShown);
}
}
<h2>Remove Element Example</h2>
<nav>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>
@if (isShown()) {
<div class="insert-container" animate.leave="deleting">
<p>The box is inserted</p>
</div>
}
:host {
display: block;
}
.insert-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
font-weight: bold;
font-size: 20px;
opacity: 1;
transition: opacity 200ms ease-in;
@starting-style {
opacity: 0;
}
}
.deleting {
opacity: 0;
transform: translateY(20px);
transition:
opacity 500ms ease-out,
transform 500ms ease-out;
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
animate.enterとanimate.leaveについて詳しくは、EnterとLeaveのアニメーションガイドを参照してください。
インクリメントとデクリメントに合わせてアニメーションするのは、アプリケーションでよくあるパターンです。以下はその例です。
import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';
@Component({
selector: 'app-increment-decrement',
templateUrl: 'increment-decrement.html',
styleUrls: ['increment-decrement.css'],
})
export class IncrementDecrement implements OnInit {
num = signal(0);
el = viewChild<ElementRef<HTMLParagraphElement>>('el');
ngOnInit() {
this.el()?.nativeElement.addEventListener('animationend', (ev) => {
if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) {
this.animationFinished();
}
});
}
modify(n: number) {
const targetClass = n > 0 ? 'increment' : 'decrement';
this.num.update((v) => (v += n));
this.el()?.nativeElement.classList.add(targetClass);
}
animationFinished() {
this.el()?.nativeElement.classList.remove('increment', 'decrement');
}
ngOnDestroy() {
this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished);
}
}
<h3>Increment and Decrement Example</h3>
<section>
<p #el>Number {{ num() }}</p>
<div class="controls">
<button type="button" (click)="modify(1)">+</button>
<button type="button" (click)="modify(-1)">-</button>
</div>
</section>
:host {
display: block;
font-size: 32px;
margin: 20px;
text-align: center;
}
section {
border: 1px solid lightgray;
border-radius: 50px;
}
p {
display: inline-block;
margin: 2rem 0;
text-transform: uppercase;
}
.increment {
animation: increment 300ms;
}
.decrement {
animation: decrement 300ms;
}
.controls {
padding-bottom: 2rem;
}
button {
font: inherit;
border: 0;
background: lightgray;
width: 50px;
border-radius: 10px;
}
button + button {
margin-left: 10px;
}
@keyframes increment {
33% {
color: green;
transform: scale(1.3, 1.2);
}
66% {
color: green;
transform: scale(1.2, 1.2);
}
100% {
transform: scale(1, 1);
}
}
@keyframes decrement {
33% {
color: red;
transform: scale(0.8, 0.9);
}
66% {
color: red;
transform: scale(0.9, 0.9);
}
100% {
transform: scale(1, 1);
}
}
指定したアニメーションを無効にする方法はいくつかあります。
animationとtransitionをnoneに強制するカスタムクラスを作成します。
.no-animation {
animation: none !important;
transition: none !important;
}
このクラスを要素に適用すると、その要素ではアニメーションが一切実行されません。また、DOM全体またはDOMの一部にスコープすることで、この挙動を強制できます。ただし、この方法ではアニメーションイベントも発生しなくなります。要素の削除のためにアニメーションイベントを待っている場合、この方法は機能しません。回避策としては、継続時間を1ミリ秒に設定します。
prefers-reduced-motionメディアクエリを使用して、アニメーションを控えたいユーザーにはアニメーションを再生しないようにします。
プログラムからアニメーションクラスを追加しないようにします。
アニメーション中の特定のタイミングで処理を実行したい場合、リッスンできるイベントがいくつかあります。次はその一例です。
OnAnimationStart
OnAnimationEnd
OnAnimationIteration
OnAnimationCancel
OnTransitionStart
OnTransitionRun
OnTransitionEnd
OnTransitionCancel
Web Animations APIには、他にも多くの機能があります。利用できるアニメーション関連APIについては、ドキュメントを参照してください。
NOTE: これらのコールバックはバブリング(イベント伝播)します。子要素と親要素の両方をアニメーション化している場合、イベントは子要素から親要素へ伝播します。意図したイベントターゲットに応答しているかを判断するために、伝播を停止するか、イベントの詳細を確認してください。適切なノードを対象にしていることは、animationnameプロパティやトランジション対象のプロパティを確認すると検証できます。
アニメーションは、単純なフェードインやフェードアウトだけではありません。複数のアニメーションを組み合わせた複雑なシーケンスを実行したいこともあるでしょう。ここでは、そうしたシナリオをいくつか見ていきましょう。
よくある効果の1つに、リスト内の各要素のアニメーションを段階的に遅らせてカスケード効果を作るものがあります。これはanimation-delayまたはtransition-delayを利用して実現できます。次はそのCSSの例です。
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-stagger',
templateUrl: './stagger.html',
styleUrls: ['stagger.css'],
})
export class Stagger {
show = signal(true);
items = [1, 2, 3];
refresh() {
this.show.set(false);
setTimeout(() => {
this.show.set(true);
}, 10);
}
}
<h1>Stagger Example</h1>
<button type="button" class="toggle-btn" (click)="refresh()">Refresh</button>
@if (show()) {
<ul class="items">
@for (item of items; track item) {
<li class="item" style="--index: {{ item }}">{{ item }}</li>
}
</ul>
}
.items {
list-style: none;
padding: 0;
margin: 0;
}
.items .item {
transition-property: opacity, transform;
transition-duration: 500ms;
transition-delay: calc(200ms * var(--index));
@starting-style {
opacity: 0;
transform: translateX(-10px);
}
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
animationショートハンドプロパティを使用すると、1つの要素に複数のアニメーションを同時に適用できます。それぞれに継続時間や遅延を個別に設定できるため、アニメーションを合成して複雑な効果を作成できます。
.target-element {
animation:
rotate 3s,
fade-in 2s;
}
この例では、rotateとfade-inのアニメーションは同時に実行されますが、継続時間が異なります。
@forループ内の要素は削除されて再追加されるため、enterアニメーションとして@starting-stylesを使用したアニメーションが実行されます。同じ挙動はanimate.enterでも実現できます。要素が削除されるときにアニメーションするには、以下の例のようにanimate.leaveを使用します。
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-reorder',
templateUrl: './reorder.html',
styleUrls: ['reorder.css'],
})
export class Reorder {
show = signal(true);
items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];
randomize() {
const randItems = [...this.items];
const newItems = [];
for (let i of this.items) {
const max: number = this.items.length - newItems.length;
const randNum = Math.floor(Math.random() * max);
newItems.push(...randItems.splice(randNum, 1));
}
this.items = newItems;
}
}
<h1>Reordering List Example</h1>
<button type="button" class="toggle-btn" (click)="randomize()">Randomize</button>
<ul class="items">
@for (item of items; track item) {
<li class="item" animate.leave="fade">{{ item }}</li>
}
</ul>
.items {
list-style: none;
padding: 0;
margin: 0;
}
.items .item {
transition-property: opacity, transform;
transition-duration: 500ms;
@starting-style {
opacity: 0;
transform: translateX(-10px);
}
}
.items .item.fade {
animation: fade-out 500ms;
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
Element.getAnimations()を使用すると、要素に関連付けられたアニメーションを直接取得できます。これは、その要素上のすべてのAnimationを配列として返します。Animation APIを使えば、@angular/animationsパッケージのAnimationPlayerよりも多くの操作が可能です。cancel()、play()、pause()、reverse()などを呼び出せます。このネイティブAPIだけで、アニメーションの制御に必要な機能が揃います。
以下の項目にも興味があるかもしれません: