product.component.ts (3225B)
1 import { CommonModule } from '@angular/common'; 2 import { Component, computed, ElementRef, inject, input, signal, viewChild } from '@angular/core'; 3 import { Router, RouterLink } from '@angular/router'; 4 import { CartService } from '../services/cart.service'; 5 6 const morphAndFadeKeyframes = (sx: string, sy: string) => { 7 const rect = window.cartBtnPosition(); 8 const dx = `${rect.x}px`; 9 const dy = `${rect.y}px`; 10 console.log({ sx, sy, dx, dy }); 11 return [ 12 // 0% 13 { 14 opacity: 0, 15 transform: `translate(${sx}, ${sy}) scale(.75)`, 16 offset: 0.0, 17 }, 18 // 8% 19 { 20 opacity: 0.5, 21 transform: `translate(${sx}, ${sy}) scale(1)`, 22 offset: 0.08, 23 }, 24 // 20% 25 { 26 opacity: 1, 27 transform: `translate(${sx}, ${sy}) scale(1.5)`, 28 offset: 0.2, 29 }, 30 // 80% (Movement to destination, maintaining scale and opacity) 31 { 32 opacity: 1, 33 transform: `translate(${dx}, ${dy}) scale(1.25)`, 34 offset: 0.8, 35 }, 36 // 90% 37 { 38 opacity: 0.75, 39 transform: `translate(${dx}, ${dy}) scale(1)`, 40 offset: 0.8, 41 }, 42 { 43 opacity: 0.5, 44 transform: `translate(${dx}, ${dy}) scale(.5)`, 45 offset: 0.9, 46 }, 47 // 100% 48 { 49 opacity: 0, 50 transform: `translate(${dx}, ${dy}) scale(.1)`, 51 offset: 1.0, 52 }, 53 ]; 54 }; 55 56 @Component({ 57 selector: 'div.product', 58 templateUrl: './product.component.html', 59 styleUrls: ['./product.component.scss'], 60 standalone: true, 61 imports: [CommonModule, RouterLink], 62 }) 63 export class ProductComponent { 64 private cartService = inject(CartService); 65 readonly product = input<ProductCard>({} as ProductCard); 66 readonly addingToCart = signal<boolean>(false); 67 private morphDiv = viewChild<ElementRef<HTMLDivElement>>('morphDiv'); 68 private div = computed(() => this.morphDiv()?.nativeElement as HTMLDivElement); 69 70 ratingStars = computed(() => { 71 const rating = this.product().average_rating || 0; 72 return rating === 0 ? '' : Number.isInteger(rating) ? '-fill' : '-half'; 73 }); 74 75 get discount(): number { 76 const initial = parseFloat(this.product().initial_price || '0'); 77 const discount = parseFloat(this.product().discount || '0'); 78 return initial > 0 ? Math.round((discount / initial) * 100) : 0; 79 } 80 81 addToCart($event: Event): void { 82 $event.preventDefault(); 83 $event.stopPropagation(); 84 const btn = $event.target as HTMLButtonElement; 85 86 this.addingToCart.set(true); 87 const options = { 88 duration: 1000, // Example duration in milliseconds (adjust as needed) 89 iterations: 1, 90 fill: 'forwards', // Keeps the final state (opacity: 0, scaled down, at destination) 91 easing: 'cubic-bezier(0.42, 0, 0.58, 1)', // Applies a general easing across the duration 92 } as KeyframeAnimationOptions; 93 94 const { x, y, width, height } = btn.getBoundingClientRect(); 95 const keyframes = morphAndFadeKeyframes(`${x + 10}px`, `${y - 3}px`); 96 const div = document.body.appendChild(this.div().cloneNode(true) as HTMLDivElement); 97 const animation = div.animate(keyframes, options); 98 animation.onfinish = () => { 99 this.cartService.addToCart(this.product()); 100 this.addingToCart.set(false); 101 div.remove(); 102 }; 103 } 104 }