zoukankan      html  css  js  c++  java
  • 【初窥javascript奥秘之事件冒泡】那些年我们一起冒的泡

    前言

    若是我说会有此文又是因为一次面试的话,我自己都不知道该怎么形容我的心情了,好似我的所有动力皆来自于面试似的。

    其实不是这样的,我原来一个项目经理对我说,隔一两个月出去面试下,一来你会知道自己的不足知道怎么提高,二来你就知道自己涨价没。

    现在回想起来他说的是很对的,面对这次的团队解散,我反而不那么着急,因为我不太惧怕面试,走到哪里都能找到工作,当然这里没有鼓励大家出去面试的意思啦。

    回到本文,当时面试官问了我一个问题,我当时就支支吾吾的答不上来,其实细细思考的话这道题还是有点意思的:

    在一个div上画div(绝对定位),然后再在刚刚那个div里面(上面更合适)画一个div,现在去拖到div,你知道你拖动的是哪个div吗?

    说这道题本身并不难绝对是骗人的(反正时至今日我都有点没底...),说他难,他又没有难到哪里去,每个人都知道是事件冒泡相关的东西。

    还是那句话知道不等于了解,了解不等于深入,皮毛说不清楚事情,今天让我们带着问题一起学习吧!

    由于本文是边学边写的,可能会有点乱,可能会有我的唧唧歪歪请各位大哥见谅。

    基础知识

    刀不磨不亮,我们先从基础来,这里就直接忽略IE的attachEvent了吧,我们说下标准的就是,请看以下代码(这里我确实没脸再用jquery了,原生吧):

    基础事件绑定
     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml">
     3 <head>
     4     <title></title>
     5 </head>
     6 <body>
     7     <div id="div">
     8         <input id="input" value="点击我" type="button" />
     9     </div>
    10     <script type="text/javascript">
    11         var bt = document.getElementById('input');
    12         bt.addEventListener('click', function (e) {
    13             var scope = this;
    14             var s = '';
    15 
    16         }, false);
    17     
    18     </script>
    19 </body>
    20 </html>

    我们看到点击按钮后,执行点击事件,其中this便是对应元素,e包含了许多有用信息比如:

    阻止冒泡(e.stopPropagation();)、阻止浏览器默认事件(e.preventDefault();)、当前点击元素、鼠标位置......

    好,我们在此基础上做一个扩展,我们多增加两个元素,然后给document绑定事件,最后看看我们点击的是哪个:

     1 <body>
     2     <div id="div">
     3         <input id="input" value="点击我" type="button" />
     4         <ul>
     5             <li><a>湿湿的</a></li>
     6             <li><a>湿湿的1</a></li>
     7             <li><a>湿湿的2</a></li>
     8         </ul>
     9         <span>安能辨我<b></b>雄雌</span>
    10     </div>
    11     <script type="text/javascript">
    12         document.addEventListener('click', function (e) {
    13             var scope = this;
    14             var cur_el = e.target.tagName;
    15             var s = '';
    16         }, false);
    17     </script>
    18 </body>

    现在我不论点击哪个标签皆会将其标签名字打印出来,请在s=''处设置断点查看:

    于是我突然感觉知道了什么,又不能确认,所以我们都不敲板,来捋一捋:

    1 我们为根元素绑定点击事件
    2 我们点击按钮后,按钮并没有事件处理,所以将事件向上传递,直到document
    3 document是绑定了事件的,所以触发了事件,其中this指向document,但是e中的当前点击元素却是按钮

    好,先将一切暂停,我们来做一个很土的选项卡:

    丑陋的选项卡
     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml">
     3 <head>
     4     <title></title>
     5     <style type="text/css">
     6         body { font: 12px 'PTSansRegular',Arial,Helvetica,sans-serif; background: #D1D1D1; }
     7         ul, li { margin: 0; padding: 0}
     8         li { list-style: none; }
     9         
    10         #tabs_container { width: 400px; height: 200px; background: white; }
    11         #tabs { border-bottom: 1px solid #1C87D5; padding: 5px 5px 0; }
    12         #tabs li { padding: 5px 10px;  cursor: pointer; display: inline-block; background: #1C87D5; color: White; }
    13         
    14         #content { padding: 5px;}
    15     </style>
    16 </head>
    17 <body id='body'>
    18     <div id="tabs_container">
    19         <ul id="tabs">
    20             <li tab="1">选项卡1</li>
    21             <li tab="2">选项卡2</li>
    22             <li tab="3">选项卡3</li>
    23         </ul>
    24         <div id="content">
    25         内容区域
    26         </div>
    27     </div>
    28     <script type="text/javascript">
    29         
    30 
    31     </script>
    32 </body>
    33 </html>

    若是现在需要完成一个简单功能,点击每个选项卡,内容区域文字便会变成选项卡的标签文字你会怎么做呢?

    很多年以前,我会使用jquery的标签选择器为每个li添加事件,我确信我会这样做,这样做有几个问题:

    1 事件绑定过多

    2 动态添加li标签的话,是不具备事件的

    于是这里我们就可以用到前面的事件冒泡机制啦:

    选项卡代码完整版
     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml">
     3 <head>
     4     <title></title>
     5     <style type="text/css">
     6         body { font: 12px 'PTSansRegular',Arial,Helvetica,sans-serif; background: #D1D1D1; }
     7         ul, li { margin: 0; padding: 0}
     8         li { list-style: none; }
     9         
    10         #tabs_container { width: 400px; height: 200px; background: white; }
    11         #tabs { border-bottom: 1px solid #1C87D5; padding: 5px 5px 0; }
    12         #tabs li { padding: 5px 10px;  cursor: pointer; display: inline-block; background: #1C87D5; color: White; }
    13         
    14         #content { padding: 5px;}
    15     </style>
    16 </head>
    17 <body id='body'>
    18     <div id="tabs_container">
    19         <ul id="tabs">
    20             <li tab="1">选项卡1</li>
    21             <li tab="2">选项卡2</li>
    22             <li tab="3">选项卡3</li>
    23         </ul>
    24         <div id="content">
    25         内容区域
    26         </div>
    27     </div>
    28     <script type="text/javascript">
    29         var tabs = document.getElementById('tabs');
    30         var con = document.getElementById('content');
    31         tabs.addEventListener('click', function (e) {
    32             var cur_el = e.target;
    33             con.innerHTML = cur_el.innerHTML;
    34         }, false);
    35     </script>
    36 </body>
    37 </html>

    根据这个例子,我相信,各位都对事件冒泡有一个认识了,我们继续。

    绝对定位·乱了的一切

    这里会说到绝对定位,只是因为他比较特殊,其实漂浮、元素之间重叠都有可能让我们的冒泡变得复杂,先上代码:

    复杂的情况
     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     2 <html xmlns="http://www.w3.org/1999/xhtml">
     3 <head>
     4     <title></title>
     5     <style type="text/css">
     6        div { padding: 10px; margin: 10px; filter:alpha(opacity=50); -moz-opacity:0.5; opacity:0.5; font-weight:bold; }
     7        #d1 { width: 700px; height: 400px; background: #D1D1D1;}
     8        #d1_1 { width: 200px; height: 200px; background: gray;}
     9        #d2 { width: 200px; height: 200px; background: #1C87D5; position: absolute; top: 10px; left: 150px;}
    10        #d3 { width: 200px; height: 200px; color:White; background: black; position: absolute; top: 150px; left: 150px;}
    11     </style>
    12 </head>
    13 <body>
    14     <div id="d1">
    15         d1
    16         <div id="d1_1">
    17             d1_1
    18         </div>
    19     </div>
    20     <div id="d2">
    21         d2
    22     </div>
    23     <div id="d3">
    24         d3
    25     </div>
    26     <script type="text/javascript">
    27         document.addEventListener('click', function (e) {
    28             var scope = this;
    29             var cur_el = e.target;
    30             var id = cur_el.id;
    31             var s = '';
    32         }, false);
    33     </script>
    34 </body>
    35 </html>

    现在各位还知道自己在干什么吗???这个将情况变的复杂,因为我们必须清晰的知道谁离我最近。

    解决问题

    到了解决问题的时候了,要不我们将最开始出现那道题做了吧?我们先规定一下做法,只能这样做哟:

    1 给一个按钮给用户让用户选择现在是画矩形或者拖动矩形

    2 在给定的一个div(相对定位)中画div,画的div(绝对定位)将append到父div里面

    3 选择拖动div,便可在父层div中进行拖动了

    4 不能为除父div以为的div添加事件!

    5 可以使用jquery,可以暂时不考虑滚动条

    怎么样简单吧,让我们动手吧:

    为了方便各位拖到,我先上个图,然后再来一段丑陋的代码:

    丑陋的代码
      1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      2 <html xmlns="http://www.w3.org/1999/xhtml">
      3 <head>
      4     <title></title>
      5     <style type="text/css">
      6         body { font: 12px 'PTSansRegular',Arial,Helvetica,sans-serif; background: #D1D1D1; }
      7         ul, li { margin: 0; padding: 0}
      8         li { list-style: none; }
      9         
     10         #tabs_container { width: 400px; height: 200px; background: white;}
     11         #tabs { border-bottom: 1px solid #1C87D5; padding: 5px 5px 0;  width: 690px; margin: 0 auto;  }
     12         #tabs li { padding: 5px 10px;  cursor: pointer; display: inline-block; background: #1C87D5; color: White; }
     13         #tabs li.sec { color: black;}
     14         
     15         #parent { width: 700px; height: 400px; background: white; margin: 0 auto; position: relative; }
     16         #parent div { filter:alpha(opacity=50); -moz-opacity:0.5; opacity:0.5; font-weight:bold; background: red; position: absolute; }
     17     </style>
     18 </head>
     19 <body>
     20     <ul id="tabs" >
     21         <li type="draw" class="sec">画矩形框</li>
     22         <li type="drag">拖到矩形框</li>
     23     </ul>
     24     <div id="parent">
     25     </div>
     26     <div id="state"></div>
     27     <script type="text/javascript">
     28         var parent = document.getElementById('parent'),
     29             tabs = document.getElementById('tabs'),
     30             state = document.getElementById('state'),
     31             con = document.getElementById('content'),
     32             li = tabs.getElementsByTagName('li'),
     33             type = 'draw'; //drag
     34 
     35         tabs.addEventListener('click', function (e) {
     36             for (var i = 0; i < li.length; i++) {
     37                 var el = li[i];
     38                 el.className = '';
     39                 var s = '';
     40             }
     41             var cur_el = e.target;
     42             cur_el.className = 'sec';
     43             type = cur_el.type;
     44 
     45             var s = '';
     46         }, false);
     47 
     48         parent.addEventListener('mousedown', function (e) {
     49             var scope = this;
     50             if (type == 'draw') {
     51                 (function () {
     52                     var el = e.target, //当前点击元素,父div或者拖到div
     53                         x = e.clientX,
     54                         y = e.clientY,
     55                         offsetTop = scope.offsetTop,
     56                         offsetLeft = scope.offsetLeft;
     57                     var div_x = x - offsetLeft,
     58                         div_y = y - offsetTop;
     59                     var div = document.createElement('div');
     60                     var style = 'left: ' + div_x + 'px; top: ' + div_y + 'px;';
     61                     div.setAttribute('style', style);
     62                     div.innerHTML = scope.getElementsByTagName('div').length;
     63                     scope.appendChild(div);
     64                     var func_move = function (ee) {
     65                         var x1 = ee.clientX,
     66                         y1 = ee.clientY;
     67                         var w = x1 - x;
     68                         var h = y1 - y;
     69                         if (w > 5 || h > 5) {
     70                             div.setAttribute('style', style + ' ' + w + 'px; height: ' + h + 'px;');
     71                         }
     72                     }; 
     73                     var func_up = function (ee) {
     74                         document.removeEventListener('mousemove', func_move, false);
     75                         document.removeEventListener('mouseup', func_up, false);
     76                     };
     77                     document.addEventListener('mousemove', func_move, false);
     78                     document.addEventListener('mouseup', func_up, false);
     79                 })();
     80 
     81             } else {
     82                 (function () {
     83                     var el = e.target; //当前点击元素,父div或者拖到div
     84                     var x1 = e.clientX - el.offsetLeft;
     85                     var y1 = e.clientY - el.offsetTop;
     86 
     87                     var func_move = function (ee) {
     88                         var cur_el = ee.target;
     89                         if (cur_el.id && cur_el.id == 'parent') return false;
     90                         var x2 = 0;
     91                         var y2 = 0;
     92 
     93                         x2 = ee.clientX - x1 ;
     94                         y2 = ee.clientY - y1 ;
     95                         x2 = x2 > 0 ? x2 : 0;
     96                         y2 = y2 > 0 ? y2 : 0;
     97 
     98                         if (Math.abs(x2) > 5 || Math.abs(y2) > 5) {
     99                             cur_el.style.top = (y2) + 'px';
    100                             cur_el.style.left = (x2) + 'px';
    101                         }
    102                     };
    103 
    104                     var func_up = function (ee) {
    105                         document.removeEventListener('mousemove', func_move, false);
    106                         document.removeEventListener('mouseup', func_up, false);
    107                     };
    108                     document.addEventListener('mousemove', func_move, false);
    109                     document.addEventListener('mouseup', func_up, false);
    110                 })();
    111             }
    112 
    113         }, false);
    114     </script>
    115 </body>
    116 </html>

    大家去运行看看,就会发现问题了:

    我们拖动1时,没有问题,但是当我们拖到0时,一旦碰到1就会出状况!!!

    这是因为我们mousedown事件是绑定到了父div上,当我们鼠标点下时,便给个元素绑定了滑动事件,但是在1

    上时,因为他离我们近,所以我们不会滑动到0上,。。。。滑动0时碰到1,鼠标自然就在1

    上滑动了,所以拖动的就是1。。。。

    我不知道各位晕不晕,我可能有点晕,今天暂时如此了。。。。

    师兄讨论的结果

    刚刚和师兄就这个问题做了一个讨论,他的意见还是很中肯的:

    我们mousedown的时候便获取该元素,然后直接给该子元素绑定各种事件,mouseup后便把事件取消,便不会有以上问题了,

    另外可能起z-index还需要进行设置,我这里就不管了:

    师兄意见
      1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      2 <html xmlns="http://www.w3.org/1999/xhtml">
      3 <head>
      4     <title></title>
      5     <style type="text/css">
      6         body { font: 12px 'PTSansRegular',Arial,Helvetica,sans-serif; background: #D1D1D1; }
      7         ul, li { margin: 0; padding: 0}
      8         li { list-style: none; }
      9         
     10         #tabs_container { width: 400px; height: 200px; background: white;}
     11         #tabs { border-bottom: 1px solid #1C87D5; padding: 5px 5px 0;  width: 690px; margin: 0 auto;  }
     12         #tabs li { padding: 5px 10px;  cursor: pointer; display: inline-block; background: #1C87D5; color: White; }
     13         #tabs li.sec { color: black;}
     14         
     15         #parent { width: 700px; height: 400px; background: white; margin: 0 auto; position: relative; }
     16         #parent div { filter:alpha(opacity=50); -moz-opacity:0.5; opacity:0.5; font-weight:bold; background: red; position: absolute; }
     17     </style>
     18 </head>
     19 <body>
     20     <ul id="tabs" >
     21         <li type="draw" class="sec">画矩形框</li>
     22         <li type="drag">拖到矩形框</li>
     23     </ul>
     24     <div id="parent">
     25     </div>
     26     <div id="state"></div>
     27     <script type="text/javascript">
     28         var parent = document.getElementById('parent'),
     29             tabs = document.getElementById('tabs'),
     30             state = document.getElementById('state'),
     31             con = document.getElementById('content'),
     32             li = tabs.getElementsByTagName('li'),
     33             type = 'draw'; //drag
     34 
     35         tabs.addEventListener('click', function (e) {
     36             for (var i = 0; i < li.length; i++) {
     37                 var el = li[i];
     38                 el.className = '';
     39                 var s = '';
     40             }
     41             var cur_el = e.target;
     42             cur_el.className = 'sec';
     43             type = cur_el.type;
     44 
     45             var s = '';
     46         }, false);
     47 
     48         parent.addEventListener('mousedown', function (e) {
     49             var scope = this;
     50             if (type == 'draw') {
     51                 (function () {
     52                     var el = e.target, //当前点击元素,父div或者拖到div
     53                         x = e.clientX,
     54                         y = e.clientY,
     55                         offsetTop = scope.offsetTop,
     56                         offsetLeft = scope.offsetLeft;
     57                     var div_x = x - offsetLeft,
     58                         div_y = y - offsetTop;
     59                     var div = document.createElement('div');
     60                     var style = 'left: ' + div_x + 'px; top: ' + div_y + 'px;';
     61                     div.setAttribute('style', style);
     62                     div.innerHTML = scope.getElementsByTagName('div').length;
     63                     scope.appendChild(div);
     64                     var func_move = function (ee) {
     65                         var x1 = ee.clientX,
     66                         y1 = ee.clientY;
     67                         var w = x1 - x;
     68                         var h = y1 - y;
     69                         if (w > 5 || h > 5) {
     70                             div.setAttribute('style', style + ' ' + w + 'px; height: ' + h + 'px;');
     71                         }
     72                     }; 
     73                     var func_up = function (ee) {
     74                         document.removeEventListener('mousemove', func_move, false);
     75                         document.removeEventListener('mouseup', func_up, false);
     76                     };
     77                     document.addEventListener('mousemove', func_move, false);
     78                     document.addEventListener('mouseup', func_up, false);
     79                 })();
     80 
     81             } else {
     82                 (function () {
     83                     var el = e.target; //当前点击元素,父div或者拖到div
     84                     var x1 = e.clientX - el.offsetLeft;
     85                     var y1 = e.clientY - el.offsetTop;
     86 
     87                     var func_move = function (ee) {
     88                         var cur_el = ee.target;
     89                         if (cur_el.id && cur_el.id == 'parent') return false;
     90                         var x2 = 0;
     91                         var y2 = 0;
     92 
     93                         x2 = ee.clientX - x1 ;
     94                         y2 = ee.clientY - y1 ;
     95                         x2 = x2 > 0 ? x2 : 0;
     96                         y2 = y2 > 0 ? y2 : 0;
     97 
     98                         if (Math.abs(x2) > 5 || Math.abs(y2) > 5) {
     99                             cur_el.style.top = (y2) + 'px';
    100                             cur_el.style.left = (x2) + 'px';
    101                         }
    102                     };
    103 
    104                     var func_up = function (ee) {
    105                         el.removeEventListener('mousemove', func_move, false);
    106                         el.removeEventListener('mouseup', func_up, false);
    107                     };
    108                     el.addEventListener('mousemove', func_move, false);
    109                     el.addEventListener('mouseup', func_up, false);
    110                 })();
    111             }
    112 
    113         }, false);
    114     </script>
    115 </body>
    116 </html>

    结语

    小弟对冒泡机制理解还是很浅的,若是各位大哥看到此文觉得有什么不足,或者有什么问题,请一定指出来哦。

    另外那道题建议各位做下,我那个代码写得有点水,期待高质量的代码。。。。

    如果你觉得这篇文章还不错,请帮忙点击一下推荐,谢谢!

  • 相关阅读:
    如何成为一个合格的数据架构师?
    证道:零售企业如何借助数据智能提升人效?| 数智加速度10课回顾
    终于,数据中台成为3000万企业的增长引擎
    凯德中国 × 阿里云 × 奇点云 | 沉淀数据资产,遇见数智未来
    追风:数据中台如何驱动全域消费者运营?| 数智加速度09课回顾
    何夕:跟上趋势,拥抱全域数据中台 | 数智加速度08课回顾
    南弈:释放数据价值的「三个关键点」 | 数智加速度07课回顾
    百然:智能算法如何落地商业化?| 数智加速度06课回顾
    星魁:管理数据资产的「五步骤」与「六要素」 | 数智加速度05课回顾
    pytest系列(四)- pytest+allure+jenkins
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3037064.html
Copyright © 2011-2022 走看看