「面试题」web 实现一个中空的点击区域

最近面试遇到了一个没有见过的题,回答的也不好,回来后总结一下该题的实现思路:

题目:

  • 如图,页面密密麻麻的布满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
思路:

  1. 计算得到圆的位置、半径、宽高等等参数
  2. 监听鼠标移动事件,计算鼠标的位置
  3. 比较半径到鼠标距离,当小于半径时为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属性来解决的,这里不做延伸,一些相关库的链接放到了最后的参考当中,可以用来拓展学习。

最后,虽然本次面试没有通过,不过也提醒了我,面试题有时候也可以是现实场景的简化,通过这类仿真题考验候选人的思考能力和基本能力。因此,学习不能只关注基础,实践上也要踏实,并多做思考,才能更好的将自己的技术用于业务。

参考

pointer-events
driver.js
usablica/intro.js

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,712评论 1 45
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,142评论 1 32
  • 本节介绍各种常见的浏览器事件。 鼠标事件 鼠标事件指与鼠标相关的事件,主要有以下一些。 click 事件,dblc...
    许先生__阅读 2,499评论 0 4
  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,538评论 1 11
  • 1.几种基本数据类型?复杂数据类型?值类型和引用数据类型?堆栈数据结构? 基本数据类型:Undefined、Nul...
    极乐君阅读 5,589评论 0 106