一、分析原理
线上商城的商品详情页经常用到图片放大镜的设计,以京东为例,黄色图层部分为需要在右侧大框内放大展示的区域:
分析上图:
- 有四个区域:图片区域(宽高记为imgX,imgY),一个遮罩(黄色图层部分)(宽高记为maskX,maskY),一个放大的图片区域(宽高记为bigImgX,bigImgY),大图(宽高记为bigX,bigY)。
- 先确定遮罩层的移动:理论上,鼠标应该位于遮罩的中心点。但是当遮罩左上角已经和图片左上角重合时,移动鼠标,应该保持遮罩层位置固定,换言之,已经移动到边界了,不能再往外了。同理,右下角、右上角、左下角也是保持遮罩层位置固定。
- 遮罩层左侧距离left应为鼠标左侧距离减去遮罩宽度的一半:event.offsetX - maskX / 2。
- 遮罩层头部距离top应为鼠标顶部距离减去遮罩高度的一半:event.offsetY - maskY / 2。
- 当left < 0,left固定为0;当left > imgX - maskX,left固定为imgX - maskX。
- 当top < 0,top固定为0;当top > imgY - maskY,top固定为imgY - maskY。
- 由于放大的图片区域实际是对遮罩区域的放大,且为了保证图片不被拉伸,故而放大的图片区域实际上也是对遮罩图层的放大,则maskX / bigImgX = maskY / bigImgY。当然了,大图(bigX,bigY)是对小图的放大。则imgX / bigX = imgY / bigY。要保证移动遮罩的过程中刚好完成展示区域细节,则maskX / bigImgX = maskY / bigImgY = imgX / bigX = imgY / bigY。简言之,就是遮罩层与放大图片区域的比例应该和大小图放大比例保持一致。
- 假设放大比例是5倍,遮罩层是30*40,则放大图片区域就应该是150*200,小图宽高假设为50*50,则大图宽高就应该是250*250。大图在放大图片区域的偏移量应该是倍数乘遮罩的偏移量:
- 当left < 0,left固定为0;当left > imgX - maskX,left固定为imgX - maskX,大图左侧偏移 -5 * left。
- 当top < 0,top固定为0;当top > imgY - maskY,top固定为imgY - maskY,大图顶部偏移 -5 * top。
上述分析过程中,我们可以看出存在很多偏移量的计算,确定应该使用相对定位和绝对定位。
二、具体实现
笔者为了方便,这里使用vue来演示,其他框架也是一样的原理。
四个区域:
<div class="preview">
<img :src="imgUrl" v-if="imgUrl" class="img" @mousemove="(e) => handlerMouseMove(e)">
<el-image :src="defaultImg" alt="" class="img" v-else />
<!-- 遮罩 -->
<div class="mask" ref="mask"></div>
<!-- 放大的图片显示区域 -->
<div class="big">
<img :src="bigImgUrl" ref="bigImg" />
</div>
</div>
设置样式:
- 设置图片展示区域宽高为50*50。
- 遮罩和大图展示区域默认不展示,当鼠标移动到图片上才展示。
- 遮罩层设置为25*20,绝对定位,初始化为左上角。
- 设置大图展示区域,按5倍比例,设置为125*100。
- 大图相对于大图展示区域定位,初始为左上角。
.preview {
width: 50px;
height: 50px;
margin: 0 auto;
position: relative;
.img {
width: 50px;
height: 50px;
position: relative;
top: 0;
left: 0;
&:hover {
cursor: crosshair;
}
}
.img:hover~.mask,
.img:hover~.big {
display: block;
}
.mask {
width: 25px;
height: 20px;
background: rgb(109, 109, 109);
position: absolute;
left: 0;
top: 0;
display: none;
transform: translate3d(0px, 0px, 0px);
pointer-events: none;
}
.big {
width: 125px;
height: 100px;
position: absolute;
top: 0;
left: 110%;
overflow: hidden;
z-index: 998;
display: none;
background: white;
border-radius: 2px;
img {
width: 250px;
height: 250px;
position: absolute;
left: 0;
top: 0;
}
}
}
鼠标位置:event.offsetX、event.offsetY,按上文算出left和top,分别给mask遮罩和big大图展示区域进行定位:
handlerMouseMove(event) {
let mask = this.$refs.mask;
let big = this.$refs.big;
let left = event.offsetX - mask.offsetWidth / 2
let top = event.offsetY - mask.offsetHeight / 2
//当left < 0,left固定为0;当left > imgX - maskX,left固定为imgX - maskX,50-25=25
if (left < 0) {
left = 0
} else if (left > 25) {
left = 25
}
//当top < 0,top固定为0;当top > imgY - maskY,top固定为imgY - maskY,50-20=30
if (top < 0) {
top = 0
} else if (top > 30) {
top = 30
}
mask.style.left = left + 'px'
mask.style.top = top + 'px'
//大图左侧偏移 -5 * left,125/25 === 100/20 === 250/50 === 250/50 === 5
big.style.left = -5 * left + 'px'
//大图顶部偏移 -5 * top
big.style.top = -5 * top + 'px'
},