之前接触一个需求是点击弹出一个弹窗,再点击页面空白地方弹窗消失,类似这个:

android 音频焦点管理 APP监听到有其音频播放时需要压低其他声音 监听失去焦点事件_前端

        点击按钮出现一个dom节点,既可以再点击dom节点关闭也可以点击页面其他部分关闭,点击页面其他部分关闭的时候就用到了失焦触发这个方法。

        但是一般情况下,onblur事件只在input等元素中才有,而div是没有的。

        如果想让div也能拥有失焦方法,只能通过其他事件来达到类似onBlur的方法。

1.用防抖实现鼠标移出DIV消失

        利用onmouseout和事件来实现DIV失去焦点消失的功能。直接使用onmouseout来实现移出消失可能会有一个问题:假设你的按钮的位置和弹出的div的位置不是重合的那么会导致鼠标移动就会马上去触发onmouseout事件,从而没什么作用。

        利用防抖、onmouseout、onmouseover组合来实现一个体验很好的blur事件:

/** 
 *鼠标移动过div事件 
*/
function moveOverEvent(ele,outTimer) {    
    let overTimer = null;
    return function(){
    	clearTimeout(outTimer);     //div没有消失的情况下,在移动进来div,那么就清除上次移出的事件
    	clearTimeout(overTimer);    //防抖
    	overTimer = setTimeout(()=>{        
              ele.style.display = "block";
    	},500);                     
    }
}

/** 
  * 鼠标移出
*/
function 
    moveOutEvent(ele,outTimer) {
     return function(){            
        	clearTimeout(outTimer);     //防抖
        	outTimer = setTimeout(()=>{     //移动出去后等500ms,在消失这div                
        	ele.style.display = "none";            
        	},500);
    }
}

2.使用div的tabindex属性

        一般情况下,onblur事件只在input等元素中才有,而div却没有,因为div没有tabindex属性,所以要给div加上此属性。

<div tabindex="0" onfocus='alert("得到焦点");' onblur='alert("失去焦点");'></div>

        定义tabindex属性后,元素是默认会加上焦点虚线的,那么在IE中可以通过hidefocus="true"去除!其他浏览器通过outline=0进行去除!

        也可以通过样式去除:

<style>
 	p{
  	    resize: none;
       outline: none;
    }
</style>

        tabindex 全局属性 指示其元素是否可以聚焦,以及它是否/在何处参与顺序键盘导航

        tabinde还可以改变键盘上的 “Tab” 键在链接之间进行导航的顺序链接地址

        它接受一个整数作为值,具有不同的结果,具体取决于整数的值:

  • tabindex=负值

        通常是tabindex=“-1”,表示元素是可聚焦的,但是不能通过键盘导航来访问到该元素,用JS做页面小组件内部键盘导航的时候非常有用。

  • tabindex=“0”

        表示元素是可聚焦的,并且可以通过键盘导航来聚焦到该元素,它的相对顺序是当前处于的DOM结构来决定的。

  • tabindex=正值,

        表示元素是可聚焦的,并且可以通过键盘导航来访问到该元素;它的相对顺序按照tabindex 的数值递增而滞后获焦。如果多个元素拥有相同的 tabindex,它们的相对顺序按照他们在当前DOM中的先后顺序决定。

        注:tabindex 的最大值不应超过 32767。如果没有指定,它的默认值为 -1。

3.监听所有的点击事件

        添加监听事件监听ref或者id定义好的dom节点,从里面筛选出来这个div,除了点击这个div,其他地方全部隐藏。

<el-form-item label="样例" prop="test" id = "formItem">
      <div class="box" v-show="typePanelFlag" ref="boxRef"></div>
</el-form-item>

document.addEventListener('click', (e) => {
    let typePanel = document.getElementById("formItem");
    if(typePanel){
      if (!typePanel.contains(e.target)){
        this.typePanelFlag = false;
      }
    }
})

        但是这种方法有个问题,就是如果目标dom节点里面有很多的子节点,子节点的失焦也会触发该事件。但是我们看这个触发方法中的e参数,就能找到他的parentElement:

android 音频焦点管理 APP监听到有其音频播放时需要压低其他声音 监听失去焦点事件_点击事件_02

android 音频焦点管理 APP监听到有其音频播放时需要压低其他声音 监听失去焦点事件_点击事件_03

        在事件的target属性下

        所以从理论上来说我们可以网上迭代它的父节点然后去和通过ref获取到的节点进行比较,来判断这个节点是不是目标dom的子节点,从而判断该节点失焦的时候需不需要隐藏目标dom。

        但是缺点也是显而易见的:首先运算量庞大,算法复杂,其次判断逻辑很麻烦。

4.监听点击事件和鼠标移入相结合

        思路分析:

        可以通过在目标dom结构上挂载mousemove事件来判断鼠标是移入还是移出状态,触发鼠标移入时给一个移入状态,鼠标移出时同时将移入状态移除。然后捕捉点击事件,如果该点击事件是在移入状态存续的情况下发生的,则不触发目标dom隐藏事件,如果点击事件是在移出状态已经被移除的状态下发生的则触发隐藏目标dom的隐藏事件。

        这样就实现了当鼠标移动到页面空白处点击,弹窗关闭的效果。

        代码:

import React, { Fragment } from 'react'
import styles from './index.less'

// 鼠标移入判断标志数
let mouseIn = false

const Panel: React.FC<Props> = (props) => {
  const [show, setShow] = useState<boolean>(true)
  
  // 点击时触发事件
  const click = (e: any) => {
    if (!mouseIn) {
        setShow(false)
    }
  }

  useEffect(() => {
    // 添加点击事件监听函数
    document.addEventListener('click', click)
    // 卸载点击事件监听函数
    return () => {
      document.removeEventListener('click', click)
    }
  }, [])

  return (
      <Fragment>
        {show && (
            <div 
                 className={styles.wrap} 
                 // 鼠标移入事件            
                 onMouseEnter={() => (mouseIn = true)}
                 // 鼠标移出事件
                 onMouseLeave={() => (mouseIn = false)}
            >
        
                 面板内容
            </div>
        }
     </Fragment>
  )
}

export default Panel

         注意:

        因为鼠标移入移出触发有失效情况,可以最好加个定时器循环监听鼠标状态。

5.监听点击事件和鼠标移入相结合思路优化(终极版)

        思路分析:

        众所周知,当阻止冒泡的时候,点击面板和面板内部并不会触发document下挂载的监听点击事件,所以我们可以给面板设置阻止冒泡,然后以此为依据来区分鼠标的点击对象,当鼠标点击在面板或者面板内部的dom元素的时候,因为冒泡的阻挡不会触发关闭事件,但是在外部则没有冒泡就会触发点击事件。

        注意:

        未测试在页面下其他组件dom同样设置不冒泡的情况下是否生效的情况,此情况待注意!

import React, { Fragment } from 'react'
import styles from './index.less'

const Panel: React.FC<Props> = (props) => {
  const [show, setShow] = useState<boolean>(true)
  
  // 点击时触发关闭事件
  const click = (e: any) => {
     setShow(false)
  }

  useEffect(() => {
    // 添加点击事件监听函数
    document.addEventListener('click', click)
    // 卸载点击事件监听函数
    return () => {
      document.removeEventListener('click', click)
    }
  }, [])

  return (
      <Fragment>
        {show && (
            <div className={styles.wrap} onClick={(e) => e.stopPropagation()}>
                 面板内容
            </div>
        }
     </Fragment>
  )
}

export default Panel