之前有一次美团的面试,面试官问我:如果有两个元素,一个元素覆盖到另一个元素上,如何做到点击上面的元素触发下面的元素?
当时的我在工作生涯中其实并没遇到过这种问题,也没有想到为什么会有这种情况的出现,也只是凭着自己的工作经验想出一个可以通过父子元素冒泡的做法实现这个做法,面试官虽说这个方法确实可以实现这个需求,我向他询问应该如何实现这个功能的时候,才首次认识了我们在这篇文章中要了解的 pointer-events 属性。
# pointer-event 是什么?有什么作用?
pointer-events 属性用于设置元素是否对鼠标事件做出反应。
默认值为 auto,就是元素对鼠标事件做出反应,可设置为 pointer-events: none; 表示元素忽略鼠标事件。
用最简单的话来说,只要设置了 pointer-events: none; 任何鼠标事件都将被无效化(例如:点击事件、鼠标移入移除、css 的 hover 等)。
让我们上代码来看下没有设置 pointer-events: none; 的效果是什么
<!DOCTYPE html> | |
<html lang="zh"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Document</title> | |
</head> | |
<style> | |
#demo { | |
width: 200px; | |
height: 200px; | |
background-color: aqua; | |
text-align: center; | |
line-height: 200px; | |
} | |
#demo:hover { | |
background-color: bisque; | |
} | |
</style> | |
<body> | |
<div id="demo">我是测试的div</div> | |
<script> | |
const demo = document.getElementById('demo'); | |
demo.onclick = e => { | |
console.log('点击了'); | |
}; | |
demo.onmouseenter = e => { | |
console.log('移入了'); | |
}; | |
demo.onmouseleave = e => { | |
console.log('移出了'); | |
} | |
</script> | |
</body> | |
</html> |
上面的代码写了一个 div 元素,我们在这个元素上绑定了点击、鼠标移入、鼠标移出这三个 js 鼠标事件,并在 css 上绑定了 hover 样式,鼠标进入会改变元素的背景色,现在这个 demo 是这个样子的
这个就是正常我们元素对鼠标事件响应的样子,接下来我们在 demo 元素上加上 pointer-events: none; 属性 看一下是什么样子
<!DOCTYPE html> | |
<html lang="zh"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Document</title> | |
</head> | |
<style> | |
#demo { | |
width: 200px; | |
height: 200px; | |
background-color: aqua; | |
text-align: center; | |
line-height: 200px; | |
pointer-events: none; | |
} | |
#demo:hover { | |
background-color: bisque; | |
} | |
</style> | |
<body> | |
<div id="demo">我是测试的div</div> | |
<script> | |
const demo = document.getElementById('demo'); | |
demo.onclick = e => { | |
console.log('点击了'); | |
}; | |
demo.onmouseenter = e => { | |
console.log('移入了'); | |
}; | |
demo.onmouseleave = e => { | |
console.log('移出了'); | |
} | |
</script> | |
</body> | |
</html> |
上面代码仅增加了一行 pointer-events: none; 接下来我们看看元素对于鼠标事件是什么反应
我们可以看到代码中的 js 鼠标事以及 hover 全部都无效化了,元素没有对以上操作做出反应(鼠标点击选中文字是可以的)
现在我们知道了 pointer-events 的作用了,那我们来做一下文章开头说的面试题吧 一个元素覆盖在另一个元素,点击上面的元素触发下面元素的点击事件。
<!DOCTYPE html> | |
<html lang="zh"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Document</title> | |
</head> | |
<style> | |
#demo { | |
width: 350px; | |
height: 400px; | |
background-color: #f3f3f3; | |
position: fixed; | |
left: 50%; | |
transform: translateX(-50%); | |
color: red; | |
} | |
#bottom { | |
width: 200px; | |
height: 200px; | |
background-color: red; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
z-index: 1; | |
color: azure; | |
} | |
#top { | |
width: 300px; | |
height: 300px; | |
background-color: yellow; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
opacity: 0.4; | |
z-index: 2; | |
color: black; | |
} | |
</style> | |
<body> | |
<div id="demo"> | |
我是父元素盒子 | |
<div id="bottom">我是下面的元素</div> | |
<div id="top">我是上面的元素</div> | |
</div> | |
<script> | |
const bottom = document.getElementById('bottom'); | |
bottom.onclick = e => { | |
console.log('点击了'); | |
}; | |
bottom.onmouseenter = e => { | |
console.log('移入了'); | |
}; | |
bottom.onmouseleave = e => { | |
console.log('移出了'); | |
} | |
</script> | |
</body> | |
</html> |
在最上层盒子上加上 pointer-events: none;
之后
<!DOCTYPE html> | |
<html lang="zh"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Document</title> | |
</head> | |
<style> | |
#demo { | |
width: 350px; | |
height: 400px; | |
background-color: #f3f3f3; | |
position: fixed; | |
left: 50%; | |
transform: translateX(-50%); | |
color: red; | |
} | |
#bottom { | |
width: 200px; | |
height: 200px; | |
background-color: red; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
z-index: 1; | |
color: azure; | |
} | |
#top { | |
width: 300px; | |
height: 300px; | |
background-color: yellow; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
opacity: 0.4; | |
z-index: 2; | |
color: black; | |
pointer-events: none; | |
} | |
</style> | |
<body> | |
<div id="demo"> | |
我是父元素盒子 | |
<div id="bottom">我是下面的元素</div> | |
<div id="top">我是上面的元素</div> | |
</div> | |
<script> | |
const bottom = document.getElementById('bottom'); | |
bottom.onclick = e => { | |
console.log('点击了'); | |
}; | |
bottom.onmouseenter = e => { | |
console.log('移入了'); | |
}; | |
bottom.onmouseleave = e => { | |
console.log('移出了'); | |
} | |
</script> | |
</body> | |
</html> |
pointer-events 的基础知识我们就说完了,附上一个最近在实际工作中使用 pointer-events 完成的一个功能模块的图片吧(工作代码就不展示了,就当做是一个使用场景,以后有这种需求的话可以用 pointer-events 进行实现)。
需求:有一个球场的图片,里面分割了很多模块,点击某一模块展示一个模块被选中的样式。
当时的第一想法就是背景图上覆盖元素,图片上有 24 个模块,只需要循环 24 个元素覆盖到上面,点击的时候某个模块展示被选中的样式就好,做出来的效果就是这样的
这么做的问题就是,整个背景图都会被覆盖掉,球场线根本就看不到,尤其是我全都选中之后,根本就看不出这是个球场了,所以就稍加改动,让背景图覆盖我要展示选中的元素上面,通过给背景图所在元素上添加 pointer-events: none; 即可完美的实现我们要实现的功能了