JavaScript-事件

事件基础

EventTarget  谁   事件目标
Event 信   事件
谁收到了信  		EventTarget收到了Event
HTMLDivElement->HTMLElement->Element->Node->EventTarget->Object

//创建一个事件目标对象
      var target=new EventTarget();

// target.addEventListener(事件类型,事件回调函数,是否捕获侦听/事件设置对象)
添加事件侦听器
// 给target对象侦听一个事件,事件的类型叫做xietian,收到这个事件后执行回调函数handler
      target.addEventListener("xiaobo",handler)

//创建一个事件对象-->消息体
//new Event(事件类型,事件初始化对象(可以不写))
      var evt=new Event("xiaobo")
      
      evt.num=10;
// 将事件对象发送给target事件目标对象(触发事件)
//用于触发特定类型的事件,以便事件能够在指定的元素上进行处理
      target.dispatchEvent(evt)


注意点
1. 事件侦听和抛发的事件目标必须一致  (target)
2. 事件侦听的目标对象必须和事件抛发目标对象一致 ("xiaobo")
3. 先侦听后抛发


	   function handler(e){
            // 参数e就是向target发送事件对象evt
            console.log(e)
            console.log(e.num)
        }

强耦合

//强耦合
      var o1 = {
         num: 10,
         a() {
            o2.b(this.num)
             console.log(this.num)//10
         }
      }
      var o2 = {
         b(n) {
            console.log(n)
         }
      }
      o1.a()//10

解耦 中介者模式

//解耦
      var o1 = {
         num: 10,
         a() {
            var evt = new Event("abc");
            evt.num = this.num
            document.dispatchEvent(evt)
         }
      }
      var o2 = {
         init() {
            document.addEventListener("abc", this.b);
         },
         b(e) {
            console.log(e.num)
         }
      }
      o2.init();
      o1.a();

事件原理

事件触发分为三个阶段
1、捕获阶段  外->2、目标阶段
3、冒泡阶段  内->外
事件默认触发的阶段是冒泡阶段

1. true/capture  // 是否捕获阶段触发
	  //可以用来改变捕获顺序 有true的先触发
      //全都有true  触发捕获阶段 外->内
      //没有true  就是冒泡阶段  内->外
div1.addEventListener("click",clickHandler1,true);
div1.addEventListener("click",clickHandler1,{capture:true});//两者效果相同

2. once //侦听一次,执行完成自动销毁事件
div3.addEventListener("click",clickHandler3,{capture:true,once:true});

3.e.stopPropagation(); //阻止冒泡 后续冒泡的父容器不再收到这个事件了
   阻止捕获的话: 就从外向里 停止到添加他的地方

4.e.stopImmediatePropagation(); //阻止后续的同类型事件触发函数继续执行

5.e.preventDefault()  阻止事件的默认后续的行为
         可以阻止以下事件
         1)表单提交
         2)表单的重置
         3)鼠标在表单中选中文本
         4)取消拖拽时禁拖标志
    
6.div.removeEventListenter()  //删除事件
//删除事件时,事件函数必须和侦听事件时的回调函数引用地址一致,同一个函数

7. 不会自动冒泡   {bubbles:true} 允许冒泡
 	var evt=new Event("click",{bubbles:true});
        div3.dispatchEvent(evt);

8.this 就是侦听的事件目标对象,谁.addEventListener,this就是谁
e.currentTarget 就是侦听的事件目标对象 和this相同  `用这个不用this`
 
 e.target 事件实际触发的目标对象 是指目标阶段最终被触发的对象(可以使用事件委托) 
e.srcElement(不使用了) 与e.target相同  IE浏览器兼容
 /*
 事件委托
 	不需要对每一个被点击的元素侦听点击事件,只需要给他们共同的父元素添加事件,点击后子元素的事件都会冒泡给父元素
 	所以父元素侦听的事件都会收到子元素被点击的情况,根据事件e.target 可以获取到被点击的子元素
*/
事件委托
<form action="http://www.baidu.com">
      <input type="text" name="user">
      <button type="submit">按钮</button>
</form>
   
   // 表单提交时,都会跳转到form表单中指出action中
      var form = document.querySelector("form");
      //form是父元素侦听者  submit是子元素
      form.addEventListener("submit", submitHandler)
      function submitHandler(e) {
         e.preventDefault()//阻止了跳转
         console.log("aaa")
      }

不同时期的事件区别

<!-- 行内事件 -->
    <button onclick="clickHandler1()">按钮1</button>
    <button id="bn1">按钮2</button>
//行内事件为什么要在其中执行 (必须加小括号)
因为:行内事件中,会将一个字符串存储给onclick这个标签属性,当触发事件时,会调用onclick标签属性中存储的字符串,并且将这个字符串通过eval转换为代码运行
此情况会导致事件函数中e不存在,可以通过window.event获取这个触发事件


补充内容:||  ??
e || 内容
e ?? 内容

e 是 undefinednullNaN0""false 就会获取|| 后面的内容
e 是 undefinednull就会获取?? 后面的内容
e=e || window.event 若e不存在把window.event赋给e


//   当自定义抛发事件时,默认是不会冒泡的,因此在设置new Event()第二个参数写一个对象,对象中设置bubbles:true就会让这个事件冒泡
      var evt=new Event("click",{bubbles:true});
      div2.dispatchEvent(evt);


`onclick 特征`
1、因为onclick是一个属性,这个属性只能赋值一个函数,因此,触发时只能执行一个函数
2、这个方法时无法判断捕获阶段还是冒泡阶段,因此都被锁死为冒泡阶段
3、因为早期浏览器支持的问题,
                e.cancelBubble=true取消冒泡     现在解决方法: e.stopPropagation()
                e.returnValue=false 阻止默认默认行为   现在解决方法: e.preventDefault()
4、使用onclick经常性就会直接将代码写出一个匿名函数,这时候代码逐渐趋向于过程性开发,形成回调地狱
5. onclick在发展称为addEventListener的过程中出现了一个事件 `attachEvent` IE8及以下浏览器才可以用
bn.attachEvent("onclick",clickHandler);//添加事件
bn.detachEvent("onclick",clickHandler);//删除事件
6. 取消事件  点击之后没有意义的话就取消事件
        	 bn.onclick=function(){
                bn.onclick=null;//取消事件
       		 }

Event 基础事件类型

1.submit/reset
这两个事件只针对表单form侦听
        	表单输入完成后点击submit按钮或者敲回车都会提交
            	`侦听 重置按钮和提交按钮`
form.addEventListener("submit",submitHandler);
form.addEventListener("reset",resetHandler);

补充://收集数据   new FromData(表单)
 //只能遍历文本类型
var fd=new FormData(form)
        for(var [name,value] of fd){
            console.log(name,value)
        }
方法:
1)// 向表单数据添加新的数据 
   fd.set()	set只能设置一个键一个值
   fd.append()	append可以为一个键设置多个值
2)// 删除键对应的所有值
   fd.delete()
3)// 获取值
   fd.get()	仅能获取到第一个值
   fd.getAll()	获取到key对应的所有值
4)// 判断key是否存在
   fd.has()
5)// 可以使用forEach
fd.forEach((value,key)=>{
  console.log(key,value)
 })
6)// 可以使用for of
 //只能遍历文本类型
for (var [name, value] of fd) {
  console.log(name, value);
 }

2.change事件  //当表单元素的value发生改变,并且失焦后触发 change是可以冒泡的,因此,change可以针对表单元素也可以针对表单侦听

3.select事件	//不是针对select标签,也不能针对div,只针对input输入框和textArea文本域
          input.selectionStart);// 选择的起始位置
    	 input.selectionEnd);// 选择的结束位置
    	 input.setSelectionRange(2,6,"none"); // 限制选中范围  当获得焦点的时候自动选中

4.scroll 滚动条滚动事件 //当滚动条滚动,触发事件

5.load 加载 //当加载完成 触发load事件

6.error //当发生错误时,触发error事件

事件属性罗列

1.e.isTrusted  //判断是否是系统触发还是自定义抛发 系统触发就是true,自定义抛发就是false

2.e.bubbles //在自定义抛发时,可以设置这个属性为运行冒泡,如果是系统触发,默认是运行冒泡的,true就是允许冒泡

3.e.cancelBubble  //取消冒泡兼容IE浏览器  现在使用e.stopPropagation()

4.e.currentTarget  //侦听事件对象

5.e.eventPhase  //事件触发的阶段  0事件未处理  1 捕获阶段 2目标阶段  3冒泡阶段

6.e.returnValue //阻止默认行为 兼容IE浏览器  现在使用e.preventDefault()

7.e.srcElement //事件触发的目标对象 兼容IE浏览器

8.e.target    //事件触发的目标对象

9.e.timeStamp //页面渲染完成后到事件被触发的毫秒数

10.e.type //事件触发的类型

11.e.preventDefault() //阻止默认行为

12. defaultPrevented 返回一个布尔值,表明当前事件是否调用了 e.preventDefault()

13.e.stopImmediatePropagation() //阻止后续事件函数执行

14.e.stopPropagation() //阻止冒泡

菜单案例

<style>
      ul {
         max-height: 800px;
         overflow: hidden;
         transition: all 0.5s;
      }

      ul[close] {
         max-height: 0;
      }
   </style>
   
   <ul class="city">
      <li>
         北京
         <ul>
            <li>
               昌平
               <ul>
                  <li>沙河</li>
                  <li>回龙观</li>
                  <li>霍营</li>
                  <li>天通苑</li>
               </ul>
            </li>
            <li>海淀</li>
            <li>朝阳</li>
            <li>东城</li>
         </ul>
      </li>
      <li>
         河北
         <ul>
            <li>张家口</li>
            <li>
               邯郸
               <ul>
                  <li>丛台</li>
                  <li>邯山</li>
                  <li>永年</li>
               </ul>
            </li>
            <li>石家庄</li>
            <li>保定</li>
         </ul>
      </li>
      <li>
         山西
         <ul>
            <li>太原</li>
            <li>大同</li>
            <li>临汾</li>
            <li>运城</li>
         </ul>
      </li>
      <li>
         陕西
         <ul>
            <li>西安</li>
            <li>咸阳</li>
            <li>宝鸡</li>
            <li>渭南</li>
         </ul>
      </li>
   </ul>
   <script>
       
   /* var lis;

   init();
   function init() {
      lis = document.querySelectorAll("li")
      for (var i = 0; i < lis.length; i++) {
         lis[i].addEventListener("click", clickHandler);
      }
   }

   function clickHandler(e) {
      if (this.nodeName !== "LI") return;
      e.stopPropagation();//阻止其他事件冒泡
      var ul = this.firstElementChild;
      if (!ul) return;
      if (ul.getAttribute("close") === null) {
         ul.setAttribute("close", "");
      } else {
         ul.removeAttribute("close");
      }
   } */


   //事件委托
   var city;
   init()
   function init() {
      city = document.querySelector(".city")
      city.addEventListener("click", clickHandler)
   }

   function clickHandler(e) {
      if (e.target.nodeName !== "LI") return;
      var ul = e.target.firstElementChild;
      if (!ul) return;
      if (ul.getAttribute("close") === null) {
         ul.setAttribute("close", "");
      } else {
         ul.removeAttribute("close");
      }
   }
</script>

图片预加载

//创建图片标签
var img=document.createElement("img")
var img = new Image();
以上两者方式都可以

img.src = "./img/5-.jpg";//获取图片路径
//让图片加载完成后执行函数 
img.onload = function () {
         console.log(img.width)
      }

打印多张图片

//阶段1
var arr = [];
      for (var i = 5; i < 14; i++) {
         var img = new Image();
         img.src = `./img/${i}-.jpg`
         img.onload = function () {
             //让图片先加载 再存入
            arr.push(this);
            //照片一共有9张,当数组长度为9时
            //说明已经照片已经全部加载放到数组中
            //此时循环打印出每一张照片的地址
            if (arr.length == 9) {
               arr.forEach(item => {
                  document.write(img.src,"<br>")
               })
            }
         }
      }//上面代码 因为图片的大小不一样 打印的地址不是按顺序出来的


//改进  阶段2
var arr = [];
      var j = 0;
      for (var i = 5; i < 14; i++) {
         var img = new Image();
         img.src = `./img/${i}-.jpg`;
         arr.push(img);
         img.onload = function () {
            j++;
            if (j === 9) {
               arr.forEach(item => {
                  console.log(item.src)
               })
            }
         }
      }//上面代码 打印的地址是按顺序出来的
//但是图片被一次性放到了数组中,同时加载


//改进(要让图片顺序加载 但不能形成回调地狱)  阶段3   
//预加载

//1.删除事件侦听的方式
var i = 5;
      var arr = [];

      loadImage(`./img/${i}-.jpg`)
      function loadImage(src) {
         var img = new Image();
         //获取图片的地址
         img.src = src;
         //添加load侦听事件
         //{ once: true }  每一次执行完后 事件都会挂起 需要删除
         img.addEventListener('load', loadHandler, { once: true });
      }

      function loadHandler(e) {
         //把获取的图片添加到数组中
         //e.currentTarget 指img
         arr.push(e.currentTarget)
         i++;
         if (i > 13) {
            arr.forEach(item => console.log(item.src))
            //打印完之后跳出  不在执行后续代码
            return;
         }
         //当i不大于13时  把下一张图片的地址做为参数  再次执行函数
         loadImage(`./img/${i}-.jpg`)
      }

//2.不删除侦听方式  使用复制
      var i = 5;
      var arr = [];

      loadImage();
      function loadImage() {
         var img = new Image();
         img.src = "./img/5-.jpg";
         img.addEventListener("load", loadHandler);
      }
      function loadHandler(e) {
         //this.cloneNode(false)  浅复制this
         //把复制的副本放到数组中,让原来的img继续变化
         //this就是 img
         arr.push(this.cloneNode(false));
         i++;
         if (i > 13) {
            arr.forEach(item => console.log(item.src))
            return;
         }
         //把新图片的地址 赋给this的地址
         //相当于把img.src = "./img/5-.jpg";
         //换成img.src = "./img/6-.jpg";
         this.src = `./img/${i}-.jpg`;
      }

从数组中取得图片加载

 var arr = ["./img/5-.jpg", "./img/6-.jpg", "./img/7-.jpg", "./img/100-.jpg", "./img/8-.jpg", "./img/9-.jpg", "./img/10-.jpg", "./img/11-.jpg", "./img/12-.jpg", "./img/13-.jpg"];

      loadImage(arr);
      function loadImage(arr) {
         var img = new Image();
         //让img的地址为数组中第一个值
         img.src = arr[0];
         //以下是使用dom属性定义
         //计数器
         img.n = 0;
         img.source = arr;
         //定义一个新数组
         img.finishList = []
         img.addEventListener("load", loadHandler);
         img.addEventListener("error", errorHandler)
      }

      //当发生错误时才会触发
      function errorHandler(e) {
         //this就是img
         nextLoad(this);
      }
      function loadHandler(e) {
         //this就是img
         //把复制的副本放到新数组中,让原来的img继续变化
         this.finishList.push(this.cloneNode(false));
         nextLoad(this);
      }

      function nextLoad(img) {
         img.n++;
         //当计数器的值等于数组长度时 执行其中语句
         if (img.n === img.source.length) {
            //删除事件 避免内存泄漏
            img.removeEventListener("load", loadHandler)
            img.removeEventListener("error", errorHandler)
            //执行该函数--打印图片地址
            finishHandler(img.finishList)
            return;
         }
         //若计数器长度不满足数组长度时
         //让此时数组中对应的值为图片的地址
         img.src = img.source[img.n]
      }
      function finishHandler(list) {
         //list就是上面传入的img.finishList 这一整个数组
         console.log(list)
         /* for(var item of list){
            console.log(item.src)
         } */
      }

纯函数:函数执行过程中不会改变其他函数外的内容

补充 href和src的区别

script 	中src同步
img	中src异步

link中href 同步
a中href 不需要考虑 同步或异步  点击时触发

//同步:当默认状态时,js加载先加载完成一个文件后,执行js后才会继续向后执行
//异步:如果不希望在这里等待加载,希望这里加载的同时,继续向后执行后面的html

对于路径的理解
<!-- 
   /  相对当前服务的根路径
  ./ 相对当前文件的路径
 ../ 相对向前文件的上一级路径
-->

鼠标事件

 //MouseEvent
`事件类型`
      /*
         click  左键单击
         dblclick 双击
         mousedown 按下键
         mouseup 释放键
         mousemove 移动鼠标
         mouseover 鼠标滑过会向上冒泡 子元素事件会向上传递
         mouseout 鼠标滑出会向上冒泡 子元素事件会向上传递
         mouseenter 鼠标进入 不会向上冒泡 无法做事件委托
         mouseleave 鼠标离开 不会向上冒泡 无法做事件委托 
         contextmenu 右键菜单
      */
只有mouseenter和mouseleave不会向上冒泡 无法做事件委托 都可以


`事件属性`
1.在鼠标事件操作时,是否按下对应的键 (true 指按下对应键)
//eg: 按下alt键 鼠标操作后  返回true,否则返回false
e.altKey
e.ctrlKey
e.shiftKey
e.metaKey 苹果系统 command按键


2. 针对mousedown mouseup事件 确定当前是哪个键被按下或释放	
          左 中   (鼠标按键)
e.button: 0  1  2 	 //用的多
e.buttons:1     2  //可以获取到鼠标点击速度
e.which:  1  2  3	//经常使用判断


3. 连续点击的次数
        	detail:1  必须是连续点击的才可以计数

            
4. 事件的次要目标 ,就是上次触发的事件对象
        	relatedTarget:触发的对象

            
5. 鼠标移动相对上次横向和纵向距离(针对mousemove事件触发)
            e.movementX
            e.movementY


6. 鼠标当前相对视口的距离(相对定位)
            e.clientX
            e.clientY
7. 鼠标当前相对视口距离 与clientX和clientY相同(早期使用IE浏览器)
            e.x
            e.y



8. 绝对页面顶端 相对页面左上角位置(绝对定位)
            e.pageX
            e.pageY



9. 相对屏幕左上角位置
            e.screenX
            e.screenY



10. 鼠标相对目标元素的左上角位置(具体被点击到的谁)
   定位不定位都行
            e.offsetX
            e.offsetY
11. 定位时和未定位时  定位时 同offsetX,offsetY
				  不定位时 向上查找定位的父容器,相对这个定位的父容器的左上角;如果没有查找到对应的定位父容器,相对html左上角
		   e.layerX
		   e.layerY

input和focus

input 是一个冒泡事件

//使用form侦听input
 var form = document.querySelector("form");
      form.addEventListener("input", inputHandler)

      function inputHandler(e) {
         //e.target 指input
         console.log(e.target.value)
      }

节流和防抖

节流就先进入在等待在进入
防抖就是先等待在进入在等待在进入

节流

/*
节流
	第一次进入后,设置一个时间间隔,时间间隔ids不是undefined,再次输入就不能进入,500毫秒后打印当前input结果
 	当500毫秒,清除时间间隔,并且将ids设为undefined,那么下次输入就可以进入 	
*/

	  //用form侦听和input侦听都可
      //这里使用的是form侦听
      var form = document.querySelector("form")
      form.addEventListener("input", inputHandler);

      //此时ids是undefined
      var ids;
      function inputHandler(e) {
         // console.log(e)
         //若ids为真(有值) 则跳出
         //否则(为undefined)执行下面的代码
         if (ids) return;
         ids = setTimeout(() => {
            console.log(e.target.value);
            clearTimeout(ids);
            //最后要让ids为undefined,否则ids会一直有值会跳出,只会执行一次
            ids = undefined;
         }, 500)
      }

聚焦和失焦

 /* 
focus  聚焦
blur  失焦
两个都不会冒泡  不能让form做委托
       
focusin 聚焦
focusout 失焦
可以冒泡,所以可以让form委托        
*/

var input = document.querySelectorAll("input");
      for (var i = 0; i < input.length; i++) {
         input[i].addEventListener("focus", focushandler)
         input[i].addEventListener("blur", focushandler)
      }
	  //自动聚焦
      // input.focus()
      function focushandler(e){
         console.log(e)
      }

e.relatedTarget
focus 上一次失焦的DOM元素   blur 失焦时是本次聚焦的对象

按键事件

主要针对于document

keydown 按下去时
keyup 松开时

`属性含义`
code:"ArrowLeft"
key:"ArrowLeft"
keyCode:37   键码  一般用这个
which:37  键码  不常用
repeat:false  长按状态下是true  是否重复

滚轮事件

mouseWheel 除了火狐都能用
DOMMouseScroll  火狐浏览器专用

e.wheelDelY  谷歌浏览器使用
e.detail  火狐浏览器用的

document.addEventListener("mousewheel", mouseHandler)//除火狐以外
document.addEventListener("DOMMouseScroll", mouseHandler);//火狐浏览器专用

      function mouseHandler(e) {
         console.log(e.detail)//火狐浏览器使用
         console.log(e.wheelDeltaY)//谷歌浏览器用
      }

行走案例

<style>
      .avatar {
         width: 32px;
         height: 33px;
         background-image: url(./img/Actor01-Braver03.png);
         background-position: 0 0;
         position: absolute;
         left: 0;
         top: 0;
      }
</style>

<body>
   <div class="avatar"></div>
   <script>
      //使用symbol防止被另外的属性覆盖
      const LEFT = Symbol("left"),
         RIGHT = Symbol("right"),
         TOP = Symbol("top"),
         BOTTOM = Symbol("bottom");
      const MAX_TIME = 50;
      //使用数组存储定义的四个方位
      var stateList = [LEFT, TOP, RIGHT, BOTTOM];
      //使用对象存储四个方位在图片中的位置
      var direction = {
         [LEFT]: 1,
         [RIGHT]: 2,
         [TOP]: 3,
         [BOTTOM]: 0
      }

      var state, avatar;
      var x = 0, y = 0, n = 0, time = MAX_TIME, speed = 1;

      init();
      function init() {
         avatar = document.querySelector(".avatar");
         //因为keydown和keyup主要作用于document
         //所以用document侦听他们
         document.addEventListener("keydown", mouseHandler);
         document.addEventListener("keyup", mouseHandler);
         setInterval(animation, 16);
      }

      function mouseHandler(e) {
         //e.which获取键码
         //键码值分别时 左37 上38 右39 下40
         //通过键码获取 按键时所对应的方位
         //当为37时e.which-37=0  获取到LEFT
         state = stateList[e.which - 37];
         //当点击的不是四个方位键就跳出
         if (!state) return;
         //如果事件触发的类型是keydown 执行其中代码
         if (e.type === "keydown") {
            //通过图片获取样式(获取纵向的样式)
            //注意获取的是图片中对应值的样式
            //direction[state] 通过state 在对象中取得值 获取对应图片的样式
            avatar.style.backgroundPositionY = -33 * direction[state] + "px";
         } else {
            //否则停止
            state = undefined
         }
      }

      function animation() {
         if (!state) return;
         avatarMove()
         avatarChange()
      }

      //动画移动
      function avatarMove() {
         //speed 步幅  图片移动的距离
         switch (state) {
            case LEFT:
               x -= speed;
               break;
            case RIGHT:
               x += speed;
               break;
            case TOP:
               y -= speed;
               break;
            case BOTTOM:
               y += speed;
               break;
         }
         avatar.style.left = x + "px"
         avatar.style.top = y + "px"
      }

      //动画改变
      function avatarChange() {
         if (--time > 0) return;
         time = MAX_TIME;

         ++n == 4 ? n = 0 : n;
         //通过图片获取样式(获取横向的样式)
         avatar.style.backgroundPositionX = -32 * n + "px"
      }
   </script>
</body>

拖拽案例

阶段1

//一个div
	  var div = document.querySelector("div")
      div.onmousedown = function (e1) {
         document.onmousemove = function (e) {
            //clientX clientY相对视口的距离
            //offsetX offsetY相对目标元素的左上角位置
            div.style.left = e.clientX - e1.offsetX + "px";
            div.style.top = e.clientY - e1.offsetY + "px";
         }
         document.onmouseup = function () {
            document.onmousemove = null;
            document.onmouseup = null;
         }
      }

阶段2 使用函数

//一个div
	  var div = document.querySelector("div")
      div.addEventListener("mousedown", mousedownhandler);

      //设置两个空值标记
      var offsetX = 0; offsetY = 0;
      function mousedownhandler(e) {
         // console.log(this)//div
         //把div当前 鼠标相对目标元素的左上角位置的距离赋值
         offsetX = e.offsetX;
         offsetY = e.offsetY;
         document.addEventListener("mousemove", mousemoveHandler);
         document.addEventListener("mouseup", mouseupHandler)
      }

      function mousemoveHandler(e) {
         // console.log(this)//document
         //用相对于视口的位置 减去相对目标元素的位置就是移动的距离
         div.style.left = e.clientX - offsetX + "px";
         div.style.top = e.clientY - offsetY + "px";
      }

      function mouseupHandler(e) {
         // console.log(this)//document
         document.removeEventListener("mousemove", mousemoveHandler);
         document.removeEventListener("mouseup", mouseupHandler)
      }

阶段2_1 使用判断

//一个div
	 var div = document.querySelector("div");
      var offsetX = 0, offsetY = 0;
      div.addEventListener("mousedown", mouseHandler)

      //相当于上面三个函数的形式
      //若e.type是mousedown 进入其中侦听事件
      //侦听之后 e.type是mousemove 执行else if 中的语句
      //当移动停止之后 e.type不是mousedown也不是mousemove 所以执行最后一个语句块 清除事件
      function mouseHandler(e) {
         if (e.type === "mousedown") {
            // console.log(this)//div
            //div 侦听mousedown
            offsetX = e.offsetX;
            offsetY = e.offsetY;
            document.addEventListener("mousemove", mouseHandler)
            document.addEventListener("mouseup", mouseHandler);
         } else if (e.type === "mousemove") {
            // console.log(this)//document
            //document 侦听mousemove和mouseup
            div.style.left = e.clientX - offsetX + "px";
            div.style.top = e.clientY - offsetY + "px";
         } else {
            // console.log(this)//document
            document.removeEventListener("mousemove", mouseHandler);
            document.removeEventListener("mouseup", mouseHandler);
         }
      }

阶段2_2 多个div

 	  var divs = document.querySelectorAll("div")
      for (var i = 0; i < divs.length; i++) {
         divs[i].addEventListener("mousedown", mouseHandler)
      }

      function mouseHandler(e) {
         if (e.type === "mousedown") {
            // console.log(this)//div
            document.offsetX = e.offsetX;
            document.offsetY = e.offsetY;
            document.div = this;//获取所点击的那个div
            document.addEventListener("mousemove", mouseHandler);
            document.addEventListener("mouseup", mouseHandler)
         } else if (e.type === "mousemove") {
            // console.log(this);//document
            //this是document  this.div指获取上面所点击的那个div
            this.div.style.left = clientX - this.offsetX + "px";
            this.div.style.top = e.clientY - this.offsetY + "px";
         } else {
            // console.log(this);//document
            document.removeEventListener("mousemove", mouseHandler);
            document.removeEventListener("mouseup", mouseHandler)
         }
      }

补充内容

	    // var div1=document.querySelector(".div1");
        //  var rect=div1.getBoundingClientRect()
        //  console.log(rect)
        //  left=x 左上角向对视口的x
        //  top=y 左上角向对视口的y
        // right 右下角相对视口的x
        // bottom 右下角相对视口的y
        // width 矩形范围的宽度
        // height 矩形范围的高度

阶段3 终极阶段

	  var divs = document.querySelectorAll(".div1>div")
      for (var i = 0; i < divs.length; i++) {
         divs[i].addEventListener("mousedown", mouseHandler);
      }

      function mouseHandler(e) {
            if (e.type === "mousedown") {
            // console.log(this)//div
            //e.offsetX e.offsetY 鼠标相对当前div左上角的位置
            document.offsetX = e.offsetX;
            document.offsetY = e.offsetY;
            document.div = this;
            document.addEventListener("mousemove", mouseHandler);
            document.addEventListener("mouseup", mouseHandler)
            } else if (e.type === "mousemove") {
            // console.log(this);//document
            //获取当前元素的父元素
            var parent = this.div.parentElement;
            while (parent) {
                //getComputedStyle() 计算后的样式
               //getComputedStyle(parent).position  获取计算后样式的定位
              //判断父元素是否有定位
                if (/relative|absolute/.test(getComputedStyle(parent).position)) break;//向上查找父元素容器
               parent = parent.parentElement;//是父容器就跳出 不是就让父容器赋给parent 进行进入循环査找
            }


            var rect = this.div.parentElement.getBoundingClientRect();//获取 当前点击时div对应的 父元素的getBoundingClientRect方法


            //clientLeft\clientTop边框大小
            //获取父元素边框的大小
           var borderLeft = this.div.parentElement.clientLeft;
           var borderTop = this.div.parentElement.clientTop;


            //e.clientX 鼠标当前相对视口的距离
            //this.offsetX 点击后相对this左上角的位置
            //rect.x 父元素所在当前视口的位置(不会变化)
            //父元素的offsetX和offsetY会造成偏差 需要减去
            var x = e.clientX - this.offsetX - rect.x;
            var y = e.clientY - this.offsetY - rect.y;


            //设置移动的范围
            if (x < 0) x = 0;
            //rect.width 父元素宽度
            //this.div.offsetWidth div带边框的宽度
            else if (x > rect.width - this.div.offsetWidth - borderLeft * 2) x = rect.width - this.div.offsetWidth - borderLeft * 2

            if (y < 0) y = 0;
            else if (y > rect.height - this.div.offsetHeight - borderTop * 2) y = rect.height - this.div.offsetHeight - borderTop * 2;

            this.div.style.left = x + "px";
            this.div.style.top = y + "px";
         } else {
            // console.log(this);//document
            document.removeEventListener("mousemove", mouseHandler);
            document.removeEventListener("mouseup", mouseHandler);
         }
      }

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值