整体介绍

DOM基本概念 1)DOM基本概念 节点访问和位置关系 1)访问元素节点 2)节点的关系 节点操作 1)节点操作 节点的创建、移除和克隆 1)节点的创建、移除和克隆 DOM事件 1)事件监听 2)事件传播 3)事件对象 4)事件委托 实现动画 1)定时器和延时器 2)使用定时器实现动画 3)JS和CSS3结合实现动画 动画效果开发 1)动画效果开发1 2)动画效果开发2 3)动画效果开发3

本节案例,请用Vscode打开

DOM基本概念

DOM是JS操控HTML和CSS的桥梁

DOM使JS操作HTML变得优雅

比如下面的HTML结构,现在想用JavaScripti在”牛奶”后面插入一个p标签对,内容是“可乐”。

字符串思维:

节点思维:

DOM简介

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

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

DOM节点树

nodeType常用属性值

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

document

访问元素节点

所谓“访问”元素节点,就是指“得到”、“获取”页面上的元素节点 ◆对节点进行操作,第一步就是要得到它 ◆访问元素节点主要依靠document对象

认识document对象

document对象是DOM中最重要的东西,几乎所有DOM的功能都封装在了document对象中 document对象也表示整个HTML文档,它是DOM节点树的根 document对象的nodeType属性值是9

访问元素节点的常用方法

getElementByld()

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

注意事项

如果页面上有相同id的元素,则只能得到第一个 不管元素藏的位置有多深,都能通过id把它找到

<body>
  <div id="box1">我是盒子1</div>
  <div id="box2">我是盒子2</div>
  <script>
    //得到盒子1
    var box1  = document.getElementById('box1');
    //得到盒子2
    var box2  = document.getElementById('box2');
    console.log(box1);
    console.log(box2);
  </script>
</body>

延迟运行

在测试DOM代码时,通常S代码一定要写到HTML节点的后面,否则JS无法找到相应HTML节点

可以使用window.onload=function(){}事件,使页面加载完毕后,再执行指定的代码

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        // 给window对象添加onload事件监听。onload表示页面都加载完毕了。
        window.onload = function () {
            // 得到盒子1
            var box1 = document.getElementById('box1');
            // 得到盒子2
            var box2 = document.getElementById('box2');
 
            console.log(box1);
            console.log(box2);
        };
    </script>
</head>
 
<body>
    <div id="box1">我是盒子1</div>
    <div id="box2">我是盒子2</div>
    <div id="box1">我也是盒子1</div>
</body>
 
</html>

getElementsByTagName()

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

注意事项

数组方便遍历,从而可以批量操控元素节点 即使页面上只有一个指定标签名的节点,也将得到长度为1的数组 任何一个节点元素也可以调用getElementsByTagName()方法,从而得到其内部的某种类的元素节点

<!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>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
    </div>
    <div id="box2">
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
        <p>我是段落</p>
    </div>
 
    <script>
        // 先得到box1
        var box1 = document.getElementById('box1');
        // 得到box1中的p标签的数组
        var ps_inbox1 = box1.getElementsByTagName('p');
 
        console.log(ps_inbox1);
    </script>
</body>
</html>

var ps = document.getElementById(‘p’);是选择所有p标签,无论深浅,也无论在那个盒子中

getElementsByClassName()

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

注意事项

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

querySelector()

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

注意事项

querySelector()方法只能得到页面上一个元素,如果有多个元素符合条件,则只能得到第一个元素 querySelector()方法从IE8开始兼容,但从IE9开始支持 CSS3的选择器,如:nth-child()、:[src^=‘dog’]等CSS3选择器形式都支持良好

querySelectorAll()

querySelectorAll()方法的功能是通过选择器得到元素数组 即使页面上只有一个符合选择器的节点,也将得到长度为1的数组

节点的关系

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

DOM中,文本节点也属于节点,在使用节点的关系时一定要注意 在标准的W3C规范中,空白文本节点也应该算作节点,但是在IE8及以前的浏览器中会有一定的兼容问题,它们不把空文本节点当做节点

排除文本节点的干扰

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

文本标签如下:在div与p标签之间的空格就是一个text节点

https://b2.hlcode.me/Qexo/2022/7/27/2ef8e09ccccc3171c079c0add75e0ebf.webp

注意一些只支持IE9,如果企业对兼容性要求高的话,不能使用此方法,需要对节点封装函数进行访问

封装节点关系函数

书写常见的节点关系函数

书写IE6也能兼容的“寻找所有元素子节点”函数 书写IE6也能兼容的“寻找前一个元素兄弟节点”函数 如何编写函数,获得某元素的所有的兄弟节点?

封装children功能

// 封装一个函数,这个函数可以返回元素的所有子元素节点(兼容到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));

封装previousElementSibling功能

// 封装一个函数,这个函数可以返回元素的前一个元素兄弟节点(兼容到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));

节点操作

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

改变元素节点中的内容可以使用两个相关属性: ①innerHTML②innerText ◆innerHTML属性能以HTML语法设置节点中的内容 ◆innerText属性只能以纯文本的形式设置节点中的内容

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

改为使用innerText

oBox.innerText = 'MK网';
oBox.innerText = '牛奶咖啡';

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

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

<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属性

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

<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 = '去MK网';
    </script>
</body>

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

<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>

节点的创建、移除和克隆

节点的创建

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

孤儿节点

新创建出的节点是“孤儿节点”,这意味着它并没有被挂载到DOM树上,我们无法看见它 必须继续使用appendChild()或insertBefore()方法将孤儿节点插入到DOM树上

appendChild()

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

insertBefore()

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

<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.appendChild(oP);
        oBox.insertBefore(oP, oPs[2]);
    </script>
</body>

移动节点

如果将已经挂载到DOM树上的节点成为appendChild()或者insertBefore()的参数,这个节点将会被移动 新父节点.appendChild(已经有父亲的节点); 新父节点.insertBefore(已经有父亲的节点,标杆子节点); ◆这意味着一个节点不能同时位于DOM树的两个位置

<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 box1 = document.getElementById('box1');
        var box2 = document.getElementById('box2');
        var para = document.getElementById('para');
        var ps_inbox2 = box2.getElementsByTagName('p');
 
        // box2.appendChild(para);
        box2.insertBefore(para, ps_inbox2[2]);
    </script>
</body>

删除节点

removeChild()方法从DOM中删除一个子节点 父节点.removeChild(要删除子节点); ◆节点不能主动删除自己,必须由父节点删除它

<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>

克隆节点

cloneNode()方法可以克隆节点,克隆出的节点是”孤儿节点” var 孤儿节点=老节点.cloneNode(); var 孤儿节点=老节点.cloneNode(true);

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

<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>

事件监听

什么是“事件监听”

◆DOM允许我们书写JavaScript代码以让HTML元素对事件作出反应 ◆什么是“事件”:用户与网页的交互动作 当用户点击元素时 当鼠标移动到元素上时 当文本框的内容被改变时 当键盘在文本框中被按下 当网页已加载完毕时 …

“监听”,顾名思义,就是让计算机随时能够发现这个事件发生了,从而执行程序员预先编写的一些程序 设置事件监听的方法主要有onxxx和addEventListener()两种,二者的区别将在“事件传播”一课中介绍

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

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

var oBox  = document.getElementById('box');
oBox.onclick function() {
//点击盒子时,将执行这里的语句
}

常见的鼠标事件监听

onclick是鼠标按下并抬起来,onmousedown是只要鼠标按下就触发 onclick使用场景比如点击确定按钮但是反悔可以移动到边框外再抬起来就不会触发

常见的键盘事件监听

常见的表单事件监听

常见的页面事件监听

事件传播

研究:当盒子嵌套时事件监听的执行顺序

点击最中心盒子,三个事件都会被触发,但是触发顺序如何? 由里到外执行点击顺序

测试小结论(不成熟)

我们感觉事件的传播是从内到外的

事件的传播

实际上,事件的传播是:先从外到内,然后再从内到外

onxxx写法只能监听冒泡阶段

onxxx这样的写法只能监听冒泡阶段

addEventListener()方法

DOM0级事件监听:只能监听冒泡阶段 oBox.onclick function (){}; DOM2级事件监听:

oBox.addEventListener('click',function(){
//这是事件处理函数
},true);

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>
    <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.addEventListener('click', function() {
            console.log('我是box2的冒泡阶段');
        }, false);
 
 
        oBox3.addEventListener('click', function() {
            console.log('我是box3的捕获阶段');
        }, true);
 
        oBox3.addEventListener('click', function() {
            console.log('我是box3的冒泡阶段');
        }, false);
 
        oBox3.onclick = function () {
            console.log('我是box3的onclick');
        };
 
        oBox1.addEventListener('click', function() {
            console.log('我是box1的冒泡阶段');
        }, false);
 
        oBox2.addEventListener('click', function() {
            console.log('我是box2的捕获阶段');
        }, true);
 
        oBox1.addEventListener('click', function() {
            console.log('我是box1的捕获阶段');
        }, true);
 
        oBox1.onclick = function () {
            console.log('我是box1的onclick');
        };
 
        oBox2.onclick = function () {
            console.log('我是box2的onclick');
        };
 
 
    </script>
</body>
</html>

onclick与冒泡的顺序也由代码书写位置即执行顺序有关,即DOM0级与DOM2级弹出顺序与书写位置有关

注意事项

  1. 最内部元素不再区分捕获和冒泡阶段,会先执行写在前面的监听,然后执行后写的监听
  2. 如果给元素设置相同的两个或多个同名事件,则DOM0级写法后面写的会覆盖先写的;而DOM2级会按顺序执行

DOM0级写法:

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

只会弹出B

DOM2级写法:

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

按顺序弹出C,D

  1. DOM0级与DOM2级弹出顺序与书写位置有关
oBox2.addEventListener('click', function() {
            alert('C');
        }, false);
 
        oBox2.addEventListener('click', function() {
            alert('D');
        }, false);
 
        oBox2.onclick = function () {
            alert('A');
        };
 
        oBox2.onclick = function () {
            alert('B');
        };

弹出C、D、B

事件对象

什么是事件对象

◆事件处理函数提供一个形式参数,它是一个对象,封装了本次事件的细节 ◆这个参数通常用单词event或字母e来表示

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

鼠标位置

offset是到元素位置

client是到浏览器窗口界面位置

page是到整个浏览器网页位置,包括水平数值滚动条移动出来的位置

<!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;
        }
        body{
            height: 2000px;
        }
        #info{
            font-size: 30px;
        }
 
    </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>

若黑色方框内在添加一个绿色方块,绿色方块与黑色方块的左上角的offset都是0,0

e.charCode和e.keyCode属性

◆e.charCode,属性通常用于onkeypress事件中,表示用户输入的字符的“字符码” ◆e.keyCode属性通常用于onkeydown事件和onkeyup中,表示用户按下的按键的“键码”

charCode字符码

keyCodet键码

e.preventDefault()方法

◆e.preventDefault()方法用来阻止事件产生的”默认动作” ◆一些特殊的业务需求,需要阻止事件的“默认动作’

小案例

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

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

<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>

e.stopPropagation()方法

◆e.stopPropagation()方法用来阻止事件继续传播 在一些场合,非常有必要切断事件继续传播,否则会造成页面特效显示出bug

<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>

事件委托

批量添加事件监听

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

性能问题

◆每一个事件监听注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗会非常大 ◆实际上,每个<li>的事件处理函数都是不同的函数,这些函数本身也会占用内存

新增元素动态绑定事件

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

性能问题

◆新增元素必须分别添加事件监听,不能自动获得事件监听 ◆大量事件监听、大量事件处理函数都会产生大量消耗内存

事件委托

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

e.target和e.currentTarget属性

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

<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>

效果:点击我是新来的也会变红

事件委托的使用场景

当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销 ◆当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听

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

◆onmouseenter和onmouseover都表示”鼠标进入”,它 们有什么区别呢? 答:onmouseenter不冒泡,onmouseover冒泡。

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

一点击全部变红

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

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

点击内层会失效

定时器与延时器

定时器

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

时间不宜过小,计算机性能受限

函数的参数

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

具名函数也可以传入setInterval

具名函数也可以传入setInterval

不加圆括号表示传入函数,加圆括号表示传入函数的值型

清除定时器

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

<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>

需要设置全局变量,需要使用两个函数控制timer,onclick作用是事件监听,还要注意定时器叠加问题,用户连续点击开始,计数会越来越快,因为有很多定时器同时工作叠加

延时器

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

清除延时器

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

<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>

初步认识异步语句

◆setInterval()和setTimeout()是两个异步语句 异步(asynchronous):不会阻塞CPU继续执行其他语句,当异步完成时,会执行“回调函数”(callback)

使用定时器实现动画

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

使用定时器实现动画较为不便: ①不方便根据动画总时间计算步长 ②运动方向要设置正负 ③多种运动进行叠加较为困难(比如一个方形一边移动一边变为圆形)

<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>

JS和CSS3结合实现动画

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

添加盒子运动按钮,控制盒子来回运动,同时,防止用户频繁点击,导致盒子还没运动到终点就返回

函数节流

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

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

<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>