如何解決 iframe 無法觸發(fā) clickOutside,iframeclickoutside

    前言

    在公司的一次小組分享會上,組長給我們分享了一個他在項目中遇到的一個問題。愛掏網(wǎng) - it200.com在一個嵌入 iframe 的系統(tǒng)中,當(dāng)我們點擊按鈕展開 Dropdown 展開后,再去點擊 iframe 發(fā)現(xiàn)無法觸發(fā) Dropdown 的 clickOutside 事件,導(dǎo)致 Dropdown 無法關(guān)閉。愛掏網(wǎng) - it200.com

    查看在線示例

    為什么無法觸發(fā) clickOutside

    目前大多數(shù)的 UI 組件庫,例如 Element、Ant Design、iView 等都是通過鼠標(biāo)事件來處理, 下面這段是 iView 中的 clickOutside 代碼,iView 直接給 Document 綁定了 click 事件,當(dāng) click 事件觸發(fā)時候,判斷點擊目標(biāo)是否包含在綁定元素中,如果不是就執(zhí)行綁定的函數(shù)。愛掏網(wǎng) - it200.com

    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);
    }
    復(fù)制代碼

    但 iframe 中加載的是一個相對獨立的 Document,如果直接在父頁面中給 Document 綁定 click 事件,點擊 iframe 并不會觸發(fā)該事件。愛掏網(wǎng) - it200.com

    知道問題出現(xiàn)在哪里,接下來我們來思考怎么解決?

    給 iframe 的 body 元素綁定事件

    我們可以通過一些特殊的方式給 iframe 綁定上事件,但這種做法不優(yōu)雅,而且也是存在問題的。愛掏網(wǎng) - it200.com我們來想想一下這樣一個場景,左邊是一個側(cè)邊欄(導(dǎo)航欄),上面是一個 Header 里面有一些 Dropdown 或是 Select 組件,下面是一個頁面區(qū)域。愛掏網(wǎng) - it200.com

    但這些頁面有的是嵌入 iframe,有些是當(dāng)前系統(tǒng)的頁面。愛掏網(wǎng) - it200.com如果使用這種方法,我們在切換路由的時候就要不斷的去判斷這個頁面是否包含 iframe,然后重新綁定/解綁事件。愛掏網(wǎng) - it200.com而且如果 iframe 和當(dāng)前系統(tǒng)不是同域(大多數(shù)情況都不是同域的),那么這種做法是無效的。愛掏網(wǎng) - it200.com

    添加遮罩層

    我們可以通過給 iframe 添加一個透明遮罩層,點擊 Dropdown 的時候顯示透明遮罩層,點擊 Dropdown 之外的區(qū)域或遮罩層,就派發(fā) clickOutside 事件并關(guān)閉遮罩層,這樣雖然可以觸發(fā) clickOutside 事件,但存在一個問題,如果用戶點擊的區(qū)域正好是 iframe 頁面中的某個按鈕,那么第一次點擊是不會生效的,這種做法對于交互不是很友好。愛掏網(wǎng) - it200.com

    監(jiān)聽 focusin 與 focusout 事件

    其實我們可以換一種思路,為什么一定要用鼠標(biāo)事件來做這件事呢?focusin 與 focusout 事件就很適合處理當(dāng)前這種情況。愛掏網(wǎng) - it200.com

    當(dāng)我們點擊綁定的元素之外時就觸發(fā) focusout 事件,這時我們可以添加一個定時器,延時調(diào)用我們綁定的函數(shù)。愛掏網(wǎng) - it200.com而當(dāng)我們點擊綁定元素例如 Dropdown 會觸發(fā) focusin 事件,這時候我們判斷目標(biāo)是否包含在綁定元素中,如果包含在綁定元素中就清除定時器。愛掏網(wǎng) - it200.com

    不過使用 focusin 與 focusout 事件需要解決一個問題,那就是要將綁定的元素變成 focusable 元素,那么怎么將元素變成 focusable 元素呢?我們通過將元素的 tabindex 屬性置為 -1 , 該元素就變成 focusable 的元素。愛掏網(wǎng) - it200.com

    需要注意的是,元素變成 focusable 元素之后,當(dāng)它獲取焦點的時候,瀏覽器會給它加上默認(rèn)的高亮樣式,如果你不需要這種樣式可以將 outline 屬性設(shè)置為 none。愛掏網(wǎng) - it200.com

    不過這種方法雖然很棒,但是也會存在一些問題,瀏覽器兼容性,下面是 MDN 給出的瀏覽器兼容情況,從圖中可以看出 Firefox 低版本不支持這個事件,所以你需要去權(quán)衡你的項目是否支持低版本的 Firefox 瀏覽器。愛掏網(wǎng) - it200.com

    使用 focus-outside 庫

    focus-outside?正是為了解決上述問題所創(chuàng)建的倉庫,代碼不到 200 行。愛掏網(wǎng) - it200.com使用起來也非常方便,它只有兩個方法,bind 與 unbind,不依賴其他第三方庫,并且支持為多個元素綁定同一個函數(shù)。愛掏網(wǎng) - it200.com

    為什么要給多個元素綁定同一個函數(shù),這么做是為了兼容 Element 與 Ant Design,因為 Element 與 Ant Design 會將 Dropdown 插入 body 元素中,它的按鈕和容器是分離的,當(dāng)我們點擊按鈕顯示 Dropdown,當(dāng)我們點擊 Dropdown 區(qū)域,這時候按鈕會失去焦點觸發(fā) focusout 事件。愛掏網(wǎng) - it200.com事實上我們并不希望這時關(guān)閉 Dropdown,所以我將它們視為同一個綁定源。愛掏網(wǎng) - it200.com

    這里說明下 Element 與 Ant Design 為什么要將彈出層放在 body 元素中,因為如果直接將 Dropdown 掛載在父元素下,會受到父元素樣式的影響。愛掏網(wǎng) - it200.com比如當(dāng)父元素有 overflow: hidden,Dropdown 就有可能被隱藏掉。愛掏網(wǎng) - it200.com

    簡單使用

    // import { bind, unbidn } from 'focus-outside'
    // 建議使用下面這種別名,防止和你的函數(shù)命名沖突了。愛掏網(wǎng) - it200.com
    import { bind: focusBind, unbind: focusUnbind } from 'focus-outside'
    
    // 如果你是使用 CDN 引入的,應(yīng)該這樣使用
    // 
    // const { bind: focusBind, unbind: focusUnbind } = FocusOutside
    
    const elm = document.querySelector('#dorpdown-button')
    // 綁定函數(shù)
    focusBind(elm, callback)
    
    function callback () {
      console.log('您點擊了 dropdown 按鈕外面的區(qū)域')
      // 清除綁定
      focusUnbind(elm, callback)
    }
    復(fù)制代碼

    查看在線示例

    注意

    前面說到過元素變成 focusable 元素后,當(dāng)它獲取焦點瀏覽器會給它加上高亮樣式,如果你不希望看到和這個樣式,你需要將這個元素的 CSS 屬性 outline 設(shè)置為 none。愛掏網(wǎng) - it200.comfocsout-outside 0.5.0 版本中新增 className 參數(shù),為每個綁定的元素添加 focus-outside 默認(rèn)類名,你要可以通過傳遞 className 參數(shù)自定義類名,當(dāng)執(zhí)行 unbind 函數(shù)時候會將類名從元素上刪除 。愛掏網(wǎng) - it200.com

    
    
    // js
    const elm = document.querySelector('#focus-ele')
    // 默認(rèn)類名是 focus-outside
    focusBind(elm, callback, 'my-focus-name')
    
    // css
    // 如果你需要覆蓋所有的默認(rèn)樣式,可以在這段代碼放在全局 CSS 中。愛掏網(wǎng) - it200.com
    .my-focus-name {
      outline: none;
    }
    復(fù)制代碼

    在 Vue 中使用

    // outside.js
    export default {
      bind (el, binding) {
        focusBind(el, binding.value)
      },
    
      unbind (el, binding) {
        focusUnbind(el, binding.value)
      }
    }
    
    // xx.vue
    復(fù)制代碼

    查看在線示例

    在 Element 中使用

    
          下拉菜單
        
        黃金糕獅子頭螺螄粉雙皮奶蚵仔煎復(fù)制代碼

    查看在線示例

    在 Ant Design 中使用

    import { Menu, Dropdown, Icon, Button } = antd
    import { bind: focusBind, unbind: focusUnbind } = 'focus-outside'
    
    function getItems () {
      return [1,2,3,4].map(item => {
        return {item} st menu item 
      })
    }
    
    class MyMenu extends React.Component {
      constructor (props) {
        super(props)
        this.menuElm = null
      }
    
      render () {
        return ({getItems()})
      }
    
      componentDidMount () {
        this.menuElm = ReactDOM.findDOMNode(this.refs.menu)
        if (this.menuElm && this.props.outside) focusBind(this.menuElm, this.props.outside)
      }
    
      componentWillUnmount () {
        if (this.menuElm && this.props.outside) focusUnbind(this.menuElm, this.props.outside)
      }
    }
    
    class MyDropdown extends React.Component {
      constructor (props) {
        super(props)
        this.dropdownElm = null
      }
    
      state = {
        visible: false
      }
    
      render () {
        const menu = ()
        return (
          
          
        )
      }
    
      componentDidMount () {
        this.dropdownElm = ReactDOM.findDOMNode(this.refs.divRef)
        if (this.dropdownElm) focusBind(this.dropdownElm, this.handleOutside)
      }
    
      componentWillUnmount () {
        if (this.dropdownElm) focusUnbind(this.dropdownElm, this.handleOutside)
      }
    
      handleOutside = () => {
        this.setState({ visible: false })
      }
    
      handleClick = () => {
        this.setState({ visible: !this.state.visible })
      }
    }
    
    ReactDOM.render(
      ,
      document.getElementById('container')
    )
    復(fù)制代碼

    查看在線示例

    總結(jié)

    iframe 元素?zé)o法觸發(fā)鼠標(biāo)事件,如果在嵌入 iframe 的系統(tǒng)中觸發(fā) clickOutside, 更好的做法是使用 focusin 與 focusout 事件,將 HTML 屬性 tabindex 設(shè)置為 -1 可以將元素變成 focusable 元素。愛掏網(wǎng) - it200.com瀏覽器會給 focusable 元素加上默認(rèn)的高亮樣式,如果你不需要這種樣式,可以將 CSS 屬性 outline 設(shè)置為 none。愛掏網(wǎng) - it200.com

    相關(guān)鏈接

    • MDN focusin
    • MDN focusout
    • focus-outside
    • 說說 tabindex 的那些事兒
    • HTML tabindex 屬性與 web 網(wǎng)頁鍵盤無障礙訪問




    原文發(fā)布時間為:2024年07月02日

    作者:掘金

    本文來源:掘金?如需轉(zhuǎn)載請聯(lián)系原作者
    聲明:所有內(nèi)容來自互聯(lián)網(wǎng)搜索結(jié)果,不保證100%準(zhǔn)確性,僅供參考。如若本站內(nèi)容侵犯了原著者的合法權(quán)益,可聯(lián)系我們進行處理。
    發(fā)表評論
    更多 網(wǎng)友評論0 條評論)
    暫無評論

    返回頂部

    主站蜘蛛池模板: 国产亚洲福利精品一区二区| 久久人妻av一区二区软件| 无码国产精品一区二区免费16| 国精产品一区一区三区有限在线| 久久无码人妻精品一区二区三区 | 久久精品视频一区二区三区| 精品一区二区三区无码视频| 亚洲欧美日韩一区二区三区在线 | 中文激情在线一区二区| 国产成人精品无码一区二区三区 | 亚洲日本精品一区二区| 亚洲日韩国产一区二区三区| 国产精品无码一区二区三区免费| 乱码人妻一区二区三区| 在线日产精品一区| 无码精品尤物一区二区三区| 国产爆乳无码一区二区麻豆| 国产丝袜美女一区二区三区| 国产福利精品一区二区| 人妻无码视频一区二区三区| 香蕉久久一区二区不卡无毒影院| 国产精品一区二区av| 国产亚洲一区二区精品| 中文字幕亚洲乱码熟女一区二区| A国产一区二区免费入口| 国产婷婷一区二区三区| 国产色精品vr一区区三区| 日本一区二区三区高清| 久久国产精品亚洲一区二区| 一区二区精品在线| 中文字幕乱码人妻一区二区三区| 亚洲一区二区三区免费观看| 波多野结衣一区二区| 日韩最新视频一区二区三| 亚无码乱人伦一区二区| 精品人妻一区二区三区四区在线| 无码人妻AⅤ一区二区三区水密桃| 美女视频一区二区三区| 熟女性饥渴一区二区三区| 亚洲国产成人一区二区精品区| 亚洲AV无码一区东京热|