先上效果,代码在下面
<template>
<!-- 图片列表 -->
<div class="image-list">
<img
:src="imageSrc"
v-for="(imageSrc, index) in images"
:key="index"
@click="openImage(index)"
@error="handleImageError(index)"
alt="Thumbnail Image"
/>
</div>
<!-- 点击的图片 -->
<div
v-if="selectedImage"
class="enlarged-image-box"
@wheel="onZoom"
@click="closeImage"
>
<img
:src="selectedImage"
draggable="true"
@dragstart="onDragStart"
@dragend="onDragEnd"
@load="onImageLoad"
@click.stop
ref="enlargedImageRef"
:style="{
left: `${imageLeft}px`,
top: `${imageTop}px`,
transform: `translate(-50%, -60%) scale(${scale}) ${
isFlippedX ? 'scaleX(-1)' : 'scaleX(1)'
} ${isFlippedY ? 'scaleY(-1)' : 'scaleY(1)'} rotate(${rotation}deg)`,
}"
/>
</div>
<!-- 控制按钮 -->
<div class="control-buttons" v-if="selectedImage" v-show="areControlsVisible">
<img
:src="buttonSrc"
v-for="(buttonSrc, index) in controlButtons"
:key="index"
@click="onControlButtonClick(index)"
@error="handleButtonError(index)"
:class="{ active: activeControlIndex === index }"
alt="Control Button"
/>
</div>
<!-- 中间显示的倍数 -->
<div class="zoom-percentage" v-if="isZoomVisible">
{{ zoomPercentage.toFixed(0) }}%
</div>
<!-- 全屏的背景图片 -->
<div class="fullscreen-overlay" v-if="isFullscreen">
<img :src="selectedImage" class="fullscreen-image" />
</div>
<!-- 点击叉号 -->
<div v-if="selectedImage" @click="closeImage" class="close-button">
<img src="/assets/叉号.svg" class="close-icon" />
</div>
<!-- 最下面显示的图片 -->
<div class="thumbnail-container" v-if="selectedImage">
<img
:src="imageSrc"
v-for="(imageSrc, index) in images"
:key="index"
:style="{ transform: `translateX(${-thumbnailOffsetLeft}px)` }"
class="thumbnail"
@click="onThumbnailClick(index)"
:class="{ active: activeThumbnailIndex === index }"
/>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
// 状态变量
const selectedImage = ref(""); // 当前显示的大图
const scale = ref(1); // 缩放比例
const isZoomVisible = ref(false); // 控制倍数显示与隐藏
const zoomTimeout = ref(null); // 定时器 ID
const oneToOneScale = ref(1); // 1:1 缩放比例
const imageLeft = ref(window.innerWidth / 2); // 图片初始 left
const imageTop = ref(window.innerHeight / 2); // 图片初始 top
const startPosition = ref({ x: 0, y: 0 }); // 拖拽开始时的鼠标位置
const isAtOneToOne = ref(false); // 是否处于1:1状态
const initialScale = ref(1); // 初始缩放比例
const lastScale = ref(1); // 上一次的缩放比例
const activeControlIndex = ref(null); // 当前激活的控制按钮索引
const activeThumbnailIndex = ref(null); // 当前激活的缩略图索引
const rotation = ref(0); // 图片旋转角度
const isFullscreen = ref(false); // 全屏遮罩
const areControlsVisible = ref(true); // 控制按钮显示与隐藏
const isFlippedX = ref(false); // 是否水平翻转
const isFlippedY = ref(false); // 是否上下翻转
const thumbnailOffsetLeft = ref(0); // 缩略图左侧的偏移量
const zoomPercentage = computed(() => scale.value * initialScale.value * 100); // 计算属性:百分比显示
const enlargedImageRef = ref(null);
// 图片数据
const images = ref([
"/assets/tibet-1.jpg",
"/assets/tibet-2.jpg",
"/assets/tibet-3.jpg",
"/assets/tibet-4.jpg",
"/assets/tibet-5.jpg",
"/assets/tibet-6.jpg",
"/assets/tibet-7.jpg",
"/assets/tibet-8.jpg",
"/assets/tibet-9.jpg",
]);
// 控制按钮数据
const controlButtons = ref([
"/assets/加号.svg", // 加号
"/assets/减号.svg", // 减号
"/assets/1_1.svg", // 1:1
"/assets/刷新.svg", // 刷新
"/assets/左箭头.svg", // 左箭头
"/assets/播放.svg", // 播放
"/assets/右箭头.svg", // 右箭头
"/assets/左旋转.svg", // 左旋转
"/assets/右旋转.svg", // 右旋转
"/assets/左右箭头.svg", // 左右箭头
"/assets/上下箭头.svg", // 上下箭头
]);
// 点击关闭
const closeImage = () => {
selectedImage.value = "";
};
// 重置所有相关状态的函数
const resetImageState = () => {
scale.value = 1;
imageLeft.value = window.innerWidth / 2;
imageTop.value = window.innerHeight / 2;
isAtOneToOne.value = false;
rotation.value = 0;
isFlippedX.value = false;
isFlippedY.value = false;
activeControlIndex.value = null;
};
// 图片点击事件
const openImage = (index) => {
activeThumbnailIndex.value = index;
selectedImage.value = images.value[index];
resetImageState(); // 重置所有状态
};
// 图片加载完成事件,用于计算初始缩放比例
const onImageLoad = () => {
if (enlargedImageRef.value) {
const naturalWidth = enlargedImageRef.value.naturalWidth;
const rect = enlargedImageRef.value.getBoundingClientRect();
const displayedWidth = rect.width;
// 计算初始缩放比例(显示尺寸与自然尺寸的比例)
initialScale.value = displayedWidth / naturalWidth;
// 初始化缩放比例为1
scale.value = 1;
// 设置 1:1 缩放比例
oneToOneScale.value = 1 / initialScale.value;
}
};
// 拖拽开始事件
const onDragStart = (event) => {
startPosition.value = { x: event.clientX, y: event.clientY }; // 记录开始时的鼠标位置
};
// 拖拽结束事件
const onDragEnd = (event) => {
imageLeft.value += event.clientX - startPosition.value.x; // 更新元素的左偏移量
imageTop.value += event.clientY - startPosition.value.y; // 更新元素的上偏移量
};
// 图片缩放处理函数
const onZoom = (event) => {
isZoomVisible.value = true;
// 重置定时器
clearTimeout(zoomTimeout.value);
zoomTimeout.value = setTimeout(() => {
isZoomVisible.value = false;
}, 1000);
// 判断滚动方向并设置新的缩放比例
if (event.deltaY < 0) {
// 向上滚动
scale.value += 0.1;
} else {
// 向下滚动
scale.value -= 0.1;
scale.value = Math.max(scale.value, 0.3); // 确保最小缩放比例为0.3
}
};
// 控制按钮点击事件
const onControlButtonClick = (index) => {
switch (index) {
case 0: // 加号
if (scale.value < 3) {
// 最大缩放比例为3
scale.value += 0.1;
isZoomVisible.value = true;
}
break;
case 1: // 减号
if (scale.value > 0.5) {
// 最小缩放比例为0.5
scale.value -= 0.1;
scale.value = Math.max(scale.value, 0.1);
isZoomVisible.value = true;
}
break;
case 2: // 1:1
if (!isAtOneToOne.value) {
lastScale.value = scale.value;
scale.value = oneToOneScale.value;
isAtOneToOne.value = true;
} else {
scale.value = lastScale.value;
isAtOneToOne.value = false;
}
isZoomVisible.value = true;
break;
case 3: // 刷新
resetImageState(); // 重置所有状态
isZoomVisible.value = true;
break;
case 4: // 左箭头
navigateToPreviousImage();
break;
case 5: // 全屏
toggleFullscreen();
break;
case 6: // 右箭头
navigateToNextImage();
break;
case 7: // 左旋转
rotation.value -= 90;
break;
case 8: // 右旋转
rotation.value += 90;
break;
case 9: // 左右翻转
isFlippedX.value = !isFlippedX.value; // 切换翻转状态
break;
case 10: // 上下翻转
isFlippedY.value = !isFlippedY.value; // 切换翻转状态
break;
default:
break;
}
// 设置当前激活的控制按钮索引
activeControlIndex.value = index;
// 重置定时器
clearTimeout(zoomTimeout.value);
zoomTimeout.value = setTimeout(() => {
isZoomVisible.value = false;
}, 1000);
};
// 导航到上一张图片
const navigateToPreviousImage = () => {
if (activeThumbnailIndex.value > 0) {
activeThumbnailIndex.value -= 1;
} else {
// 跳转到最后一张图片
activeThumbnailIndex.value = images.value.length - 1;
}
selectImage(activeThumbnailIndex.value);
};
// 导航到下一张图片
const navigateToNextImage = () => {
if (activeThumbnailIndex.value < images.value.length - 1) {
activeThumbnailIndex.value += 1;
} else {
// 跳转到第一张图片
activeThumbnailIndex.value = 0;
}
selectImage(activeThumbnailIndex.value);
};
// 选择图片并更新状态
const selectImage = (index) => {
selectedImage.value = images.value[index];
activeThumbnailIndex.value = index;
resetImageState(); // 重置所有状态
};
// 全屏切换函数
const toggleFullscreen = () => {
const element = document.documentElement; // 全屏元素可以是 `document.documentElement`,也可以是图片元素等
if (
!document.fullscreenElement &&
!document.webkitFullscreenElement &&
!document.mozFullScreenElement &&
!document.msFullscreenElement
) {
// 进入全屏
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
isFullscreen.value = true;
areControlsVisible.value = false;
} else {
// 退出全屏
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
isFullscreen.value = false;
areControlsVisible.value = true;
}
};
// 监听全屏变化以确保遮罩层正确隐藏
document.addEventListener("fullscreenchange", () => {
if (!document.fullscreenElement) {
isFullscreen.value = false; // 确保退出全屏时隐藏遮罩层
areControlsVisible.value = true;
}
});
// 点击缩略图事件
const onThumbnailClick = (index) => {
activeThumbnailIndex.value = index;
selectedImage.value = images.value[index];
const thumbnailWidth = 32; // 图片宽度(30px)加上左右间距(2px)
const centerIndex = 4; // 中间显示第5张图片(索引为4)
if (index <= centerIndex) {
thumbnailOffsetLeft.value = (centerIndex - index) * thumbnailWidth;
} else {
// 点击右边的图片,调整右边距,清零左边距
thumbnailOffsetLeft.value = (centerIndex - index) * thumbnailWidth;
}
resetImageState(); // 重置所有状态
};
// 错误处理函数:图片加载失败
const handleImageError = (index) => {
console.error(`图片加载失败: ${images.value[index]}`);
// 可选:设置为占位图
images.value[index] = "/assets/placeholder.png";
};
// 错误处理函数:控制按钮图片加载失败
const handleButtonError = (index) => {
console.error(`按钮图片加载失败: ${controlButtons.value[index]}`);
// 可选:设置为占位图
controlButtons.value[index] = "/assets/button-placeholder.png";
};
</script>
<style scoped>
/* 图片列表 */
.image-list {
width: 540px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.image-list img {
width: 170px;
height: 170px;
cursor: pointer;
object-fit: cover;
border-radius: 4px;
transition: transform 0.3s;
}
/* 放大的图片框架 */
.enlarged-image-box {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5); /* 背景加深 */
display: flex;
justify-content: center;
align-items: center;
z-index: 1003;
}
/* 放大的图片 */
.enlarged-image-box img {
height: 70%;
position: absolute;
z-index: 9999;
cursor: grab; /* 鼠标样式为抓取 */
user-select: none; /* 禁止用户选择图片 */
transition: transform 0.3s ease; /* 平滑缩放 */
}
/* 控制按钮图片 */
.control-buttons {
position: fixed;
top: 85%;
left: 50%;
transform: translate(-50%);
display: flex;
z-index: 1008;
}
.control-buttons img {
width: 20px;
height: 20px;
background-color: rgba(0, 0, 0, 0.5);
z-index: 5;
padding: 3px;
margin-right: 2px;
border-radius: 50%;
cursor: pointer;
transition: background-color 0.3s, transform 0.3s;
}
.control-buttons img.active {
background-color: rgba(0, 0, 0, 0.8);
}
/* 激活缩略图 */
.thumbnail-container .thumbnail.active {
filter: brightness(100%) !important;
}
/* 中间显示的倍数 */
.zoom-percentage {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 5px;
padding: 8px 12px;
font-size: 18px;
z-index: 1111;
opacity: 0.9;
transition: opacity 0.3s;
}
/* 全屏遮罩 */
.fullscreen-overlay {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
background-color: black;
z-index: 1010;
}
.fullscreen-image {
height: 100%;
position: fixed;
top: 50%;
left: 50%;
z-index: 2000;
transform: translate(-50%, -50%);
}
/* 点击叉号 */
.close-button {
background-color: rgba(0, 0, 0, 0.7);
position: fixed;
right: 0;
top: 0;
border-bottom-left-radius: 50px;
padding: 4px 4px 5px 8px;
z-index: 1005;
}
.close-icon {
height: 17px;
width: 17px;
}
/* 最下面显示的缩略图 */
.thumbnail-container {
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
bottom: 0;
left: 0;
height: 50px;
width: 100vw;
display: flex;
justify-content: center;
z-index: 1008;
}
.thumbnail-container .thumbnail {
height: 100%;
width: 30px;
margin-right: 2px;
filter: brightness(70%);
cursor: pointer;
transition: filter 0.3s;
}
.thumbnail-container .thumbnail:hover {
filter: brightness(50%);
}
.thumbnail-container {
transition: transform 0.8s ease;
}
</style>