事件(下)

115 阅读7分钟

事件委托

事件委托的用法,给目标元素的父级绑定事件,然后通过父级元素去找目标元素

事件委托的概念

  • 把我们需要做的事情交给别人去做
  • 因为我们的冒泡机制,点击子元素,父级元素也会触发相同的事件
  • 所以我们可以通过子元素的事件委托给父级来做

事件委托的优缺点

  • 优点1:减少事件的绑定,带来的内存消耗,想能降低问题
  • 优点2:可以随意给动态的子元素,绑定事件
  • 缺点:代码比较繁琐,需要寻找目标target
<body>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
  <script>
    var oUl = docuemnt.querySelector('ul')
    
    oUl.addEventListener('click', function (evt) {
      var e = evt || window.event
      // 判断你点击的是 li
      if (e.target.nodeName != 'LI') {
        return;
      }
        // 确定点击的是 li
        // 因为当你点击在 ul 上面的时候,nodeName 应该是 'UL'
        // 去做点击 li 的时候该做的事情了
        console.log('我是 li,我被点击了')
    })
  </script>
</body>

事件监听

事件监听器, 是JS事件中的一种事件触发机制, 我们可以通过添加事件监听器的方式给元素添加事件及执行函数

  • 通过on来帮绑定事件,无法给同一个dom事件,绑定多个监听事件,会被后写的给覆盖
  • 通过添加事件监听器的方式addEventListener(),可以给同一个dom事件绑定多个监听事件
  • removeEventListener 可以移除事件监听

添加事件监听

  • box.addEventListener(“click”, func, false) : 给box元素添加点击事件(click), 以及事件执行函数func. 针对该函数的三个参数作说明:
    • 第一个参数(“click”) : 事件名称(前面没有on)
    • 第二个参数(func): 事件执行函数名称(没有双引号, 也没有())
    • 第三个参数(false/true) : true:捕捉(反向冒泡),默认false,为冒泡
    • 第三个参数为对象:
      • {once:true,capture:true}
      • once:可写可不写,写了且值为true,则只能点击一次
      • capture:捕获一定要写true
<style>
    .box{
        width: 300px;
        height: 300px;
        background-color: red;
    }
</style>

<body>
    <div class="box"></div>
    <button>绑定事件</button><button>移除事件</button>
    <script>
        var oBox = document.querySelector(".box");
        var oBtns = document.querySelectorAll("button");
        oBox.onclick = function(){
            console.log("11111");
        }

        function fn1(){
            console.log("22222");
        }

        function fn2(){
            console.log("33333");
        }

        oBtns[0].onclick = function(){
            oBox.addEventListener("click",fn1);
            oBox.addEventListener("click",fn2)
        }

        //移除事件监听
        oBtns[1].onclick =function(){
            oBox.removeEventListener("click",fn1);
        }
        
    </script>
</body>

注意问题

  • 第二个参数是一个回调函数(局部),而不是通过全局函数,在第二个参数调用函数名
  • 按钮重复点击,绑定事件就会越多
  • 核心问题,每点击一次,就会创建一个新的函数,创建新的函数,地址不同
  • 如何避免?要保证添加的函数的唯一性
  • 函数是引用类型,添加和移除必须是同一个地址,移除的不是同一个地址,移除会失效

移除事件监听

  • box.removeEventListener(“click”, func) : 将box元素的点击事件(click), 以及对应的事件执行函数func移除
  • 注: 这里只会删除使用box.addEventListener()方法添加的事件监听器
  • 移除监听事件必须跟添加事件监听的第三个参数保持一致,否则会失效
//移除事件监听
oBtns[1].onclick =function(){
    oBox.removeEventListener("click",fn1);
}

事件流

事件流是描述的从页面接受事件的顺序,当几个都具有事件的元素层叠在一起的时候, 那么你点击其中一个元素,并不是只有当前被点击的元素会触发事件,而层叠在你点击范围的所有元素都会触发事件。

  • 事件流,当元素叠在一起,点击某个元素,事件会传递
  • 事件流包括两种模式:冒泡和捕获
  • 事件流包括三个阶段:1.冒泡-->2.目标-->3.捕获
  • 事件冒泡: 从内向外传递事件
  • 事件捕获: 从外向内传递事件

事件冒泡

  • 事件冒泡: 从内向外传递事件
<style>
    div{
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-50%);
    }
    .big{
        width: 300px;
        height: 300px;
        background-color: red;
        .center{
            width: 200px;
            height:200px;
            background-color: yellow;
            .small{
                width: 100px;
                height: 100px;
                background-color: greenyellow;
            }
        }
    }
</style>
<body>
    <div class="big">big
        <div class="center">center
            <div class="small">
                small
            </div>
        </div>
    </div>
    <script>
        var oBig = document.body.querySelector(".big");
        var oCenter = oBig.querySelector(".center");
        var oSmall = oBig.querySelector(".small");

        oBig.onclick = function(){
            console.log("oBig");
        }
        oCenter.onclick = function(){
            console.log("oCenter");
        }
        oSmall.onclick = function(){
            console.log("oSmall");
        }
    </script>
</body>
  • 点击small盒子,会从里向外依次触发点击事件 image.png

阻止事件冒泡

  • 阻止事件冒泡都只会阻止自己不往外面冒泡出去,在指定不想再继续传递事件的节点的事件执行函数中使用
  • 方法1:取消冒泡 IE使用
e.cancelBubble = true; //ie下写法 了解
  • 方法2:停止传播 非IE使用
 e.stopPropagation();//重点
  • 兼容写法:
if(e.stopPropagation){
    e.stopPropagation();
}else{
    e.cancelBubble = true 
};

事件捕获

  • 事件捕获:从外向内传递事件
  • addEventListener 默认是冒泡(第三个参数不写或是false),捕获就需要把第3个参数,设置true 或是把第三个参数,设置一个对象
  • addEventListener 和 removeEventListener的第三个参数必须相同,同为true或false否则移除不会生效
<style>
    div{
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-50%);
    }
    .big{
        width: 500px;
        height: 500px;
        background-color: red;
        .center{
            width: 400px;
            height: 400px;
            background-color: yellow;
            .small{
                width: 300px;
                height: 300px;
                background-color: greenyellow;
            }
        }
    }
</style>

<body>
    <div class="big">big
        <div class="center">center
            <div class="small">
                small
            </div>
        </div>
    </div>
    <script>
        var oBig = document.body.querySelector(".big");
        var oCenter = oBig.querySelector(".center");
        var oSmall = oBig.querySelector(".small");
      
        oBig.addEventListener("click",function(){
            console.log("oBig");
        },{
            // once:true, //只能点击一次,可写可不写
            capture:true,   //捕获一定要写capture:true
        })
        oCenter.addEventListener("click",function(){
            console.log("oBoCenterox");
        },{
            // once:true,
            capture:true,
        })
        oSmall.addEventListener("click",function(){
            console.log("oSmall");
        },{
             //once:true,
            capture:true,
        })
    </script>
</body>

默认行为

  • 默认行为,就是不用我们注册,它自己就存在的事情

    • 比如我们点击鼠标右键的时候,会自动弹出一个菜单
    • 比如我们点击 a 标签的时候,我们不需要注册点击事件,他自己就会跳转页面
    • 表单的提交,重置行为
    • 图片的拖拽行为
  • 这些不需要我们注册就能实现的事情,我们叫做 默认事件

阻止默认行为

  • 方法1: return false
  • 方法2: e.returnValue = false
  • 方法3: e.preventDefault()
  • 方法4: 兼容写法
<body>
    <a href="http://baidu.com">百度</a>
    <script>
        var oA = document.querySelector("body a");
        oA.onclick = function(evt){
            //1.重点1
            // return false;   //语法  
            var e = evt || window.event;
            //2.(了解)
            // e.returnValue = false;
            //3.重点2
            // e.preventDefault(); 
            //4.兼容写法(了解)
            if(e.preventDefault){
                e.preventDefault()
            }else{
                e.returnValue = false
            }
        }
    </script>
</body>

阻止右键菜单

  • 在之前使用event对象的button属性时, 点击鼠标右键会弹出系统菜单, 如果我们想要创建自己的右键菜单, 则需要先阻止默认的右键菜单
document.oncontextmenu = function(){
      console.log("右键被按下");
      return false;
}

拖拽

  • 按住元素后移动位置, 最后松开的过程
  • 主要通过三个事件控制
    • onmousedown: 鼠标按下
    • onmousemove: 鼠标移动
    • onmouseup: 鼠标松开
<style>
    .box{
        width: 100px;
        height: 100px;
        background-color: skyblue;
        position: absolute;//盒子动不动,就看这个
    }
</style>
<body>
    <div class="box"></div>
    <script>
        var oBox = document.querySelector(".box");
        oBox.onmousedown = function(e){
        //获取鼠标点击位置到元素左上角的距离
        //可以使用e.offsetX和e.offsetY获取,但是在页面结构复杂情况下,不建议使用
            var disX = e.pageX-this.offsetLeft;
            var disY = e.pageY -this.offsetTop;
            //鼠标按住并移动,重新设置盒子的left和top
            //获取鼠标点击到页面左上角距离 - 鼠标点在元素自身的距离
            document.onmousemove = function(e){
                oBox.style.left = e.pageX- disX+"px";
                oBox.style.top = e.pageY -disY+"px";
            }
            //鼠标松开时,鼠标移动失效
            document.onmouseup = function(){
                document.onmouseup =  document.onmousemove=null;
            }
        }
    </script>
</body>

三大'DOM'的属性

offsetParent

  • 找带有就近定位的父元素,如果父级们都没有定位,默认找body 返回dom对象

offsetLeft,offsetTop

  • 获取当前元素到带有定位父级元素的距离,默认到body的距离 返回数值

offsetWidth,offsetHeight

  • 获取自身元素的宽与高 = 自身宽高+padding+border,就是不包含margin 返回数值

封装一个通用的方法,获取某个元素始终到body的距离

//obj是一个dom对象
function offset(obj){
    var o={
        left:obj.offsetLeft,
        top:obj.offsetTop
    };
    while(obj.offsetParent){
        //如果obj.offsetParent是null,就说明没有定位的父元素了,null就是false,false中断循环
        obj = obj.offsetParent;
        o.left = obj.offsetLeft + o.left;
        o.top +=obj.offsetTop;
    }
    return o;
}