To generate an aminated credit card form in Angular, you can modify the app.module.ts file as follows:
Final Screen animated credit card
Final Screen animated credit card for the below sample.
In ngModule
- declarations: Specifies the components, directives, and pipes that belong to this module. In this case, AppComponent is declared here.
- imports: Lists other modules whose exported classes are needed by component templates declared in this NgModule. Here, various Angular modules like CommonModule, AppRoutingModule, BrowserAnimationsModule, FormsModule, and ReactiveFormsModule are imported.(CommonModule for Using *ngfor and *ngIf directive, FormsModule, ReactiveFormsModule - to use the form, BrowserAnimationsModule- in the following sample, we used animation module).
- schemas: This is an array of schema types that should be ignored or not checked for this module. CUSTOM_ELEMENTS_SCHEMA tells Angular to ignore or accept any elements and attributes that are not standard HTML but are custom elements. NO_ERRORS_SCHEMA tells Angular to ignore unknown elements and attributes altogether.
- bootstrap: Specifies the main component that should be bootstrapped when this NgModule is bootstrapped. In this case, it's AppComponent.
@NgModule({
declarations: [AppComponent], // Components, directives, and pipes that belong to this NgModule.
imports: [
CommonModule, // Provides commonly used directives, pipes, and services.
AppRoutingModule, // The routing module for the application.
BrowserAnimationsModule, // Module for providing animations support in Angular.
FormsModule, // Module for two-way data binding using ngModel directive.
ReactiveFormsModule // Module for reactive forms support.
],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA], // Defines the schema to be used for components.
bootstrap: [AppComponent] // The main component to be bootstrapped when this NgModule is bootstrapped.
})
In (app.component.ts)
- selector: Defines the HTML tag that will represent this component in other HTML files.
- encapsulation: Specifies the encapsulation strategy for the component's styles. ViewEncapsulation.None means that the styles defined in the component will affect the entire application without any encapsulation.
- templateUrl: Points to the external HTML file containing the template for this component.
- styleUrls: An array of URLs pointing to external style files (like SCSS or CSS) for this component.
- animations: An array of animation definitions for this component. In this case, it defines an animation trigger named 'flipState' which flips the component around the Y-axis when the state changes from 'active' to 'inactive' and vice versa.
@Component({
selector: 'app-root', // The CSS selector that identifies this component in a template.
encapsulation: ViewEncapsulation.None, // Defines how the styles of the component should be encapsulated. 'None' means no encapsulation.
templateUrl: './app.component.html', // The URL of the template file for this component.
styleUrls: ['./app.component.scss'], // An array of URLs of the style files for this component.
animations: [ // An array of animation definitions for this component.
trigger('flipState', [ // Defines an animation trigger named 'flipState'.
state('active', style({ // Defines a state named 'active' with a specific style.
transform: 'rotateY(179deg)' // CSS transformation to rotate the element.
})),
state('inactive', style({ // Defines a state named 'inactive' with a specific style.
transform: 'rotateY(0)' // CSS transformation to rotate the element back to its original position.
})),
transition('active => inactive', animate('500ms ease-out')), // Defines a transition from 'active' to 'inactive' state with specific animation.
transition('inactive => active', animate('500ms ease-in')) // Defines a transition from 'inactive' to 'active' state with specific animation.
])
]
})
In the below session, continue the app component.
Added the CardDetails object to get the card info, and while clearing, the object was reset to the initial state.
- toggleFlip(): If we are entering CVV, only the credit card needs to be animated and show the back card, so here we have set the flip animation trigger we have defined as 'active'
- toggleFront(): If we are unfocused CVV input, we need to animate and show the front card, so here we have set the flip animation trigger we have defined as 'inactive'
export class AppComponent {
isShowBackCard: boolean = false;
flip: string = 'inactive';
cardDetails:any=
{
Name:null,
CardNumber:null,
CVV:null,
ExpiryMonth:2,
ExpiryYear:24
};
toggleFlip() {
this.flip ='active';
}
toggleFront()
{
this.flip = 'inactive';
}
constructor() {}
Clear()
{
this.cardDetails=
{
Name:null,
CardNumber:null,
CVV:null,
ExpiryMonth:2,
ExpiryYear:24
};
}
title = 'card-test';
}
In (app.component.html)
Here, we have two sections: one credit card front and back design and form with validation using template-driven forms.
Credit card design
- [@flipState]="flip" we have using the flipstate animation trigger.
- if filpstate is inactive, we will display the front card, and active means we will show the back card.
- the flipstate will set based on focus on cvv input.
<main class="main">
<div class="content">
<div>
<div class="tp-box" [@flipState]="flip">
<div class="front-card" *ngIf="flip === 'inactive'">
<p class="card-number">CARD NUMBER</p>
<p class="card-number">{{!cardDetails.CardNumber? "XXXX-XXXX-XXXX-XXXX" :cardDetails.CardNumber}}</p>
<div class="info-container row">
<div class="col-md-9">
<p class="cardholder-name ">CARD HOLDER NAME</p>
<p class="cardholder-name">{{!cardDetails.Name? "YYYYYYY YYYYYYYY" :cardDetails.Name}}</p>
</div>
<div class="col-md-3">
<p class="exp-date ">EXPIRY (MM/YY)</p>
<p class="exp-date ">{{cardDetails.ExpiryMonth}}/{{cardDetails.ExpiryYear}}</p>
</div>
</div>
</div>
<div class="back-card" *ngIf="flip === 'active'">
<p class="cvc">{{cardDetails.CVV}}</p>
</div>
</div>
<div class="panel panel-default card-glass">
<div class="panel-heading no-bg-color">
<h1 class="or-color">Enter Card Details</h1>
</div>
<div class="panel-body">
<div class="right-payment-sec">
<form #cardForm="ngForm" autocomplete="off" name="cardForm" (ngSubmit)="cardForm.valid">
<div class="row">
<div class="col-lg-12 col-xs-12">
<div class="form-group">
<label class="no-bg-color" for="name">Name On Card</label>
<input type="text" class="form-control no-bg-color"
[ngClass]="{ 'has-error': cardForm.submitted && name.invalid }" #name="ngModel" id="name"
[(ngModel)]="cardDetails.Name" required autofocus name="name" placeholder="Name On Card">
<div *ngIf="cardForm.submitted && name.invalid" class="">
<label class="control-label error-message" *ngIf="name.errors?.['required']"><i
class="fa fa-times"></i> Required</label>
</div>
</div>
</div>
<div class="col-lg-12 col-xs-12">
<div class="form-group">
<label class="no-bg-color" for="cardnumber">Card Number</label>
<input type="number" class="form-control no-bg-color" #cardnumber="ngModel" id="cardnumber"
[(ngModel)]="cardDetails.CardNumber" name="cardnumber"
onKeyPress="if(this.value.length==16) return false;" required
[ngClass]="{ 'has-error': cardForm.submitted && cardnumber.invalid }"
placeholder="XXXX-XXXX-XXXX-XXXX">
<div *ngIf="cardForm.submitted && cardnumber.invalid" class="">
<label class="control-label error-message" *ngIf="cardnumber.errors?.['required']"><i
class="fa fa-times"></i> Required</label>
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 xol-sm-6 col-xs-12 card-infos-container">
<label class="no-bg-color" for="expiry">Expiration (MM/YY)</label>
<div class="form-group date-container" id="date">
<div class="date">
<input type="number" class="form-control no-bg-color " placeholder="MM" #expiryMonth="ngModel"
[ngClass]="{ 'has-error': cardForm.submitted && expiryMonth.invalid }" id="expiryMonth"
[(ngModel)]="cardDetails.ExpiryMonth" name="expiryMonth" [min]="1" [max]="12" required>
<input type="number" class="form-control no-bg-color" placeholder="YY" #expiryYear="ngModel"
[ngClass]="{ 'has-error': cardForm.submitted && expiryYear.invalid }" id="expiryYear"
[(ngModel)]="cardDetails.ExpiryYear" name="expiryYear" [min]="01" [max]="99" required>
</div>
<div *ngIf="cardForm.submitted && (expiryMonth.invalid||expiryYear.invalid)" class="">
<label class="control-label error-message" *ngIf="expiryMonth.errors?.['required']"><i
class="fa fa-times"></i> Required</label>
<label class="control-label error-message" *ngIf="expiryMonth.errors?.['min']"><i
class="fa fa-times"></i> month 01-12 only</label>
<label class="control-label error-message" *ngIf="expiryMonth.errors?.['max']"><i
class="fa fa-times"></i> month 01-12 only</label>
<label class="control-label error-message" *ngIf="expiryYear.errors?.['required']"><i
class="fa fa-times"></i> Required</label>
<label class="control-label error-message" *ngIf="expiryYear.errors?.['min']"><i
class="fa fa-times"></i> Year 01-99 only</label>
<label class="control-label error-message" *ngIf="expiryYear.errors?.['max']"><i
class="fa fa-times"></i> Year 01-99 only</label>
</div>
</div>
</div>
<div class="col-lg-3 col-md-3 xol-sm-6 col-xs-12">
<div class="form-group">
<label class="no-bg-color" for="cvv">CCV</label>
<input type="number" class="form-control no-bg-color" #cvv="ngModel"
[ngClass]="{ 'has-error': cardForm.submitted && cvv.invalid }" id="cvv"
[(ngModel)]="cardDetails.CVV" name="cvv" required
(focus)="$event.stopPropagation();$event.preventDefault();toggleFlip()"
(blur)="$event.stopPropagation();$event.preventDefault();toggleFront()"
onKeyPress="if(this.value.length==3) return false;" placeholder="XXX">
<div *ngIf="cardForm.submitted && cvv.invalid" class="">
<label class="control-label error-message" *ngIf="cvv.errors?.['required']"><i
class="fa fa-times"></i> Required</label>
</div>
</div>
</div>
<div class="modal-footer">
<div class="form-group">
<button type="submit" class="btn btn-primary button-form" style="margin-right: 10px ;">Save</button>
<button type="button" class="btn btn-primary button-form" (click)="Clear()">Clear</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</main>
app.component.scss
.background {
width: 100%;
height: 47.5vw;
background-image: url(../assets/Credit-card-bro.svg), linear-gradient(to right, #00e7ff, #634d38);
background-repeat: no-repeat;
background-size: cover;
}
.card-glass {
backdrop-filter: blur(3px) saturate(113%);
-webkit-backdrop-filter: blur(3px) saturate(113%);
background-color: rgba(255, 255, 255, 0);
border-radius: 12px;
border: 2px solid rgb(123 107 107);
}
input {
border: 1px solid rgb(123 107 107);
}
.no-bg-color {
background-color: transparent !important;
font-size: medium;
color: #df7017;
}
.or-color {
color: #df7017;
}
.front-card {
-webkit-transform: rotateY(0deg);
-ms-transform: rotateY(0deg);
transform: rotateY(0deg);
background-image: url(../assets/1234.png);
margin-left: 19%;
// display: flex;
// flex-direction: column;
justify-content: space-around;
border-radius: 8px;
// *{
// margin-left: 1.5rem;
// margin-top: 10.5rem;
// }
.exp-date {
font-size: 2rem;
}
.card-number {
font-size: 2rem;
margin-left: 1.5rem;
}
.cardholder-name {
margin: 0;
font-size: 2rem;
}
.info-container {
width: 85%;
// display: flex;
// justify-content: space-between;
margin-left: 1.5rem;
margin-top: 6.5rem;
font-family: sans-serif;
font-weight: lighter;
text-transform: uppercase;
}
img {
width: 20%;
height: 20%;
}
}
.back-card {
background-image: url(../assets/bg-card-back.png);
display: flex;
align-items: center;
justify-content: end;
-webkit-transform: rotateY(-180deg);
-ms-transform: rotateY(-180deg);
transform: rotateY(-180deg);
margin-left: 19%;
backface-visibility: hidden;
p {
margin-right: 6rem;
font-size: medium;
}
}
.back-card,
.front-card {
background-repeat: no-repeat;
background-size: cover;
position: absolute;
top: 0;
left: 0;
width: 447px;
height: 245px;
color: whitesmoke;
}
@media screen and (max-width: 768px) {
.front-card,
.back-card {
position: absolute;
width: 248px;
height: 136px;
margin-left: 0;
}
.front-card {
bottom: -2rem;
left: 1rem;
z-index: 10;
.card-number {
font-size: 1rem;
}
.cardholder-name {
font-size: 0.8rem;
}
.exp-date {
font-size: 0.8rem;
}
}
.back-card {
top: 2rem;
right: 1rem;
p {
margin-right: 2rem;
font-size: 0.8rem;
}
}
}
.credit-form {
min-width: 150px;
max-width: 400px;
width: 100%;
}
.full-width {
width: 100%;
}
form {
width: 100%;
display: flex;
flex-direction: column;
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.card-infos-container {
.date-container {
width: 47%;
.date {
display: flex;
gap: 1rem;
width: 100%;
mat-form-field {
width: 40%;
}
}
}
}
button {
height: fit-content;
}
}
.button-form {
margin-right: 10px;
height: fit-content;
font-size: larger;
}
.tp-box__side {
width: 100%;
height: 100%;
position: absolute;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
color: #fff;
text-align: center;
line-height: 100px;
font-size: 24px;
font-weight: 700;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.tp-box {
position: relative;
height: 215px;
z-index: 15;
margin: 3rem auto;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-transform: transform 1s;
-ms-transform: transform 1s;
transform: transform 1s;
}
I hope this will be interesting......
See you on next one........
Here in the attached sample.