|
@@ -0,0 +1,411 @@
|
|
|
+<script setup>
|
|
|
+import { nextTick, onMounted, ref, toRefs, watch } from "vue";
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ value: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ isThumb: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ list: {
|
|
|
+ type: Array,
|
|
|
+ default: () => [],
|
|
|
+ },
|
|
|
+ initialIndex: {
|
|
|
+ type: Number,
|
|
|
+ default: 0,
|
|
|
+ },
|
|
|
+ clickHide: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const isShow = ref(false);
|
|
|
+const { value, initialIndex } = toRefs(props);
|
|
|
+isShow.value = value.value;
|
|
|
+const pIndex = ref(0);
|
|
|
+pIndex.value = initialIndex.value;
|
|
|
+let elWidth = 0; // 预览图片宽度
|
|
|
+let elHeight = 0; // 预览图片高度
|
|
|
+let elPosition = { x: 0, y: 0 }; // 预览图片当前的位置
|
|
|
+let elScale = { x: 1, y: 1 }; // 预览图片的缩放旋转
|
|
|
+let elRotate = 0;
|
|
|
+let screenWidth = 0; // 显示区域宽度
|
|
|
+let screenHeight = 0; // 显示区域高度
|
|
|
+const tempMove = { x: 0, y: 0 };
|
|
|
+let isDrag = false;
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.value,
|
|
|
+ () => {
|
|
|
+ isShow.value = props.value;
|
|
|
+ if (isShow.value) {
|
|
|
+ nextTick(() => {
|
|
|
+ initialPreview();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+);
|
|
|
+
|
|
|
+const emit = defineEmits(["update:value"]);
|
|
|
+watch(isShow, () => {
|
|
|
+ emit("update:value", isShow.value);
|
|
|
+});
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.initialIndex,
|
|
|
+ () => {
|
|
|
+ pIndex.value = props.initialIndex;
|
|
|
+ },
|
|
|
+);
|
|
|
+
|
|
|
+watch(pIndex, () => {
|
|
|
+ if (props.isThumb) {
|
|
|
+ nextTick(() => {
|
|
|
+ const prevThumbBox = HbPrevThumbBox.value;
|
|
|
+ if (prevThumbBox) {
|
|
|
+ prevThumbBox.scrollLeft = (pIndex.value - 1) * 100;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+function getElementPos(el) {
|
|
|
+ const pos = [parseInt(el.style.left + 0), parseInt(el.style.top + 0)];
|
|
|
+ return { x: pos[0], y: pos[1] };
|
|
|
+}
|
|
|
+
|
|
|
+function initialImage(el, type = "fit") {
|
|
|
+ const image = new Image();
|
|
|
+ image.src = props.list[pIndex.value];
|
|
|
+ if (image.complete) {
|
|
|
+ setImage(image, el, type);
|
|
|
+ } else {
|
|
|
+ image.onload = () => {
|
|
|
+ setImage(image, el, type);
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function setImage(image, el, type) {
|
|
|
+ const imgWidth = image.naturalWidth;
|
|
|
+ const imgHeight = image.naturalHeight;
|
|
|
+ const imgRatio = imgWidth / imgHeight;
|
|
|
+ if (type === "fit") {
|
|
|
+ elWidth = Math.ceil((screenHeight * imgRatio) / 2);
|
|
|
+ elHeight = Math.ceil(screenHeight / 2);
|
|
|
+ } else if (type === "original") {
|
|
|
+ elWidth = imgWidth;
|
|
|
+ elHeight = imgHeight;
|
|
|
+ }
|
|
|
+ // 设置预览图的初始宽高度
|
|
|
+ el.style.width = elWidth + "px";
|
|
|
+ el.style.height = elHeight + "px";
|
|
|
+ // 设置预览图的初始位置
|
|
|
+ el.style.left = Math.ceil((screenWidth - elWidth) / 2) + "px";
|
|
|
+ el.style.top = Math.ceil((screenHeight - elHeight) / 2) + "px";
|
|
|
+
|
|
|
+ elScale = { x: 1, y: 1 };
|
|
|
+ elRotate = 0;
|
|
|
+ el.style.transform = "scale(1) rotate(0deg)";
|
|
|
+}
|
|
|
+
|
|
|
+function initialPreview() {
|
|
|
+ if (props.list.length > 0) {
|
|
|
+ const prevImg = HbPrevImg.value;
|
|
|
+ if (prevImg) {
|
|
|
+ screenWidth = prevImg.parentNode.offsetWidth;
|
|
|
+ screenHeight = prevImg.parentNode.offsetHeight;
|
|
|
+ initialImage(prevImg);
|
|
|
+
|
|
|
+ if (props.isThumb) {
|
|
|
+ const prevThumb = HbPrevThumb.value;
|
|
|
+ prevThumb.style.width = props.list.length * 100 + "px";
|
|
|
+ }
|
|
|
+
|
|
|
+ prevImg.addEventListener("mousedown", (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ elPosition = getElementPos(prevImg);
|
|
|
+ tempMove.x = e.clientX;
|
|
|
+ tempMove.y = e.clientY;
|
|
|
+ isDrag = true;
|
|
|
+ });
|
|
|
+ prevImg.addEventListener("mousemove", (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ if (isDrag) {
|
|
|
+ document.body.style.cursor = "move";
|
|
|
+ const left = elPosition.x + e.clientX - tempMove.x;
|
|
|
+ const top = elPosition.y + e.clientY - tempMove.y;
|
|
|
+
|
|
|
+ prevImg.style.transition = "none";
|
|
|
+ prevImg.style.left = left + "px";
|
|
|
+ prevImg.style.top = top + "px";
|
|
|
+
|
|
|
+ tempMove.x = e.clientX;
|
|
|
+ tempMove.y = e.clientY;
|
|
|
+ elPosition = getElementPos(prevImg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ prevImg.addEventListener("mouseup", (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ document.body.style.cursor = "default";
|
|
|
+ isDrag = false;
|
|
|
+ prevImg.style.transition = "all .3s";
|
|
|
+ });
|
|
|
+ HbPrevWrap.value.addEventListener("wheel", (e) => {
|
|
|
+ // 前面是谷歌的,后面是火狐的
|
|
|
+ if (e.wheelDelta > 0 || e.detail > 0) {
|
|
|
+ if (elScale.x < 1.9) {
|
|
|
+ elScale.x += 0.1;
|
|
|
+ elScale.y += 0.1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (e.wheelDelta < 0 || e.detail < 0) {
|
|
|
+ if (elScale.x > 0.4) {
|
|
|
+ elScale.x -= 0.1;
|
|
|
+ elScale.y -= 0.1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置缩放
|
|
|
+ prevImg.style.transform = `scale(${elScale.x},${elScale.y}) rotate(${elRotate}deg)`;
|
|
|
+ elPosition = getElementPos(prevImg);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ isShow.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handlePrevIndex(index) {
|
|
|
+ const prevImg = HbPrevImg.value;
|
|
|
+ prevImg.style.transition = "none";
|
|
|
+ pIndex.value = index;
|
|
|
+ elScale = { x: 1, y: 1 };
|
|
|
+ elRotate = 0;
|
|
|
+ HbPrevImg.value.style.transform = "scale(1) rotate(0deg)";
|
|
|
+ setTimeout(() => {
|
|
|
+ prevImg.style.transition = "all .3s";
|
|
|
+ }, 30);
|
|
|
+}
|
|
|
+
|
|
|
+function prevLeft() {
|
|
|
+ if (pIndex.value > 0) {
|
|
|
+ pIndex.value--;
|
|
|
+ } else {
|
|
|
+ pIndex.value = props.list.length - 1;
|
|
|
+ }
|
|
|
+ const prevImg = HbPrevImg.value;
|
|
|
+
|
|
|
+ prevImg.style.transition = "none";
|
|
|
+ initialImage(prevImg);
|
|
|
+ setTimeout(() => {
|
|
|
+ prevImg.style.transition = "all .3s";
|
|
|
+ }, 30);
|
|
|
+}
|
|
|
+
|
|
|
+function prevRight() {
|
|
|
+ if (pIndex.value < props.list.length - 1) {
|
|
|
+ pIndex.value++;
|
|
|
+ } else {
|
|
|
+ pIndex.value = 0;
|
|
|
+ }
|
|
|
+ const prevImg = HbPrevImg.value;
|
|
|
+ prevImg.style.transition = "none";
|
|
|
+ initialImage(prevImg);
|
|
|
+ setTimeout(() => {
|
|
|
+ prevImg.style.transition = "all .3s";
|
|
|
+ }, 30);
|
|
|
+}
|
|
|
+
|
|
|
+function prevOriginal() {
|
|
|
+ const prevImg = HbPrevImg.value;
|
|
|
+ initialImage(prevImg, "original");
|
|
|
+ prevImg.style.transform = `scale(${elScale.x},${elScale.y}) rotate(${elRotate}deg)`;
|
|
|
+}
|
|
|
+
|
|
|
+function prevRotateLeft() {
|
|
|
+ const prevImg = HbPrevImg.value;
|
|
|
+ prevImg.style.transform = `scale(${elScale.x},${elScale.y}) rotate(${(elRotate -= 90)}deg)`;
|
|
|
+}
|
|
|
+
|
|
|
+function prevRotateRight() {
|
|
|
+ const prevImg = HbPrevImg.value;
|
|
|
+ prevImg.style.transform = `scale(${elScale.x},${elScale.y}) rotate(${(elRotate += 90)}deg)`;
|
|
|
+}
|
|
|
+
|
|
|
+function handleClose() {
|
|
|
+ if (props.clickHide) {
|
|
|
+ isShow.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ initialPreview();
|
|
|
+});
|
|
|
+
|
|
|
+const HbPrevWrap = ref();
|
|
|
+const HbPrevImg = ref();
|
|
|
+const HbPrevThumbBox = ref();
|
|
|
+const HbPrevThumb = ref();
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <div v-if="isShow" ref="HbPrevWrap" class="hb-prev-wrap">
|
|
|
+ <div class="hb-prev-mask" @click="handleClose" />
|
|
|
+ <img ref="HbPrevImg" class="hb-prev-img" alt="预览图" :src="list[pIndex]" />
|
|
|
+ <div v-if="props.isThumb" ref="HbPrevThumbBox" class="hb-prev-thumb-box">
|
|
|
+ <div ref="HbPrevThumb" class="hb-prev-thumb-list">
|
|
|
+ <div v-for="(item, i) in props.list" :key="i" class="hb-prev-thumb-item"
|
|
|
+ :class="pIndex === i ? 'hb-prev-thumb-item-active' : ''" @click="handlePrevIndex(i)">
|
|
|
+ <img class="hb-prev-thumb-img" :src="item" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="hb-prev-bar">
|
|
|
+ <div class="hb-prev-btn" title="向左旋转" @click="prevRotateLeft">
|
|
|
+ <i class="ri-anticlockwise-line" />
|
|
|
+ </div>
|
|
|
+ <div class="hb-prev-btn" title="前一张" @click="prevLeft">
|
|
|
+ <i class="ri-arrow-left-s-line" />
|
|
|
+ </div>
|
|
|
+ <div class="hb-prev-btn" title="图片原始大小" @click="prevOriginal">
|
|
|
+ <i class="ri-aspect-ratio-line" />
|
|
|
+ </div>
|
|
|
+ <div class="hb-prev-btn" title="后一张" @click="prevRight">
|
|
|
+ <i class="ri-arrow-right-s-line" />
|
|
|
+ </div>
|
|
|
+ <div class="hb-prev-btn" title="向右旋转" @click="prevRotateRight">
|
|
|
+ <i class="ri-clockwise-line" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="hb-prev-close" @click="isShow = false">
|
|
|
+ <i class="ri-close-line" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.hb-prev-wrap {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ z-index: 99999;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-mask {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ background-color: rgba(0, 0, 0, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-img {
|
|
|
+ position: fixed;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-bar {
|
|
|
+ position: fixed;
|
|
|
+ left: 50%;
|
|
|
+ bottom: 30px;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 250px;
|
|
|
+ height: 50px;
|
|
|
+ background-color: rgba(96, 98, 102, 0.71);
|
|
|
+ border-radius: 25px;
|
|
|
+ padding: 0 25px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-btn {
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ border-radius: 60px;
|
|
|
+ background-color: #999999;
|
|
|
+ color: #ffffff;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-btn:hover {
|
|
|
+ background-color: #aaaaaa;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-close {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ position: fixed;
|
|
|
+ top: 50px;
|
|
|
+ right: 50px;
|
|
|
+ font-size: 36px;
|
|
|
+ color: #f2f3f5;
|
|
|
+ cursor: pointer;
|
|
|
+ background-color: #999999;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ border-radius: 50px;
|
|
|
+ padding: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-close:hover {
|
|
|
+ background-color: #aaaaaa;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-thumb-box {
|
|
|
+ max-width: 50%;
|
|
|
+ overflow-x: scroll;
|
|
|
+ position: fixed;
|
|
|
+ left: 50%;
|
|
|
+ bottom: 90px;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ padding: 5px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background-color: rgba(96, 98, 102, 0.71);
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-thumb-box::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-thumb-list {
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-thumb-item {
|
|
|
+ height: 100px;
|
|
|
+ width: 100px;
|
|
|
+ margin: 0 5px;
|
|
|
+ padding: 5px 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-thumb-item:hover {
|
|
|
+ background-color: #888888;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-thumb-item-active {
|
|
|
+ padding: 0;
|
|
|
+ background-color: #888888;
|
|
|
+ border: 1px solid #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.hb-prev-thumb-img {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ object-fit: scale-down;
|
|
|
+}
|
|
|
+</style>
|