zl程序教程

您现在的位置是:首页 >  前端

当前栏目

跨浏览器的mouseentermouseleave以及compareDocumentPosition的使用说明

浏览器 使用 说明 以及
2023-06-13 09:14:18 时间

写了这么久js应用我居然不知道这两个事件于是去google搜索了一番.才发现这两个事件是如此的优秀且好用...但搜索过程中发现好多人似乎不太明白这两个事件和mouseovermouseout真正的区别和用途.. 并且看到google中搜索得到的 一些朋友实现的跨浏览器解决方案.觉得似乎有些繁琐...所以产生了写一篇blog把这玩意说透彻的冲动...好啦.我们进入正题.

对于mouseover和mouseenter两个事件最大的区别就是mouseenter 是不冒泡的事件..这话怎么理解呢?

<divid=="parent">
<divid="child"></div>
</div>

对于mouseover时间来说当鼠标从其他元素移动到child节点时发生但此事件会冒泡所以会导致parent也出发mouseover

如果我们对parent注册了mouseover监听.则可能会产生一个什么问题呢?从parent移动到child同样出发parent的mouseover 有时候我们不希望这样的事情发生.这时候如果注册的监听是mouseenter的话 无论鼠标从任何元素移动到child时只有child元素发生mouseenter事件而其祖宗节点都不会因为冒泡而触发此事件...这就使我们可以彻底放弃 我们以往为了实现同样的逻辑 又要对子节点禁止mouseover冒泡或者又去判断事件源对象或判断srcElement/relatedTarget那样麻烦的方案.

对于mouseout和mouseleave也是如此当鼠标从child移出时mouseout同样会冒泡到parent从而触发parent的mouseout二mouseleave同样无此问题.

 

知道了区别剩下的事情就好办多了.遇到此类需求我们一律mouseentermouseleave就好..问题是这玩意只有ie支持怎么办呢?

我们只能用mouseover和mouseout来模拟 但是如果我们的模拟方案太过复杂那是在就意义不大了...这时候我们就可以借助xml方法compareDocumentPosition来彻底解决这个问题

我在我的类库中封装了一个方法专门用来判断某个节点的位置是否在另一个节点的子节点中...

ie可以用parentNode.contains(childNode)来判断这没什么好说的childNode在parentNodeDOM树中存在 那么就是true 

而contains方法ie专属那么我们就是借助!!(node.compareDocumentPosition(node2)&16)来实现同样的效果.

那么接下来我们就来谈谈compareDocumentPosition方法否则你看到上面的&16一定会困惑无比...

 

compareDocumentPosition方法在非ie浏览器都被实现到节点对象的中了 所以

node.compareDocumentPosition(node2)的作用就是比较node节点与node2节点之间的位置关系..

他的返回值是一个number值...

一般来说对我们有用的是以下几个值

1. 20 (2进制:010100)

2. 10 (2进制:  001010)

3. 4   (2进制:  000100)

4. 2   (2进制:  000010) 

5. 0   (2进制:  000000)

6. 2进100***的数...

那么这些2010420是怎么来的呢?我们接着往下看...

 

试试上这个2进制算法是专门用来解释两个节点之间的关系的

 

这个6位2进制数才是根本所在

第6位代表两个节点是否一个在DOM树上一个不在这个是针对整个DOM树而言的.也就是说如果两个都不在或两个都在则为0否则为1

第5位代表node是否是node2的父节点如果是则为1否则为0

第4位代表node是否是node2的子节点如果是则为1否则为0

第3位代表node是否在node2的前面如果是则为1否则为0(注:如果node是node2的父节则node同时也看做在node2的前面)

第2位代表node是否在node2的后面如果是则为1否则为0(注如果node是node2的子节点则node同时也看做在node2的后面)

最后如果23456位都为0即000000说明两个节点要么同时在DOM树上要么同时不在.且两个节点没有任何关系那么只有一种可能即两个节点是同一个节点...也就是node==node2

 

所以node.compareDocumentPosition(node2)&16位运算的结果是什么呢?以上几种可能的组合中只有010100 &010000 以及110***&16 的结果是010000 即返回16其他情况 均返回0然后用!!吧number隐式转型成boolean类型我们就可以判断出node是否是node2的父节点了...

所以我们也可以使用node2.compareDocumentPosition(node)&&8来判断node2是不是node的子节点道理是同样的

或者我们也可以直接node.compareDocumentPosition(node2)==20来做判断这样还可以省略!!做转型..也是可以的.

 

到了这里聪明你的一定发现这玩意是什么?分明就是c#中 flag标识枚举的用法...

c#中File.Attributes枚举可能同时具备n多属性比如一个文件可以是只读的同时还可以是隐藏的或者同时还可以是共享的.等等

那么用一个枚举值如何确定一个文件同时具备哪些属性又不产生冲突呢?答案于compareDocumentPosition是一样的...

 

我用js实现了一个类似逻辑来管理flag标识的类来说明这个问题代码如下

//flag类
   functionflag(sFlags){
       this._flags={};
       this._status=0;
       sFlags&&this.initFlags(sFlags);
   }
   flag.prototype={
       constructor:flag,
       initFlags:function(sFlags){//sFlags"状态1,状态2,状态3......初始化原始标识集合...
           sFlags=sFlags.split(",");
           for(vari=0,len=sFlags.length;i<len;i++)this._flags[sFlags[i]]=1<<i;

          //这里初始化标识的值如果同时具备n种状态则每种状态的值一定是000001 000010000100001000010000100000 
       },
       setStatus:function(sFlags){//sFlags"状态1,状态3......设置当前状态
           sFlags=sFlags.split(",");

    this._status=0;
           for(vari=0,len=sFlags.length;i<len;i++){
               this._check[sFlags[i]];
               this._status|=this._flags[sFlags[i]];
           }
       },
       addStatus:function(sFlags){//sFlags"状态1,状态3......检查当前状态标示是否有状态1和状态3如果没有则添加
           sFlags=sFlags.split(",");
           for(vari=0,len=sFlags.length;i<len;i++){
               this._check(sFlags[i]);
               if(this.hasStatus(sFlags[i]))continue;//判断是否已经有这个状态如果有跳过.
               this._status|=this._flags[sFlags[i]];

               //当前的状态值 与允许的标识值做|运算 即添加状态
           }
       },
       removeStatus:function(sFlags){
           sFlags=sFlags.split(",");
           for(vari=0,len=sFlags.length;i<len;i++){
               this._check(sFlags[i]);
               if(!this.hasStatus(sFlags[i]))continue;
               this._status^=this._flags[sFlags[i]];

               //当前状态值与要去掉的状态值做^运算 即删除状态
           }
       },
       clear:function(){
           this._status=0;
       },
       hasStatus:function(sFlags){//sFlags"状态1,状态3.....状态n.检查当前状态标识是否同时具备状态1和状态3以及状态n
           sFlags=sFlags.split(",");
           for(vari=0,len=sFlags.length;i<len;i++){
               this._check(sFlags[i]);
               if((this._status&this._flags[sFlags[i]])!=this._flags[sFlags[i]])returnfalse;

                //当前状态值与输入的状态做&运算 如果返回值不等于改状态的标识值则returnfalse.

               //比如010101&010000返回010000则说明当前标识值具备010000这个状态.

           }
           returntrue;
       },
       _check:function(sFlag){
           if(!sFlaginthis._flags)thrownewError("当前flag中不存在"+sFlag+"标识");

           //检查当前输入状态字符串是否是合法值.
       }
   }

 

 用法:

varfileStatus=newflag("readOnly,hidden,otherStatus");

fileStatus.setStatus("readOnly,hidden");

alert(fileStatus.hasStatus("readOnly"))//true;

alert(fileStatus.hasStatus("hidden"))//true;

alert(fileStatus.hasStatus("otherStatus"))//false;

 

 

最后我们回到正题我们借助compareDocumentPosition来模拟mouseentermouseleave

 

DOM结构:

<divid="dd"style="background-color:#369;width:50%;height:50%;position:absolute;left:25%;top:25%;">
   <divstyle="background-color:#ff0;width:50%;height:50%;position:relative;left:25%;top:25%">
       <divstyle="background-color:#789;width:50%;height:50%;position:relative;left:25%;top:25%">
           <divstyle="background-color:#123;width:50%;height:50%;position:relative;left:25%;top:25%">
               <divstyle="background-color:#456;width:50%;height:50%;position:relative;left:25%;top:25%">
               </div>
           </div>
       </div>
   </div>
</div>

 

js脚本:

 

 

   vardd=document.getElementById("dd")

   if(!+"\v1"){//ie
       dd.onmouseenter=function(){alert(1);};
       dd.onmouseleave=function(){alert(2);};
   }
   else{//others
       dd.onmouseover=function(e){
           vart=e.relatedTarget;
           vart2=e.target;
           this==t2&&t&&!(t.compareDocumentPosition(this)&8)&&alert(1);
       };
       dd.onmouseout=function(e){
           vart=e.relatedTarget;
           vart2=e.target;
           this==t2&&t&&!(t.compareDocumentPosition(this)&8)&&alert(2);
       };
   }

大功告成!!!!!