Add digital catalog with shareable flipbook and ktg.montzar.ir deploy files.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
images: string[];
|
||||
index: number;
|
||||
mode: "slide" | "fade" | "card";
|
||||
zoom: boolean;
|
||||
flipMs: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
next: [];
|
||||
prev: [];
|
||||
}>();
|
||||
|
||||
const dir = ref<"next" | "prev">("next");
|
||||
const current = computed(() => props.images[props.index] ?? "");
|
||||
const transitionName = computed(() => `fx-${props.mode}-${dir.value}`);
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
(now, was) => {
|
||||
if (was === undefined) return;
|
||||
dir.value = now >= was ? "next" : "prev";
|
||||
},
|
||||
);
|
||||
|
||||
let startX = 0;
|
||||
let tracking = false;
|
||||
|
||||
function onPointerDown(ev: PointerEvent) {
|
||||
tracking = true;
|
||||
startX = ev.clientX;
|
||||
(ev.currentTarget as HTMLElement).setPointerCapture(ev.pointerId);
|
||||
}
|
||||
|
||||
function onPointerUp(ev: PointerEvent) {
|
||||
if (!tracking) return;
|
||||
tracking = false;
|
||||
const dx = ev.clientX - startX;
|
||||
if (Math.abs(dx) < 48) {
|
||||
const el = ev.currentTarget as HTMLElement;
|
||||
const x = ev.clientX - el.getBoundingClientRect().left;
|
||||
if (x < el.clientWidth / 2) emit("next");
|
||||
else emit("prev");
|
||||
return;
|
||||
}
|
||||
if (dx < 0) emit("next");
|
||||
else emit("prev");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="fx-stage"
|
||||
:class="[mode, { 'zoom-in': zoom }]"
|
||||
:style="{ '--flip-ms': `${flipMs}ms` }"
|
||||
@pointerdown.prevent="onPointerDown"
|
||||
@pointerup.prevent="onPointerUp"
|
||||
@pointercancel="tracking = false"
|
||||
>
|
||||
<Transition :name="transitionName">
|
||||
<div :key="`${mode}-${index}`" class="page-wrap">
|
||||
<img class="page-img" :src="current" alt="" draggable="false" />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fx-stage {
|
||||
position: relative;
|
||||
width: min(920px, 100%);
|
||||
margin: 0 auto;
|
||||
height: min(74vh, 780px);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
perspective: 2200px;
|
||||
touch-action: pan-y;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.fx-stage.card {
|
||||
perspective: 2600px;
|
||||
}
|
||||
|
||||
.page-wrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.page-img {
|
||||
max-width: min(100%, 820px);
|
||||
max-height: 72vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow:
|
||||
0 8px 10px rgba(0, 0, 0, 0.18),
|
||||
0 28px 60px rgba(0, 0, 0, 0.45),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
user-select: none;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.zoom-in .page-img {
|
||||
max-height: 90vh;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.fx-slide-next-enter-active,
|
||||
.fx-slide-next-leave-active,
|
||||
.fx-slide-prev-enter-active,
|
||||
.fx-slide-prev-leave-active,
|
||||
.fx-fade-next-enter-active,
|
||||
.fx-fade-next-leave-active,
|
||||
.fx-fade-prev-enter-active,
|
||||
.fx-fade-prev-leave-active,
|
||||
.fx-card-next-enter-active,
|
||||
.fx-card-next-leave-active,
|
||||
.fx-card-prev-enter-active,
|
||||
.fx-card-prev-leave-active {
|
||||
position: absolute;
|
||||
transition:
|
||||
transform var(--flip-ms, 1s) cubic-bezier(0.22, 1, 0.36, 1),
|
||||
opacity var(--flip-ms, 1s) cubic-bezier(0.22, 1, 0.36, 1),
|
||||
filter var(--flip-ms, 1s) ease;
|
||||
}
|
||||
|
||||
.fx-slide-next-enter-from {
|
||||
transform: translateX(-58%) scale(0.86) rotateY(-22deg);
|
||||
opacity: 0;
|
||||
filter: blur(10px);
|
||||
}
|
||||
.fx-slide-next-leave-to {
|
||||
transform: translateX(52%) scale(0.88) rotateY(16deg);
|
||||
opacity: 0;
|
||||
filter: blur(8px);
|
||||
}
|
||||
.fx-slide-prev-enter-from {
|
||||
transform: translateX(58%) scale(0.86) rotateY(22deg);
|
||||
opacity: 0;
|
||||
filter: blur(10px);
|
||||
}
|
||||
.fx-slide-prev-leave-to {
|
||||
transform: translateX(-52%) scale(0.88) rotateY(-16deg);
|
||||
opacity: 0;
|
||||
filter: blur(8px);
|
||||
}
|
||||
|
||||
.fx-fade-next-enter-from,
|
||||
.fx-fade-prev-enter-from {
|
||||
opacity: 0;
|
||||
transform: scale(1.12);
|
||||
filter: blur(16px) brightness(1.15);
|
||||
}
|
||||
.fx-fade-next-leave-to,
|
||||
.fx-fade-prev-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
filter: blur(10px) brightness(0.7);
|
||||
}
|
||||
|
||||
.fx-card-next-enter-from {
|
||||
transform: rotateY(-95deg) translateZ(80px) scale(0.92);
|
||||
opacity: 0.15;
|
||||
}
|
||||
.fx-card-next-leave-to {
|
||||
transform: rotateY(95deg) translateZ(80px) scale(0.92);
|
||||
opacity: 0.15;
|
||||
}
|
||||
.fx-card-prev-enter-from {
|
||||
transform: rotateY(95deg) translateZ(80px) scale(0.92);
|
||||
opacity: 0.15;
|
||||
}
|
||||
.fx-card-prev-leave-to {
|
||||
transform: rotateY(-95deg) translateZ(80px) scale(0.92);
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fx-slide-next-enter-active,
|
||||
.fx-slide-next-leave-active,
|
||||
.fx-slide-prev-enter-active,
|
||||
.fx-slide-prev-leave-active,
|
||||
.fx-fade-next-enter-active,
|
||||
.fx-fade-next-leave-active,
|
||||
.fx-fade-prev-enter-active,
|
||||
.fx-fade-prev-leave-active,
|
||||
.fx-card-next-enter-active,
|
||||
.fx-card-next-leave-active,
|
||||
.fx-card-prev-enter-active,
|
||||
.fx-card-prev-leave-active {
|
||||
transition-duration: 0.01ms;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user