<template>
<div class=”magnifier-system”>
<!– 小图区域 –>
<el-popover
placement=”right”
trigger=”hover”
v-model=”isActive”
:popper-class=”‘magnifier-popover'”
>
<!– 大图区域内容 –>
<div class=”enlarged-area” :style=”bigContainerStyle”>
<img :src=”bigImg” alt=”Enlarged” :style=”enlargedImgStyle” />
</div>
<!– 小图区域触发器 –>
<template v-slot:reference>
<div
class=”preview-area”
ref=”previewArea”
@mousemove=”handleMouseMove”
@mouseenter=”activateMagnifier”
@mouseleave=”deactivateMagnifier”
>
<div class=”img-container” :style=”smallContainerStyle”>
<img :src=”smallImg” alt=”Preview” class=”preview-img” />
</div>
</div>
</template>
</el-popover>
</div>
</template>
<script>
export default {
name: ‘ResponsiveMagnifier’,
props: {
smallImg: String,
bigImg: String,
zoomLevel: { type: Number, default: 2 },
imgSmall: {
type: Object,
default: () => ({ W: 100, H: 100 }),
},
imgBig: {
type: Object,
default: () => ({ W: 300, H: 300 }),
},
imgNumber: {
type: Number,
default: 1,
},
imgMoveWidth: {
type: String,
default: ’15’,
},
},
data() {
return {
isActive: false,
lensPosition: { x: 0, y: 0 },
containerSize: { w: 0, h: 0 },
};
},
computed: {
smallContainerStyle() {
return {
width: `${this.imgSmall.W}px`,
height: `${this.imgSmall.H}px`,
};
},
bigContainerStyle() {
return {
width: `${this.imgBig.W}px`,
height: `${this.imgBig.H}px`,
};
},
imageOffset() {
const { x, y } = this.lensPosition;
const scaleX = this.imgBig.W / this.imgSmall.W;
const scaleY = this.imgBig.H / this.imgSmall.H;
// 计算镜头中心位置
const lensCenterX = x + this.imgSmall.W / this.zoomLevel / 2;
const lensCenterY = y + this.imgSmall.H / this.zoomLevel / 2;
return {
x: -(lensCenterX * scaleX – this.imgBig.W / 2),
y: -(lensCenterY * scaleY – this.imgBig.H / 2),
};
},
enlargedImgStyle() {
return {
transform: `translate(${this.imageOffset.x}px, ${this.imageOffset.y}px)`,
width: `${this.imgBig.W}px`,
height: `${this.imgBig.H}px`,
};
},
},
watch: {
‘imgSmall.W'() {
this.updateContainerSize();
},
‘imgSmall.H'() {
this.updateContainerSize();
},
‘imgBig.W'() {
this.updateContainerSize();
},
‘imgBig.H'() {
this.updateContainerSize();
},
},
mounted() {
this.updateContainerSize();
},
methods: {
updateContainerSize() {
this.$nextTick(() => {
this.containerSize = {
w: this.$refs.previewArea.querySelector(‘.img-container’).offsetWidth,
h: this.$refs.previewArea.querySelector(‘.img-container’).offsetHeight,
};
});
},
activateMagnifier() {
this.isActive = true;
},
deactivateMagnifier() {
this.isActive = false;
},
handleMouseMove(e) {
if (!this.isActive) return;
const container = this.$refs.previewArea.querySelector(‘.img-container’);
const rect = container.getBoundingClientRect();
let x = e.clientX – rect.left – window.pageXOffset;
let y = e.clientY – rect.top – window.pageYOffset;
x = Math.max(0, Math.min(x, rect.width));
y = Math.max(0, Math.min(y, rect.height));
const lensWidth = this.imgSmall.W / this.zoomLevel;
const lensHeight = this.imgSmall.H / this.zoomLevel;
this.lensPosition = {
x: Math.min(Math.max(x – lensWidth / 2, 0), rect.width – lensWidth),
y: Math.min(Math.max(y – lensHeight / 2, 0), rect.height – lensHeight),
};
},
},
};
</script>
<style scoped>
.magnifier-system {
display: flex;
align-items: flex-start;
}
.preview-area {
position: relative;
border: 1px solid #e0e0e0;
overflow: hidden;
cursor: crosshair;
}
.img-container {
position: relative;
overflow: hidden;
}
.preview-img {
width: 100%;
height: 100%;
object-fit: contain;
}
.magnifier-lens {
position: absolute;
backdrop-filter: blur(2px);
pointer-events: none;
transition: 0.05s linear;
}
.enlarged-area {
border: 1px solid #e0e0e0;
overflow: hidden;
background: #fff;
}
.enlarged-img {
position: absolute;
width: auto;
height: auto;
min-width: 100%;
min-height: 100%;
transition: transform 0.05s linear;
}
</style>
<style>
/* 覆盖el-popover的样式 */
.magnifier-popover {
padding: 0 !important;
border: none !important;
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
}
.magnifier-popover .popper__arrow {
display: none !important;
}
</style>