第7章 DOM(下)

2024-01-09 13:33:09

学习目标

  • 熟悉节点的概念,能够说出节点的属性和层级

  • 掌握节点操作,能够完成节点的获取、创建、添加、移除和复制操作

  • 掌握事件的进阶操作,能够实现事件的监听和移除

  • 熟悉DOM事件流,能够说出事件捕获和事件冒泡两种方式的区别

  • 掌握事件对象,能够利用事件对象进行事件操作

  • 掌握常用事件,能够通过常用事件完成常见的网页交互效果

  • 掌握元素其他操作,能够对元素的位置、大小、可视区域和滚动进行操作, 能够获取鼠标指针位置

通过第6章的学习,大家应该已经掌握了DOM中元素的相关操作以及事件的基本使用,可以通过注册事件以及元素操作的方式完成页面的交互效果。接下来,本章将继续讲解DOM中的进阶内容,如节点操作、事件监听等。通过本章学习,大家可以实现更加复杂的页面交互效果。

7.1 节点基础

7.1.1 节点的属性

如何理解节点及其作用?

节点:网页中的所有内容在文档树中都是节点,即元素、属性、文本等都属于节点。

作用:当利用DOM进行网页开发时,通过操作节点可以更加灵活地实现网页中的交互效果。

本节针对节点的属性和层级进行讲解。

节点有3个常用属性,具体如下。

nodeName :用于获取节点名称,全大写形式,如div标签的节点名称为DIV。

nodeValue:用于获取节点值,一般适用于文本、注释类型的节点。

nodeType:用于获取数字表示的节点类型,如1表示元素节点。

常见的节点类型如下。

类型常量常量的值
元素节点Node.ELEMENT_NODE1
文本节点Node.TEXT_NODE3
注释节点Node.COMMENT_NODE8
文档节点Node.DOCUMENT_NODE9
文档类型节点Node.DOCUMENT_TYPE_NODE10

在实际开发中,开发者可以根据节点的3个常用属性获取节点的名称、值和类型,示例代码如下。

var node = document.body;	// 获取body节点
console.log(node.nodeName);	// 获取节点名称,输出结果:BODY
console.log(node.nodeValue);	// 获取节点值,输出结果:null
console.log(node.nodeType);	// 获取节点类型,输出结果:1

有这样一个问题:节点操作时,页面中有一个div标签,那么这个div标签在DOM中是元素还是节点呢?

答案:既可以将它称为div元素,也可以将它称为div节点。

元素是节点的一种类型,即元素节点。从程序角度来说,节点的构造函数是Node,元素的构造函数是Element,Element继承Node。

7.1.2 节点的层级

在家庭中,成员之间存在父子关系、兄弟关系。类似地,不同节点之间也存在这种关系。如何描述不同节点之间的关系呢?

不同节点可以划分为不同层级,比如根节点、父节点、子节点、兄弟节点,节点层级示例代码如下。

<!DOCTYPE html>
<html>
  <head>
    <title>测试</title>
  </head>
  <body>
    <a href="#">链接</a>
    <p>段落...</p>
  </body>
</html>

依据层级结构对代码中的节点进行介绍。

根节点:document节点是整个文档的根节点,它的子节点包括文档类型节点和html元素。

父节点 :它是指某一节点的上级节点,例如,html元素是head元素和body元素的父节点,body元素是a元素和p元素的父节点。

子节点:它是指某一节点的下级节点,例如:head元素和body元素是html元素的子节点,a元素和p元素是body元素的子节点。

兄弟节点:它是指同属于一个父节点的两个子节点,例如:head元素和body元素互为兄弟节点,a元素和p元素互为兄弟节点。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>
    <h1>
      <span class="child">span元素</span>
    </h1>
  </div>
  <script>
    var child = document.querySelector('.child');
    console.log(child.parentNode); 
  </script>
</body>
</html>

7.2 节点操作

7.2.1 获取节点

获取父节点:在JavaScript中,可以使用parentNode属性获取当前节点的父节点,如果该节点没有父节点,那么parentNode属性返回null。

获取父节点的示例代码如下。

获取子节点:在DOM中,用来获取子节点的属性有很多,可以结合子节点的特征进行获取。

属性说明
firstChild获取当前节点的首个子节点
lastChild获取当前节点的最后一个子节点
firstElementChild获取当前节点的首个子元素节点
lastElementChild获取当前节点的最后一个子元素节点
children获取当前节点的所有子元素节点集合
childNodes获取当前节点的所有子节点集合

IE 6~IE 8中childNodes属性不会获取文本节点。

在IE 9及以上版本和主流浏览器中则可以获取文本节点。

获取兄弟节点:可以使用previousSibling属性和nextSibling属性获取当前节点的上一个兄弟节点和下一个兄弟节点,进而获取到元素节点、文本节点等内容。若没有兄弟节点,就返回null。

获取兄弟元素节点:可以使用nextElementSibling属性返回当前元素的下一个兄弟元素节点,使用previousElementSibling属性返回当前元素的上一个兄弟元素节点。如果没有兄弟元素节点,则返回null。

nextElementSibling属性和previousElementSibling属性存在有兼容性问题,IE 9以下不支持。

多学一招:兼容获取兄弟元素节点的属性

使用封装函数的方式解决nextElementSibling属性和previousElementSibling属性的浏览器兼容问题,示例代码如下。

function getNextElementSibling(element) {
  var el = element;
  while (el = el.nextSibling) {
    if (el.nodeType === Node.ELEMENT_NODE) { return el; }
  }
  return null;
}
var div = document.querySelector('div');
console.log(getNextElementSibling(div));
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <ul>
    <li>我是li中的文本1</li>
    <li>我是li中的文本2</li>
    <li>我是li中的文本3</li>
  </ul>
  <script>
    var ul = document.querySelector('ul');
    // 获取当前节点的首个子节点
    console.log(ul.firstChild);
    // 获取当前节点的首个子元素节点
    console.log(ul.firstElementChild);
    // 获取当前节点的所有子节点的集合
    console.log(ul.childNodes);
    // 获取当前节点的所有子元素节点的集合
    console.log(ul.children);
  </script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div>第一个</div>
  <div class="second">第二个</div>
  <div>第三个</div>
  <script>
    var second = document.querySelector('.second');
    // 获取当前节点的上一个兄弟节点
    console.log(second.previousSibling);
    // 获取当前节点的下一个兄弟节点
    console.log(second.nextSibling);
    // 获取当前节点的上一个兄弟元素节点
    console.log(second.previousElementSibling);
    // 获取当前节点的下一个兄弟元素节点
    console.log(second.nextElementSibling);
  </script>
</body>
</html>

7.2.2 创建并添加节点

在开发过程中,有时需要创建一个新节点并添加到文档中。例如,在“百度”搜索引擎中进行搜索后,搜索框下方的搜索记录列表中会增加一个新历史记录,这个新历史记录就可以通过创建并添加节点实现。

创建节点:使用document对象的createElement()方法可以创建元素节点,语法如下。

document.createElement('标签名');

创建节点的示例代码如下。

var div = document.createElement('div');
console.log(div);    // 结果为:<div></div>

使用createElement()方法创建的节点是页面中原本不存在的,所以这种方式也称为动态创建节点。

添加节点:节点创建后,我们需要根据实际的开发需求将节点添加到文档中的指定位置。

添加节点的方法:DOM中提供了appendChild()方法和insertBefore()方法用于添加节点,这两个方法都由父节点的对象调用。

appendChild()方法表示将一个节点添加到父节点的所有子节点的末尾。

insertBefore()方法表示将一个节点添加到父节点中的指定子节点的前面,该方法需要接收两个参数,第1个参数表示要添加的节点,第2个参数表示父节点中的指定子节点。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <ul>
    <li>第一个li元素</li>
    <li>第二个li元素</li>
  </ul>
  <button>appendChild()方法</button>
  <button>insertBefore()方法</button>
  <script>
    var ul = document.querySelector('ul');
    var btn = document.querySelectorAll('button');
    btn[0].onclick = function () {
      var div = document.createElement('div');
      div.innerHTML = '通过appendChild()方法新添加的节点';
      ul.appendChild(div);
    };
    btn[1].onclick = function () {
      var div = document.createElement('div');
      div.innerHTML = '通过insertBefore()方法新添加的节点';
      ul.insertBefore(div, ul.children[1]);
    };
  </script>
</body>
</html>

7.2.3 移除节点

移除节点:在DOM中,可以通过removeChild()方法将一个父节点的指定子节点移除,语法格式如下。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <ul>
    <li>第一个li元素</li>
    <li>第二个li元素</li>
  </ul>
  <button>移除ul元素的第2个子元素节点</button>
  <script>
    var ul = document.querySelector('ul');
    var btn = document.querySelector('button');
    btn.onclick = function () {
      ul.removeChild(ul.children[1]);
    };
  </script>
</body>
</html>

7.2.4 【案例】简易留言板

搭建页面结构。

实现单击“发布”按钮发表留言的功能。

实现删除留言功能。

左图为单击“发布”按钮添加留言效果,右图为单击第一条留言中“删除”按钮后的效果。

* {
  margin: 0;
  padding: 0;
}

body {
  padding: 10px;
}

div {
  border: 3px solid seagreen;
  width: 700px;
}

h1 {
  text-align: center;
  color: yellowgreen;
}

ul {
  margin-top: 50px;
  width: 500px;
  height: 200px;
  border: seagreen 3px dashed;
  margin: 30px auto;
  list-style: none;
}

li {
  width: 400px;
  padding: 5px;
  background-color: #eee;
  font-size: 14px;
  margin: 15px auto;
}

li a {
  float: right;
}

textarea {
  width: 400px;
  height: 50px;
  border: 3px solid seagreen;
  outline: none;
  resize: none;
  display: block;
  margin: 20px auto 5px;
}

.sub {
  display: block;
  background-color: seagreen;
  color: seashell;
  width: 200px;
  height: 30px;
  margin: 10px auto;
}

.remove {
  float: right;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="message.css">
</head>
<body>
  <div>
    <h1>留言板</h1>
    <ul></ul>
    <textarea placeholder="请编辑您的留言"></textarea>
    <button class="sub">发布</button>
  </div>
  <script>
    // 获取元素
    var btn = document.querySelector('.sub');
    var text = document.querySelector('textarea');
    var ul = document.querySelector('ul');
    // 注册事件
    btn.onclick = function () {
      if (text.value == '') {
        alert('您没有输入内容');
        return false;
      } else {
        // 创建li元素节点
        var li = document.createElement('li'); 
        li.innerHTML = text.value;
        var button = document.createElement('button');
        button.className = 'remove';
        button.innerHTML = '删除';
        button.onclick = function () {
          ul.removeChild(this.parentNode);
        };
        li.appendChild(button);
        // 在ul元素中添加节点
        ul.insertBefore(li, ul.children[0]);
         // 将文本域中的内容清空
         text.value = '';
      }
    };
  </script>
</body>
</html>

7.2.5 复制节点

复制节点:在DOM中,可以通过cloneNode()方法复制节点。

通过一个节点对象调用cloneNode()方法后,该方法会返回节点对象的副本。该方法的第1个参数是可选参数,具体说明如下。

默认为false,表示只复制节点本身,不复制节点内部的子节点。

如果设为true,表示复制节点本身及里面所有的子节点。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <ul id="myList"><li>苹果</li><li>橙子</li><li>橘子</li></ul>
  <ul id="op"></ul>
  <button>点我</button>
  <script>
    var btn = document.querySelector('button');
    btn.onclick = function () {
      var item = document.getElementById('myList').firstChild;
      var cloneItem = item.cloneNode(true);
      document.getElementById('op').appendChild(cloneItem);
    };
  </script>
</body>
</html>

7.3 事件进阶

7.3.1 事件监听

我们知道,事件需要先注册,然后才能使用。除了通过将对象的事件属性赋值一个事件处理函数来完成事件的注册外,还有什么其他方法可以完成事件注册?

事件的注册还可以通过事件监听的方式实现。

通过事件监听可以给一个事件类型注册多个事件处理函数,但这种方式存在浏览器兼容问题,我们需要根据不同的浏览器编写相应的代码进行处理。

?在早期版本IE浏览器中,事件监听的语法格式如下。

?

在新版浏览器中,事件监听的语法格式如下。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="t">test</div>
  <script>
    var obj = document.getElementById('t');
    // 添加第1个事件处理函数
    obj.attachEvent('onclick', function () {
      console.log('one');
    });
    // 添加第2个事件处理函数
    obj.attachEvent('onclick', function () {
      console.log('two');
    });
  </script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="t">test</div>
  <script>
    var obj = document.getElementById('t');
    // 添加第1个事件处理函数
    obj.addEventListener('click', function () {
      console.log('one');
    });
    // 添加第2个事件处理函数
    obj.addEventListener('click', function () {
      console.log('two');
    });
  </script>
</body>
</html>

7.3.2 事件移除

事件移除:不同方式注册的事件移除方式不同,并且需要考虑兼容性问题。

移除传统方式注册的事件,语法格式如下。

移除事件监听方式注册的事件,语法格式如下。

对象.onclick = null;
对象.detachEvent(type, callback);		// 早期版本IE浏览器
对象.removeEventListener(type, callback);	// 新版浏览器

type表示要移除的事件类型(与添加事件监听时一致),callback表示事件处理函数,且该callback必须与注册时的事件处理函数是同一个函数。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="t">test</div>
  <script>
    var obj = document.getElementById('t');
    // 定义事件处理函数
    function test() {
      console.log('test');
    }
    // obj.attachEvent('onclick', test);      // 事件监听(早期版本IE浏览器)
    // obj.detachEvent('onclick', test);      // 事件移除(早期版本IE浏览器)
    obj.addEventListener('click', test);      // 事件监听(新版浏览器)
    obj.removeEventListener('click', test);    // 事件移除(新版浏览器)
  </script>
</body>
</html>

7.3.3 DOM事件流

假如页面中有一个父div元素嵌套子div元素的结构,并且父div元素和子div元素都有单击事件,当用户单击子div元素后,会先触发子div元素的事件,后触发父div元素的事件。这是为什么呢?

我们想要知道原因,首先需要明白事件流的执行过程。当事件发生时,事件会在发生事件的目标节点与DOM树根节点之间按照特定的顺序进行传播,这个事件传播的过程就是事件流。

事件流分为事件捕获和事件冒泡两种。

事件捕获:由网景公司的团队提出,指的是事件流传播的顺序应该是从DOM树的根节点开始出发一直到发生事件的节点。

事件冒泡:由微软公司的团队提出,指的是事件流传播的顺序应该是从发生事件的节点到DOM树的根节点。

W3C对网景公司和微软公司提出的方案进行了中和处理,将DOM事件流分为3个阶段,具体如下。

事件捕获阶段:事件从document节点自上而下向目标节点传播的阶段。

事件目标阶段:事件流到达目标节点后,执行相应的事件处理函数的阶段。

事件冒泡阶段:事件从目标节点自上而下向document节点传播的阶段。

当事件发生后,浏览器首先进行事件捕获,但不会对事件进行处理;然后进入目标阶段,执行目标节点的事件处理程序;最后实现事件的冒泡,逐级对事件进行处理。

下面以一个包含div元素的页面为例,演示事件流的具体过程。

默认情况下事件处理函数是按照冒泡阶段的顺序执行的,也就是说,先执行目标节点的事件处理函数,再向上“冒泡”,执行父节点的事件处理函数。

若要实现事件捕获,则需要将addEventListener()方法的第3个参数设置为true,表示在捕获阶段完成事件处理。

JavaScript中的事件类型有很多,但不是所有的事件类型都能冒泡,如blur事件、focus事件、mouseenter事件、mouseleave事件。

7.4 事件对象

7.4.1 事件对象的使用

当一个事件被触发后,与该事件相关的一系列信息和数据的集合会被放入一个对象,这个对象称为事件对象。

事件存在时,事件对象才会存在,它是JavaScript自动创建的。

例如,鼠标单击的事件对象中,包含鼠标指针的坐标等相关信息;键盘按键的事件对象中,包含被按按键的键值等相关信息。

在新版浏览器中,通过事件处理函数的参数即可获得事件对象;在早期版本的IE浏览器中,只能通过window对象获取事件对象。

获取事件对象的示例代码如下。

对象.事件属性 = function (event) {};		// 新版浏览器
var 事件对象 = window.event;        		// 早期版本IE浏览器
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <button id="btn">获取event事件对象</button>
  <script>
    var btn = document.getElementById('btn');
    btn.onclick = function (e) {
      var event = e || window.event;    // 获取事件对象的兼容处理
      console.log(event);
    };
  </script>
</body>
</html>

7.4.2 事件对象的常用属性和方法

在事件发生后,通过事件对象的属性和方法可以获取触发事件的对象和事件类型等信息。

属性说明兼容浏览器
e.target获取触发事件的对象新版浏览器
e.srcElement获取触发事件的对象早期版本IE浏览器
e.type获取事件的类型所有浏览器
e.stopPropagation()阻止事件冒泡新版浏览器
e.cancelBubble阻止事件冒泡早期版本IE浏览器
e.preventDefault()阻止默认事件(默认行为)新版浏览器
e.returnValue阻止默认事件(默认行为)早期版本IE浏览器

在前面的内容中,我们在事件处理函数的内部使用this获取当前触发事件的对象。除了这种方式,还可以用e.target 获取(早期版本E浏览器需要用e.srcElement )。通常情况下,这两种方式返回的对象是同一个对象。

代码演示:为一个内容为“单击”的div元素注册单击事件,示例代码如下。

div.onclick = function (e) {
  var e = e || window.event;
  var target = e.target || e.srcElement;
  console.log(target);   	// 结果为:<div>单击</div>
  console.log(this);      	// 结果为:<div>单击</div>
};

?

在HTML中,有些元素自身拥有一些默认行为。但是在实际开发中,有时需要阻止元素的默认行为,例如,在表单验证时发现表单填写有误,需要阻止表单提交。

在事件处理函数中,阻止默认行为可以通过return false来实现,除此之外,还可以通过事件对象的preventDefault()方法实现。

只有事件对象的cancelable属性设置为true,才可以使用preventDefault()方法取消其默认行为。早期版本IE浏览器不支持preventDefault()方法,需要通过将e.returnValue设置为false来实现阻止默认行为。

下面以阻止a标签的默认行为为例进行演示,示例代码如下。

var a = document.querySelector('a');
a.onclick = function (e) {
  var e = e || window.event;
  e.preventDefault();		// 在新版浏览器中阻止默认行为
  e.returnValue = false;  		// 在早期版本IE浏览器中阻止默认行为
};

对于一个注册了事件的节点来说,有时我们希望只有该元素触发事件,但因为事件冒泡的存在,该元素的子元素触发事件时会使该节点的事件被触发,并且该元素的父元素的事件也会被触发,这种现象与预期效果不一致,所以需要阻止事件冒泡。

事件对象的stopPropagation()方法可以阻止事件冒泡行为。

代码演示:阻止事件冒泡,并解决浏览器兼容问题,示例代码如下。

if (window.event) {
  window.event.cancelBubble = true;	// 早期版本IE浏览器
} else {
  e.stopPropagation();		// 新版浏览器(e是事件对象)
}

?

对于早期版本IE浏览器,应使用cancelBubble属性。

事件委托就是将子节点对应的事件注册给父节点,然后利用事件冒泡的原理影响到每个子节点。当子节点触发事件时,会执行注册在父节点上的事件。

事件委托优点:不需要为每个子节点注册事件,而是只给父节点注册事件,当父节点动态添加子节点时,新添加的子节点也可以触发事件。

事件委托适用场景:当多个子节点上的事件类型以及事件处理函数相同时,才适合使用事件委托。

多学一招:禁用右键菜单和文本选中

在DOM中,打开右键菜单时会触发contextmenu事件,开始选择文本时会触发selectstart事件。若要禁用右键菜单和文本选中,可以通过如下代码来实现。

document.addEventListener('contextmenu', function (e) {
  e.preventDefault();	// 禁用右键菜单
});
document.addEventListener('selectstart', function (e) {
  e.preventDefault();	// 禁用文本选中
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <ul>
    <li>我是第 1 个li</li>
    <li>我是第 2 个li</li>
    <li>我是第 3 个li</li>
    <li>我是第 4 个li</li>
    <li>我是第 5 个li</li>
  </ul>
  <script>
    var ul = document.querySelector('ul');
    ul.addEventListener('click', function (e) {
      e.target.style.backgroundColor = 'pink';
    });
  </script>
</body>
</html>

7.5 常用事件

7.5.1 焦点事件

焦点事件包括获得焦点事件和失去焦点事件,常用于表单验证。

事件名称事件触发时机
focus当获得焦点时触发(不会冒泡)
blur当失去焦点时触发(不会冒泡)

案例:检测文本框是否失去焦点,如果失去焦点说明用户填写过文本框,需要验证用户填写的内容是否正确。

左图为初始页面效果图,右图为密码框为空且失去焦点时的效果图。

body {
  background: #ddd;
}

.box {
  background: #fff;
  padding: 20px 30px;
  width: 400px;
  margin: 0 auto;
  text-align: center;
}

label {
  display: block;
  margin-bottom: 10px;
}

button {
  width: 120px;
  height: 30px;
  background: #3388ff;
  border: 1px solid #fff;
  color: #fff;
  font-size: 14px;
}

.ipt {
  width: 260px;
  padding: 4px 2px;
}

#tips {
  width: 440px;
  height: 30px;
  margin: 5px auto;
  background: #fff;
  color: red;
  border: 1px solid #ccc;
  display: none;
  line-height: 30px;
  padding-left: 20px;
  font-size: 13px;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="login.css">
</head>
<body>
  <div id="tips"></div>
  <div class="box">
    <label>用户名:<input id="user" type="text"></label>
    <label>密 码:<input id="pwd" type="password"></label>
    <button type="button">登录</button>
  </div>
  <script>
    var tips = document.getElementById('tips');
    document.getElementById('user').onblur = blur;
    document.getElementById('pwd').onblur = blur;
    // 失去焦点时执行的函数
    function blur() {
      if (this.value === '') {
        tips.style.display = 'block';
        tips.innerHTML = '注意:输入内容不能为空! ';
      } else {
        tips.style.display = 'none';
      }
    }
  </script>
</body>
</html>

7.5.2 【案例】文本框内容的显示和隐藏

本案例需要实现为一个文本框添加提示文本,当单击文本框时,里面的默认提示文字会隐藏,当鼠标指针离开文本框时,里面的文字会显示出来。

【案例】文本框内容的显示和隐藏具体实现思路如下。

为元素注册获取文本框焦点事件focus和失去焦点事件blur。

获取焦点时,判断文本框里面的内容是否为默认文字“手机”,如果是默认文字,就清空表单内容。

失去焦点时,判断文本框里面的内容是否为空,如果为空,则表单里面的内容改为默认文字“手机”。

左图为文本框内容显示的效果,右图为文本框内容隐藏效果。

?

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text" value="手机" style="color:#999">
  <script>
    var text = document.querySelector('input'); 
    text.onfocus = function () {   // 注册获得焦点事件focus
      if (this.value === '手机') {
        this.value = '';
      }
      this.style.color = '#333';
    };
    text.onblur = function () {    // 注册失去焦点事件blur
      if (this.value === '') {
        this.value = '手机';
      }
      this.style.color = '#999';   // 失去焦点需要把文本框里面的文字颜色变浅色
    };
  </script>
</body>
</html>

7.5.3 鼠标事件

鼠标事件是鼠标在页面中进行的一些操作所触发的事件,例如,鼠标单击、鼠标双击、鼠标指针进入、鼠标指针离开等事件。

事件名称事件触发时机
click当鼠标单击时触发
dblclick当鼠标双击时触发
mouseover当鼠标指针移入时触发(当前元素和其子元素都触发)
mouseout当鼠标指针移出时触发(当前元素和其子元素都触发)
mouseenter当鼠标指针移入时触发(子元素不触发)
mouseleave当鼠标指针移出时触发(子元素不触发)
mousedown当按下任意鼠标按键时触发
mouseup当释放任意鼠标按键时触发
mousemove在元素内当鼠标指针移动时持续触发
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .bg { background-color: pink; }
  </style>
</head>
<body>
  <table border="1">
    <thead>
      <tr>
        <th>代码</th>
        <th>名称</th>
        <th>最新公布净值</th>
        <th>累计净值</th>
        <th>前单位净值</th>
        <th>净值增长率</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>0035**</td>
        <td>3个月定期开放债券</td>
        <td>1.075</td>
        <td>1.079</td>
        <td>1.074</td>
        <td>+0.047%</td>
      </tr>
      <tr>
        <td>0035**</td>
        <td>3个月定期开放债券</td>
        <td>1.075</td>
        <td>1.079</td>
        <td>1.074</td>
        <td>+0.047%</td>
      </tr>
      <tr>
        <td>0035**</td>
        <td>3个月定期开放债券</td>
        <td>1.075</td>
        <td>1.079</td>
        <td>1.074</td>
        <td>+0.047%</td>
      </tr>
      <tr>
        <td>0035**</td>
        <td>3个月定期开放债券</td>
        <td>1.075</td>
        <td>1.079</td>
        <td>1.074</td>
        <td>+0.047%</td>
      </tr>
    </tbody>
  </table>
  <script>
    var trs = document.querySelector('tbody').querySelectorAll('tr');
    for (var i = 0; i < trs.length; i++) {
      trs[i].onmouseover = function () {
        this.className = 'bg';
      };
      trs[i].onmouseout = function () {
        this.className = '';
      };
    }
  </script>
</body>
</html>

7.5.4 【案例】下拉菜单

下拉菜单是网页中的常见结构之一,当鼠标指针移入下拉菜单中的某一项时,显示该项的子菜单;当鼠标指针移出所在项时,隐藏子菜单。

下拉菜单案例的具体实现步骤如下。

编写页面布局:编写HTML代码搭建页面的整体布局。

实现鼠标指针移入、移出的效果:首先获取元素节点,然后为获取的每个元素节点注册鼠标移入和移出事件,最后在事件处理函数中,设置子菜单的显示和隐藏。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="nav.css">
</head>
<body>
  <ul class="nav">
    <li>
      <a href="#">微博</a>
      <ul style="display:none">
        <li><a href="#">私信</a></li>
        <li><a href="#">评论</a></li>
        <li><a href="#">@我</a></li>
      </ul>
    </li>
    <li>
      <a href="#">留言板</a>
      <ul>
        <li>
          <a href="">私信</a>
        </li>
        <li>
          <a href="">评论</a>
        </li>
        <li>
          <a href="">@我</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#">电话</a>
      <ul>
        <li>
          <a href="">私信</a>
        </li>
        <li>
          <a href="">评论</a>
        </li>
        <li>
          <a href="">@我</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#">邮箱</a>
      <ul>
        <li>
          <a href="">私信</a>
        </li>
        <li>
          <a href="">评论</a>
        </li>
        <li>
          <a href="">@我</a>
        </li>
      </ul>
    </li>
  </ul>
  <script>
    var nav = document.querySelector('.nav');
    var lis = nav.children;
    for (var i = 0; i < lis.length; i++) {
      lis[i].onmouseenter = function () {
        this.children[1].style.display = 'block'; 
      };
      lis[i].onmouseleave = function () {
        this.children[1].style.display = 'none';
      };
    }
  </script>
</body>
</html>

7.5.5 键盘事件

键盘事件是指用户按键盘上的按键时触发的事件。例如,用户按“Esc”键退出全屏,按“Enter”键换行等。

事件名称事件触发时机
keypress按键盘按键(Shift、Fn、CapsLock等非字符键除外)时触发
keydown按键盘按键时触发
keyup键盘按键弹起时触发

keypress事件获得的键码是ASCII码,keydown和keyup事件获得的键码是虚拟键码。

常用的虚拟键码如下。

虚拟键码48~57代表横排数字键0~9。 虚拟键码65~90代表A~Z键。 虚拟键码13代表“Enter”键。 虚拟键码27代表“Esc”键。 虚拟键码32代表“Space”键。 虚拟键码37~40代表方向键左(←)、上(↑)、右(→)、下(↓)。

?

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <p>用户姓名:<input type="text"></p>
  <p>电子邮箱:<input type="text"></p>
  <p>手机号码:<input type="text"></p>
  <p>个人描述:<input type="text"></p>
  <script>
    var inputs = document.getElementsByTagName('input');
    for (var i = 0; i < inputs.length; ++i) {
      inputs[i].onkeydown = next;
    }
    function next(e) {
      // 判断按下的是否为回车键,回车键的键码为13
      if (e.keyCode === 13) {
        // 遍历所有input,找到当前input的下标
        for (var i = 0; i < inputs.length; ++i) {
          if (inputs[i] === this) {
            // 计算下一个input元素的下标
            var index = i + 1 >= inputs.length ? 0 : i + 1;
            break;
          }
        }
        // 如果下一个input还是文本框,则获取键盘焦点
        if (inputs[index].type === 'text') {
          inputs[index].focus();    // 触发focus事件
        }
      }
    }
  </script>
</body>
</html>

7.5.6 表单事件

表单事件是指对表单操作时发生的事件。例如,单击表单提交按钮时,会触发表单提交事件,单击表单重置按钮时,会触发表单重置事件。

事件名称事件触发时机
submit当表单提交时触发,用于form标签
reset当表单重置时触发,用于form标签
change当内容发生改变时触发,一般用于input、select、textarea标签
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <form id="form">
    <label>用户名:<input id="user" type="text"></label>
    <input type="submit" value="提交">
  </form>
  <script>
    var form = document.getElementById('form');
    var user = document.getElementById('user');
    form.onsubmit = function () {
      if (user.value == '') {
        alert('请填写用户名!');
        return false;
      }
    };
  </script>
</body>
</html>

?

7.6 元素其他操作

7.6.1 获取元素的位置和大小

通过元素的offset系列属性可以获取元素的位置、大小等,offset的含义是偏移量。

属性说明
offsetParent向上层查找最近的设置定位的父元素,或最近的table、td、th、body元素,返回找到的元素
offsetLeft获取元素相对其offsetParent元素左边界的偏移量
offsetTop获取元素相对其offsetParent元素上边界的偏移量
offsetWidth获取元素(包括padding、border和内容区域)宽度
offsetHeight获取元素(包括padding、border和内容区域)高度

使用offset系列属性时的注意事项如下。

offset系列属性都是只读的,获取结果为数字型像素值。

定位是指元素的样式中设置了position定位。

offsetParent在查找父元素时,如果父元素没有设置定位,则继续向上层查找祖先元素。

在Chrome浏览器中,如果一个元素被隐藏(display为none),或其祖先元素被隐藏,或元素的position被设为fixed,则offsetParent属性返回null。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    #a { position: relative; }
    #b { margin-left: 5px; }
    #c { width: 50px; height: 50px; background: pink; margin-left: 10px; }
  </style>
</head>
<body>
  <div id="a">
    <div id="b">
      <div id="c"></div>
    </div>
  </div>
  <script>
    var c = document.getElementById('c');
    console.log(c.offsetParent);
    console.log(c.offsetLeft);
    console.log(c.offsetWidth);
  </script>
</body>
</html>

?

7.6.2 获取元素的可视区域

通过client系列属性可以获取元素的可视区域。

属性说明
clientLeft获取元素左边框的大小
clientTop获取元素上边框的大小
clientWidth获取元素的宽度,包括padding,不包括border、margin和垂直滚动条
clientHeight获取元素的高度,包括padding,不包括border、margin和水平滚动条

使用client系列属性时的注意事项如下。

client系列属性都是只读的,获取结果为数字型像素值。

当内容区域超出容器大小时,clientWidth和clientHeight属性仍然按照CSS中设置的宽度、高度和padding来计算。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    div {
      width: 200px;
      height: 200px;
      background-color: pink;
      border: 10px solid red;
    }
  </style>
</head>
<body>
  <div>
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容
  </div>
  <script>
    var div = document.querySelector('div');
    console.log(div.clientHeight);
    console.log(div.clientTop);
    console.log(div.clientLeft);
  </script>
</body>
</html>

?

7.6.3 元素的滚动操作

通过scroll系列属性可以实现元素的滚动操作。

属性说明
scrollLeft获取或设置元素被“卷去”的左侧距离
scrollTop获取或设置元素被“卷去”的上方距离
scrollWidth获取元素内容的完整宽度,不含边框
scrollHeight获取元素内容的完整高度,不含边框

使用scroll系列属性时的注意事项如下。

scroll系列属性的获取结果为数字型像素值。

scrollWidth和scrollHeight属性是只读属性,不能修改。

scrollLeft和scrollTop属性允许修改。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    div {
      width: 200px;
      height: 200px;
      background-color: pink;
      overflow: auto;
      border: 10px solid red;
    }
  </style>
</head>
<body>
  <div>
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
    我是内容我是内容我是内容我是内容我是内容
  </div>
  <script>
    var div = document.querySelector('div');
    div.addEventListener('scroll', function () {
      console.log(div.scrollTop, div.scrollHeight);
    });
  </script>
</body>
</html>

7.6.4 获取鼠标指针位置

当鼠标事件触发后,若要获取鼠标的位置信息,可以通过事件对象中的鼠标位置属性获取。

属性说明
clientX鼠标指针位于浏览器窗口中页面可视区的水平坐标
clientY鼠标指针位于浏览器窗口中页面可视区的垂直坐标
pageX鼠标指针位于文档的水平坐标,早期版本IE浏览器不支持
pageY鼠标指针位于文档的垂直坐标,早期版本IE浏览器不支持
screenX鼠标指针位于屏幕的水平坐标
screenY鼠标指针位于屏幕的垂直坐标

使用鼠标位置属性时的注意事项如下。

属性都是只读的,无法修改。

当鼠标指针放在网页上并且向下滚动页面时,clientX、clientY获取的结果不会随着页面滚动改变,而pageX、pageY会随着页面滚动改变,这是因为pageX、pageY获取的是文档中的坐标。

早期版本IE浏览器不支持pageX和pageY属性,可以用如下代码进行兼容处理。

?

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .mouse {
      position: absolute;
      background: #ffd965;
      width: 48px;
      height: 48px;
      border-radius: 24px;
    }
  </style>
</head>
<body>
  <div id="div" class="mouse"></div>
  <script>
    var div = document.getElementById('div');
    document.onclick = function (event) {
      var e = event || window.event;
      var pageX = e.pageX || e.clientX + document.documentElement.scrollLeft;
      var pageY = e.pageY || e.clientY + document.documentElement.scrollTop;
      // 计算div的显示位置
      var targetX = pageX - div.offsetWidth / 2;
      var targetY = pageY - div.offsetHeight / 2;
      // 设置div的位置并让它显示
      div.style.display = 'block';
      div.style.left = targetX + 'px';
      div.style.top = targetY + 'px';
    };
  </script>
</body>
</html>

动手实践:鼠标拖曳效果

案例需求:编写一个简单的对话框,实现鼠标拖曳效果。

案例实现思路如下。

根据效果图编写页面结构和样式。

为对话框的标题区域注册鼠标按下事件和鼠标释放事件。

在鼠标按下时注册鼠标移动事件,在鼠标释放时移除鼠标移动事件。

在鼠标按下时,用鼠标指针距离文档左侧和顶部的距离,分别减去div元素距离文档左侧和顶部的距离,得到鼠标指针在div元素内的X、Y坐标值。

在鼠标移动事件中更改div元素的left和top值,计算方式为:使用鼠标指针距离文档左侧和顶部的距离,分别减去鼠标指针在div元素内的X、Y坐标值。

下面通过示意图演示鼠标拖曳时的距离。

body {
  margin: 0;
}

.dialog {
  width: 400px;
  height: 300px;
  box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.46);
  border: 1px solid #ccc;
  position: fixed;
  left: 30px;
  top: 30px;
}

.dialog-title {
  height: 40px;
  background: #f6f6f6;
  line-height: 40px;
  padding-left: 20px;
  color: #555;
  cursor: move;
  user-select: none;
}

.dialog-close {
  float: right;
  font-size: 22px;
  cursor: pointer;
  padding: 0 20px 5px;
  transition: background 0.2s;
  font-family: 'Segoe UI';
  line-height: 35px;
}

.dialog-close:hover {
  background-color: #e81123;
  color: #fff;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="dialog.css">
</head>
<body>
  <div class="dialog" id="box">
    <div class="dialog-title" id="drop">
      <span>注册信息(可以拖曳)</span>
      <div class="dialog-close" id="close">×</div>
    </div>
    <div class="dialog-body"></div>
  </div>
  <script>
    var box = document.getElementById('box');
    var drop = document.getElementById('drop');
    // 鼠标按下时开启鼠标拖曳效果
    drop.onmousedown = function (e) {
      // 计算鼠标指针在div元素内的位置
      var spaceX = e.pageX - box.offsetLeft;
      var spaceY = e.pageY - box.offsetTop;
      // 让div元素跟随鼠标指针移动
      document.onmousemove = function (e) { 
        // 设置div元素移动后的位置
        box.style.left = e.pageX - spaceX + 'px';
        box.style.top = e.pageY - spaceY + 'px';
      };
    };
    // 鼠标释放时取消鼠标拖曳效果
    document.onmouseup = function () {
      document.onmousemove = null;
    };
    // 单击右上角的关闭按钮可关闭对话框
    document.getElementById('close').onclick = function () {
      box.style.display = 'none'; 
    };
  </script>
</body>
</html>

本章小结

本章首先讲解了节点基础和节点操作,然后讲解了事件进阶、事件对象和常用事件,最后讲解了元素其他操作。通过本章的学习,希望读者能够对DOM有更加全面的认识,能够运用DOM相关知识编写一些交互性强的页面。

文章来源:https://blog.csdn.net/u010321564/article/details/135475203
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。