JavaScript 【DOM】

news2024/10/7 4:26:33

【DOM】

原创内容,转载请注明出处!

一、DOM基本概念

DOM(Document Object Model,文档对象模型)是 JavaScript 操作 HTML 文档的接口,使文档操作变得非常优雅、简便。

DOM 最大的特点就是将 HTML 文档表示为 “节点树”。

DOM 元素/节点:就是渲染到页面上的,一个个的 HTML 标签体(标签 + 属性 + 内容)。

二、DOM节点树

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>DOM</title>
</head>

<body>
    <h1>IMOOC</h1>
    <div>
        <h2>Coder Dream</h2>
        <img src="logo.png">
        <div class="box">
            文本内容
        </div>
    </div>
</body>

</html>

【DOM】

在这里插入图片描述

整个 html 文档就对应一个 document 对象,可以操作 html 文档里面所有的标签及其属性和文本。

三、nodeType

节点的 nodeType 属性可以显示这个节点具体的类型。

document.nodeType; // 9

nodeType值节点类型
1元素节点,例如 <p><div>
3文字节点
8注释节点
9document 节点
10DTD 节点(文档类型声明)

四、document

4.1 访问元素节点

所谓 “访问” 元素节点,就是指 “得到”、“获取” 页面上的元素节点。

对节点进行操作,第一步就是要得到它。

访问元素节点主要依靠 document 对象。

4.2 认识 document 对象

document 对象是 DOM 中最重要的东西,几乎所有 DOM 的功能都封装在了 document 对象中。

document 对象也表示整个 HTML 文档,它是 DOM 节点树的根。

document 对象的 nodeType 属性值是 9。

typeof document;	// object
document.nodeType;	// 9

4.3 访问元素节点的常用方法

注意:以下方法的参数都是字符串类型 ''

方法功能兼容性
document.getElementById()通过 id 得到元素IE 6
document.getElementsByTagName()通过标签名得到元素数组IE 6
document.getElementsByClassName()通过类名得到元素数组IE 9
document.querySelector()通过选择器得到元素IE 8 部分兼容、IE 9 完全兼容
document.querySelectorAll()通过选择器得到元素数组IE 8 部分兼容、IE 9 完全兼容

Element:元素。

query:查询。

Selector:选择器。

4.4 getElementById()

document.getElementById() 功能是通过 id 得到元素节点。

  • HTML
<div id="box">我是一个盒子</div>
<p id="para">我是一个段落</p>
  • JS
var box = document.getElementById('box');
var para = document.getElementById('para');

【注意事项】

如果页面上有相同 id 的元素,则只能得到第一个。

原则上,html 中同一名称的 id 也只能出现一次。

4.5 延迟运行

通常 JS 代码要写到 HTML 结构的最后,否则 JS 无法找到相应的 DOM 节点。

可以使用 window.onload = function(){} 来延迟 JS 的执行,直到 HTML 文档加载完毕后(触发 window.onload 事件),再执行函数里的代码。

一般 script 标签会被放在头部或尾部。

头部就是 <head></head> 里面,尾部一般指 </body> 前,但也有放在 </body> 之后的(最好不要这样)!

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box">我是一个盒子</div>
    <p id="para">我是一个段落</p>
    <script>
        // 获取 DOM 节点
        var box = document.getElementById('box');
        var para = document.getElementById('para');
        // 输出获取到的 DOM 节点
        console.log(box);	// <div id="box">我是一个盒子</div>
        console.log(para);	// <p id="para">我是一个段落</p>
    </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        // 获取 DOM 节点
        var box = document.getElementById('box');
        var para = document.getElementById('para');
        // 输出获取到的 DOM 节点
        // 由于 HTML 代码是顺序执行的,当执行到此处的 JS 代码时,后面 body 内的 DOM 节点还没来得及执行
        console.log(box);	// null
        console.log(para);	// null
    </script>
</head>

<body>
    <div id="box">我是一个盒子</div>
    <p id="para">我是一个段落</p>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        // window.onload 事件:当 HTML 文档加载完毕后触发
        window.onload = function () {
            // 获取 DOM 节点
            var box = document.getElementById('box');
            var para = document.getElementById('para');
            // 输出获取到的 DOM 节点
            console.log(box);	// <div id="box">我是一个盒子</div>
            console.log(para);	// <p id="para">我是一个段落</p>
        }
    </script>
</head>

<body>
    <div id="box">我是一个盒子</div>
    <p id="para">我是一个段落</p>
</body>

</html>

4.6 getElementsByTagName()

getElementsByTagName() 方法的功能是通过标签名得到节点数组。

注意:得到的是一个数组!

<p>我是段落</p>
<p>我是段落</p>
<p>我是段落</p>
<p>我是段落</p>
var ps = document.getElementsByTagName('p');

【注意事项】

数组方便遍历,从而可以批量操控元素节点。

即使页面上只有一个指定标签名的节点,也将得到长度为 1 的数组。

任何一个节点元素也可以调用 getElementsByTagName() 方法,从而得到其内部的某种标签名的元素节点。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        window.onload = function () {
            var ps = document.getElementsByTagName('p');
            console.log(ps);	// HTMLCollection(6) [p, p, p, p, p, p]
        }
    </script>
</head>

<body>
    <div id="box1">
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
    </div>
    <div id="box2">
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
    </div>
</body>

</html>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        window.onload = function () {
            // 先得到 box1
            var box1 = document.getElementById('box1');
            // 得到 box1 中的 p 标签的数组
            var ps_inbox1 = box1.getElementsByTagName('p');
            console.log(ps_inbox1);	// HTMLCollection(3) [p, p, p]
        }
    </script>
</head>

<body>
    <div id="box1">
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
    </div>
    <div id="box2">
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
    </div>
</body>

</html>

4.7 getElementsByClassName()

getElementsByClassName() 方法的功能是通过类名得到节点数组。

  • HTML
<div class="spec">我是盒子</div>
<div>我是盒子</div>
<div class="spec">我是盒子</div>
<div class="spec">我是盒子</div>
  • JS
var spec_divs = document.getElementsByClassName('spec');

【注意事项】

getElementsByClassName() 方法从 IE9 开始兼容。

某个节点元素也可以调用 getElementsByClassName() 方法,从而得到其内部的某类名的元素节点。

4.8 querySelector()

querySelector() 方法的功能是通过选择器得到元素。

  • HTML
<div id="box1">
	<p>我是段落</p>
    <p class="spec">我是段落</p>
    <p>我是段落</p>
</div>
  • JS
var the_p = document.querySelector('#box1 .spec');

【注意事项】

querySelector() 方法只能得到页面上一个元素,如果有多个元素符合条件,则只能得到第一个元素。

querySelector() 方法从 IE8 开始兼容,但从 IE9 开始支持 CSS3 的选择器,如:nth-child():[src^='dog'] 等 CSS3 选择器形式都支持良好。

注意:不能选择伪类!

4.9 querySelectorAll()

querySelectorAll() 方法的功能是通过选择器得到元素数组。

即使页面上只有一个符合选择器的节点,也将得到长度为 1 的数组。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul id="list1">
        <li>我是li</li>
        <li>我是li</li>
        <li>我是li</li>
        <li>我是li</li>
        <li>我是li</li>
    </ul>
    <ul id="list2">
        <li>我是li</li>
        <li>我是li</li>
        <li>我是li</li>
        <li>我是li</li>
        <li>我是li</li>
    </ul>
    <script>
        var lis_inlist1 = document.querySelectorAll('#list1 li');

        console.log(lis_inlist1);	// NodeList(5) [li, li, li, li, li]
    </script>
</body>

</html>

五、节点的关系

在这里插入图片描述

关系考虑所有结点
子节点childNodes
父节点parentNode
第一个子节点firstChild
最后一个子节点lastChild
前一个兄弟节点previousSibling
后一个兄弟节点nextSibling

【注意:文本节点也属于节点】

DOM 中,文本节点也属于节点,在使用节点的关系时一定要注意。

在标准的 W3C 规范中,空白文本节点也应该算作节点,但是在 IE8 及以前的浏览器中会有一定的兼容问题,它们不把空白文本节点当作节点。

【排除文本节点的干扰】

从 IE9 开始支持一些 “只考虑元素节点” 的属性。

如果考虑兼容性,可以通过后面的函数封装来实现。

关系考虑所有结点只考虑元素节点
子节点childNodeschildren
父节点parentNode
第一个子节点firstChildfirstElementChild
最后一个子节点lastChildlastElementChild
前一个兄弟节点previousSiblingpreviousElementSibling
后一个兄弟节点nextSiblingnextElementSibling
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box">
        <p>我是段落A</p>
        <p id="para">我是段落B</p>
        <p>我是段落C</p>
    </div>

    <script>
        var box = document.getElementById('box');
        var para = document.getElementById('para');

        // 所有子节点
        console.log(box.childNodes);
        // 所有的元素子节点(IE9开始兼容)
        console.log(box.children);
        console.log(box.children.para);

        // 第一个子节点
        console.log(box.firstChild);
        console.log(box.firstChild.nodeType);
        // 第一个元素子节点(IE9开始兼容)
        console.log(box.firstElementChild);

        // 最后一个子节点
        console.log(box.lastChild);
        console.log(box.lastChild.nodeType);
        // 最后一个元素子节点(IE9开始兼容)
        console.log(box.lastElementChild);

        // 父节点
        console.log(para.parentNode);

        // 前一个兄弟节点
        console.log(para.previousSibling);
        // 前一个元素兄弟节点(IE9开始兼容)
        console.log(para.previousElementSibling);

        // 后一个兄弟节点
        console.log(para.nextSibling);
        // 后一个元素兄弟节点(IE9开始兼容)
        console.log(para.nextElementSibling);
    </script>
</body>

</html>
  • 结果

在这里插入图片描述

注意:文本也算作节点(如图选中空白部分)
在这里插入图片描述

六、书写常见的节点关系函数

书写 IE6 也能兼容的 “寻找所有元素子节点” 函数。

书写 IE6 也能兼容的 “寻找前一个元素兄弟节点” 函数。

如何编写函数,获得某元素的所有的兄弟节点。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box">
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
        <p id="fpara">我是段落fpara</p>
        我是文本
        <!-- 我是注释 -->
        <p id="para">
            我是段落para
            <span>1</span>
            <span>2</span>
            <span>3</span>
        </p>
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
    </div>

    <script>
        var box = document.getElementById('box');
        var para = document.getElementById('para');
        var fpara = document.getElementById('fpara');

        // 封装一个函数,这个函数可以返回元素的所有子元素节点(兼容到 IE6),类似 children 的功能
        function getChildren(node) {
            // 结果数组
            var children = [];
            // 遍历 node 这个节点的所有子节点,判断每一个子节点的 nodeType 属性是不是 1
            // 如果是 1,就推入结果数组
            for (var i = 0; i < node.childNodes.length; i++) {
                if (node.childNodes[i].nodeType == 1) {
                    children.push(node.childNodes[i]);
                }
            }
            return children;
        }

        console.log(getChildren(box));
        console.log(getChildren(para));

        // 封装一个函数,这个函数可以返回元素的前一个元素兄弟节点(兼容到 IE6),类似 previousElementSibling 的功能
        function getElementPrevSibling(node) {
            var o = node;
            // 使用 while 语句
            while (o.previousSibling != null) {
                if (o.previousSibling.nodeType == 1) {
                    // 结束循环,找到了
                    return o.previousSibling;
                }
                // 让 o 成为它的前一个节点
                o = o.previousSibling;
            }
            return null;
        }

        console.log(getElementPrevSibling(para));
        console.log(getElementPrevSibling(fpara));

        // 封装第三个函数,这个函数可以返回元素的所有元素兄弟节点
        function getAllElementSibling(node) {
            // 前面的元素兄弟节点
            var prevs = [];
            // 后面的元素兄弟节点
            var nexts = [];

            var o = node;
            // 遍历 node 的前面的节点
            while (o.previousSibling != null) {
                if (o.previousSibling.nodeType == 1) {
                    prevs.unshift(o.previousSibling);
                }
                o = o.previousSibling;
            }

            o = node;

            // 遍历 node 的后面的节点
            while (o.nextSibling != null) {
                if (o.nextSibling.nodeType == 1) {
                    nexts.push(o.nextSibling);
                }
                o = o.nextSibling;
            }

            // 将两个数组进行合并,然后返回
            return prevs.concat(nexts);
        }

        console.log(getAllElementSibling(para));
    </script>
</body>

</html>

七、节点操作

7.1 如何改变元素节点中的内容

改变元素节点中的内容可以使用两个相关属性。

  • innerHTML
  • innerText

innerHTML 属性能以 HTML 语法设置节点中的内容。

innerText 属性只能以纯文本的形式设置节点中的内容。

  • innerHTML
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box"></div>
    <script>
        var oBox = document.getElementById('box');
        oBox.innerHTML = '周吉瑞';
    </script>
</body>

</html>

在这里插入图片描述

  • innerHTML
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box"></div>
    <script>
        var oBox = document.getElementById('box');
        // 注意:此处的 HTML 字符串不允许换行!
        oBox.innerHTML = '<ul><li>牛奶</li><li>咖啡</li></ul>';
    </script>
</body>

</html>

  • innerText
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box"></div>
    <script>
        var oBox = document.getElementById('box');
        oBox.innerText = '周吉瑞';
    </script>
</body>

</html>

  • innerText
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box"></div>
    <script>
        var oBox = document.getElementById('box');
        oBox.innerText = '<ul><li>牛奶</li><li>咖啡</li></ul>';
    </script>
</body>

</html>

7.2 如何改变元素节点的CSS样式

改变元素节点的 CSS 样式需要使用这样的语句:

  • oBox.style.backgroundColor = 'red';
  • oBox.style.backgroundImage = 'url(images/1.jpg)';
  • oBox.style.fontSize = '32px';

【注意事项】

  • CSS 属性要写成 “驼峰” 形式
  • CSS 属性值要设置成完整形式
  • 注意写单位
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <div class="box" id="box">
        你好
    </div>

    <script>
        var oBox = document.getElementById('box');
        oBox.style.backgroundColor = 'rgb(100, 200, 123)';
        oBox.style.backgroundColor = '#f80';
        oBox.style.backgroundImage = 'url(https://www.imooc.com/static/img/index/logo-recommended.png)';
        oBox.style.backgroundSize = 'contain';
        oBox.style.fontSize = '50px';
    </script>
</body>

</html>

JS 修改的 CSS 样式,属于行内式,优先级最高!所以可以覆盖原有的样式。

7.3 如何改变元素节点的HTML属性

标准 W3C 属性,如 srchreftitlealt 等等,只需要直接打点进行更改即可。

oImg.src = 'images/2.jpg';

【案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <img src="images/1.jpg" id="pic">
    <a href="http://www.baidu.com" id="link">
        去百度
    </a>

    <script>
        var oPic = document.getElementById('pic');
        var oLink = document.getElementById('link');
        oPic.src = 'images/2.jpg';
        oLink.href = 'http://www.imooc.com';
        oLink.innerText = '去慕课网';
    </script>
</body>

</html>

对于不符合 W3C 标准的属性,要使用 setAttribute()getAttribute() 来设置、读取。

oBox.setAttribute('data-n', 10);
var n = oBox.getAttribute('data-n');
alert(n);

HTML 的自定义属性,主要用途就是与 JS 配合方便实现一些效果。

【案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box"></div>

    <script>
        var box = document.getElementById('box');
        box.setAttribute('data-n', 10);
        var n = box.getAttribute('data-n');
        alert(n);
    </script>
</body>

</html>

八、节点的创建、移动、删除和克隆

8.1 节点的创建

document.createElement() 方法用于创建一个指定 tag name 的 HTML 元素。

var oDiv = document.createElement('div');

8.2 “孤儿节点”

新创建出的节点是 “孤儿节点”,这意味着它并没有被挂载到 DOM 树上,我们无法看见它。

必须继续使用 appendChild()insertBefore() 方法将孤儿节点插入到 DOM 树上。

8.2.1 appendChild()

任何已经在 DOM 树上的节点,都可以调用 appendChild() 方法,它可以将孤儿节点挂载到它的内部,成为它的最后一个子节点。

父节点.appendChild(孤儿节点);

【小案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box">
        <p>我是原本的段落0</p>
        <p>我是原本的段落1</p>
        <p>我是原本的段落2</p>
    </div>
    <script>
        var oBox = document.getElementById('box');
        // 创建孤儿节点
        var oP = document.createElement('p');
        // 设置内部文字
        oP.innerText = '我是新来的';
        // 上树
        oBox.appendChild(oP);
    </script>
</body>

</html>

8.2.2 insertBefore()

任何已经在 DOM 树上的节点,都可以调用 insertBefore() 方法,它可以将孤儿节点挂载到它的内部,成为它的 “标杆子节点” 之前的节点。

父节点.insertBefore(孤儿节点, 标杆节点);

【小案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box">
        <p>我是原本的段落0</p>
        <p>我是原本的段落1</p>
        <p>我是原本的段落2</p>
    </div>
    <script>
        var oBox = document.getElementById('box');
        var oPs = oBox.getElementsByTagName('p');
        // 创建孤儿节点
        var oP = document.createElement('p');
        // 设置内部文字
        oP.innerText = '我是新来的';
        // 上树
        oBox.insertBefore(oP, oPs[1]);
    </script>
</body>

</html>

8.3 节点创建小案例

【动态创建一个20行12列的表格】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        td {
            width: 20px;
            height: 20px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <table id="mytable"></table>

    <script>
        // 请动态创建出一个 20 行 12 列的表格
        var mytable = document.getElementById('mytable');

        for (var i = 0; i < 20; i++) {
            // 创建了新的 tr 标签
            var tr = document.createElement('tr');
            for (var j = 0; j < 12; j++) {
                // 创建了新的 td 标签
                var td = document.createElement('td');
                // 让 tr 追加 td 标签
                tr.appendChild(td);
            }
            // 让 mytable 追加 tr 标签
            mytable.appendChild(tr);
        }
    </script>
</body>

</html>

【九九乘法表】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        td {
            width: 80px;
            height: 30px;
            padding-left: 10px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <table id="mytable"></table>

    <script>
        // 请创建九九乘法表
        var mytable = document.getElementById('mytable');
		//控制行数
        for (var i = 1; i <= 9; i++) {
            // 创建了新的 tr 标签
            var tr = document.createElement('tr');
            for (var j = 1; j <= i; j++) {
                // 创建了新的 td 标签
                var td = document.createElement('td');
                // 设置 td 内部的文字
                td.innerText = i + '×' + j + '=' + (i * j);
                // 让tr追加 td 标签
                tr.appendChild(td);
            }
            // 让 mytable 追加 tr 标签
            mytable.appendChild(tr);
        }
    </script>
</body>

</html>

8.4 移动节点

如果将已经挂载到 DOM 树上的节点成为 appendChild() 或者 insertBefore() 的参数,这个节点将会被移动。

新父节点.appendChild(已经有父亲的节点);
新父节点.insertBefore(已经有父亲的节点, 标杆子节点);

这意味着一个节点不能同时位于 DOM 树的两个位置。

【小案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box1">
        <p id="para">我是段落</p>
    </div>

    <div id="box2">
        <p>我是box2的原有p标签</p>
        <p>我是box2的原有p标签</p>
        <p>我是box2的原有p标签</p>
        <p>我是box2的原有p标签</p>
    </div>

    <script>
        var box2 = document.getElementById('box2');
        var para = document.getElementById('para');
        var ps_inbox2 = box2.getElementsByTagName('p');

        box2.insertBefore(para, ps_inbox2[2]);
    </script>
</body>

</html>

8.5 删除节点

removeChild() 方法从 DOM 中删除一个子节点。

父节点.removeChild(要删除子节点);

节点不能主动删除自己,必须由父节点删除它。

【小案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box">
        <p>我是p节点0</p>
        <p>我是p节点1</p>
        <p>我是p节点2</p>
    </div>

    <script>
        var box = document.getElementById('box');
        var the_first_p = box.getElementsByTagName('p')[0];

        box.removeChild(the_first_p);
    </script>
</body>

</html>

8.6 克隆节点

cloneNode() 方法可以克隆节点,克隆出的节点是 “孤儿节点”。

必须继续使用 appendChild()insertBefore() 方法将孤儿节点插入到 DOM 树上。

var 孤儿节点 = 老节点.cloneNode();
var 孤儿节点 = 老节点.cloneNode(true);

参数是一个布尔值,表示是否采用深度克隆:如果为 true,则该节点的所有后代节点也会被克隆,如果为 false,则只克隆该节点本身。

【小案例】

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="box1">
        <ul>
            <li>牛奶</li>
            <li>咖啡</li>
            <li>可乐</li>
        </ul>
    </div>

    <div id="box2"></div>

    <script>
        var box1 = document.getElementById('box1');
        var box2 = document.getElementById('box2');
        var theul = box1.getElementsByTagName('ul')[0];

        // 克隆节点
        var new_ul = theul.cloneNode(true);
        box2.appendChild(new_ul);
    </script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="box1">
        <ul>
            <li>牛奶</li>
            <li>咖啡</li>
            <li>可乐</li>
        </ul>
    </div>

    <div id="box2"></div>

    <script>
        var box1 = document.getElementById('box1');
        var box2 = document.getElementById('box2');
        var theul = box1.getElementsByTagName('ul')[0];

        // 克隆节点
        var new_ul = theul.cloneNode(false);
        box2.appendChild(new_ul);
    </script>
</body>
</html>

九、事件监听

DOM 允许我们书写 JavaScript 代码以让 HTML 元素作出反应。

什么是 “事件”:用户与网页的交互动作。

9.1 什么是事件监听

“监听” 顾名思义,就是让计算机随时能够发现这个事件发生了,从而执行程序员预先编写好的一些程序。

设置事件监听的方法主要有 onxxxaddEventListener() 两种,二者的区别将在 “事件传播” 一课中介绍。

原始的事件处理方法:“直接通过事件绑定函数”

比如:

HTML:<button onclick="add();">点击</button>

JS:function add() { alert("相加"); }

以上方式不推荐使用!!!

9.2 最简单的设置事件监听的方法

最简单的给元素设置事件监听的方法就是设置它们的 onxxx 属性,像这样:

oBox.onclick = function() {
    // 点击盒子时,将执行这里的语句
}
function fun() {
    ...
}

oBox.onclick = fun;

【小案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 200px;
            height: 200px;
            background-color: #ccc;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <script>
        var oBox = document.getElementById('box');

        // 给box这个盒子添加点击事件监听
        oBox.onclick = function () {
            alert('你好,我是点击事件函数');
        };
    </script>
</body>

</html>

9.3 常见的鼠标事件监听

事件名事件描述
onclick当鼠标单击某个对象
ondblclick当鼠标双击某个对象
onmousedown当某个鼠标按键在某个对象上被按下
onmouseup当某个鼠标按键在某个对象上被松开
onmousemove当某个鼠标按键在某个对象上被移动
onmouseenter当鼠标进入某个对象(相似事件 onmouseover
onmouseleave当鼠标离开某个对象(相似事件 onmouseout

9.4 常见的键盘事件监听

事件名事件描述
onkeypress当某个键盘的键被按下(系统按钮如箭头键和功能键无法得到识别)
onkeydown当某个键盘的键被按下(系统按钮可以识别,并且会先于 onkeypress 发生)
onkeyup当某个键盘的键被松开

9.5 常见的表单事件监听

事件名事件描述
onchange当用户改变完成域的内容
onfocus当某元素获得焦点(比如 tab 键或鼠标点击)
onblur当某元素失去焦点
onsubmit当表单被提交
onreset当表单被重置
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <form id="myform">
        <p>
            姓名:
            <input type="text" name="nameField">
        </p>
        <p>
            年龄:
            <input type="text" name="ageField">
        </p>
        <p>
            <input type="submit">
        </p>
    </form>

    <script>
        var myform = document.getElementById('myform');
        // 表单对象可以通过 “打点” name 属性,得到里面的子元素。
        var nameField = myform.nameField;
        var ageField = myform.ageField;

        nameField.onchange = function () {
            console.log('你已经修改完了姓名');
        };

        nameField.oninput = function () {
            console.log('你正在修改姓名');
        };

        nameField.onfocus = function () {
            console.log('姓名框已经得到了焦点');
        }

        nameField.onblur = function () {
            console.log('姓名框已经失去了焦点');
        }

        myform.onsubmit = function () {
            alert('你正在尝试提交表单');
         /* return true;  可以省略,默认执行表单提交 */
         /* return false; 不执行表单提交,比如用户信息填写不全就不应该提交表单 */
        }
    </script>
</body>

</html>

表单对象可以通过 “打点” name 属性,得到里面的子元素。

9.6 常见的页面事件监听

事件名事件描述
onload当页面或图像被完成加载
onunload当用户退出页面

更多有关窗口或页面的事件在 BOM 课程中介绍。

十、事件传播

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box1 {
            width: 202px;
            height: 202px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box2 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box3 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        var oBox1 = document.getElementById('box1');
        var oBox2 = document.getElementById('box2');
        var oBox3 = document.getElementById('box3');

        oBox2.onclick = function () {
            console.log('我是 box2 的 onclick');
        };

        oBox3.onclick = function () {
            console.log('我是 box3 的 onclick');
        };

        oBox1.onclick = function () {
            console.log('我是 box1 的 onclick');
        };
    </script>
</body>

</html>



10.1 捕获阶段和冒泡阶段

10.2 onxxx写法只能监听冒泡阶段

10.3 addEventListener()方法

oBox.addEventListener('click', function(){}, true);

Event:事件

【小案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box1 {
            width: 202px;
            height: 202px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box2 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box3 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        var oBox1 = document.getElementById('box1');
        var oBox2 = document.getElementById('box2');
        var oBox3 = document.getElementById('box3');

        oBox1.onclick = function () {
            console.log('我是box1的onclick');
        };

        oBox2.onclick = function () {
            console.log('我是box2的onclick');
        };

        oBox3.onclick = function () {
            console.log('我是box3的onclick');
        };

        oBox3.addEventListener('click', function () {
            console.log('我是box3的捕获阶段');
        }, true);

        oBox2.addEventListener('click', function () {
            console.log('我是box2的捕获阶段');
        }, true);

        oBox1.addEventListener('click', function () {
            console.log('我是box1的捕获阶段');
        }, true);


        oBox1.addEventListener('click', function () {
            console.log('我是box1的冒泡阶段');
        }, false);

        oBox2.addEventListener('click', function () {
            console.log('我是box2的冒泡阶段');
        }, false);

        oBox3.addEventListener('click', function () {
            console.log('我是box3的冒泡阶段');
        }, false);
    </script>
</body>

</html>

【注意事项】

  • 最内部元素不再区分捕获和冒泡阶段,会先执行写在前面的监听,然后执行后写的监听

原因:先捕获进入了最内层,然后从最内层开始冒泡,此时对于最内层元素来说,捕获和冒泡都是同一时间段,所以谁写在前就先执行谁。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box1 {
            width: 202px;
            height: 202px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box2 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box3 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        var oBox1 = document.getElementById('box1');
        var oBox2 = document.getElementById('box2');
        var oBox3 = document.getElementById('box3');

        oBox3.onclick = function () {
            console.log('我是box3的onclick');
        };

        oBox3.addEventListener('click', function () {
            console.log('我是box3的冒泡阶段');
        }, false);

        oBox3.addEventListener('click', function () {
            console.log('我是box3的捕获阶段');
        }, true);
    </script>
</body>

</html>
我是box3的onclick
我是box3的冒泡阶段
我是box3的捕获阶段

注意:在新版的 Chrome 中都默认先执行最内层元素的捕获再执行冒泡!

  • 如果给元素设置相同的两个或多个同名事件,则 onclick 写法后面写的会覆盖先写的;而 addEventListener 会按顺序执行(原因是 onclick 函数赋值多次会发生覆盖)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box1 {
            width: 202px;
            height: 202px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box2 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
            padding: 50px;
        }

        #box3 {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        var oBox1 = document.getElementById('box1');
        var oBox2 = document.getElementById('box2');
        var oBox3 = document.getElementById('box3');

        oBox2.onclick = function () {
            alert('A');
        };

        oBox2.onclick = function () {
            alert('B');
        };

        oBox2.addEventListener('click', function () {
            alert('C');
        }, false);

        oBox2.addEventListener('click', function () {
            alert('D');
        }, false);
    </script>
</body>

</html>

10.4 removeEventListener()方法

当我们 addEventListener() 后,该监听事件就会一直生效,直到关闭页面或是移除该对应的监听!

removeEventListener() 方法用来移除监听事件(只能移除具名函数的监听,且方法名称后面不能带)

var body = document.querySelector('body'),
var clickTarget = document.getElementById('click-target'),
var mouseOverTarget = document.getElementById('mouse-over-target'),
var toggle = false;

// 具名函数
function makeBackgroundYellow() {
    'use strict';

    if (toggle) {
        body.style.backgroundColor = 'white';
    } else {
        body.style.backgroundColor = 'yellow';
    }

    toggle = !toggle;
}

// 注册监听
clickTarget.addEventListener('click',
    makeBackgroundYellow,
    false
);

// 注册监听
mouseOverTarget.addEventListener('mouseover', function () {
    'use strict';

    // 移除监听
    clickTarget.removeEventListener('click',
        makeBackgroundYellow,
        // 可以指定是移除冒泡类型的,还是捕获类型的!
        false
    );
});

10.5 特别注意

addEventListener() 一但注册某个事件,那么这个事件是会一直生效的,就算是该注册事件写在某个函数中,那个函数调用已经结束了,但是该事件还是会存在!因为事件的注册是直接绑定到相应的元素上的,并且是异步的,除非页面被关闭,或者是移除该监听!

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn">点击</button>
    <script>
        var btn = document.getElementById('btn');

        function demo() {
            console.log('btn');
        }

        function test() {
            btn.addEventListener('click', demo, false);
            return 'over';
        }
        
        console.log(test());
    </script>
</body>

</html>

执行结果:
over
(点击按钮)
btn

特别注意:事件传播过程中如果没有控制,那么事件捕获最先从 document 对象开始,事件冒泡最晚到达 document 对象!

十一、事件对象

11.1 什么是事件对象

事件处理函数提供一个形式参数,它是一个对象,封装了本次事件的细节。

这个参数通常用单词 event 或字母 e 来表示。

oBox.onmousemove = function(e) {
    // 对象 e 就是这次事件的“事件对象”
}

这个对象 e 接受的值由浏览器或操作系统传递。

11.2 鼠标位置

属性属性描述
clientX鼠标指针相对于浏览器的水平坐标
clientY鼠标指针相对于浏览器的垂直坐标
pageX鼠标指针相对于整张网页的水平坐标
pageY鼠标指针相对于整张网页的垂直坐标
offsetX鼠标指针相对于事件源元素的水平坐标
offsetY鼠标指针相对于事件源元素的垂直坐标
  • 浏览器

  • 整张网页

  • 事件源

【小案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #box {
            width: 200px;
            height: 200px;
            background-color: #333;
            margin: 100px;
            margin-top: 150px;
        }

        body {
            height: 2000px;
        }

        #info {
            font-size: 30px;
            margin: 60px;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <div id="info"></div>

    <script>
        var oBox = document.getElementById('box');
        var oInfo = document.getElementById('info');

        oBox.onmousemove = function (e) {
            oInfo.innerHTML = 'offsetX/Y:' + e.offsetX + ',' + e.offsetY + '<br>'
                + 'clientX/Y:' + e.clientX + ',' + e.clientY + '<br>'
                + 'pageX/Y:' + e.pageX + ',' + e.pageY;
        };
    </script>
</body>

</html>

【注意事项】

对于 offsetX 和 offsetY 而言,总是以最内层元素为事件源。

所以应避免事件源盒子内部有小盒子的存在。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        #box {
            overflow: hidden;
            width: 200px;
            height: 200px;
            background-color: #333;
            margin: 100px;
            margin-top: 150px;
        }

        body {
            height: 2000px;
        }

        #info {
            font-size: 30px;
            margin: 60px;
        }

        p {
            width: 100px;
            height: 100px;
            background-color: pink;
            margin: 50px;
        }
    </style>
</head>

<body>
    <div id="box">
        <p></p>
    </div>
    <div id="info"></div>

    <script>
        var oBox = document.getElementById('box');
        var oInfo = document.getElementById('info');

        oBox.onmousemove = function (e) {
            oInfo.innerHTML = 'offsetX/Y:' + e.offsetX + ',' + e.offsetY + '<br>';
        };
    </script>
</body>

</html>

11.3 e.charCode和e.keyCode属性

e.charCode 属性通常用于 onkeypress 事件中,表示用户输入的字符的 “字符码”。

e.keyCode 属性通常用于 onkeydown 事件和 onkeyup 中,表示用户按下的按键的 “键码”。

11.3.1 charCode字符码

字符字符码
0 ~ 948 ~ 57
A ~ Z65 ~ 90
a ~ z97 ~ 122

11.3.2 keyCode键码

按键键码
0 ~ 948 ~ 57(同 charCode 键码完全相同)
A ~ Z a ~ z65 ~ 90(字母不分大小写)
方向键 ← ↑ → ↓37、38、39、40
退格键 Backspace8
Tab9
回车键 enter13
Shift16
Ctrl17
Alt18
大小键 CapsLK20
Esc27
空格键 space32
删除键 Delete46
开始键 Start91
F1 ~ F12112 ~ 123

以上只是部分!

11.3.3 案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input type="text" id="field1">
    <h1 id="info1"></h1>
    <input type="text" id="field2">
    <h1 id="info2"></h1>

    <script>
        var oField1 = document.getElementById('field1');
        var oInfo1 = document.getElementById('info1');
        var oField2 = document.getElementById('field2');
        var oInfo2 = document.getElementById('info2');

        oField1.onkeypress = function (e) {
            oInfo1.innerText = '你输入的字符的字符码是' + e.charCode;
        };

        oField2.onkeydown = function (e) {
            oInfo2.innerText = '你按下的按键的键码是' + e.keyCode;
        };
    </script>
</body>

</html>

【小案例 - 盒子移动】

制作一个特效:按方向键可以控制页面上的盒子移动。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            position: absolute;
            top: 200px;
            left: 200px;
            width: 100px;
            height: 100px;
            background-color: orange;
        }

        #info {
            position: fixed;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <h1 id="info"></h1>

    <script>
        var oBox = document.getElementById('box');
        var oInfo = document.getElementById('info');

        // 全局变量 t、l,分别表示盒子的 top 属性值和 left 属性值
        var t = 200;
        var l = 200;

        // 监听 document 对象的键盘按下事件监听,表示当用户在整个网页上按下按键的时候
        document.onkeydown = function (e) {
            oInfo.innerText = '你按下的按键的键码是' + e.keyCode;

            switch (e.keyCode) {
                case 37:
                    l -= 3;
                    break;
                case 38:
                    t -= 3;
                    break;
                case 39:
                    l += 3;
                    break;
                case 40:
                    t += 3;
                    break;
            }

            // 更改样式
            oBox.style.left = l + 'px';
            oBox.style.top = t + 'px';
        };
    </script>
</body>

</html>

11.4 e.preventDefault()方法

e.preventDefault() 方法用来阻止事件产生的 “默认动作”。

一些特殊的业务需求,需要阻止事件的 “默认动作”。

【小案例1】

制作一个文本框,只能让用户在其中输入小写字母和数字,其他字符输入没有效果。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <p>
        只能输入小写字母和数字:
        <input type="text" id="field">
    </p>
    <script>
        var oField = document.getElementById('field');

        oField.onkeypress = function (e) {
            console.log(e.charCode);

            // 根据用户输入的字符的字符码(e.charCode)
            // 数字 0~9,字符码 48~57
            // 小写字母 a~z,字符码 97~122
            if (!(e.charCode >= 48 && e.charCode <= 57 || e.charCode >= 97 && e.charCode <= 122)) {
                // 阻止浏览器的默认行为
                e.preventDefault();
            }
        };
    </script>
</body>

</html>

【小案例2】

制作鼠标滚轮事件:当鼠标在盒子中向下滚动时,数字加 1;反之,数字减 1。

鼠标滚轮事件是 onmousewheel,它的事件对象 e 提供 deltaY 属性表示鼠标滚动方向,向下滚动是返回正值,向上滚动时返回负值。

  • 没有阻止事件的 “默认动作” 时
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width: 200px;
            height: 200px;
            background-color: #333;
        }

        body {
            height: 2000px;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <h1 id="info">0</h1>

    <script>
        var oBox = document.getElementById('box');
        var oInfo = document.getElementById('info');

        // 全局变量就是 info 中显示的数字
        var a = 0;

        // 给 box 盒子添加鼠标滚轮事件监听
        oBox.onmousewheel = function (e) {
            if (e.deltaY > 0) {
                a++;
            } else {
                a--;
            }
            oInfo.innerText = a;
        }
    </script>
</body>

</html>

  • 阻止事件的 “默认动作” 后
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width: 200px;
            height: 200px;
            background-color: #333;
        }

        body {
            height: 2000px;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <h1 id="info">0</h1>

    <script>
        var oBox = document.getElementById('box');
        var oInfo = document.getElementById('info');

        // 全局变量就是 info 中显示的数字
        var a = 0;

        // 给 box 盒子添加鼠标滚轮事件监听
        oBox.onmousewheel = function (e) {
            // 阻止默认事件:就是说当用户在盒子里面滚动鼠标滚轮的时候,此时不会引发页面的滚动条的滚动
            e.preventDefault();

            if (e.deltaY > 0) {
                a++;
            } else {
                a--;
            }
            oInfo.innerText = a;
        }
    </script>
</body>

</html>

【小案例3】

制作鼠标右键点击事件:当鼠标右键被点击时,改变背景颜色。

oncontextmenu:在用户点击鼠标右键打开上下文菜单时触发。

  • 没有阻止事件的 “默认动作” 时
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var oBody = document.getElementsByTagName('body')[0];
        // 给 document 添加鼠标右键事件监听
        var flag = 0;
        document.oncontextmenu = function (e) {
            if (flag == 0) {
                oBody.style.backgroundColor = 'black';
                flag = 1;;
            } else {
                oBody.style.backgroundColor = 'white';
                flag = 0;
            }
        }
    </script>
</body>

</html>

  • 阻止事件的 “默认动作” 后
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var oBody = document.getElementsByTagName('body')[0];
        // 给 document 添加鼠标右键事件监听
        var flag = 0;
        document.oncontextmenu = function (e) {
            // 阻止默认事件:就是说当用户右键的时候,此时不会弹出页面的右键菜单
            e.preventDefault();

            if (flag == 0) {
                oBody.style.backgroundColor = 'black';
                flag = 1;;
            } else {
                oBody.style.backgroundColor = 'white';
                flag = 0;
            }
        }
    </script>
</body>

</html>

11.5 e.stopPropagation()方法

e.stopPropagation() 方法用来阻止事件继续传播。

在一些场合,非常有必要切断事件继续传播,否则会造成页面特效显示出 bug。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 200px;
            height: 200px;
            background-color: #333;
        }
    </style>
</head>

<body>
    <div id="box">
        <button id="btn">按我</button>
    </div>
    <script>
        var oBox = document.getElementById('box');
        var oBtn = document.getElementById('btn');

        oBox.onclick = function () {
            console.log('我是盒子');
        };

        oBtn.onclick = function () {
            console.log('我是按钮');
        };
    </script>
</body>

</html>

【阻止冒泡】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div{
            width: 200px;
            height: 200px;
            background-color: #333;
        }
    </style>
</head>

<body>
    <div id="box">
        <button id="btn">按我</button>
    </div>
    <script>
        var oBox = document.getElementById('box');
        var oBtn = document.getElementById('btn');

        oBox.onclick = function () {
            console.log('我是盒子');
        };

        oBtn.onclick = function (e) {
            // 阻止事件继续传播
            e.stopPropagation();
            console.log('我是按钮');
        };
    </script>
</body>

</html>

【阻止传播】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 200px;
            height: 200px;
            background-color: #333;
        }
    </style>
</head>

<body>
    <div id="box">
        <button id="btn">按我</button>
    </div>
    <script>
        var oBox = document.getElementById('box');
        var oBtn = document.getElementById('btn');

        oBox.addEventListener('click', function (e) {
            // 阻止事件继续传播
            e.stopPropagation();
            console.log('我是盒子');
        }, true);

        oBtn.addEventListener('click', function () {
            console.log('我是按钮');
        }, true);
    </script>
</body>

</html>

【小案例】

制作一个弹出层:点击按钮显示弹出层,点击网页任意地方,弹出层关闭。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .modal {
            width: 400px;
            height: 140px;
            background-color: #333;
            position: absolute;
            top: 50%;
            left: 50%;
            margin-top: -70px;
            margin-left: -200px;
            display: none;
        }
    </style>
</head>

<body>
    <button id="btn">按我弹出弹出层</button>
    <div class="modal" id="modal"></div>

    <script>
        var oBtn = document.getElementById('btn');
        var oModal = document.getElementById('modal');

        // 点击按钮的时候,弹出层显示
        oBtn.onclick = function (e) {
            // 阻止事件继续传播到document身上
            e.stopPropagation();
            oModal.style.display = 'block';
        };

        // 点击页面任何部分的时候,弹出层关闭
        document.onclick = function () {
            oModal.style.display = 'none';
        };

        // 点击弹出层内部的时候,不能关闭弹出层的,所以应该阻止事件继续传播
        oModal.onclick = function (e) {
            // 阻止事件继续传播到document身上
            e.stopPropagation();
        };
    </script>
</body>

</html>

十二、事件委托

12.1 批量添加事件监听

题目:页面上有一个无序列表 <ul>,它内部共有 20 个 <li> 元素,请批量给它们添加事件监听,实现效果:点击哪个 <li> 元素,哪个 <li> 元素就变红。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul id="list">
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
    </ul>

    <script>
        var oList = document.getElementById('list');
        var lis = oList.getElementsByTagName('li');

        // 书写循环语句,批量给元素添加监听
        for (var i = 0; i < lis.length; i++) {
            lis[i].onclick = function () {
                // 在这个函数中,this 表示点击的这个元素,this 涉及函数上下文的相关知识,我们在“面向对象”课程中介绍
                this.style.color = 'red';
            };
        }
    </script>
</body>

</html>

12.2 批量添加事件监听的性能问题

每一个事件监听注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗会非常大。

实际上,每个 <li> 的事件处理函数都是不同的函数,这些函数本身也会占用内存。

12.3 新增元素动态绑定事件

题目:页面上有一个无序列表 <ul>,它内部没有 <li> 元素,请制作一个按钮,点击这个按钮就能增加一个 <li> 元素。并且要求每个增加的 <li> 元素也要有点击事件监听,实现效果:点击哪个 <li> 元素,哪个 <li> 元素就变红。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn">按我添加新的li列表项</button>
    <ul id="list"></ul>

    <script>
        var oBtn = document.getElementById('btn');
        var oList = document.getElementById('list');
        var lis = oList.getElementsByTagName('li');

        // 按钮的点击事件
        oBtn.onclick = function () {
            // 创建一个新的 li 列表项,孤儿节点
            var oLi = document.createElement('li');
            oLi.innerHTML = '我是列表项';
            // 上树
            oList.appendChild(oLi);
            // 给新创建的这个 li 节点添加 onclick 事件监听
            oLi.onclick = function () {
                this.style.color = 'red';
            };
        };
    </script>
</body>

</html>

12.4 动态绑定事件的问题

新增元素必须分别添加事件监听,不能自动获得事件监听。

大量事件监听、大量事件处理函数都会产生大量的内存消耗。

12.5 事件委托

利用事件冒泡机制,将后代元素事件委托给祖先元素。

12.6 e.target和e.currentTarget属性

事件委托通常需要结合使用 e.target 属性。

属性属性描述
target触发此事件的最早元素,即 “事件源元素”
currentTarget事件处理程序附加到的元素
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn">按我创建一个新列表项</button>
    <ul id="list">
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
    </ul>
    <script>
        var oList = document.getElementById('list');
        var oBtn = document.getElementById('btn');

        oList.onclick = function (e) {
            // e.target 表示用户真正点击的那个元素,即 “事件源元素”
            e.target.style.color = 'red';
        };

        oBtn.onclick = function () {
            // 创建新的li元素
            var oLi = document.createElement('li');
            // 写内容
            oLi.innerText = '我是新来的';
            // 上树
            oList.appendChild(oLi);
        };
    </script>
</body>

</html>

12.7 事件委托的使用场景

当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销。

当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听。

12.8 使用事件委托时需要注意的事项

(1)onmouseenteronmouseover 都表示 “鼠标进入”,它们有什么区别呢?

答:onmouseenter 不冒泡,onmouseover 冒泡。

  • 使用事件委托时要注意:不能委托不冒泡的事件给祖先元素。

【onmouseenter】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn">按我创建一个新列表项</button>
    <ul id="list">
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
    </ul>
    <script>
        var oList = document.getElementById('list');
        var oBtn = document.getElementById('btn');

        // onmouseenter这个属性天生就是“不冒泡”的,相当于你事件处理函数附加给了那个DOM节点
        // 就是哪个DOM节点自己触发的事件,没有冒泡过程
        // 再因为继承性,所以所有 li 会一起变色
        oList.onmouseenter = function (e) {
            // e.target表示用户真正点击的那个元素
            e.target.style.color = 'red';
        };

        // oList.onmouseover = function (e) {
        //     // e.target表示用户真正点击的那个元素,即:那个 li
        //     e.target.style.color = 'red';
        // };

        oBtn.onclick = function () {
            // 创建新的li元素
            var oLi = document.createElement('li');
            // 写内容
            oLi.innerText = '我是新来的';
            // 上树
            oList.appendChild(oLi);
        };
    </script>
</body>

</html>

【onmouseover】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn">按我创建一个新列表项</button>
    <ul id="list">
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
    </ul>
    <script>
        var oList = document.getElementById('list');
        var oBtn = document.getElementById('btn');

        // onmouseenter这个属性天生就是“不冒泡”的,相当于你事件处理函数附加给了那个DOM节点
        // 就是哪个DOM节点自己触发的事件,没有冒泡过程
        // 再因为继承性,所以所有 li 会一起变色
        // oList.onmouseenter = function (e) {
        //     // e.target表示用户真正点击的那个元素
        //     e.target.style.color = 'red';
        // };

        oList.onmouseover = function (e) {
            // e.target表示用户真正点击的那个元素,即:那个 li
            e.target.style.color = 'red';
        };

        oBtn.onclick = function () {
            // 创建新的li元素
            var oLi = document.createElement('li');
            // 写内容
            oLi.innerText = '我是新来的';
            // 上树
            oList.appendChild(oLi);
        };
    </script>
</body>

</html>

(2)最内层元素不能再有额外的内层元素了,比如:

这会导致点击 li 时效果正常,但是点击 span 时,只有 span 会变色。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn">按我创建一个新列表项</button>
    <ul id="list">
        <li><span>我是span</span>列表项</li>
        <li><span>我是span</span>列表项</li>
        <li><span>我是span</span>列表项</li>
        <li><span>我是span</span>列表项</li>
        <li><span>我是span</span>列表项</li>
    </ul>
    <script>
        var oList = document.getElementById('list');
        var oBtn = document.getElementById('btn');

        oList.onmouseover = function (e) {
            // e.target表示用户真正点击的那个元素,即:那个 li
            e.target.style.color = 'red';
        };

        oBtn.onclick = function () {
            // 创建新的li元素
            var oLi = document.createElement('li');
            // 写内容
            oLi.innerText = '我是新来的';
            // 上树
            oList.appendChild(oLi);
        };
    </script>
</body>

</html>

十三、定时器和延时器

13.1 定时器

setInterval() 函数可以重复调用一个函数,在每次调用之间有固定的时间间隔。

Interval:间隔

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        var a = 0;

        setInterval(function () {
            console.log(++a);
        }, 1000);
    </script>
</body>

</html>

13.2 函数的参数

setInterval() 函数可以接收第 3、4、…… 个参数,它们将按顺序传入函数。

13.3 具名函数也可以传入setInterval

具名函数也可以传入 setInterval

具名函数:有名称的函数。

匿名函数:无名称的函数。

13.4 清除定时器

clearInterval() 函数可以清除一个定时器。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1 id="info">0</h1>
    <button id="btn1">开始</button>
    <button id="btn2">暂停</button>

    <script>
        var oInfo = document.getElementById('info');
        var oBtn1 = document.getElementById('btn1');
        var oBtn2 = document.getElementById('btn2');

        var a = 0;

        // 全局变量
        var timer;

        oBtn1.onclick = function () {
            // 更改全局变量 timer 的值为一个定时器实体
            timer = setInterval(function () {
                oInfo.innerText = ++a;
            }, 1000);
        };

        oBtn2.onclick = function () {
            clearInterval(timer);
        };
    </script>
</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdpxTbs3-1691636164721)(mark-img/29a4a59f27474586835c03a03161e6c2.gif)]

但是,此时有一个 BUG,那就是当重复点击时,会发生定时器叠加。

定时器叠加:同一时间有多个定时器在同时工作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7LXhb1L-1691636164722)(mark-img/91287644df004f6e9a212bba504d5c24.gif)]

改进:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1 id="info">0</h1>
    <button id="btn1">开始</button>
    <button id="btn2">暂停</button>

    <script>
        var oInfo = document.getElementById('info');
        var oBtn1 = document.getElementById('btn1');
        var oBtn2 = document.getElementById('btn2');

        var a = 0;

        // 全局变量
        var timer;

        oBtn1.onclick = function () {
            // 为了防止定时器叠加,我们应该在设置定时器之前先清除定时器
            clearInterval(timer);
            // 更改全局变量timer的值为一个定时器实体
            timer = setInterval(function () {
                oInfo.innerText = ++a;
            }, 1000);
        };

        oBtn2.onclick = function () {
            clearInterval(timer);
        };
    </script>
</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Nofk27X-1691636164722)(mark-img/bda8b440190f4de690e33cedc1bd8d4d.gif)]

13.5 延时器

setTimeout() 函数可以设置一个延时器,当指定时间到了之后,会执行函数一次,不再重复执行。

13.6 清除延时器

clearTimeout() 函数可以清除延时器,和 clearInterval() 非常类似。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button id="btn1">2秒后弹出你好</button>
    <button id="btn2">取消弹出</button>

    <script>
        var btn1 = document.getElementById('btn1');
        var btn2 = document.getElementById('btn2');
        var timer;

        btn1.onclick = function () {
            timer = setTimeout(function () {
                alert('你好');
            }, 2000);
        }

        btn2.onclick = function () {
            clearTimeout(timer);
        }
    </script>
</body>

</html>

13.7 初步认识异步语句

setInterval()setTimeout() 是两个异步语句。

异步(asynchronous):不会阻塞 CPU 继续执行其他语句,当异步完成时,会执行 “回调函数”(callback)。

13.8 使用定时器实现动画

动画是网页上非常常见的业务需求。

使用定时器实现动画就是利用 “视觉暂留” 原理。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            position: absolute;
            top: 100px;
            left: 100px;
            width: 100px;
            height: 100px;
            background-color: orange;
        }
    </style>
</head>

<body>
    <button id="btn">开始运动</button>
    <div id="box"></div>

    <script>
        // 得到元素
        var btn = document.getElementById('btn');
        var box = document.getElementById('box');

        // 全局变量盒子的left值
        var left = 100;

        // 按钮监听
        btn.onclick = function () {
            var timer = setInterval(function () {
                // 改变全局变量
                left += 10;
                if (left >= 1000) {
                    clearInterval(timer);
                }
                // 设置left属性
                box.style.left = left + 'px';
            }, 20);
        };
    </script>
</body>

</html>

使用定时器实现动画较为不便:

  1. 不方便根据动画总时间计算步长
  2. 运动方向要设置正负
  3. 多种运动进行叠加较为困难

所以实际开发中是利用:

JQuery 等 JS 库(目前利用得越来越少)

JS + CSS3 的模式制作动画

十四、JS和CSS3结合实现动画

14.1 JS和CSS3结合实现动画

  • 我们知道,CSS3 的 transition 过渡属性可以实现动画。
  • JS 可以利用 CSS3 的 transition 属性轻松实现元素动画。
  • JS 和 CSS3 结合实现动画规避了定时器制作动画的缺点。

14.2 函数节流

函数节流:一个函数执行一次后,只有大于设定的执行周期后才允许执行第二次。

函数节流的意义:在许多 JS + CSS3 实现的动画中,如果没有函数节流,那么当一个动画还没有执行完时,如果用户再次要求执行动画,则动画会直接中断还未执行完的动画,然后执行新的动画。我们要通过函数节流来避免这种情况,比如:轮播图中就要避免用户高频地点击轮播图切换按钮时轮播图飞快切换的问题,而应该做到无论用户点击多块,轮播图的切换速度始终是平稳的。

函数节流非常容易实现,只需借助 setTimeout 延时器。

【函数节流模板】

// 声明节流锁
var lock = true;

function 需要节流的函数() {
    // 如果锁是关闭状态,则不执行
    if (!lock) {
        return;
    }
    
    // 函数核心语句
    // ……
    // 函数核心语句
    
    // 关锁
    lock = false;
    
    // 指定毫秒数后将锁打开
    setTimeout(function() {
        lock = true;
    }, 2000);
}

【案例】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width: 100px;
            height: 100px;
            background-color: orange;
            position: absolute;
            top: 100px;
            left: 100px;
        }
    </style>
</head>

<body>
    <button id="btn">按我运动</button>
    <div id="box"></div>

    <script>
        // 得到元素
        var btn = document.getElementById('btn');
        var box = document.getElementById('box');

        // 标识量,指示当前盒子在左边还是右边
        var pos = 1; // 1左边,2右边

        // 函数节流锁
        var lock = true;

        // 事件监听
        btn.onclick = function () {
            // 首先检查锁是否是关闭
            if (!lock) return;

            // 把过渡加上
            box.style.transition = 'all 2s linear 0s';
            if (pos == 1) {
                // 瞬间移动,但是由于有过渡,所以是动画
                box.style.left = '1100px';
                pos = 2;
            } else if (pos == 2) {
                // 瞬间移动,但是由于有过渡,所以是动画
                box.style.left = '100px';
                pos = 1;
            }

            // 关锁
            lock = false;
            // 指定时间后,将锁打开
            setTimeout(function () {
                lock = true;
            }, 2000);
        };
    </script>
</body>

</html>

十五、常见动画制作

15.1 动画效果开发1-无缝连续滚动特效

原理:

代码:

此动画利用定时器反而比 JS + CSS3 更方便。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .box {
            width: 1000px;
            height: 130px;
            border: 1px solid #000;
            margin: 50px auto;
            overflow: hidden;
        }

        .box ul {
            list-style: none;
            /* 设置大一点,这样li才能浮动 */
            width: 5000px;
            position: relative;
        }

        .box ul li {
            float: left;
            margin-right: 10px;
        }
    </style>
</head>

<body>
    <div id="box" class="box">
        <ul id="list">
            <li><img src="images/number/0.png" alt=""></li>
            <li><img src="images/number/1.png" alt=""></li>
            <li><img src="images/number/2.png" alt=""></li>
            <li><img src="images/number/3.png" alt=""></li>
            <li><img src="images/number/4.png" alt=""></li>
            <li><img src="images/number/5.png" alt=""></li>
        </ul>
    </div>
    <script>
        var box = document.getElementById('box');
        var list = document.getElementById('list');

        // 复制多一遍所有的li
        list.innerHTML += list.innerHTML;

        // 全局变量,表示当前list的left值
        var left = 0;

        // 定时器,全局变量
        var timer;

        move();

        // 动画封装成函数
        function move() {
            // 设表先关,防止动画积累
            clearInterval(timer);

            timer = setInterval(function () {
                left -= 4;
                // 验收
                if (left <= -1260) {
                    left = 0;
                }
                list.style.left = left + 'px';
            }, 20);
        }

        // 鼠标进入停止定时器
        box.onmouseenter = function () {
            clearInterval(timer);
        };

        // 鼠标离开继续定时器
        box.onmouseleave = function () {
            move();
        };
    </script>
</body>

</html>

15.2 动画效果开发2-跑马灯轮播图特效

跑马灯轮播图,又叫:滑动轮播图。此种轮播图需要将图片排成一行,并在最后一张图片后克隆一份第一张图片(当到达最后一张时,实现瞬间轮回效果)。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .carousel {
            width: 650px;
            height: 360px;
            border: 1px solid #000;
            margin: 50px auto;
            position: relative;
            overflow: hidden;
        }

        .carousel ul {
            list-style: none;
            width: 6000px;
            position: relative;
            left: 0px;
            transition: left .5s ease 0s;
        }

        .carousel ul li {
            float: left;
        }

        .carousel .leftbtn {
            position: absolute;
            left: 20px;
            top: 50%;
            margin-top: -25px;
            width: 50px;
            height: 50px;
            background-color: rgb(28, 180, 226);
            border-radius: 50%;
        }

        .carousel .rightbtn {
            position: absolute;
            right: 20px;
            top: 50%;
            margin-top: -25px;
            width: 50px;
            height: 50px;
            background-color: rgb(28, 180, 226);
            border-radius: 50%;
        }
    </style>
</head>

<body>
    <div class="carousel">
        <ul id="list">
            <li><img src="images/beijing/0.jpg" alt=""></li>
            <li><img src="images/beijing/1.jpg" alt=""></li>
            <li><img src="images/beijing/2.jpg" alt=""></li>
            <li><img src="images/beijing/3.jpg" alt=""></li>
            <li><img src="images/beijing/4.jpg" alt=""></li>
        </ul>
        <a href="javascript:;" class="leftbtn" id="leftbtn"></a>
        <a href="javascript:;" class="rightbtn" id="rightbtn"></a>
    </div>
    <script>
        // 得到按钮和ul,ul整体进行运动
        var leftbtn = document.getElementById('leftbtn');
        var rightbtn = document.getElementById('rightbtn');
        var list = document.getElementById('list');

        // 克隆第一张图片
        var cloneli = list.firstElementChild.cloneNode(true); // 记得要写true,不然就只会克隆li而不会克隆img
        list.appendChild(cloneli);

        // 当前ul显示到第几张了,从0开始数
        var idx = 0;

        // 节流锁
        var lock = true;

        // 右边按钮监听
        rightbtn.onclick = function () {
            // 判断锁的状态
            if (!lock) return;

            lock = false;

            // 给list加过渡,为什么要加??css中不是已经加了么??这是因为最后一张图片会把过渡去掉
            list.style.transition = 'left .5s ease 0s';
            idx++;
            if (idx > 4) {
                // 设置一个延时器,延时器的功能就是将ul瞬间拉回0的位置,延时器的目的就是让过渡动画结束之后
                setTimeout(function () {
                    // 取消掉过渡,因为要的是瞬间移动,不是“咕噜”回去
                    list.style.transition = 'none';
                    list.style.left = 0;
                    idx = 0;
                }, 500);
            }
            list.style.left = -idx * 650 + 'px';

            // 函数节流
            setTimeout(function () {
                lock = true;
            }, 500);
        }

        // 左边按钮监听
        leftbtn.onclick = function () {
            if (!lock) return;

            lock = false;

            // 判断是不是第0张,如果是,就要瞬间用假的替换真的
            if (idx == 0) {
                // 取消掉过渡,因为要的是瞬间移动,不是“咕噜”过去
                list.style.transition = 'none';
                // 直接瞬间移动到最后的假图片上
                list.style.left = -5 * 650 + 'px';
                // 设置一个延时器,这个延时器的延时时间可以是0毫秒,虽然是0毫秒,但是可以让我们过渡先是瞬间取消,然后再加上
                setTimeout(function () {
                    // 加过渡
                    list.style.transition = 'left .5s ease 0s';
                    // idx改为真正的最后一张
                    idx = 4;
                    list.style.left = -idx * 650 + 'px';
                }, 0);
            } else {
                idx--;
                list.style.left = -idx * 650 + 'px';
            }

            // 函数节流
            setTimeout(function () {
                lock = true;
            }, 500);
        }
    </script>
</body>

</html>

原理:

15.3 动画效果开发3-呼吸灯轮播图特效

呼吸灯轮播图,又叫:淡入淡出轮播图。此种轮播图需要将图片叠到一起。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .carousel {
            width: 650px;
            height: 360px;
            border: 1px solid #000;
            margin: 50px auto;
            position: relative;

        }

        .carousel ul {
            list-style: none;
        }

        .carousel ul li {
            position: absolute;
            top: 0;
            left: 0;
            /* 透明度都是0 */
            opacity: 0;
            transition: opacity 1s ease 0s;
        }

        /* 只有第一张透明度是1 */
        .carousel ul li:first-child {
            opacity: 1;
        }

        .carousel .leftbtn {
            position: absolute;
            left: 20px;
            top: 50%;
            margin-top: -25px;
            width: 50px;
            height: 50px;
            background-color: rgb(28, 180, 226);
            border-radius: 50%;
        }

        .carousel .rightbtn {
            position: absolute;
            right: 20px;
            top: 50%;
            margin-top: -25px;
            width: 50px;
            height: 50px;
            background-color: rgb(28, 180, 226);
            border-radius: 50%;
        }
    </style>
</head>

<body>
    <div class="carousel">
        <ul id="list">
            <li><img src="images/beijing/0.jpg" alt=""></li>
            <li><img src="images/beijing/1.jpg" alt=""></li>
            <li><img src="images/beijing/2.jpg" alt=""></li>
            <li><img src="images/beijing/3.jpg" alt=""></li>
            <li><img src="images/beijing/4.jpg" alt=""></li>
        </ul>
        <a href="javascript:;" class="leftbtn" id="leftbtn"></a>
        <a href="javascript:;" class="rightbtn" id="rightbtn"></a>
    </div>
    <script>
        // 得到按钮和ul,ul整体进行运动
        var leftbtn = document.getElementById('leftbtn');
        var rightbtn = document.getElementById('rightbtn');
        var list = document.getElementById('list');
        var lis = list.getElementsByTagName('li');

        // 当前是第几张图显示
        var idx = 0;

        // 节流
        var lock = true;

        // 右按钮
        rightbtn.onclick = function () {
            // 判断节流
            if (!lock) return;

            lock = false;

            // 还没有改idx,此时的idx这个图片就是老图,老图淡出
            lis[idx].style.opacity = 0;
            idx++;
            if (idx > 4) idx = 0;
            // 改了idx,此时的idx这个图片就是新图,新图淡入
            lis[idx].style.opacity = 1;

            // 动画结束之后,开锁
            setTimeout(function () {
                lock = true;
            }, 1000);
        }

        // 左按钮
        leftbtn.onclick = function () {
            // 判断节流
            if (!lock) return;

            lock = false;

            // 还没有改idx,此时的idx这个图片就是老图,老图淡出
            lis[idx].style.opacity = 0;
            idx--;
            if (idx < 0) idx = 4;
            // 改了idx,此时的idx这个图片就是新图,新图淡入
            lis[idx].style.opacity = 1;

            // 动画结束之后,开锁
            setTimeout(function () {
                lock = true;
            }, 1000);
        }
    </script>
</body>

</html>

十六、JQuery

  • jQuery 教程 (w3school.com.cn)

  • jQuery 教程 | 菜鸟教程 (runoob.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/882497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pycharm找不到Conda可执行文件路径(Pycharm无法导入Anaconda已有环境)

在使用Pycharm时发现无法导入Anaconda创建好的环境&#xff0c;会出现找不到Conda可执行文件路径的问题。 解决 在输入框内输入D:\anaconda3\Scripts\conda.exe&#xff0c;点击加载环境。 注意前面目录是自己Anaconda的安装位置&#xff0c;之后就可以找到Anaconda的现有环…

STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器

只用STM32单片机SD卡耳机插座&#xff0c;实现播放MP3播放器&#xff01; 看过很多STM32软解MP3的方案&#xff0c;即不通过类似VS1053之类的解码器芯片&#xff0c;直接用STM32和软件库解码MP3文件&#xff0c;通常使用了labmad或者Helix解码库实现&#xff0c;Helix相对labm…

Mariadb高可用MHA

本节主要学习了Mariadb高可用MHA的概述&#xff0c;案例如何构建MHA 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、概述 1、概念 MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。…

如何使用Kali Linux进行渗透测试?

1. 渗透测试简介 渗透测试是通过模拟恶意攻击&#xff0c;评估系统、应用或网络的安全性的过程。Kali Linux为渗透测试人员提供了丰富的工具和资源&#xff0c;用于发现漏洞、弱点和安全风险。 2. 使用Kali Linux进行渗透测试的步骤 以下是使用Kali Linux进行渗透测试的基本…

搭建WebDAV服务手机ES文件浏览器远程访问

文章目录 1. 安装启用WebDAV2. 安装cpolar3. 配置公网访问地址4. 公网测试连接5. 固定连接公网地址6. 使用固定地址测试连接 有时候我们想通过移动设备访问群晖NAS 中的文件,以满足特殊需求,我们在群辉中开启WebDav服务,结合cpolar内网工具生成的公网地址,通过移动客户端ES文件…

【Unity每日一记】进行发射,位置相关的方法总结

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

绘制世界地图or中国地图

写在前面 在8月初,自己需要使用中国地图的图形,自己就此也查询相关的教程,自己也做一下小小总结,希望对自己和同学们有所帮助。 最终图形 这个系列从2022年开始,一直更新使用R语言分析数据及绘制精美图形。小杜的生信笔记主要分享小杜学习日常!如果,你对此感兴趣可以加…

【C++面向对象】--- 继承 的奥秘(下篇)

个人主页&#xff1a;平行线也会相交&#x1f4aa; 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】&#x1f48c; 本专栏旨在记录C的学习路线&#xff0c;望对大家有所帮助&#x1f647;‍ 希望我们一起努力、成长&…

自动化测试用例设计实例

在编写用例之间&#xff0c;笔者再次强调几点编写自动化测试用例的原则&#xff1a; 1、一个脚本是一个完整的场景&#xff0c;从用户登陆操作到用户退出系统关闭浏览器。 2、一个脚本脚本只验证一个功能点&#xff0c;不要试图用户登陆系统后把所有的功能都进行验证再退出系统…

CAS 的执行流程 ?CAS 中 ABA 问题如何解决 ?CAS 在 Java 中有哪些实现类 ?

目录 1. CAS 的执行流程 2. CAS 中的 ABA 问题 3. 如何解决 CAS 中的 ABA 问题 4.CAS 在Java 中的实现类有哪些 1. CAS 的执行流程 CAS 比较并替换的大致流程是这样的&#xff1a; 它有三个操作单位&#xff1a;V&#xff08;内存值&#xff09;&#xff0c;A&#xff08;…

【C++】做一个飞机空战小游戏(八)——生成敌方炮弹(rand()和srand()函数应用)

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…

Iceberg 学习笔记

本博客对应于 B 站尚硅谷教学视频 尚硅谷数据湖Iceberg实战教程&#xff08;尚硅谷&Apache Iceberg官方联合推出&#xff09;&#xff0c;为视频对应笔记的相关整理。 1. Iceberg简介 1.1 概述 为了解决数据存储和计算引擎之间的适配的问题&#xff0c;Netflix 开发了 I…

【算法题】螺旋矩阵IV (求解n阶折线蛇形矩阵)

一、问题的提出 n阶折线蛇形矩阵的特点是按照图1所示的方式排列元素。n阶蛇形矩阵是指矩阵的大小为nn&#xff0c;其中n为正整数。 题目背景 一个 n 行 n 列的螺旋矩阵可由如图1所示的方法生成&#xff0c;观察图片&#xff0c;找出填数规律。填数规则为从 1 开始填到 nn。 …

如何使用AIGC人工智能辅助开发?

文章目录 引言AIGC辅助开发的应用场景代码生成图像识别与生成自然语言处理视频剪辑与生成 AIGC辅助开发的实现步骤数据准备模型选择模型训练结果评估与优化应用开发 AIGC辅助开发的优势与局限优势局限 未来展望总结 &#x1f389;欢迎来到AIGC人工智能专栏~如何使用AIGC人工智能…

计算机网络----CRC冗余码的运算

目录 1. 冗余码的介绍及原理2. CRC检验编码的例子3. 小练习 1. 冗余码的介绍及原理 冗余码是用于在数据链路层的通信链路和传输数据过程中可能会出错的一种检错编码方法&#xff08;检错码&#xff09;。原理&#xff1a;发送发把数据划分为组&#xff0c;设每组K个比特&#…

【Python】解决“Tk_GetPixmap: Error from CreateDIBSection”闪退问题

解决Python使用Tkinter的Notebook切换标签时出现的“Tk_GetPixmap: Error from CreateDIBSection 操作成功完成”闪退问题 零、问题描述 在使用Tkinter的Notebook控件时&#xff0c;对其标签进行切换&#xff0c;发现切换不了&#xff0c;一切换就报如下图错误&#xff1a; …

Python学习笔记_基础篇(五)_数据类型之字典

一.基本数据类型 整数&#xff1a;int 字符串&#xff1a;str(注&#xff1a;\t等于一个tab键) 布尔值&#xff1a; bool 列表&#xff1a;list 列表用[] 元祖&#xff1a;tuple 元祖用&#xff08;&#xff09; 字典&#xff1a;dict 注&#xff1a;所有的数据类型都存在想对…

3. 爬取自己CSDN博客列表(分页查询)(网站反爬虫策略,需要在代码中添加合适的请求头User-Agent,否则response返回空)

文章目录 步骤打开谷歌浏览器输入网址按F12进入调试界面点击网络&#xff0c;清除历史消息按F5刷新页面找到接口&#xff08;community/home-api/v1/get-business-list&#xff09;接口解读 撰写代码获取博客列表先明确返回信息格式json字段解读 Apipost测试接口编写python代码…

每天一道leetcode:646. 最长数对链(动态规划中等)

今日份题目&#xff1a; 给你一个由 n 个数对组成的数对数组 pairs &#xff0c;其中 pairs[i] [lefti, righti] 且 lefti < righti 。 现在&#xff0c;我们定义一种 跟随 关系&#xff0c;当且仅当 b < c 时&#xff0c;数对 p2 [c, d] 才可以跟在 p1 [a, b] 后面…

JMeter 特殊组件-逻辑控制器与BeanShell PreProcessor 使用示例

文章目录 前言JMeter 特殊组件-逻辑控制器与BeanShell PreProcessor 使用示例1. 逻辑控制器使用1.1. While Controller 使用示例1.2. 如果&#xff08;If&#xff09;控制器 使用示例 2. BeanShell PreProcessor 使用示例 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞…