点击外部检测器

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

从用户体验的角度来看,点击打开某个内容,然后不仅可以通过再次点击该内容来关闭它,还可以点击该内容外部来关闭它,这是一种合理的做法。Kitty Giraudel 刚刚 写了一篇关于此的博文。诀窍在于,一旦内容打开,您就在 window 上附加一个事件处理程序,该处理程序监视事件(例如另一个点击)。如果后续点击未发生在新打开的区域内,则将其关闭。例如,字面意思是 thing.contains(event.target)。我认为这是一个不错的技巧。

不过,还有很多小细节需要考虑。例如

我们必须停止在切换本身上的点击事件的传播。否则,它会传递到窗口点击监听器,并且由于切换不包含在菜单中,因此在我们尝试打开它时,它会立即关闭后者。

没错。不能这样,否则会破坏整个程序。

我们在 CodePen 上的很多地方都使用了相同的模式。就像 Kitty 一样,我们在 React 中实现了它。在查看我们的实现时,我发现它有一些值得一提的花哨功能。例如,我们的不是一个函数或钩子,而是一个组件包装器,我们像这样使用它

<ClickOutsideDetector 
  listen 
  onClickOutside={() => { closeTheThing(); }}
>
  A Menu or Modal or something.
</ClickOutsideDetector>

这样,它就是一个通用的包装器,我们可以将其用于任何“点击外部”的操作。这些花哨的功能包括

  • 您可以传入 component 属性,以便它不必显示为 <div>,而是您可以根据语义将其设置为任何您想要的内容。
  • listen 属性允许您切换它当前是否正在积极监听事件。就像一种快速绕过它的方法。
  • ESC 键按下等同于点击外部。
  • 处理触摸事件以及点击事件
  • 处理点击外部发生在 <iframe> 中的情况,在这种情况下,window 会发生 blur 事件而不是点击事件。
  • 允许您传入要忽略的元素,因此,与其使用 Kitty 记录的 stopPropagation 技巧,我们可以针对不会触发点击外部的元素进行具体指定。

这么多小细节!对我来说,这是现实世界开发中完美的例子。您只需要一个小的行为,但最终需要考虑大量因素和边缘情况,而且它永远不会真正完成。就在几个月前,我刚刚修改了我们的组件,因为我们使用的第三方工具更改了它们执行某些操作的方式,从而影响了页面上使用的 iframe。最终,我不得不监视 blur 事件,然后检查 document.activeElementclassList,以查看是否是该元素吞噬了点击外部事件!

无论如何,我在这里发布了一个 我们代码的稍微简化版本

而且我从 Kitty 的文章中看到了一些我们没有处理的内容,它就在第一句话中

我们需要一种方法来在点击菜单外部或跳出菜单时关闭菜单。

我加粗了重点。别担心,我现在已经在我们的代码中添加了一个 TODO 用于处理这种情况。