animaciones
This commit is contained in:
@@ -1,9 +1,17 @@
|
|||||||
@if(thereIsFullscreenImage) {
|
@if(thereIsFullscreenImage) {
|
||||||
<mid-res-image [src]="midResImage!.src" [alt]="midResImage!.alt" (closed)="onClose()"></mid-res-image>
|
<mid-res-image
|
||||||
|
[src]="midResImage!.src"
|
||||||
|
[alt]="midResImage!.alt"
|
||||||
|
(closed)="onClose()"
|
||||||
|
></mid-res-image>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ul class="low-res-image-list">
|
<ul class="low-res-image-list">
|
||||||
@for (image of images; track image.id) {
|
@for (image of images; track image.id) {
|
||||||
<low-res-image [src]="image.src" [alt]="image.alt" (clicked)="onClickedImage($event)"></low-res-image>
|
<low-res-image
|
||||||
|
[src]="image.src"
|
||||||
|
[alt]="image.alt"
|
||||||
|
(clicked)="onClickedImageWithEvent($event)"
|
||||||
|
></low-res-image>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
@@ -13,14 +13,47 @@ export class Gallery {
|
|||||||
protected images : LowResImage[] = [];
|
protected images : LowResImage[] = [];
|
||||||
thereIsFullscreenImage: boolean = false;
|
thereIsFullscreenImage: boolean = false;
|
||||||
midResImage: MidResImage | null = null;
|
midResImage: MidResImage | null = null;
|
||||||
|
isAnimatingToFullscreen: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.loadImages();
|
this.loadImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickedImage(midResImage: MidResImage): void {
|
onClickedImage(midResImage: MidResImage, event?: MouseEvent): void {
|
||||||
|
// Capturar información de la animación si hay evento disponible
|
||||||
|
if (event && event.target) {
|
||||||
|
this.prepareImageTransition(event.target as HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isAnimatingToFullscreen = true;
|
||||||
this.midResImage = midResImage;
|
this.midResImage = midResImage;
|
||||||
this.thereIsFullscreenImage = this.midResImage !== null;
|
this.thereIsFullscreenImage = this.midResImage !== null;
|
||||||
|
|
||||||
|
// Resetear el estado de animación después de un breve delay
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isAnimatingToFullscreen = false;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickedImageWithEvent(data: {midResImage: MidResImage, event: MouseEvent}): void {
|
||||||
|
this.onClickedImage(data.midResImage, data.event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepareImageTransition(imageElement: HTMLElement): void {
|
||||||
|
// Capturar la posición inicial de la imagen para animación coordinada
|
||||||
|
const rect = imageElement.getBoundingClientRect();
|
||||||
|
const initialPosition = {
|
||||||
|
x: rect.left + rect.width / 2,
|
||||||
|
y: rect.top + rect.height / 2,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height
|
||||||
|
};
|
||||||
|
|
||||||
|
// Guardar información de transición en el document para uso del mid-res-image
|
||||||
|
(document as any).__imageTransition = {
|
||||||
|
...initialPosition,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose(): void {
|
onClose(): void {
|
||||||
|
@@ -11,6 +11,7 @@ $gap-size: 20px;
|
|||||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||||
margin-top: calc($gap-size / 2);
|
margin-top: calc($gap-size / 2);
|
||||||
margin-bottom: calc($gap-size / 2);
|
margin-bottom: calc($gap-size / 2);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.low-res-image-container:hover {
|
.low-res-image-container:hover {
|
||||||
@@ -18,11 +19,22 @@ $gap-size: 20px;
|
|||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.low-res-image-container:active {
|
||||||
|
transform: scale(1.05);
|
||||||
|
transition: transform 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.low-res-image {
|
.low-res-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
display: block;
|
display: block;
|
||||||
|
transition: transform 0.3s ease, filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.low-res-image-container:hover .low-res-image {
|
||||||
|
transform: scale(1.02);
|
||||||
|
filter: brightness(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tablets in landscape - 1 column */
|
/* Tablets in landscape - 1 column */
|
||||||
|
@@ -12,13 +12,13 @@ export class LowResImage {
|
|||||||
@Input({ required: true }) alt: string = '';
|
@Input({ required: true }) alt: string = '';
|
||||||
public id : string = 'low-res-image-' + Math.random().toString(36).substring(2, 15);
|
public id : string = 'low-res-image-' + Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
@Output() clicked = new EventEmitter<MidResImage>();
|
@Output() clicked = new EventEmitter<{midResImage: MidResImage, event: MouseEvent}>();
|
||||||
|
|
||||||
openFullscreen(event: MouseEvent): void {
|
openFullscreen(event: MouseEvent): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const midResImage = new MidResImage();
|
const midResImage = new MidResImage();
|
||||||
midResImage.src = this.src.replace('low', 'mid');
|
midResImage.src = this.src.replace('low', 'mid');
|
||||||
midResImage.alt = this.alt;
|
midResImage.alt = this.alt;
|
||||||
this.clicked.emit(midResImage);
|
this.clicked.emit({midResImage, event});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,28 @@
|
|||||||
<div class="mid-res-image-container">
|
<div
|
||||||
<img [src]="src" [alt]="alt" class="img" loading="lazy" />
|
class="mid-res-image-container"
|
||||||
<div class="panel" (click)="openFullscreen($event)">
|
[class.panel-shown]="!isInfoPanelHidden"
|
||||||
|
[class.panel-hidden]="isInfoPanelHidden"
|
||||||
|
(click)="closeFullscreen($event)"
|
||||||
|
>
|
||||||
|
<div class="image-container">
|
||||||
|
<img
|
||||||
|
[src]="src"
|
||||||
|
[alt]="alt"
|
||||||
|
loading="lazy"
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@if(isInfoPanelHidden) {
|
||||||
|
<app-button
|
||||||
|
label="ShowInfoPanel"
|
||||||
|
(click)="showInfoPanel($event)"
|
||||||
|
class="show-button"
|
||||||
|
tooltip="Show Image Info"
|
||||||
|
icon="assets/icons/push-left-svgrepo-com.svg"
|
||||||
|
></app-button>
|
||||||
|
} @else {
|
||||||
|
<div class="panel" (click)="$event.stopPropagation()">
|
||||||
<div class="infoPanelMenu">
|
<div class="infoPanelMenu">
|
||||||
@if(isInfoPanelHidden) {
|
|
||||||
<app-button
|
|
||||||
label="ShowInfoPanel"
|
|
||||||
(click)="showInfoPanel($event)"
|
|
||||||
class="show-button"
|
|
||||||
tooltip="Show Image Info"
|
|
||||||
icon="assets/icons/push-left-svgrepo-com.svg"
|
|
||||||
></app-button>
|
|
||||||
} @else{
|
|
||||||
<app-button
|
<app-button
|
||||||
label="HideInfoPanel"
|
label="HideInfoPanel"
|
||||||
(click)="hideInfoPanel($event)"
|
(click)="hideInfoPanel($event)"
|
||||||
@@ -18,7 +30,6 @@
|
|||||||
tooltip="Hide Image Info"
|
tooltip="Hide Image Info"
|
||||||
icon="assets/icons/push-right-svgrepo-com.svg"
|
icon="assets/icons/push-right-svgrepo-com.svg"
|
||||||
></app-button>
|
></app-button>
|
||||||
}
|
|
||||||
<app-button
|
<app-button
|
||||||
label="CloseFullscreen"
|
label="CloseFullscreen"
|
||||||
(click)="closeFullscreen($event)"
|
(click)="closeFullscreen($event)"
|
||||||
@@ -72,14 +83,15 @@
|
|||||||
></app-button>
|
></app-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-section">
|
<div class="comment-section">
|
||||||
<div class="header">
|
<!-- <div class="header">
|
||||||
<h4>Comment</h4>
|
<h4>Comment</h4>
|
||||||
<button (click)="toggleCommentForm($event)">Add Comment</button>
|
<button (click)="toggleCommentForm($event)">Add Comment</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h4>Comments</h4>
|
<h4>Comments</h4>
|
||||||
<div class="list"></div>
|
<div class="list"></div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,27 +4,329 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100dvw;
|
||||||
height: 100vh;
|
height: 100dvh;
|
||||||
background-color: rgba(42, 41, 38, 0.95);
|
background-color: rgba(42, 41, 38, 0.95);
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
flex-grow: 1;
|
||||||
-webkit-backdrop-filter: blur(5px);
|
-webkit-backdrop-filter: blur(5px);
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
|
|
||||||
|
// Entrada animada del contenedor
|
||||||
|
animation: fadeInContainer 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
|
||||||
|
// Animación coordinada desde la posición de la imagen clickeada
|
||||||
|
&.coordinated-animation {
|
||||||
|
animation: fadeInContainerCoordinated 0.5s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
forwards;
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
animation: coordinatedImageTransition 0.7s
|
||||||
|
cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
animation: slideInPanelDelayed 0.5s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
0.4s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-button {
|
||||||
|
animation: fadeInButtonDelayed 0.4s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
0.5s both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animaciones de salida coordinada
|
||||||
|
&.coordinated-exit {
|
||||||
|
animation: fadeOutContainerCoordinated 0.6s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
forwards;
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
animation: coordinatedImageExitTransition 0.6s
|
||||||
|
cubic-bezier(0.7, 0, 0.84, 0) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
animation: slideOutPanel 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-button {
|
||||||
|
animation: fadeOutButton 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animación de salida simple (sin coordinación)
|
||||||
|
&.simple-exit {
|
||||||
|
animation: fadeOutContainerSimple 0.4s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
forwards;
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
animation: zoomOutImage 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
animation: slideOutPanel 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-button {
|
||||||
|
animation: fadeOutButton 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInContainer {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-backdrop-filter: blur(0px);
|
||||||
|
backdrop-filter: blur(0px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-backdrop-filter: blur(5px);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Image section
|
// Image section
|
||||||
.img {
|
.image-container {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
height: 100vh;
|
height: 100dvh;
|
||||||
object-fit: contain;
|
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
cursor: zoom-in;
|
display: flex;
|
||||||
transition: transform 0.3s ease;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
// Animación de entrada de la imagen
|
||||||
|
animation: slideInImage 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: scale-down;
|
||||||
|
|
||||||
|
// Animación de zoom elegante
|
||||||
|
animation: zoomInImage 0.6s cubic-bezier(0.34, 1.56, 0.64, 1)
|
||||||
|
forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInImage {
|
||||||
|
from {
|
||||||
|
transform: translateX(-30px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes zoomInImage {
|
||||||
|
from {
|
||||||
|
transform: scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When panel is hidden, image takes full width
|
||||||
|
&.panel-hidden {
|
||||||
|
.image-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.panel-shown {
|
||||||
|
.image-container {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
.panel {
|
||||||
|
width: 25%;
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show panel button - positioned to always be visible
|
||||||
|
.show-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 4rem;
|
||||||
|
right: 4rem;
|
||||||
|
z-index: 1001;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
// Animación de entrada del botón con retraso
|
||||||
|
animation: fadeInButton 0.4s cubic-bezier(0.4, 0, 0.2, 1) 0.3s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInButton {
|
||||||
|
from {
|
||||||
|
transform: translateY(-20px) scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animaciones coordinadas para transición suave desde galería
|
||||||
|
@keyframes fadeInContainerCoordinated {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-backdrop-filter: blur(0px);
|
||||||
|
backdrop-filter: blur(0px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-backdrop-filter: blur(5px);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes coordinatedImageTransition {
|
||||||
|
from {
|
||||||
|
transform: translate(
|
||||||
|
calc(var(--start-x, 50vw) - 50vw),
|
||||||
|
calc(var(--start-y, 50vh) - 50vh)
|
||||||
|
)
|
||||||
|
scale(calc(var(--start-width, 200px) / 400));
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(
|
||||||
|
calc((var(--start-x, 50vw) - 50vw) * 0.3),
|
||||||
|
calc((var(--start-y, 50vh) - 50vh) * 0.3)
|
||||||
|
)
|
||||||
|
scale(0.9);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translate(0, 0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInPanelDelayed {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInButtonDelayed {
|
||||||
|
from {
|
||||||
|
transform: translateY(-20px) scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animaciones de salida coordinada
|
||||||
|
@keyframes fadeOutContainerCoordinated {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-backdrop-filter: blur(5px);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-backdrop-filter: blur(0px);
|
||||||
|
backdrop-filter: blur(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes coordinatedImageExitTransition {
|
||||||
|
from {
|
||||||
|
transform: translate(0, 0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(
|
||||||
|
calc((var(--end-x, 50vw) - 50vw) * 0.3),
|
||||||
|
calc((var(--end-y, 50vh) - 50vh) * 0.3)
|
||||||
|
)
|
||||||
|
scale(0.7);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translate(
|
||||||
|
calc(var(--end-x, 50vw) - 50vw),
|
||||||
|
calc(var(--end-y, 50vh) - 50vh)
|
||||||
|
)
|
||||||
|
scale(calc(var(--end-width, 200px) / 400));
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOutPanel {
|
||||||
|
from {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOutButton {
|
||||||
|
from {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(-20px) scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animaciones de salida simple
|
||||||
|
@keyframes fadeOutContainerSimple {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-backdrop-filter: blur(5px);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-backdrop-filter: blur(0px);
|
||||||
|
backdrop-filter: blur(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes zoomOutImage {
|
||||||
|
from {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panel section
|
// Panel section
|
||||||
.panel {
|
.panel {
|
||||||
width: 30%;
|
height: 100dvh;
|
||||||
height: 100vh;
|
|
||||||
background: variables.$primary-white;
|
background: variables.$primary-white;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -32,6 +334,12 @@
|
|||||||
box-shadow: -4px 0 20px rgba(42, 41, 38, 0.2);
|
box-shadow: -4px 0 20px rgba(42, 41, 38, 0.2);
|
||||||
border-left: 1px solid variables.$border-grey;
|
border-left: 1px solid variables.$border-grey;
|
||||||
padding: 2%;
|
padding: 2%;
|
||||||
|
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
// Animación de entrada del panel
|
||||||
|
animation: slideInPanel 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s both;
|
||||||
|
|
||||||
.infoPanelMenu {
|
.infoPanelMenu {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -178,11 +486,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes slideInPanel {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Responsive design
|
// Responsive design
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.img {
|
.image-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 60vh;
|
height: 60vh;
|
||||||
}
|
}
|
||||||
@@ -195,10 +514,17 @@
|
|||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust show button position for mobile
|
||||||
|
.show-button {
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.img {
|
.image-container {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,14 +1,23 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import {
|
||||||
import { SvgLoader } from '../svg/svg';
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
HostListener,
|
||||||
|
} from '@angular/core';
|
||||||
import { Button } from '../button/button';
|
import { Button } from '../button/button';
|
||||||
|
|
||||||
|
// view: src/app/mid-res-image/mid-res-image.html
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mid-res-image',
|
selector: 'mid-res-image',
|
||||||
imports: [Button],
|
imports: [Button],
|
||||||
templateUrl: './mid-res-image.html',
|
templateUrl: './mid-res-image.html',
|
||||||
styleUrl: './mid-res-image.scss',
|
styleUrl: './mid-res-image.scss',
|
||||||
})
|
})
|
||||||
export class MidResImage {
|
export class MidResImage implements OnInit, OnDestroy {
|
||||||
@Input({ required: true }) src: string = '';
|
@Input({ required: true }) src: string = '';
|
||||||
@Input({ required: true }) alt: string = '';
|
@Input({ required: true }) alt: string = '';
|
||||||
@Input() title: string = 'Title';
|
@Input() title: string = 'Title';
|
||||||
@@ -17,25 +26,126 @@ export class MidResImage {
|
|||||||
@Output() closed = new EventEmitter<void>();
|
@Output() closed = new EventEmitter<void>();
|
||||||
|
|
||||||
isInfoPanelHidden: boolean = false;
|
isInfoPanelHidden: boolean = false;
|
||||||
|
hasCoordinatedAnimation: boolean = false;
|
||||||
|
private originalPosition: any = null;
|
||||||
|
|
||||||
comments: string[] = [];
|
comments: string[] = [];
|
||||||
|
|
||||||
|
@HostListener('document:keydown', ['$event'])
|
||||||
|
handleKeyDown(event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.closeFullscreenAnimated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Bloquear el scroll del body cuando se abre la imagen en pantalla completa
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Verificar si hay información de transición disponible
|
||||||
|
const transitionData = (document as any).__imageTransition;
|
||||||
|
if (transitionData && Date.now() - transitionData.timestamp < 200) {
|
||||||
|
this.hasCoordinatedAnimation = true;
|
||||||
|
this.setupCoordinatedAnimation(transitionData);
|
||||||
|
// Limpiar la información de transición
|
||||||
|
delete (document as any).__imageTransition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupCoordinatedAnimation(transitionData: any): void {
|
||||||
|
// Guardar la posición original para la animación de salida
|
||||||
|
this.originalPosition = { ...transitionData };
|
||||||
|
|
||||||
|
// Aplicar CSS variables para la animación coordinada
|
||||||
|
const container = document.querySelector(
|
||||||
|
'.mid-res-image-container'
|
||||||
|
) as HTMLElement;
|
||||||
|
if (container) {
|
||||||
|
container.style.setProperty('--start-x', `${transitionData.x}px`);
|
||||||
|
container.style.setProperty('--start-y', `${transitionData.y}px`);
|
||||||
|
container.style.setProperty(
|
||||||
|
'--start-width',
|
||||||
|
`${transitionData.width}px`
|
||||||
|
);
|
||||||
|
container.style.setProperty(
|
||||||
|
'--start-height',
|
||||||
|
`${transitionData.height}px`
|
||||||
|
);
|
||||||
|
container.classList.add('coordinated-animation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
// Restaurar el scroll del body cuando se destruye el componente
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
hideInfoPanel(event: MouseEvent): void {
|
hideInfoPanel(event: MouseEvent): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.isInfoPanelHidden = !this.isInfoPanelHidden;
|
this.isInfoPanelHidden = true;
|
||||||
|
// tienes que coger el div 'panel' y cambiar su display a none
|
||||||
|
// Esto es para que no se vea el panel de información cuando se cierra
|
||||||
|
// Ademas, tienes que mostrar un botón para mostrar de nuevo el panel
|
||||||
|
document
|
||||||
|
.querySelector('.panel')
|
||||||
|
?.setAttribute('style', 'display: none;');
|
||||||
}
|
}
|
||||||
|
|
||||||
showInfoPanel(event: MouseEvent): void {
|
showInfoPanel(event: MouseEvent): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.isInfoPanelHidden = !this.isInfoPanelHidden;
|
this.isInfoPanelHidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
closeFullscreen(event: MouseEvent): void {
|
closeFullscreen(event: MouseEvent): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
this.closeFullscreenAnimated();
|
||||||
}
|
}
|
||||||
|
|
||||||
openFullscreen(event: MouseEvent): void {
|
private closeFullscreenAnimated(): void {
|
||||||
event.stopPropagation();
|
// Iniciar animación de salida
|
||||||
|
this.startExitAnimation();
|
||||||
|
|
||||||
|
// Emitir el evento de cerrado después de la animación
|
||||||
|
setTimeout(() => {
|
||||||
|
// Restaurar el scroll del body
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
this.closed.emit();
|
||||||
|
}, 600); // Duración de la animación de salida
|
||||||
|
}
|
||||||
|
|
||||||
|
private startExitAnimation(): void {
|
||||||
|
const container = document.querySelector(
|
||||||
|
'.mid-res-image-container'
|
||||||
|
) as HTMLElement;
|
||||||
|
if (container) {
|
||||||
|
// Remover animaciones de entrada
|
||||||
|
container.classList.remove('coordinated-animation');
|
||||||
|
|
||||||
|
// Si tenemos información de la posición original, configurar para retorno coordinado
|
||||||
|
if (this.hasCoordinatedAnimation && this.originalPosition) {
|
||||||
|
// Volver a establecer las variables CSS para la animación de retorno
|
||||||
|
container.style.setProperty(
|
||||||
|
'--end-x',
|
||||||
|
`${this.originalPosition.x}px`
|
||||||
|
);
|
||||||
|
container.style.setProperty(
|
||||||
|
'--end-y',
|
||||||
|
`${this.originalPosition.y}px`
|
||||||
|
);
|
||||||
|
container.style.setProperty(
|
||||||
|
'--end-width',
|
||||||
|
`${this.originalPosition.width}px`
|
||||||
|
);
|
||||||
|
container.style.setProperty(
|
||||||
|
'--end-height',
|
||||||
|
`${this.originalPosition.height}px`
|
||||||
|
);
|
||||||
|
container.classList.add('coordinated-exit');
|
||||||
|
} else {
|
||||||
|
// Animación de salida simple si no hay coordinación
|
||||||
|
container.classList.add('simple-exit');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editImage(event: MouseEvent): void {
|
editImage(event: MouseEvent): void {
|
||||||
@@ -48,21 +158,16 @@ export class MidResImage {
|
|||||||
|
|
||||||
downloadImage(event: MouseEvent): void {
|
downloadImage(event: MouseEvent): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
// debe descargar la imagen en resolución media (720p)
|
||||||
}
|
}
|
||||||
|
|
||||||
shareImage(event: MouseEvent): void {
|
shareImage(event: MouseEvent): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
// tiene que abrir un modal para compartir la imagen en redes sociales
|
||||||
}
|
}
|
||||||
|
|
||||||
buyImage(event: MouseEvent): void {
|
buyImage(event: MouseEvent): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
// tiene que abrir un modal para comprar la imagen
|
||||||
|
|
||||||
toggleCommentForm(event: MouseEvent): void {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.closed.emit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user