商品图片放大镜效果

注意的点

  1. 取景框要设置csspointer-events: none;,否则鼠标移动到取景框上时,会触发鼠标移动事件,导致位置抽搐。
  2. 图片幽灵缝隙问题,可以考虑display: flex;或者其他的解决办法。
  3. 使用transform代替topleft,避免回流,优化性能。
  4. 预览图的定位问题。
  5. 考虑取景框边框对放大预览图片的影响。

直接贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<template>
<div class="container flex">
<div class="img" @mousemove="onMousemove">
<img src="../../public/logos/avatar1.png" alt="" ref="img">
<div ref="small" class="small"></div>
</div>
<div class="prev">
<img src="../../public/logos/avatar1.png" alt="" ref="prevImg">
</div>
</div>
</template>

<script setup>
import { onMounted, ref } from 'vue';

const scale = ref(1);
const img = ref(null);
const small = ref(null);
const prevImg = ref(null);
const offsetX = ref(0);
const offsetY = ref(0);

function checkElements() {
if (!img.value) throw new Error('找不到图片...');
if (!small.value) throw new Error('找不到取景框...');
if (!prevImg.value) throw new Error('找不到预览图...');
}

onMounted(() => {
checkElements();
const imgWidth = img.value.offsetWidth;
const smallWidth = small.value.offsetWidth;

scale.value = imgWidth / smallWidth;
prevImg.value.style.transform = `scale(${scale.value})`;
})

function onMousemove(e) {
checkElements();
offsetX.value = Math.max(0, e.offsetX);
offsetY.value = Math.max(0, e.offsetY);

const imgWidth = img.value.offsetWidth;
const imgHeight = img.value.offsetHeight;
const smallWidth = small.value.offsetWidth;
const smallHeight = small.value.offsetHeight;

updatePrev({ offsetX: offsetX.value, offsetY: offsetY.value, imgWidth, imgHeight, smallWidth, smallHeight });
}

function updatePrev({ offsetX, offsetY, imgWidth, imgHeight, smallWidth, smallHeight }) {
const position = {
x: Math.max(0, Math.min(offsetX - smallWidth / 2, imgWidth - smallWidth)),
y: Math.max(0, Math.min(offsetY - smallHeight / 2, imgHeight - smallHeight)),
}

small.value.style.transform = `translate(${position.x}px, ${position.y}px)`;
prevImg.value.style.transform = `translate(${-(position.x - smallWidth) * scale.value}px, ${-(position.y - smallHeight) * scale.value}px) scale(${scale.value})`;
}
</script>

<style lang='less' scoped>
.container {
width: 100%;
height: 700px;
display: flex;
justify-content: center;
border: 1px rgb(0, 0, 0) solid;
flex-direction: column;
align-items: center;
gap: 10px;
}

img {
width: 100%;
vertical-align: bottom;
}

.img {
width: 300px;
height: 300px;
border: 2px solid gray;
position: relative;
cursor: move;
display: flex;


.small {
width: 100px;
height: 100px;
border: 1.5px solid rgba(127, 255, 212, 1);
position: absolute;
top: 0;
left: 0;
background-color: rgba(127, 255, 212, 0.382);
z-index: 10;
pointer-events: none; // 防止鼠标移动到取景框上时,触发鼠标移动事件,导致位置抽搐。
box-sizing: border-box;
}
}

.prev {
width: 300px;
height: 300px;
border: 1.5px dashed gray;
overflow: hidden;
position: relative;
display: flex;

img {
position: absolute;
top: 0;
right: 0;
}
}
</style>