Does anybody know how to use/enable the animated icons in an Angular Web Application which are shown in the material design documentation: https://material.io/design/iconography/animated-icons.html#usage
As other have stated the examples on the Material Icon sites would have to be built.
However, I found my way to this question looking for a guide on how to animate angular material icons and for others looking for the same I have a solution. The default animation can be customized to something other than just a 360 degree rotation.
Basically you can create a component that swaps between mat-icon's when clicked or when a parent element like a button is clicked.
Prerequisites are you have a an angular material application with material icons installed. I used Angular Material 8.
Here is a working Stackblitz https://stackblitz.com/edit/angular-material-prototype-animated-icon
mat-animated-icon.component.ts
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'mat-animated-icon',
templateUrl: './mat-animated-icon.component.html',
styleUrls: ['./mat-animated-icon.component.scss']
})
export class MatAnimatedIconComponent implements OnInit {
@Input() start: String;
@Input() end: String;
@Input() colorStart: String;
@Input() colorEnd: String;
@Input() animate: boolean;
@Input() animateFromParent?: boolean = false;
constructor() { }
ngOnInit() {
console.log(this.colorStart);
console.log(this.colorEnd);
}
toggle() {
if(!this.animateFromParent) this.animate = !this.animate;
}
}
mat-animated-icon.component.scss
:host {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
/* Rules for sizing the icon. */
&.md-18 { font-size: 18px; }
&.md-24 { font-size: 24px; }
&.md-36 { font-size: 36px; }
&.md-48 { font-size: 48px; }
/* Rules for using icons as black on a light background. */
&.md-dark {
color: rgba(0, 0, 0, 0.54);
&.md-inactive { color: rgba(0, 0, 0, 0.26); }
}
/* Rules for using icons as white on a dark background. */
&.md-light {
color: rgba(255, 255, 255, 1);
&.md-inactive { color: rgba(255, 255, 255, 0.3); }
}
.material-icons {
transition: transform .5s;
&.animate {
transform: rotate(360deg);
}
}
}
mat-animated-icon.component.html
<mat-icon [ngClass]="{'animate' : animate}" color="{{animate ? colorEnd : colorStart}}" (click)="toggle()">{{animate ? end : start}}</mat-icon>
var.directive.ts
a little helper directive
import { Directive, Input } from '@angular/core';
@Directive({
selector: '[var]',
exportAs: 'var'
})
export class VarDirective {
@Input() var:any;
constructor() { }
}
Example of the component in use
<button (click)="!this.disabled && iconAnimate10.var=!iconAnimate10.var" #iconAnimate10="var" var="'false'" mat-icon-button [disabled]="false" aria-label="Example icon-button with a heart icon">
<mat-animated-icon start="menu" end="close" colorStart="none" colorEnd="none" [animate]="iconAnimate10.var" animateFromParent="true"></mat-animated-icon>
Error: ENOENT: No such file or directory., '/dev/null'
when it starts up the server –
Submerged There is library that easy animate angular. https://github.com/filipows/angular-animations
I just used that on angular 8 to animate favorite icons, it's very straightforward.
This example makes full star into empty star and vice versa.
Compount:
import { fadeInOnEnterAnimation, fadeOutOnLeaveAnimation } from 'angular-animations';
@Component({animations: [
fadeInOnEnterAnimation(),
fadeOutOnLeaveAnimation()
]})
public toggleFavorite() {
this.isFavorite = !this.isFavorite;
}
html:
<div style="display: grid;" id="favoriteContainer" (click)=toggleFavorite() matTooltip="Favorite" >
<mat-icon style="grid-column: 1;grid-row: 1;" *ngIf="!isFavorite" [@fadeInOnEnter] [@fadeOutOnLeave]>star_border</mat-icon>
<mat-icon style="grid-column: 1;grid-row: 1;" *ngIf="isFavorite" [@fadeInOnEnter] [@fadeOutOnLeave]>star</mat-icon>
</div>
You could implement through a component using icons. Implement a component which contains array for icons then swap the icons regular interval. Each icon represent a state/image.
For eg : Use following icons in an array then swap it every every 100ms.
- https://fontawesome.com/v4.7.0/icon/pencil
- https://fontawesome.com/v4.7.0/icon/pencil-square-o
- https://fontawesome.com/v4.7.0/icon/pencil-square
Update:
Refer to Animate Font Awesome icons in Angular article.
Forked from above https://stackblitz.com/edit/animated-icons-angular-forked
With the help of @Remy, I done a working example
- first install this package
npm i angular-animations --save
- then import the
BrowserAnimationsModule
inside your parent module imports
<mat-icon matListIcon class="menu-item-icon" *ngIf="themeService.isDark();" [@fadeInOnEnter]>dark_mode</mat-icon>
<mat-icon matListIcon class="menu-item-icon" *ngIf="themeService.isLight();" [@fadeInOnEnter]>light_mode</mat-icon>
<mat-slide-toggle [checked]="themeService.isDark()" (change)="$event.checked ? setDarkTheme() : setLightTheme()"></mat-slide-toggle>
TS file
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { fadeInOnEnterAnimation, fadeOutOnLeaveAnimation } from 'angular-animations';
@Component({
selector: 'app-user-menu',
templateUrl: './user-menu.component.html',
styleUrls: ['./user-menu.component.scss'],
animations: [
fadeInOnEnterAnimation(),
]
})
export class UserMenuComponent implements OnInit, OnDestroy {
constructor() {}
}
Output
material.io is specification and guid how to make material design ,angular material component is build on this kind of specification but the don't show any information about animate google material icon.
I know this is an older question, but I am sure people are still struggling with it.
Let me share with you a couple of files I have used for a while. I found a lot of this material from another answer on StackOverflow years ago, I have added my own features into these files.
Here is the StackBlitz that I started with and started to modify. I think the author has steadily been adding things, because this looks way beefed up since I have seen it last. So check out his stuff after you get the below code working!
Essentially you need to make a directive that you can use on html elements. I caution you though, if you are using an additional directive on the same element that has an HTML Element Ref, you will need to wrap whatever you are trying to animate in a span, or just figure out a way to only have one element ref. A good example of when you would need a wrapper would be a matBadge or button that triggers a matMenu.
I have used this animation system to fire animations on page scrolling, as well as firing them repeatedly to show background tasks loading/ etc.
The 2 files you'll need (aos-component.ts):
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, HostBinding, HostListener, ElementRef, NgZone } from '@angular/core';
import { Subject, Observable, of } from 'rxjs';
import { map, startWith, distinctUntilChanged, delay, scan, takeUntil, takeWhile, flatMap } from 'rxjs/operators';
import {$animations} from './aos-animations';
import {ScrollDispatcher} from '@angular/cdk/overlay';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
export type wmAnimations = 'landing'|'pulse'|'beat'|'heartBeat'|'fadeIn'|'fadeInAndOut'|'fadeInRight'|'fadeInLeft'|'fadeInUp'|'fadeInDown'|'zoomIn'|'fadeOut'|'fadeOutRight'|'fadeOutLeft'|'fadeOutDown'|'fadeOutUp'|'zoomOut'|'flyingStagger';
export type wmAnimateSpeed = 'slower'|'slow'|'normal'|'fast'|'faster';
export class wmRect {
constructor(readonly left: number, readonly top: number, readonly right: number, readonly bottom: number) {}
get width(): number { return this.right - this.left; }
get height(): number { return this.bottom - this.top; }
};
@Component({
selector: '[wmAnimate]',
template: '<ng-content></ng-content>',
animations: $animations
})
export class AnimateComponent implements OnInit, OnDestroy {
readonly timings = { slower: '3s', slow: '2s', normal: '1s', fast: '500ms', faster: '300ms' };
public replay$ = new Subject<boolean>();
public dispose$ = new Subject<void>();
constructor(public elm: ElementRef, public scroll: ScrollDispatcher, public zone: NgZone) {}
public get idle() { return { value: 'idle' }; }
public get play() {
return {
value: this.animate,
//delay: this.delay,
params: {
timing: this.timings[this.speed] || '1s',
stagger: this.stagger
}
};
}
/** Selects the animation to be played */
@Input('wmAnimate') animate: wmAnimations;
/** Speeds up or slows down the animation */
@Input() speed: wmAnimateSpeed = 'normal';
/**Specifies number of elements to stagger animation */
@Input() stagger: number = 0;
@HostBinding('@animate')
public trigger: string | {} = 'idle';
/** Disables the animation */
@Input('disabled') set disableAnimation(value: boolean) { this.disabled = coerceBooleanProperty(value); }
@HostBinding('@.disabled')
public disabled = false;
/** Emits at the end of the animation */
@Output() start = new EventEmitter<void>();
@HostListener('@animate.start') public animationStart() { this.start.emit(); }
/** Emits at the end of the animation */
@Output() done = new EventEmitter<void>();
@HostListener('@animate.done') public animationDone() { this.done.emit(); }
/** When true, keeps the animation idle until the next replay triggers */
@Input('paused') set pauseAnimation(value: boolean) { this.paused = coerceBooleanProperty(value); }
public paused: boolean = false;
/** When true, triggers the animation on element scrolling in the viewport */
@Input('aos') set enableAOS(value: boolean) { this.aos = coerceBooleanProperty(value); }
public aos: boolean = false;
/** When true, triggers the animation on element scrolling in the viewport */
@Input('once') set aosOnce(value: boolean) { this.once = coerceBooleanProperty(value); }
public once: boolean = false;
/** Specifies the amount of visibility triggering AOS */
@Input() threshold: number = 0.2;
/** If set to true, this will replay the animation indefinitely. Useful for loading/bg tasks*/
@Input() always: boolean = false;
/** Replays the animation */
@Input() set replay(replay: any) {
if(this.always){
setInterval(() => {
//We hardcoded 4 seconds in here, and 2 seconds in aos-animations.ts for use in only one location.
// We should pass inputs here and make multiple animations in our animations file
this.trigger = this.idle;
this.replay$.next(true);
}, 4000)
} else {
if(this.trigger === 'idle') { return; }
// Re-triggers the animation again on request
if(coerceBooleanProperty(replay)) {
this.trigger = this.idle;
this.replay$.next(true);
}
}
}
ngOnInit() {
// Triggers the animation based on the input flags
this.animateTrigger(this.elm).subscribe( trigger => {
// Triggers the animation to play or to idle
if (this.stagger > 0){
for(let i = 1; i <= this.stagger; i++){
console.log('fire staggering');
this.trigger = trigger ? this.play : this.idle;
}
} else {
this.trigger = trigger ? this.play : this.idle;
}
});
}
ngOnDestroy() { this.dispose(); }
public dispose() {
this.dispose$.next();
this.dispose$.complete();
}
// Triggers the animation
public animateTrigger(elm: ElementRef<HTMLElement>): Observable<boolean> {
return this.animateReplay().pipe( flatMap( trigger => this.aos ? this.animateOnScroll(elm) : of(trigger)) );
}
// Triggers the animation deferred
public animateReplay(): Observable<boolean> {
return this.replay$.pipe( takeUntil(this.dispose$), delay(0), startWith(!this.paused) );
}
// Triggers the animation on scroll
public animateOnScroll(elm: ElementRef<HTMLElement>): Observable<boolean> {
// Returns an AOS observable
return this.scroll.ancestorScrolled(elm, 100).pipe(
// Makes sure to dispose on destroy
takeUntil(this.dispose$),
// Starts with initial element visibility
startWith(!this.paused && this.visibility >= this.threshold),
// Maps the scrolling to the element visibility value
map(() => this.visibility),
// Applies an hysteresys, so, to trigger the animation on based on the treshold while off on full invisibility
scan<number,boolean>((result, visiblility) => (visiblility >= this.threshold || (result ? visiblility > 0 : false))),
// Distincts the resulting triggers
distinctUntilChanged(),
// Stop taking the first on trigger when aosOnce is set
takeWhile(trigger => !trigger || !this.once, true),
// Run NEXT within the angular zone to trigger change detection back on
flatMap(trigger => new Observable<boolean>(observer => this.zone.run(() => observer.next(trigger))))
);
}
// Computes the element visibility ratio
public get visibility() {
return this.intersectRatio( this.clientRect(this.elm), this.getScrollingArea(this.elm) );
}
public intersectRatio(rect: wmRect, cont: wmRect): number {
// Return 1.0 when the element is fully within its scroller container
if(rect.left > cont.left && rect.top > cont.top && rect.right < cont.right && rect.bottom < cont.bottom) {
return 1.0;
}
// Computes the intersection area otherwise
const a = Math.round(rect.width * rect.height);
const b = Math.max(0, Math.min(rect.right, cont.right) - Math.max(rect.left, cont.left));
const c = Math.max(0, Math.min(rect.bottom, cont.bottom) - Math.max(rect.top, cont.top));
// Returns the amount of visible area
return Math.round(b * c / a * 10) / 10;
}
// Returns the rectangular surface area of the element's scrolling container
public getScrollingArea(elm: ElementRef<HTMLElement>): wmRect {
// Gets the cdkScolling container, if any
const scroller = this.scroll.getAncestorScrollContainers(elm).pop();
// Returns the element's most likely scrolling container area
return !!scroller ? this.clientRect( scroller.getElementRef() ) : this.windowRect();
}
// Element client bounding rect helper
public clientRect(elm: ElementRef<HTMLElement>): wmRect {
const el = !!elm && elm.nativeElement;
return !!el && el.getBoundingClientRect();
}
public windowRect(): wmRect {
return new wmRect(0,0, window.innerWidth, window.innerHeight);
}
}
And the other one (aos-animations.ts):
import {animate, keyframes, query, stagger, state, style, transition, trigger} from '@angular/animations';
export const $animations = [
trigger('animate', [
state('idle', style({ opacity: 0 }) ),
transition('* => landing', [
style({
transform: 'scale(1.2)',
opacity: 0
}),
animate('{{timing}} ease', style('*'))
], { params: { timing: '2s'}}),
transition('* => pulse', [
style('*'),
animate('{{timing}} ease-in-out',
keyframes([
style({ transform: 'scale(1)' }),
style({ transform: 'scale(1.05)' }),
style({ transform: 'scale(1)' })
])
)], { params: { timing: '1s'}}
),
transition('* => beat', [
style('*'),
animate('{{timing}} cubic-bezier(.8, -0.6, 0.2, 1.5)',
keyframes([
style({ transform: 'scale(0.8)' }),
style({ transform: 'scale(1.5)' }),
style({ transform: 'scale(1)' })
])
)], { params: { timing: '500ms'}}
),
transition('* => heartBeat', [
style('*'),
animate('{{timing}} ease-in-out',
keyframes([
style({ transform: 'scale(1)', offset: 0 }),
style({ transform: 'scale(1.3)', offset: 0.14 }),
style({ transform: 'scale(1)', offset: 0.28 }),
style({ transform: 'scale(1.3)', offset: 0.42 }),
style({ transform: 'scale(1)', offset: 0.70 })
])
)], { params: { timing: '1s'}}
),
transition('* => fadeIn', [
style({ opacity: 0 }),
animate('{{timing}} ease-in', style('*'))
], { params: { timing: '1s'}}),
transition('* => fadeInAndOut', [
style({ opacity: 0 }),
animate('{{timing}} ease-in', style('*')),
animate('{{timing}} ease-in', style({ opacity: 0 }))
], { params: { timing: '2s'}}),
transition('* => fadeInRight', [
style({ opacity: 0, transform: 'translateX(-20px)' }),
animate('{{timing}} ease-in', style('*'))
], { params: { timing: '1s'}}),
transition('* => fadeInLeft', [
style({ opacity: 0, transform: 'translateX(20px)' }),
animate('{{timing}} ease-in', style('*'))
], { params: { timing: '1s'}}),
transition('* => fadeInUp', [
style({ opacity: 0, transform: 'translateY(20px)' }),
animate('{{timing}} ease-in', style('*'))
], { params: { timing: '1s'}}),
transition('* => fadeInDown', [
style({ opacity: 0, transform: 'translateY(-20px)' }),
animate('{{timing}} ease-in', style('*'))
], { params: { timing: '1s'}}),
transition('* => zoomIn',
animate('{{timing}} ease-in',
keyframes([
style({ opacity: 0, transform: 'scale(0.3)' }),
style({ opacity: 1, transform: 'scale(0.65)' }),
style({ opacity: 1, transform: 'scale(1)' })
])
), { params: { timing: '1s'}}
),
transition('* => bumpIn', [
style({ transform: 'scale(0.5)', opacity: 0 }),
animate("{{timing}} cubic-bezier(.8, -0.6, 0.2, 1.5)",
style({ transform: 'scale(1)', opacity: 1 }))
], { params: { timing: '500ms'}}),
transition('* => flyingStagger', [
// query(':enter', style({ opacity: 0 }), { optional: true }),
query('.logos', [
stagger(500, [
animate('{{timing}} ease-in', keyframes([
style({ opacity: 0, transform: 'translateY(-50%)', offset: 0 }),
style({ opacity: .5, transform: 'translateY(-10px) scale(1.1)', offset: 0.3 }),
style({ opacity: 1, transform: 'translateY(0)', offset: 1 }),
]))
])
])
], { params: { timing: '1s'}}),
transition('fadeOut => void', [
animate('{{timing}} ease-in', style({ opacity: 0 }))
]),
transition('fadeOutRight => void', [
animate('{{timing}} ease-in', style({ opacity: 0, transform: 'translateX(20px)' }))
], { params: { timing: '1s'}}),
transition('fadeOutLeft => void', [
animate('{{timing}} ease-in', style({ opacity: 0, transform: 'translateX(-20px)' }))
], { params: { timing: '1s'}}),
transition('fadeOutDown => void', [
animate('{{timing}} ease-in', style({ opacity: 0, transform: 'translateY(20px)' }))
], { params: { timing: '1s'}}),
transition('fadeOutUp => void', [
animate('{{timing}} ease-in', style({ opacity: 0, transform: 'translateY(-20px)' }))
], { params: { timing: '1s'}}),
transition('zoomOut => void',
animate('{{timing}} ease-in',
keyframes([
style({ opacity: 1, transform: 'scale(1)' }),
style({ opacity: 0, transform: 'scale(0.3)' }),
style({ opacity: 0, transform: 'scale(0.3)' })
])
), { params: { timing: '1s'}}
),
])
];
You will need the cdk library as well. Look at the imports in the component file above. Make sure you have BrowserAnimationsModule as well from @angular/platform-browser
Lastly, here is an example of how I am using it in one part of our app:
<span *ngIf="(loaderCount$ | async) > 0" class="bgLoad" wmAnimate="fadeInAndOut" speed="slow" replay="true" always="true"><mat-icon>cloud_download</mat-icon></span>
Where I am just logging background API requests and a download icon when 1 or more requests are still pending.
I have used this same library to animate an element when it first becomes visible on the page like so:
<div wmAnimate="landing" speed="normal" class="centerVertH head" aos once *ifIsBrowser>my content...</div>
If I wanted it to fire every time it became visible, then I would remove 'once' from that div.
Look at the documentation around the inputs in the animations component file. You can make some more to suit your needs. The background of having good animations is there. Mess around with it. It will help you understand transitions and animations in general.
I hope this helps you.
Ok, so I was also looking to animate icons. I came up with a simple CSS solution. First I wrap the icon in a small container:
<span class="icon-container">
@if(action.status === "wait"){
<mat-icon class="animate-rotate">hourglass_empty</mat-icon>
} @else {
<mat-icon class="animate-horizontal">fast_forward</mat-icon>
}
</span>
Then using the classes I start an animation in CSS:
.icon-container {
width: 2em;
overflow: clip;
display: inline-block;
border: 1px solid darkblue
}
.animate-horizontal {
animation-name: AnimateMoveOutRight;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-direction: normal;
}
.animate-rotate {
animation-name: AnimateRotate;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-direction: normal;
}
@keyframes AnimateMoveOutRight {
0% {
transform: translateX(0);
}
100%{
transform: translateX(2em);
}
}
@keyframes AnimateRotate {
0% {
transform: rotate(0);
}
100%{
transform: rotate(180deg);
}
}
Working example on Stackblitz
© 2022 - 2024 — McMap. All rights reserved.