最近面试遇到了一个没有见过的题,回答的也不好,回来后总结一下该题的实现思路:
题目:
-
如图,页面密密麻麻的布满A链接,想要你实现一个逻辑的圆,圆对用户不可见,圆心(a,b)半径为R,实现效果:圆内的链接可点击,圆外不可点击。伪代码实现。(css,js都可以):
题图例子
面试时,阅读完题目,思路是监听全局点击事件,获取点击的坐标,然后计算到圆心的距离是否超过半径R,超过距离时可以触发a链接的点击。
伪代码:
$(body).on('click', (e) => {
let position = e.position
let xLen = Math.abs(position.x - a)
let yLen = Math.abs(position.y - b)
let length = Math.sqrt(Math.pow(xLen, 2) + Math.pow(yLen, 2))
if (length > R) {
// 触发后续的点击
}
})
面试时有一些细节问题,比如圆的遮罩dom位置会影响事件传播的路径,捕获/冒泡阶段的事件捕获,是否能够触发hover等等。面试时并没有完整的实现,面试回来后,搜索了一下,有一个css属性可以控制某个特定元素能否成为鼠标事件的target,即控制元素是否触发鼠标事件: pointer-events - MDN。
接下来尝试用pointer-events
解决这题。
解题
完整Demo。
思路:
- 计算得到圆的位置、半径、宽高等等参数
- 监听鼠标移动事件,计算鼠标的位置
- 比较半径到鼠标距离,当小于半径时为mask添加
pointer-event: none
属性
html:
<body>
<div id="app">
button{click me}*100 <!-- emmet 表示这里有100个button-->
</div>
<div class="mask">
<div class="cycle"></div>
</div>
</body>
css:
body {
position: relative;
}
button {
/*... 样式省略,可以参考demo */
}
.mask {
background-color: #000;
opacity: 0.4;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.cycle {
position: absolute;
z-index: 10;
top: 40px;
left: 300px;
border: 2px solid #ccc;
height: 200px;
width: 200px;
border-radius: 100%;
}
.pointer-none {
pointer-events: none;
}
js:
const $body = $('body')
const $mask = $('.mask')
const $cycle = $('.cycle')
const cycleRect = $cycle.get(0).getBoundingClientRect() // 获取圆
// demo 中是一个圆,因此半径固定
const cycleRadius = cycleRect.width / 2
// 圆心坐标 (a, b)
const cycleLocation = {
// 获取元素左上角坐标
absoluteX: window.pageXOffset + cycleRect.x,
absoluteY: window.pageYOffset + cycleRect.y,
// 圆心
a: window.pageXOffset + cycleRect.x + cycleRadius,
b: window.pageYOffset + cycleRect.y + cycleRadius
}
// 监听鼠标事件,设置遮罩pointer-none属性
$body.on('mousemove', (e) => {
const x = e.pageX
const y = e.pageY
if (isOnCycle(x, y)) {
$mask.addClass('pointer-none')
} else {
$mask.removeClass('pointer-none')
}
})
// 判断是否在圆内
function isOnCycle (x, y) {
const xLen = Math.abs(x - cycleLocation.a)
const yLen = Math.abs(y - cycleLocation.b)
const distance = Math.sqrt(Math.pow(xLen, 2) + Math.pow(yLen, 2))
return distance <= cycleRadius
}
思考
以上的解答仅能在pc端上实现,原因是实现是通过监听mousemove
事件来获取鼠标坐标实现的,移动端上用户可能直接就点击到了圆内,先触发了点击事件再触发移动事件,但就在我的尝试过程当中没有找到实现完全中空的区域的方法,如果解决办法也欢迎大家来讨论。
此外,这次面试的是一家k12在线教育机构,这道实现题其实是一个现实业务场景的简化。如果有玩过手游的经验,一定会知道现在绝大多数手游在刚进入游戏时会有一段新手引导教程,需要用户点击指定的区域,完成教程的指引。这题也新手引导的一种实现,通过在页面上盖上一层遮罩,用户只能点击引导的地方。了解了这点后,我又去搜索了一下社区的一些实践,发现大部分实现都是通过控制点击区域元素z-index
属性来解决的,这里不做延伸,一些相关库的链接放到了最后的参考当中,可以用来拓展学习。
最后,虽然本次面试没有通过,不过也提醒了我,面试题有时候也可以是现实场景的简化,通过这类仿真题考验候选人的思考能力和基本能力。因此,学习不能只关注基础,实践上也要踏实,并多做思考,才能更好的将自己的技术用于业务。