一个很棒的 click outside 解决方案 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
mt1992
V2EX    Javascript

一个很棒的 click outside 解决方案

  •  2
     
  •   mt1992 2020-06-12 13:42:15 +08:00 1394 次点击
    这是一个创建于 2000 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    在公司的一次小组分享会上,组长 给我们分享了一个他在项目中遇到的一个问题。在一个嵌入 iframe 的系统中,当我们点击 Dropdown 展开后,再去点击 iframe 发现无法触发 Dropdown 的 clickOutside 事件,导致 Dropdown 无法收起。

    为什么无法触发 clickOutside

    目前大多数的 UI 组件库,例如 Element 、Ant Design 、iView 等都是通过鼠标事件来处理, 下面这段是 iView 中的 clickOutside 代码,iView 直接给 Document 绑定了 click 事件,当 click 事件触发时候,判断点击目标是否包含在绑定元素中,如果不是就调用绑定的函数。

    bind (el, binding, vnode) { function documentHandler (e) { if (el.contains(e.target)) { return false; } if (binding.expression) { binding.value(e); } } el.__vueClickOutside__ = documentHandler; document.addEventListener('click', documentHandler); } 

    但 iframe 中加载的是一个相对独立的 Document,如果直接在父页面中给 Document 绑定 click 事件,点击 iframe 并不会触发该事件。

    知道问题出现在哪里,接下来我们来思考怎么解决?

    给 iframe 的 body 元素绑定事件

    我们可以通过一些特殊的方式给 iframe 绑定上事件,但这种做法不优雅,而且也是存在问题的。我们来想想一下这样一个场景,左边是一个侧边栏(导航栏),上面是一个 Header 里面有一些 Dropdown 或是 Select 组件,下面是一个页面区域。但这些页面有的是嵌入 iframe,有些是当前系统的页面。如果使用这种方法,我们在切换路由的时候就要不断的去判断这个页面是否包含 iframe,然后绑定 /解绑事件。但如果 iframe 和当前系统不是同域,那么这种做法是无效的。

    添加遮罩层

    我们可以通过给 iframe 添加一个透明遮罩层,点击 Dropdown 的时候显示透明遮罩层,点击 Dropdown 之外的区域或遮罩层,就关闭遮罩层并派发 clickOutside 事件,这样虽然可以触发 clickOutside 事件,但存在一个问题,如果用户点击的区域正好是 iframe 页面中的某个按钮,那么第一次点击是不会生效的,这种做法对于交互不是很友好。

    通过 focusin 与 focusout 事件

    其实我们可以换一种思路,为什么一定要用鼠标事件呢? focusin 与 focusout 事件就很适合处理当前这种情况,当我们点击非绑定的元素时触发 focusout 事件,如果是就添加一个定时器,延时调用我们绑定的函数。当我们点击绑定元素例如 Dropdown 会触发 focusin 事件,这时候我们判断目标是否包含在绑定元素中,如果包含在绑定元素中就清除定时器。

    不过使用 focusin 与 focusout 事件需要解决一个问题,那就是要将绑定的元素变成 focusable,那么怎么将元素变成 focusable 呢?通过将 tabindex 属性置为 -1 , 该元素就变成可由代码获取焦点。需要注意的是,元素变成 focusable 后,当它获取焦点浏览器会给它加上高亮样式,如果不需要这种样式可以将 outline 设置为 none 。

    不过这种方法虽然很棒,但是也存在一些问题,浏览器兼容性,下面是 MDN 给出的浏览器兼容情况,Firefox 低版本不兼容。

    使用 focus-outside 库

    focus-outside 是我为了解决上述问题所创建的仓库。使用起来也非常方便,它只有两个方法,bind 与 unbind,它不依赖任何其他库,并且支持为多个元素绑定一个函数。

    为什么要给多个元素绑定一个函数,这么做是为了兼容 Element,因为 Element 的 Dropdown 会被插入 body 元素中,它的按钮和容器是分离的,当我们点击按钮显示 Dropdown,当我们点击 Dropdown 区域,这时候按钮会失去焦点触发 focusout 事件。事实上我们并不希望这时关闭 Dropdown,所以我将它们视为同一个绑定源。

    这里说明下 Element 为什么要将弹出层放在 body 中,如果直接挂在父元素下,会受到父元素样式的影响。比如父元素有 overflow: hidden,弹出菜单就有可能被隐藏掉。

    并且 focus-outside 1.x 版本支持了 key 属性,可以通过 key 将一组不同的函数和元素绑定在一起。

    https://github.com/txs1992/focus-outside

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1006 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 22:45 PVG 06:45 LAX 14:45 JFK 17:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86