ngx-flip-card
Version:
Angular component to animate a double-sided card.
313 lines (307 loc) • 16 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, Component, Input, Output, HostListener, ViewChild, NgModule } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
class NgxFlipCardComponent {
constructor() {
this._isBackSide = false;
this.isBackSideChange = new EventEmitter();
this.animate = true;
this.animationDuration = 2500;
this.animationEnd = new EventEmitter();
this.canAnimate = false;
this.frontRotation = 0;
this.backRotation = 180;
this.desiredFrontRotation = 0;
this.frontZindex = 1;
this.backZindex = 0;
this.translationZ = 2;
this.currentTranslationZ = 0;
this.easing = 'linear';
this.direction = 'horizontal';
this.flipOnClick = true;
this.timeLastFrame = Date.now();
this.currentAnimationTime = 0;
this.animating = false;
}
set isBackSide(value) {
if (value === this._isBackSide) {
return;
}
this._isBackSide = value;
this.isBackSideChange.emit(this._isBackSide);
this.flipInternal();
}
get isBackSide() {
return this._isBackSide;
}
ngOnInit() {
}
ngAfterViewInit() {
this.canAnimate = true;
}
flipInternal() {
this.rotateAction();
}
clickHandler() {
if (this.flipOnClick) {
this.flipCard();
}
}
flipCard() {
this.isBackSide = !this._isBackSide;
}
rotateAction() {
if (!this.animate || !this.canAnimate) {
this.rotateFlat();
}
else {
this.animateRotationStart();
}
}
rotateFlat() {
if (this.isBackSide) {
this.frontRotation = 180;
this.backRotation = 0;
this.frontZindex = 0;
this.backZindex = 1;
}
else {
this.frontRotation = 0;
this.backRotation = 180;
this.frontZindex = 1;
this.backZindex = 0;
}
}
animateRotationStart() {
this.desiredFrontRotation = this.isBackSide ? 180 : 0;
this.timeLastFrame = Date.now();
if (this.animating) {
this.currentAnimationTime = this.calculateReverseEasing();
}
else {
this.currentAnimationTime = 0;
}
if (this.animationFrameRef) {
cancelAnimationFrame(this.animationFrameRef);
}
this.animationFrameRef = requestAnimationFrame(this.animateLoop.bind(this));
this.animating = true;
this.currentTranslationZ = this.translationZ;
}
animateLoop() {
const time = Date.now();
const deltatime = time - this.timeLastFrame;
this.timeLastFrame = time;
this.currentAnimationTime += deltatime;
const amount = this.currentAnimationTime / this.animationDuration;
switch (this.easing) {
case 'linear':
this.frontRotation = this.isBackSide ?
Math.min(this.lerp(0, this.desiredFrontRotation, amount), this.desiredFrontRotation) :
Math.max(this.lerp(180, this.desiredFrontRotation, amount), this.desiredFrontRotation);
break;
case 'easeIn':
this.frontRotation = this.isBackSide ?
Math.min(this.easeIn(0, this.desiredFrontRotation, amount), this.desiredFrontRotation) :
Math.max(this.easeIn(180, this.desiredFrontRotation, amount), this.desiredFrontRotation);
break;
case 'easeOut':
this.frontRotation = this.isBackSide ?
Math.min(this.easeOut(0, this.desiredFrontRotation, amount), this.desiredFrontRotation) :
Math.max(this.easeOut(180, this.desiredFrontRotation, amount), this.desiredFrontRotation);
break;
case 'easeInOut':
this.frontRotation = this.isBackSide ?
Math.min(this.easeInOut(0, this.desiredFrontRotation, amount), this.desiredFrontRotation) :
Math.max(this.easeInOut(180, this.desiredFrontRotation, amount), this.desiredFrontRotation);
break;
}
if (this.currentAnimationTime >= this.animationDuration) {
this.frontRotation = this.desiredFrontRotation;
}
this.backRotation = this.frontRotation + 180;
if (this.isBackSide && this.frontRotation >= 90) {
this.frontZindex = 0;
this.backZindex = 1;
}
else if (!this.isBackSide && this.frontRotation <= 90) {
this.frontZindex = 1;
this.backZindex = 0;
}
if (this.frontRotation === this.desiredFrontRotation) {
this.animationEnded();
return;
}
this.animationFrameRef = requestAnimationFrame(this.animateLoop.bind(this));
}
lerp(a, b, n) {
return (1 - n) * a + n * b;
}
easeIn(a, b, n) {
return this.lerp(a, b, n * n);
}
easeOut(a, b, n) {
return this.lerp(a, b, 1 - (1 - n) * (1 - n));
}
easeInOut(a, b, n) {
return this.lerp(a, b, n < 0.5 ? 2 * n * n : 1 - Math.pow(-2 * n + 2, 2) / 2);
}
calculateReverseEasing() {
if (this.easing === 'linear') {
return this.animationDuration - this.currentAnimationTime;
}
const start = this._isBackSide ? 0 : 180;
const end = this._isBackSide ? 180 : 0;
// increment 16 milliseconds to simulate animation at 60FPS
for (let i = 0; i < this.animationDuration; i += 16) {
let inverseValue = 0;
switch (this.easing) {
case 'easeIn':
inverseValue = this.easeIn(start, end, i / this.animationDuration);
break;
case 'easeOut':
inverseValue = this.easeOut(start, end, i / this.animationDuration);
break;
case 'easeInOut':
inverseValue = this.easeInOut(start, end, i / this.animationDuration);
break;
}
if ((this._isBackSide && inverseValue >= this.frontRotation) ||
(!this._isBackSide && inverseValue <= this.frontRotation)) {
return i;
}
}
return 0;
}
animationEnded() {
this.animationEnd.emit(this.isBackSide);
this.animating = false;
this.currentTranslationZ = 0;
}
}
NgxFlipCardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxFlipCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
NgxFlipCardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: NgxFlipCardComponent, selector: "ngx-flip-card", inputs: { isBackSide: "isBackSide", animate: "animate", animationDuration: "animationDuration", translationZ: "translationZ", easing: "easing", direction: "direction", flipOnClick: "flipOnClick" }, outputs: { isBackSideChange: "isBackSideChange", animationEnd: "animationEnd" }, ngImport: i0, template: "<div class=\"card-container\" (click)=\"clickHandler()\">\n <div class=\"card-side\"\n [ngStyle]=\"{ 'transform': (direction === 'horizontal' ? 'rotateY(' + frontRotation + 'deg)' : 'rotateX(' + frontRotation + 'deg)') + ' translateZ(' + currentTranslationZ + 'px )', 'z-index': frontZindex }\">\n <ng-content select=\"[card-front]\"></ng-content>\n </div>\n <div class=\"card-side\" \n [ngStyle]=\"{ 'transform': (direction === 'horizontal' ? 'rotateY(' + backRotation + 'deg)' : 'rotateX(' + backRotation + 'deg)') + ' translateZ(' + currentTranslationZ + 'px )', 'z-index': backZindex }\">\n <ng-content select=\"[card-back]\"></ng-content>\n </div>\n</div>\n", styles: [".card-container{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);position:relative;perspective:500px;width:-moz-fit-content;width:fit-content}.card-side{grid-row:1 / -1;grid-column:1 / -1;transform-origin:center}\n"], directives: [{ type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxFlipCardComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-flip-card', template: "<div class=\"card-container\" (click)=\"clickHandler()\">\n <div class=\"card-side\"\n [ngStyle]=\"{ 'transform': (direction === 'horizontal' ? 'rotateY(' + frontRotation + 'deg)' : 'rotateX(' + frontRotation + 'deg)') + ' translateZ(' + currentTranslationZ + 'px )', 'z-index': frontZindex }\">\n <ng-content select=\"[card-front]\"></ng-content>\n </div>\n <div class=\"card-side\" \n [ngStyle]=\"{ 'transform': (direction === 'horizontal' ? 'rotateY(' + backRotation + 'deg)' : 'rotateX(' + backRotation + 'deg)') + ' translateZ(' + currentTranslationZ + 'px )', 'z-index': backZindex }\">\n <ng-content select=\"[card-back]\"></ng-content>\n </div>\n</div>\n", styles: [".card-container{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);position:relative;perspective:500px;width:-moz-fit-content;width:fit-content}.card-side{grid-row:1 / -1;grid-column:1 / -1;transform-origin:center}\n"] }]
}], ctorParameters: function () { return []; }, propDecorators: { isBackSide: [{
type: Input
}], isBackSideChange: [{
type: Output
}], animate: [{
type: Input
}], animationDuration: [{
type: Input
}], animationEnd: [{
type: Output
}], translationZ: [{
type: Input
}], easing: [{
type: Input
}], direction: [{
type: Input
}], flipOnClick: [{
type: Input
}] } });
class NgxTiltCardComponent {
constructor() {
this.xRot = 0;
this.yRot = 0;
this.rotationDegreesX = 5;
this.rotationDegreesY = 5;
this.xRotationMatrix = [];
this.yRotationMatrix = [];
}
onMouseOut() {
this.xRot = 0;
this.yRot = 0;
}
onMouseMove(event) {
let index = event.offsetY / this.tiltContentRef.nativeElement.offsetHeight;
index = Math.floor(index * 5);
index = index > this.xRotationMatrix.length - 1 ? this.xRotationMatrix.length - 1 : index;
if (index < this.xRotationMatrix.length && this.xRot !== this.xRotationMatrix[index]) {
this.xRot = this.xRotationMatrix[index];
}
else if (index >= this.xRotationMatrix.length) {
this.xRot = 0;
this.yRot = 0;
return;
}
index = event.offsetX / this.tiltContentRef.nativeElement.offsetWidth;
index = Math.floor(index * 5);
if (index < this.yRotationMatrix.length && this.yRot !== this.yRotationMatrix[index]) {
this.yRot = this.yRotationMatrix[index];
}
else if (index >= this.yRotationMatrix.length) {
this.xRot = 0;
this.yRot = 0;
}
}
ngOnInit() {
this.xRotationMatrix = [
this.rotationDegreesX,
this.rotationDegreesX / 2,
0,
(this.rotationDegreesX * -1) / 2,
this.rotationDegreesX * -1
];
this.yRotationMatrix = [
this.rotationDegreesY * -1,
(this.rotationDegreesY * -1) / 2,
0,
this.rotationDegreesY / 2,
this.rotationDegreesY
];
}
}
NgxTiltCardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxTiltCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
NgxTiltCardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: NgxTiltCardComponent, selector: "ngx-tilt-card", inputs: { rotationDegreesX: "rotationDegreesX", rotationDegreesY: "rotationDegreesY" }, host: { listeners: { "mouseout": "onMouseOut()", "mousemove": "onMouseMove($event)" } }, viewQueries: [{ propertyName: "tiltContentRef", first: true, predicate: ["tilt"], descendants: true }], ngImport: i0, template: "<div class=\"tilt-container\">\n <div #tilt class=\"tilt-content\" [ngStyle]=\"{'transform': 'rotateX(' + xRot + 'deg) rotateY(' + yRot + 'deg)'}\">\n <ng-content></ng-content>\n </div>\n</div>\n", styles: [".tilt-container{display:grid;grid-template-columns:1fr;grid-template-rows:1fr;position:relative;perspective:500px;width:-moz-fit-content;width:fit-content}.tilt-content{transform-origin:center;position:relative;transition:transform 1s ease}\n"], directives: [{ type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxTiltCardComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-tilt-card', template: "<div class=\"tilt-container\">\n <div #tilt class=\"tilt-content\" [ngStyle]=\"{'transform': 'rotateX(' + xRot + 'deg) rotateY(' + yRot + 'deg)'}\">\n <ng-content></ng-content>\n </div>\n</div>\n", styles: [".tilt-container{display:grid;grid-template-columns:1fr;grid-template-rows:1fr;position:relative;perspective:500px;width:-moz-fit-content;width:fit-content}.tilt-content{transform-origin:center;position:relative;transition:transform 1s ease}\n"] }]
}], ctorParameters: function () { return []; }, propDecorators: { onMouseOut: [{
type: HostListener,
args: ['mouseout']
}], onMouseMove: [{
type: HostListener,
args: ['mousemove', ['$event']]
}], tiltContentRef: [{
type: ViewChild,
args: ['tilt']
}], rotationDegreesX: [{
type: Input
}], rotationDegreesY: [{
type: Input
}] } });
class NgxFlipCardModule {
}
NgxFlipCardModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxFlipCardModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NgxFlipCardModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxFlipCardModule, declarations: [NgxFlipCardComponent,
NgxTiltCardComponent], imports: [CommonModule], exports: [NgxFlipCardComponent,
NgxTiltCardComponent] });
NgxFlipCardModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxFlipCardModule, imports: [[
CommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: NgxFlipCardModule, decorators: [{
type: NgModule,
args: [{
declarations: [
NgxFlipCardComponent,
NgxTiltCardComponent
],
imports: [
CommonModule
],
exports: [
NgxFlipCardComponent,
NgxTiltCardComponent
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { NgxFlipCardComponent, NgxFlipCardModule, NgxTiltCardComponent };
//# sourceMappingURL=ngx-flip-card.mjs.map