feat: add fullscreen viewer
This commit is contained in:
67
spa/src/components/FullScreenImageViewer.vue
Normal file
67
spa/src/components/FullScreenImageViewer.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="fixed z-200 top-0 inset-0 flex justify-center align-center flex-col h-full bg-black">
|
||||
<Swiper
|
||||
:zoom="true"
|
||||
:navigation="true"
|
||||
:pagination="{
|
||||
type: 'fraction',
|
||||
}"
|
||||
:activeIndex="activeIndex"
|
||||
:modules="[Zoom, Navigation, Pagination]"
|
||||
class="mySwiper w-full h-full"
|
||||
@slider-move="vibrate"
|
||||
>
|
||||
<SwiperSlide v-for="image in images">
|
||||
<div class="swiper-zoom-container">
|
||||
<img :src="image.largeURL" :alt="image.alt"/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
|
||||
<button
|
||||
class="absolute z-50 text-white text-xl right-5 cursor-pointer"
|
||||
style="top: calc(var(--tg-safe-area-inset-top, 5px) + var(--tg-content-safe-area-inset-top, 5px))"
|
||||
@click="onClose"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-10">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {Navigation, Pagination, Zoom} from "swiper/modules";
|
||||
import {Swiper, SwiperSlide} from "swiper/vue";
|
||||
import {onMounted} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
images: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
activeIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['close']);
|
||||
let canVibrate = true;
|
||||
|
||||
function vibrate() {
|
||||
if (!canVibrate) return;
|
||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('soft');
|
||||
canVibrate = false;
|
||||
setTimeout(() => {
|
||||
canVibrate = true;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('medium');
|
||||
emits('close');
|
||||
}
|
||||
</script>
|
||||
@@ -1,100 +1,49 @@
|
||||
<template>
|
||||
<div class="aspect-w-4 aspect-h-3">
|
||||
<swiper
|
||||
:lazy="true"
|
||||
:pagination="pagination"
|
||||
:navigation="true"
|
||||
:modules="modules"
|
||||
class="mySwiper w-full min-h-[200px]"
|
||||
@touchMove="onTouchMove"
|
||||
<swiper-container ref="swiperEl" pagination="true">
|
||||
<swiper-slide
|
||||
v-for="image in images"
|
||||
lazy
|
||||
>
|
||||
<swiper-slide v-for="image in images">
|
||||
<img
|
||||
:src="image.url"
|
||||
:alt="image.alt"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div
|
||||
class="swiper-lazy-preloader swiper-lazy-preloader-white"
|
||||
></div>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</div>
|
||||
|
||||
<img
|
||||
:src="image.url"
|
||||
:alt="image.alt"
|
||||
loading="lazy"
|
||||
/>
|
||||
</swiper-slide>
|
||||
</swiper-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Swiper, SwiperSlide} from 'swiper/vue';
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/pagination';
|
||||
|
||||
import {Pagination} from 'swiper/modules';
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Swiper,
|
||||
SwiperSlide,
|
||||
const props = defineProps({
|
||||
images: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
props: {
|
||||
images: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
const swiperEl = ref(null);
|
||||
|
||||
setup() {
|
||||
const throttle = (func, delay) => {
|
||||
let lastCall = 0;
|
||||
return (...args) => {
|
||||
const now = Date.now();
|
||||
if (now - lastCall >= delay) {
|
||||
lastCall = now;
|
||||
func(...args);
|
||||
}
|
||||
};
|
||||
};
|
||||
let haptic = true;
|
||||
|
||||
const hapticTick = throttle(() => {
|
||||
const haptic = window?.Telegram?.WebApp?.HapticFeedback;
|
||||
if (haptic?.selectionChanged) {
|
||||
haptic.selectionChanged();
|
||||
} else if (haptic?.impactOccurred) {
|
||||
haptic.impactOccurred('light');
|
||||
}
|
||||
}, 100);
|
||||
onMounted(async () => {
|
||||
swiperEl.value?.addEventListener('swiperactiveindexchange', (event) => {
|
||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
||||
});
|
||||
|
||||
const onTouchMove = () => {
|
||||
hapticTick();
|
||||
};
|
||||
swiperEl.value?.addEventListener('swiperprogress', (num) => {
|
||||
if (haptic === false) return;
|
||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('light');
|
||||
haptic = false;
|
||||
setTimeout(() => haptic = true, 50);
|
||||
});
|
||||
|
||||
return {
|
||||
pagination: {
|
||||
clickable: true,
|
||||
dynamicBullets: true,
|
||||
},
|
||||
modules: [Pagination],
|
||||
onTouchMove,
|
||||
};
|
||||
},
|
||||
};
|
||||
swiperEl.value?.addEventListener('swiperreachbeginning', (event) => {
|
||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('medium');
|
||||
});
|
||||
swiperEl.value?.addEventListener('swiperreachend', (event) => {
|
||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('medium');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.product-swiper {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user