Thomas Burleson PRO
FE Architect, Technical Lead, and Engineering Coach. Delivering web solutions using React, NextJS, Angular, and TypeScript.
http://bit.ly/2VIOyOp
Link to slides:
Agenda
Presenters
CSS is the easiest and fastest launching point for rendering animations on websites and web-applications.
Compared to JavaScript-level animations, CSS has pros & cons.
Wins
Cons:
And the following cons:
CSS transitions are animating the visual change between element style states. Just about all properties are supported, and it is fully supported through all browsers.
button {
background:blue;
color:white;
padding:50px;
font-size:50px;
}
button:active {
background:green;
}
There are two states in this example and the background color is changing between the states. To animate this change we can add in a transition in between the two:
button {
background:blue;
color:white;
padding:50px;
font-size:50px;
}
button:active {
background:green;
}
button {
/* same as before */
}
button:active {
transition:300ms ease-in;
background:green;
}
button {
background:blue;
color:white;
padding:50px;
font-size:50px;
/* animation css here */
transition:300ms ease-in;
}
button:active {
background:green;
}
button:hover {
background:orange;
}
The transition can also be placed into the starting CSS state value and now every state change will render a 300ms effect.
Any CSS transition change can happen if and when the styles on an element change. This can be the result of:
CSS class or attribute on an element
Media query changes
Inline styling being applied
.my-class {
/* animate specific properties */
transition:1s width;
transition:1s width, 2s height;
/* animate with easing */
transition:1s all ease-out;
transition:1s all ease-in-out;
}
Note: that no transition will take place if there is no styling change.
.my-class {
/* animate specific properties */
transition:1s width;
transition:1s width, 2s height;
/* animate with easing */
transition:1s all ease-out;
transition:1s all ease-in-out;
}
button {
background:red;
transition:300ms ease-out;
}
button:hover {
background:red;
}
.gallery-entry .picture {
transition:300ms ease-out;
}
.gallery-entry:hover .picture {
transform:scale(1.2);
}
.gallery-entry.active .picture {
transform:scale(1.3);
}
#gallery {
transition:500ms all;
}
@media only screen and (max-width: 600px) {
#gallery {
height:calc(200vh - 100px);
grid-template: repeat(2, 50fr);
}
}
CSS Keyframe animations are another option to animate styling using CSS. They are designed to be reusable and are not tied to the state of the DOM. Instead, they are activated once the animation style on an element is assigned.
@keyframes fadeIn {
from { opacity:0; }
to { opacity:1; }
}
.some-class-with-animations {
animation: fadeIn 1s;
}
CSS Keyframe animations are supported in all browsers and can be used together with class-changes, attribute mutation, inline styles and media query changes.
<div class="loading-indicator"
style="--width:100px; --height: 100px; --border:20px">
</div>
.loading-indicator {
/* CSS variables are used here so we can easily change
* values without having to rewrite everything... */
--width: 100px;
--height: 100px;
--border: 20px;
position:relative;
height:calc(var(--height) + var(--border) * 2);
width:calc(var(--width) + var(--border) * 2);
}
.loading-indicator:before {
content:"";
top:0;
left:0;
position:absolute;
border:var(--border) solid transparent;
border-top-color:rgb(248, 73, 73);
border-bottom-color:rgb(248, 73, 73);
border-radius:var(--width) var(--height);
width:var(--width);
height:var(--height);
}
.loading-indicator:before {
animation:1s linear rotate;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes colorCycle {
0% {
background:red;
}
20% {
background:purple;
}
40% {
background:brown;
}
60% {
background:maroon;
}
80% {
background:blue;
}
100% {
background:red;
}
}
CSS Keyframes can be set between 0% and 100%.
Add a Hover animation to the button to scale the button size and change the button background color.
Use the :hover pseudo selector and define a scale transformation on the `.loading` class
On the `.loading` class, use transitions to add scale transformation.
Define a keyframe animation that changes the background color from red to blue
Step 2:
Step 1:
Step 3:
with GSAP
With CSS3 Animations
CSS3 Animations GSAP
Animations with GSAP Timelines
Quickly build sequences, stagger start times, overlap tweens, experiment with eases, leverage various callbacksand labels, and create concise code.
Animation choreography is in the TypeScript layers.
With GSAP
Declarative Timelines
Demo: Click Animations
Add a Hover animation to the list item image which is to scale the button size and change the title text color.
Get a reference to 'div.picture img' DOM element
Add a hover event listener that will create a new new Timelinelite()
In the hover timeline change the title text color to red
Step 2:
Step 1:
Step 3:
GSAP Issues
In angular, CSS transitions and keyframes can be used in order to trigger animations/transitions when there are attribute or class binding changes.
Let’s imagine we have an open-close container that looks like:
<div class="open-close-container"
[style.background]="isOpen ? 'yellow' : 'red'"
(click)="isOpen = !isOpen">
<span>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</span>
</div>
When clicked, the box will set the background color of itself (which implies an open/closed state).
<div class="open-close-container"
[style.background]="isOpen ? 'yellow' : 'red'"
(click)="isOpen = !isOpen">
<span>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</span>
</div>
.open-close-container {
transition: background .3s ease;
}
This improved with refactoring to a
css class:
<div class="open-close-container"
[class.opened]="isOpen"
(click)="isOpen = !isOpen">
<span>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</span>
</div>
.open-close-container {
transition: background .3s ease;
background: red;
}
.open-close-container.opened {
background: yellow
}
Once sequencing and multiple states come into the picture, CSS does not have the power to easily do this work.
Thankfully, the functional API provided by the @angular/animations module provides a domain-specific language (DSL) for controlling animations in Angular applications and creating sophisticated animation sequences.
When it comes to Angular Animations, all animations take place inside of an animation trigger definition.
Whenever the trigger changes value its state is updated. When state changes then an animation can take control and handle the state change in a transition arc.
All animations live in a component metadata inside of the animation: block.
@Component({
animations: [
trigger('myAnimation', [
//...
])
]
})
@Component({
animations: [
trigger('myAnimation', [
//...
])
]
})
And the trigger can be referenced directly inside of the template code or host binding code:
<div [@myAnimation]="myValueExpression"></div>
// this is inside of @Component.animations...
trigger('myAnimation', [
state('open', style({
height: '200px'
})),
state('closed', style({
height: '0px'
}))
})
An animation trigger can have various state values. A state is a partial styling representation of the component in a specific state
transition('open => closed', [
// this will animate everything that has
// changed between the two styles.
animate('.5s ease')
])
Now for the actual transition (between the states): much like CSS transitions, Angular animation transitions will animate the stylistic difference between the states.
trigger('openClose', [
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'white'
})),
state('closed', style({
height: '*',
color: 'white',
backgroundColor: '#3F51B5'
})),
transition('open => closed', [
animate('.5s ease')
]),
transition('closed => open', [
animate('.2s ease')
]),
])
Let’s see an example of a closed/open box animation:
Add a disabled state to the open + closed Animations
Implement the open/close animation
Add an animation state to openClose for disabled that will change the color and background color to grey.
Setup a transition for open to disabled and closed to disabled and reverse and animate the change
Step 2:
Step 1:
Step 3:
Change the cursor on the box to react to the disabled state using the style/class binding
Step 4:
One of the major challenges with animation on the web is detecting when elements have been inserted and removed from the DOM.
Angular is able to detect and intercept this effect with the following directives:
<div *ngIf="myExpression"></div>
<div *ngFor="myExpression"></div>
<div *ngSwitch="myExpression"></div>
We can add an animation to any of these directives with an animation trigger. Let’s do this with *ngIf
<div *ngIf="myExpression" @myAnimation></div>
To tap into the enter and leave animations for this, we use the void and * animation states:
trigger('myAnimation', [
state('void', style({
height: '0px',
opacity: 0,
})),
state('*', style({
height: '*',
opacity: '*',
})),
transition('open <=> closed', [
animate('.5s ease')
]),
])
<div *ngIf="myExpression" @myAnimation></div>
Add a Enter and Leave animations
Look inside of the demo at the contentAnimation trigger.
Build animations inside of the enter and leave animations (look at the comments) and make it expand/collapse in height and fade in and out
Step 2:
Step 1:
Sequencing animations is one of the hallmark features of angular. What this feature does is allows for an animation trigger to query for elements and orchestrate a multi-element animation using the same body of code (a trigger).
trigger('myMultiElementAnimation', [
transition('* => start', [
query('.some-class', [
animate(...)
])
])
])
By collecting the elements, we can animate changes on each one that was collected.
Now with our home page and profile page animations we can start to orchestrate a router-level animation. The steps that need to be done for this to work are the following:
Most of our animation work here is already done since we have the enter/leave animations already defined for both the home and profile pages, but now we are going to have an animation take place on a higher level.
<div class="router-container"
[@routerAnimation]="prepareRouteState(outlet)"
(@routerAnimation.start)="scrollToTop()">
<router-outlet #outlet="outlet"></router-outlet>
</div>
The prepareRouteState method will provide the @routerAnimation trigger with the state of the next page. In the case of the routes, we’ve defined each route to include some extra data with some animation information
// routes.ts
export const ROUTES = [
{path:'', component: HomePageComponent, data: { animation: 'home' } },
{path:'profile/:id', component: ProfilePageComponent, data: {animation: 'profile'} },
]
Notice the animation: information?
Well this is exactly what’s provided back into the router state change. This state change will then be picked up by the trigger and a transition will occur.
// app.component.ts
@Component({
//...
animations: [
trigger('routerAnimation', [
// this will fire when the home page changes to the profile page and vice versa
transition('home <=> profile', [
style({ position: 'relative' }),
// the new routed page is first hidden
query(':enter', [
style({position:'absolute', opacity:0, top:'-100%' })
]),
// then we animate away the old page
query(':leave', [
style({ position: 'absolute', top:0 }),
animateChild(),
style({ opacity:0 }),
]),
// then we animate back in the new page
query(':enter', [
style({opacity:1, top:'0%'}),
animateChild()
]),
])
])
]
})
class AppComponent {...}
The example code is designed to animate away the old page (via :leave) and then animate in the new page (via :enter).
The styling that takes place is used to adjust both pages in an absolute way so that they sit on top of each other.
But what about the actual animations?
Well those animations are child animations and they are fired via animateChild().
By Thomas Burleson
Explore animations with CSS, GSAP and Angular 7+8.
FE Architect, Technical Lead, and Engineering Coach. Delivering web solutions using React, NextJS, Angular, and TypeScript.