案例简介

在京东等网站当中,有这种商品放大的功能,就是把鼠标放在图片上,会在旁边出现放大的效果。

image.png

具体分析:

  • 鼠标经过对应小盒子,左侧中等盒子显示对应中等图片
  • 鼠标经过中盒子,右侧会显示放大镜效果的大盒子
  • 黑色遮罩盒子跟着鼠标来移动
  • 鼠标在中等盒子上移动,大盒子的图片跟着显示对应位置

实现过程

基础样式

首先先实现基本的样式,再配合JavaScript实现功能,具体代码在后面全部代码部分

JS实现部分

鼠标经过对应小盒子,左侧中等盒子显示对应中等图片

image.png

  1. 获取对应的元素
  2. 采取事件委托的形式,监听鼠标经过小盒子里面的图片, 注意此时需要使用 mouseover 事件,因为需要事件冒泡触发small
  3. 让鼠标经过小图片的爸爸li盒子,添加外选择框类,其余的li移除类(注意先移除,后添加)
  4. 鼠标经过小图片,可以拿到小图片的src, 可以做两件事
    • 让中等盒子的图片换成这个小图片的src
    • 让大盒子的背景图片,也换成这个小图片的src
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取对应的元素
const small = document.querySelector('.small')
const middle = document.querySelector('.middle')
const large = document.querySelector('.large')
//采取事件委托的形式,监听鼠标经过
small.addEventListener('mouseover', function(e) {
if(e.target.tagName === 'IMG') {
//移除li当中的active类
this.querySelector('.active').classList.remove('active')
//添加active类,active类为选择框
e.target.classList.add('active')
//将小图片赋值给中盒子和大盒子的图片
middle.querySelector('img').src = e.target.src
large.style.backgroundImage = `url(${e.target.src})`
}
})

鼠标经过中盒子,右侧会显示放大镜效果的大盒子

image.png

  1. 用到鼠标经过和离开,鼠标经过中盒子,大盒子利用 display 来显示和隐藏
  2. 鼠标离开不会立马消失,而是有200ms的延时,用户体验更好,所以尽量使用定时器做个延时 setTimeout
  3. 显示和隐藏也尽量定义一个函数,因为鼠标经过离开中等盒子,会显示隐藏,同时,鼠标经过大盒子,也会显示和隐藏
  4. 给大盒子里面的背景图片一个默认的第一张图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//监听鼠标经过和离开
middle.addEventListener('mouseenter', show)
middle.addEventListener('mouseleave', hide)
large.addEventListener('mouseenter', show)
large.addEventListener('mouseleave', hide)
//timeId用于清除计时器
let timeId = null
//显示函数,在鼠标经过中盒子和大盒子都显示,通过修改display的值,显示时清除计时器
function show() {
clearTimeout(timeId)
large.style.display = 'block'
}
//隐藏函数,当鼠标离开中盒子和大盒子都隐藏,并设有计时器
function hide() {
timeId = setTimeout(function () {
large.style.display = 'none'
}, 200)
}
  • 为什么需要在显示时清除计时器,因为如果没有清除计时器,在鼠标离开的200ms之内再次进入盒子,在时间到时,大盒子仍然会被隐藏。

黑色遮罩盒子跟着鼠标来移动

image.png

  1. 先做鼠标经过中等盒子,显示隐藏黑色遮罩的盒子
  2. 让黑色遮罩跟着鼠标来走, 需要用到鼠标移动事件mousemove
  3. 让黑色盒子的移动的核心思想:不断把鼠标在中等盒子内的坐标给黑色遮罩层 lettop 值,这样遮罩层就可以跟着移动了
    • 得到鼠标在页面中的坐标 利用事件对象的pageX
    • 得到中等盒子在页面中的坐标middle.getBoundingClientRect()
    • 鼠标在中盒子里面的坐标 = 鼠标在页面中的坐标 - 中等盒子的坐标
    • 黑色遮罩层不断得到鼠标在中盒子中的坐标,就可以移动起来了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//监听鼠标经过离开,设置黑色遮罩层显示隐藏
middle.addEventListener('mouseenter', function() {
layer.style.display = 'block'
})
middle.addEventListener('mouseleave', function() {
layer.style.display = 'none'
})
//监听鼠标移动,实时获取鼠标在页面的坐标,通过运算得到黑色遮罩层的坐标
middle.addEventListener('mousemove', function(e) {
//middle.getBoundingClientRect().left为中盒子在页面当中的坐标,left为x坐标,top为y坐标
let x = e.pageX - middle.getBoundingClientRect().left
let y = e.pageY - middle.getBoundingClientRect().top
//mx、my为移动坐标,指黑色遮罩层关于父级的位置
let mx = 0, my = 0;
if(x < 100) mx = 0
if(x >= 100 && x <= 300) mx = x - 100
if(x > 300) mx = 200
if(y < 100) my = 0
if(y >= 100 && y <= 300) my = y - 100
if(y > 300) my = 200
//将运算得到的坐标赋值给layer黑色遮罩层盒子
layer.style.left = mx + 'px'
layer.style.top = my + 'px'
})
  • 注意:
    1. 限定遮罩的盒子只能在middle 内部移动,需要添加判断
      • 因为基础样式设定为400 x 400的中盒子
      • 限定水平方向 大于等于0 并且小于等于 400
      • 限定垂直方向 大于等于0 并且小于等于 400
    2. 遮罩盒子移动的坐标:
      • 声明一个 mx 作为移动的距离
      • 水平坐标 x 如果小于100 ,则移动的距离mx就是 0,不应该移动
      • 水平坐标如果大于等于100 并且小于等于300,移动的距离就是 mx - 100 (100是遮罩盒子自身宽度的一半)
      • 水平坐标 如果大于300,移动的距离就是mx就是200,不应该在移动了
      • 垂直同理
    3. y坐标特殊,需要减去页面被卷去的头部
      • 为什么不用box.offsetLeftbox.offsetTop因为这俩属性跟带有定位的父级有关系,很容被父级影响,而getBoundingClientRect()不受定位的父元素的影响

鼠标在中等盒子上移动,大盒子的图片跟着显示对应位置

image.png

  1. 设置大盒子的背景图片尺寸为800 x 800,大盒子为400 x 400,所以实现放大效果
  2. 鼠标在中等盒子中的移动坐标,赋值给大盒子的图片,需要放大效果,所以需要两倍赋值
  3. 大盒子的图片需要反向移动,所以需要赋的值为负值
1
2
3
//这里的mx、my可以直接使用layer黑色遮罩层的移动坐标
large.style.backgroundPositionX = -2 * mx + 'px'
large.style.backgroundPositionY = -2 * my + 'px'

全部代码

HTML部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--middle为中盒子,layer为黑色遮罩层(用于表示放大区域)-->
<!--small为小盒子,为商品列表,large为大盒子,大盒子默认隐藏(display: none)-->
<div class="box">
<div class="pic">
<div class="middle">
<img src="img/blue.jpg" alt="">
<div class="layer"></div>
</div>
<div class="small">
<ul>
<li><img src="img/blue.jpg" alt="" class="active"></li>
<li><img src="img/green.jpg" alt=""></li>
<li><img src="img/pink.jpg" alt=""></li>
<li><img src="img/yellow.jpg" alt=""></li>
<li><img src="img/black.jpg" alt=""></li>
</ul>
</div>
<div class="large"></div>
</div>
</div>

CSS部分

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
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
li{
list-style: none;
}
.box {
display: flex;
width: 100%;
height: 420px;
background-color: #bfbfbf;
margin: 0 auto;
padding-top: 10px;
}
.pic{
display: flex;
width: 480px;
height: 400px;
margin: 0 auto;
}
.middle{/*中盒子*/
width: 400px;
height: 400px;
display: flex;
position: relative;
}
.middle img {
width: 400px;
height: 400px;
}
.small{/*小盒子*/
width: 70px;
margin-left: 10px;
}
.small ul{
padding: 0;
margin: 0;
width: 70px;
height: 400px;
}
.small li{
width: 70px;
height: 70px;
margin-bottom: 12.5px;
}
.small img {
width: 70px;
height: 70px;
}
.active {/*选择框类*/
border: 3px solid green;
}
.large {/*大盒子*/
width: 400px;
height: 400px;
background-color: rgb(255, 255, 255, 0.5);
position: absolute;
left: 1194px;
display: none;
background-image: url(img/blue.jpg);
background-size: 800px 800px;
}
.layer {/*黑色遮罩层*/
width: 200px;
height: 200px;
background-color: rgba(0, 0, 0, 0.3);
position: absolute;
display: none;
left: 0;
top: 0;
}

JavaScript部分

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
const small = document.querySelector('.small')
const middle = document.querySelector('.middle')
const large = document.querySelector('.large')
const layer = document.querySelector('.layer')

small.addEventListener('mouseover', function(e) {
if(e.target.tagName === 'IMG') {
this.querySelector('.active').classList.remove('active')
e.target.classList.add('active')
middle.querySelector('img').src = e.target.src
large.style.backgroundImage = `url(${e.target.src})`
}
})
middle.addEventListener('mouseenter', show)
middle.addEventListener('mouseleave', hide)
large.addEventListener('mouseenter', show)
large.addEventListener('mouseleave', hide)
let timeId = null

function show() {
clearTimeout(timeId)
large.style.display = 'block'
}
function hide() {
timeId = setTimeout(function () {
large.style.display = 'none'
}, 200)
}

middle.addEventListener('mouseenter', function() {
layer.style.display = 'block'
})
middle.addEventListener('mouseleave', function() {
layer.style.display = 'none'
})
middle.addEventListener('mousemove', function(e) {
let x = e.pageX - middle.getBoundingClientRect().left
let y = e.pageY - middle.getBoundingClientRect().top

let mx = 0, my = 0;
if(x < 100) mx = 0
if(x >= 100 && x <= 300) mx = x - 100
if(x > 300) mx = 200
if(y < 100) my = 0
if(y >= 100 && y <= 300) my = y - 100
if(y > 300) my = 200

layer.style.left = mx + 'px'
layer.style.top = my + 'px'

large.style.backgroundPositionX = -2 * mx + 'px'
large.style.backgroundPositionY = -2 * my + 'px'
})