Angular Animations - aakash14goplani/FullStack GitHub Wiki
Topics Covered
- Introduction
- Setting Triggers and States
- Switching between States
- Transitions
- Void State
- Keyframes
- Grouping Transitions
- Animation Callbacks
- If we have a scenario to attach or detach DOM elements, apply custom transitions and effects, all these things are bit hard to achieve via CSS because as your DOM is dynamically generated by Angular, they may not be available when your CSS is triggered. To achieve animations in such cases Angular ships with default package - Animation
-
Initial Configurations:
- Install animations package
npm install --save @angular/animations
- Add
BrowserAnimationsModule
imported from@angular/platform-browser/animations
toimports[]
in app.module.ts file
- Install animations package
-
Use Case: We need to achieve some animation on certain
div
on button click -
Remember Animations are just transition of effects/commands from State A to State B, where State A is your initial state and State B is the final state.
-
Animations are basically defined by
animations
array within meta-data in Component file@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], animations: [...] })
-
Each animation has
trigger
which accepts an argument that is basically a reference to DOM element. So in DOM we have certain element with a particular reference which in turn triggers this Animation<div style="width: 100px; height: 100px;" [@divState]="state"></div>
trigger('divState', [...]) ... export class AppComponent { state = "normal"; }
- here we have
[@divState]="state"
as selector in DOM which triggers this animation. This selector is set to a propertystate
- here we have
-
Within triggers we define
state
which is an JavaScript object where we can define initial state and final state and CSS effects that need to performed on those statesanimations: [ trigger('divState', [ state('normal', style({ 'background-color': 'black', 'transform': 'translateX(0)' })), state('highlighted', style({ 'background-color': 'red', 'transform': 'translateX(100)' })) ]) ]
- the name of initial state is set to
normal
which is exactly same as the initial property that was set to our selector i.e.[@divState]="state"
wherestate="normal"
- the name of final state is set to
highlighted
which is the goal that we want to achieve - highlight certain div based on particular actions - All of these methods
trigger()
,state()
andstyle()
are imported from@angular/animations
- the name of initial state is set to
We can switch between states by changing / toggling the states name from normal
to highlighted
<button class="btn btn-primary" (click)="onAnimate()">Animate!</button>
state = 'normal';
onAnimate() {
this.state === 'normal' ? this.state = 'highlighted' : this.state = 'normal';
}
We can add simple transitions to or animations for that we need transitions()
method that takes two arguments. First is the expression from t state to final state and second argument is the time amount to perform this animation which is achieved by animate()
method
animations: [
trigger('divState', [
state('normal', style({
'background-color': 'black',
'transform': 'translateX(0)'
})),
state('highlighted', style({
'background-color': 'red',
'transform': 'translateX(100px)'
})),
transition('normal => highlighted', animate(300)),
transition('highlighted => normal', animate(600))
])
]
Complex Transitions
If we have more complex use case in which we have to achieve all of the things mentioned above plus some in-between animations like when we move from state A to State B, we need some extra transitions like DOM element getting scaled and de-scaled.
We set new DOM element to show this 3rd transition:
<button class="btn btn-primary" (click)="onAnimate()">Animate!</button>
<button class="btn btn-primary" (click)="onShrink()">Shrink!</button>
...
<div style="width: 100px; height: 100px;" [@divState]="state"></div>
<div style="width: 100px; height: 100px;" [@wildState]="wildState"></div>
We need to add new trigger to accommodate this
trigger('wildState', [
state('normal', style({
'background-color': 'black',
'transform': 'translateX(0) scale(1)'
})),
state('highlighted', style({
'background-color': 'red',
'transform': 'translateX(100px) scale(1)'
})),
state('shrunken', style({
'background-color': 'green',
'transform': 'translateX(0) scale(0.5)'
}))
])
We also add corresponding transitions
transition('normal => highlighted', animate(300)),
transition('highlighted => normal', animate(600)),
transition('shrunken <=> *', animate(900))
Here the new transition expression states that - if me move from shrunken
state to any (*
) or from any (*
) state to shrunken
state, perform this animation
state = 'normal';
wildState = 'normal';
...
onAnimate() {
this.state === 'normal' ? this.state = 'highlighted' : this.state = 'normal';
this.wildState === 'normal' ? this.wildState = 'highlighted' : this.wildState = 'normal';
}
onShrink() {
this.wildState = 'shrunken';
}
Transition Phases
If we plan to carry out multiple transitions while the animations is still on...
...
transition('shrunken <=> *', [
style({
'background-color': 'orange'
}),
animate(400, style({
'border-radius': '50px'
})),
animate(400)
])
Above code means:
- instantly change color of element to orange
- wait for 400 ms and change border radius to 50px
- move to next state after waiting for other 400 ms
- Use Case: Add - Delete items to and from list with animation
Here we make use of void
state which is the special keyword reserved by Angular which is very useful when you want to perform operations on a non-exiting state to any state. Or to perform an operation of an element which wasn't present at the beginning.
We bind the HTML list with the new trigger
<ul class="list-group">
<li class="list-group-item" [@list1] (click)="onDelete(item)" *ngFor="let item of list">
{{ item }}
</li>
</ul>
Create corresponding trigger
trigger('list1', [
state('in', style({
'opacity': 1,
'transform': 'translateX(0)'
})),
transition('void => *', [
style({
'opacity': 0,
'transform': 'translateX(-100px)'
}),
animate(300)
]),
transition('* => void', [
animate(300, style({
'transform': 'translateX(100px)',
'opacity': 0
}))
])
])
- When we add element to list it will get instant style and then transitions
void => *
get applied with some animation wait of 300 ms - When we delete element from list, we already have initial state, so we transit from
* => void
and apply some styles but this time within animation
- We want to have fine grain control over animation / transition i.e. Animation A should run for x ms and Animation B should run for y ms - in such use cases we can add keyframes to our animation
transition('void => *', [
animate(1000, keyframes([
style({
transform: 'translateX(-100px)',
opacity: 0,
offset: 0
}),
style({
transform: 'translateX(-50px)',
opacity: 0.5,
offset: 0.3
}),
style({
transform: 'translateX(-20px)',
opacity: 1,
offset: 0.8
}),
style({
transform: 'translateX(0px)',
opacity: 1,
offset: 1
})
]))
])
- If you want to group multiple animations with possibly same or different timing you can use
group()
methodtransition('* => void', [ group([ animate(300, style({ color: 'red' })), animate(800, style({ transform: 'translateX(100px)', opacity: 0 })) ]) ])
-
If an animation finish and you want to execute some other code - you could do this with help of callbacks
-
We can register callbacks in our DOM
<div style="width: 100px; height: 100px;" [@divState]="state" (@divState.start)="animationStarted($event)" (@divState.done)="animationEnded($event)"></div>
-
Syntax is like event-binding and starting with
@
to indicate that it is related to angular animations and bind it to particular method.start
will fire when Animation triggers ondivState
anddone
will fire whrn animtion finally endsanimationStarted(event) { console.log(event); } animationEnded(event) { console.log(event); }