<template>
<div ref="imageView" class="image-view-container" v-show="visible" @click.self="close">
<div class="image-view-mask" @click="close"></div>
<div class="image-view-content">
<div class="image-view-header">
<button class="close-btn" @click="close">
<i class="fa fa-times"></i>
</button>
</div>
<div class="image-view-body">
<div class="image-wrapper" ref="imageWrapper">
<img
ref="previewImage"
:src="currentUrl"
:style="{ transform: `scale(${scale}) translate(${translateX}px, ${translateY}px)` }"
@load="handleImageLoad"
@wheel="handleMouseWheel"
@mousedown="startDrag"
@touchstart="startDrag"
/>
</div>
</div>
<div class="image-view-footer">
<button class="zoom-btn" @click="zoomIn">
<a-icon type="plus" />
</button>
<button class="zoom-btn" @click="zoomOut">
<a-icon type="minus" />
</button>
<button class="zoom-btn" @click="reset">
<a-icon type="redo" />
</button>
<button class="zoom-btn" @click="close">
<a-icon type="close" />
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ImagePreview',
data() {
return {
visible: false,
currentUrl: '',
scale: 1,
translateX: 0,
translateY: 0,
startX: 0,
startY: 0,
isDragging: false,
imageWidth: 0,
imageHeight: 0
}
},
computed: {
// 计算样式
overlayStyle() {
return {
opacity: this.visible ? 1 : 0,
pointerEvents: this.visible ? 'auto' : 'none'
}
}
},
methods: {
// 打开预览
open(url) {
this.currentUrl = url
this.visible = true
this.reset()
// 添加动画类
setTimeout(() => {
this.$refs.imageView.classList.add('active')
}, 10)
// 阻止页面滚动
document.body.style.overflow = 'hidden'
},
// 关闭预览
close() {
this.$refs.imageView.classList.remove('active')
// 延迟隐藏以等待动画完成
setTimeout(() => {
this.visible = false
// 恢复页面滚动
document.body.style.overflow = ''
}, 300)
},
// 处理图片加载
handleImageLoad() {
const img = this.$refs.previewImage
this.imageWidth = img.naturalWidth
this.imageHeight = img.naturalHeight
this.reset()
},
// 缩放相关
zoomIn() {
this.scale = Math.min(this.scale + 0.1, 3)
},
zoomOut() {
this.scale = Math.max(this.scale - 0.1, 0.5)
},
reset() {
this.scale = 1
this.translateX = 0
this.translateY = 0
},
// 拖拽相关
startDrag(e) {
if (e.type === 'mousedown' && e.button !== 0) return
e.preventDefault()
this.isDragging = true
const clientX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX
const clientY = e.type === 'touchstart' ? e.touches[0].clientY : e.clientY
this.startX = clientX - this.translateX
this.startY = clientY - this.translateY
// 添加移动和结束事件监听
if (e.type === 'mousedown') {
document.addEventListener('mousemove', this.drag)
document.addEventListener('mouseup', this.endDrag)
} else {
document.addEventListener('touchmove', this.drag)
document.addEventListener('touchend', this.endDrag)
}
},
drag(e) {
if (!this.isDragging) return
e.preventDefault()
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX
const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY
this.translateX = clientX - this.startX
this.translateY = clientY - this.startY
},
endDrag() {
this.isDragging = false
document.removeEventListener('mousemove', this.drag)
document.removeEventListener('mouseup', this.endDrag)
document.removeEventListener('touchmove', this.drag)
document.removeEventListener('touchend', this.endDrag)
},
// 鼠标滚轮缩放
handleMouseWheel(e) {
e.preventDefault()
const delta = e.deltaY > 0 ? -0.1 : 0.1
this.scale = Math.max(0.5, Math.min(this.scale + delta, 3))
}
},
beforeDestroy() {
// 确保移除事件监听
document.removeEventListener('mousemove', this.drag)
document.removeEventListener('mouseup', this.endDrag)
document.removeEventListener('touchmove', this.drag)
document.removeEventListener('touchend', this.endDrag)
}
}
</script>
<style scoped lang="less">
.image-view-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
&.active {
opacity: 1;
pointer-events: auto;
}
}
.image-view-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
}
.image-view-content {
position: relative;
width: 90%;
height: 90%;
max-width: 1600px;
max-height: 900px;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
}
.image-view-header {
padding: 15px;
display: flex;
justify-content: flex-end;
}
.close-btn {
background: transparent;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
outline: none;
transition: transform 0.2s ease;
&:hover {
transform: rotate(90deg);
}
}
.image-view-body {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.image-wrapper {
max-width: 100%;
max-height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
transition: transform 0.1s ease-out;
will-change: transform;
cursor: grab;
&:active {
cursor: grabbing;
}
}
}
.image-view-footer {
padding: 15px;
display: flex;
justify-content: center;
}
.zoom-btn {
background-color: rgba(255, 255, 255, 0.1);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
margin: 0 10px;
cursor: pointer;
outline: none;
transition: background-color 0.2s ease;
&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
}
</style>
12-09
2751

08-12
935

11-25
1706

07-08
1748
