DOM基础(下)

262 阅读8分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

DOM节点操作

体验节点

我们可以使用dom内置方法,创建、删除、获取、和输出节点

<body>
    <div id="box">111</div>
    <div id="mod">
        <p>222</p>
        <p>333</p>
        <p>444<span>我在里面</span></p>
    </div>
  
    <script>
        //使用createElement新建一个div节点
        var newNode = document.createElement('div');
        //修改节点内容
        newNode.innerHTML = '我是js创建的';
        console.log(newNode);
        //把节点插入body底部
        document.body.appendChild(newNode);

        //获取box节点
        var box = my$('box');
        //删除他
        document.body.removeChild(box);

        //获取mod节点
        var mod = my$('mod');
        //删除mod下面的所有子节点(只输出子节点)
        console.log(mod.children);
    </script>
</body>

节点属性

  • 节点类型(只读):使用nodeType方法获取某个节点的类型,属性值为数字,一共12种,其中三种比较重要
    • 1:元素节点
    • 2:属性节点
    • 3:文本节点
  • 节点名称(只读):使用nodeName获取节点的名称(标签名称)
  • 节点值:使用nodeValue方法返回当前的节点值,元素节点的值为unll
<body>
    <div class="box">我是文本</div>
    <script>
        // 获取元素
        var box = document.getElementsByClassName('box')[0];
        // 查看元素三个属性
        console.dir(box); //nodeName: "DIV"  nodeType: 1  nodeValue: null

        // 获得属性节点getAttributeNode()
        var idNode = box.getAttributeNode('class');
        // 查看三个属性
        console.dir(idNode); //nodeName: "class"  nodeType: 2  nodeValue: "box"
        // 我们可以在这里修改节点值
        idNode.nodeValue = 'ddd';

        // 获取文本节点
        var chideNode = box.childNodes[0]; //获取所有子节点的第一个节点
        console.log(chideNode); //输出一下
        //修改这个文本节点的值
        chideNode.nodeValue = '我修改了';
    </script>
</body>

父子关系节点

  • childNodes,只读属性,获取当前节点的所有子节点的实时集合,集合动态变化
  • (常用)children,只读属性,返回当前节点所有的子元素节点,是一个动态的html元素集合
  • firstChild,只读属性,返回该节点的第一个子节点,没有则返回unll
  • lastChild,只读属性,返回该节点的最后一个子节点,没有则返回unll
  • (常用)parentNode,返回当前节点的父节点,如果没有,返回null(比如树结构顶端或者该元素根本就没有插入到树中)
  • parentelement,返回当前节点的父元素节点,如果没有或者福元素不是DOM元素,返回null
<body>
    <div id="box">
        <p>p元素</p>
        <span>span元素</span>
        <div>
            123
            <span id="span">我有父亲有祖先</span>
        </div>
    </div>
    <script>
        //获取元素
        var box = document.getElementById('box');

        //获取子节点
        //获取元素的全部子节点
        console.log(box.childNodes); //获取到5个,包含了两个元素之间的空白区域(换行和缩进)
        //获取全部的元素子节点
        console.log(box.children); //获取到2个---------------------常用
        //获取第一个节点(可以不是元素节点)
        console.log(box.firstChild);
        //获取最后一个子节点(可以不是元素节点)
        console.log(box.lastChild);
        //获取第一个元素子节点
        console.log(box.firstElementChild);
        //获取最后一个元素子节点
        console.log(box.lastElementChild);

        //获取父节点,西面两个都可以获取父元素节点
        var span = document.getElementById('span');
        console.log(span.parentNode); // ------------------------常用
        console.log(span.parentElement);
    </script>
</body>

案例:隔行变色(重写)

<body>
    <table border="1" style="border-collapse: collapse;">
        <tbody id="tb">
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
        </tbody>
    </table>

    <script>
        //获取所有自元素节点,使用children
        var trs = document.getElementById('tb').children;
        //遍历设置属性
        for (let i = 0; i < trs.length; i++) {
            if (i % 2 === 1) {
                trs[i].style.backgroundColor = 'pink';
            }
        }
    </script>
</body>

兄弟节点

nextSibling:只读属性,获取当前节点的下一个节点,没有返回null

previouseSibling:只读属性,获取当前节点的上一个节点,没有返回null

nextElementSibling:只读属性,获取当前节点的下一个元素节点,没有返回null

previouseElementSibling:只读属性,获取当前节点的上一个元素节点,没有返回null

nextElementSibling和previouseElementSibling在IE9以上才支持

<body>
    <div>
        <p>我是第一个</p>
        <p id="tow">我是第二个</p>
        <p>我是第三个</p>
    </div>

    <script>
        var tow = document.getElementById('tow');
        //获取下一个兄弟节点
        console.log(tow.nextSibling);
        //获取上一个兄弟节点
        console.log(tow.nextElementSibling);
        //获取下一个元素兄弟节点
        console.log(tow.previousSibling);
        // 获取上一个元素兄弟节点
        console.log(tow.previousElementSibling);
      
      //获取元素节点比较常用
    </script>
</body>

创建节点

  • document.createElement(''):创建元素节点
  • document.createAttribute(''):创建属性节点
  • document.cerateTextNode(''):创建文本节点

一般我们把节点存在变量中,方便调用

    //创建新的元素节点div
    var newdic = document.createElement('div');
    newdic.innerHTML = '新的元素节点';
    //新的属性节点id
    var newatt = document.createAttribute('id');
    newatt.nodeValue = 'aaa';
    //新的文本节点
    var newtext = document.createTextNode('我是新的文本节点');

添加节点

父节点.appendChild():将某个节点添加到父节点底部,不可添加属性节点

<body>
    <div id="box">
        <p>我是第一个</p>
        <p id="p2">我是第二个</p>
        <p>我是第三个</p>
        <p>我是第四个</p>
    </div>

    <script>
        //创建新的节点
        // 注意:我们新创建的元素节点也属于一个对象名,可以设置其属性、方法、事件,当日后插入到DOM树中后会保留这些属性、方法、事件
        var newdic = document.createElement('div');
        newdic.innerHTML = '新的元素节点';
        var newatt = document.createAttribute('id');
        newatt.nodeValue = 'aaa';
        var newtext = document.createTextNode('我是新的文本节点');

        //获取父节点
        var box = document.getElementById('box');
        //把创建的元素和文本节点插入到最后
        box.appendChild(newdic);
        box.appendChild(newtext);

        //我们不止可以操作新建的节点,也可以操作原有的节点
        var p2 = document.getElementById('p2'); //获取p2
        box.appendChild(p2); //把p2插入到最后,从原始位置移除,添加到新的指定位置
    </script>
</body>

替换、删除、插入节点

parentNode.replaceChild(新节点,老节点)用新节点替换当前的老节点,并返回被替换的节点

parentNode.insertBefore(新节点,参考节点):在参考节点之前插入一个有指定父节点的子节点,参考节点必须设置,如果想插在尾部可以写null

parentNode.removeChild(子节点):移除父节点内部的某个子节点,这个子节点必须在父节点内部才可以

<body>
    <div id="box">
        <p>我是第一个</p>
        <p id="p2">我是第二个</p>
        <p>我是第三个</p>
        <p>我是第四个</p>
    </div>

    <script>
        //创建新的节点
        // 注意:我们新创建的元素节点也属于一个对象名,可以设置其属性、方法、事件,当日后插入到DOM树中后会保留这些属性、方法、事件
        var newdic = document.createElement('div');
        newdic.innerHTML = '新的元素节点';

        //获取父节点
        var box = document.getElementById('box');
        //获取需要替换的节点
        var p2 = document.getElementById('p2');


        //执行替换
        // box.replaceChild(newdic, p2);  //用newdic替换p2

        //插入节点
        // box.insertBefore(newdic, p2); //把newdic插入到p2前面
        // box.insertBefore(newdic, null); //把newdic插入到结尾

        //移除节点
        box.removeChild(p2); //删除p2节点
        box.removeChild(ppp); //如果不存在,会报错
    </script>
</body>

克隆节点

克隆本体.colonNode(布尔值):克隆节点,根据参数决定是否克隆所有内容,默认true

如果参数为true:节点的所有后代都会被克隆

如果参数为flase:则只克隆节点本身

注意:克隆时,标签上的属性和事件都会保留,但是使用js绑定的事件不会被保留

<body>
    <div id="box">11111
        <p>我是第一个</p>
        <p id="p2">我是第二个</p>
        <p>我是第三个</p>
        <p>我是第四个</p>
    </div>

    <script>
        //创建新的节点
        // 注意:我们新创建的元素节点也属于一个对象名,可以设置其属性、方法、事件,当日后插入到DOM树中后会保留这些属性、方法、事件
        var newdic = document.createElement('div');
        newdic.innerHTML = '新的元素节点';

        // 获取父节点
        var box = document.getElementById('box');
        //获取需要替换的节点
        var p2 = document.getElementById('p2');

        //克隆节点
        var kelong = box.cloneNode(false); //浅克隆:只克隆节点,不克隆内容
        var kelong2 = box.cloneNode(true); //深克隆:全全部隆
        console.log(kelong, kelong2);
    </script>
</body>

判断节点

父节点.hasChildNodes():判断父节点里面是否包含任何子节点,返回布尔值

父节点.contains(子节点):判断子节点是否是父节点的后代节点(祖先元素也可以),返回布尔值

<body>
    <div id="box">
        <p>我是第一个</p>
        <p id="p2">我是第二个</p>
        <p>我是第三个</p>
        <p>我是第四个</p>
    </div>
    <div id="box2"></div>
    <script>
        //获取元素
        var box = document.getElementById('box'),
            box2 = box.nextElementSibling,
            p2 = document.getElementById('p2');
        //判断box是否有子节点
        console.log(box.hasChildNodes()); //true
        //判断box2是否有子节点
        console.log(box2.hasChildNodes()); // flase
        //判断p2属不属于box
        console.log(box.contains(p2)); //true
        //判断p2属不属于box2
        console.log(box2.contains(p2)); //flase
    </script>
</body>

判断方法总结

三种方法可以判断一个节点是否有子节点

  • 判断节点的第一个子节点是否是null:node.firstChild != null
  • 判断子节点长度是否大于0:node.childNodes.length > 0
  • 直接判断是否有子节点:node.hasChildNodes()
<body>
    <div id="box">
        <p>我是第一个</p>
        <p id="p2">我是第二个</p>
        <p>我是第三个</p>
        <p>我是第四个</p>
    </div>
    <div id="box2"></div>
    <script>
        //获取元素
        var box = document.getElementById('box'),
            box2 = box.nextElementSibling;
        //实验一下三种判断是否有子节点的方法
        //方法1:判断第一个子节点是否存在
        console.log(box.firstElementChild != null); //true
        console.log(box2.firstElementChild != null); //flase
        //方法2:判断子节点个数是否大于0
        console.log(box.childNodes.length > 0); //true
        console.log(box2.childNodes.length > 0); //flase
        //直接调用hasChildNode()
        console.log(box.hasChildNodes()); //true
        console.log(box2.hasChildNodes()); //flase

        //补充:判断是否有子元素
        //判断第一个子元素节点是否存在
        console.log(box.firstElementChild != null); //true
        console.log(box2.firstElementChild != null); //flase
        //判断元素内部子元素节点的数量是否大于0
        console.log(box.children.length > 0) //true
        console.log(box2.children.length > 0) //flase
    </script>
</body>

案例:动态创建列表

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

    <script>
        //获取元素
        var box = document.getElementById('box');
        //创建名字数组用于遍历
        var names = ['刘备', '关羽', '张飞', '赵云'];
        //常见一个元素节点
        var newul = document.createElement('ul');
        //循环遍历数组,并生成新的节点插入到元素节点中
        for (let i = 0; i < names.length; i++) {
            //创建新节点
            let el = document.createElement('li');
            //修改节点文本
            el.innerText = names[i];
            //添加到ul中
            newul.appendChild(el);
        }
        //将制作好的ul整体插入到页面中
        box.appendChild(newul);
    </script>
</body>

案例:动态生成表格

需求:根据数据动态生成表格,并且添加事件

<!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>
        table {
            border-collapse: collapse;
        }

        thead {
            background-color: #ccc;
        }

        th,
        td {
            width: 100px;
            height: 20px;
            text-align: center;
        }
    </style>
    <script>
        // 功能:获取指定id名的元素
        function $id(x) {
            return document.getElementById(x);
        }
    </script>
</head>

<body>
    <h1>动态创建表格</h1>
    <table id="wrap" border="1">
        <thead>
            <tr>
                <th>姓名</th>
                <th>科目</th>
                <th>成绩</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody id="tb">

        </tbody>
    </table>

    <script>
        // 数据数组
        var data = [{
            name: 'ls',
            kemu: '数学',
            num: 92
        }, {
            name: 'ww',
            kemu: '英语',
            num: 98
        }, {
            name: 'lb',
            kemu: '生物',
            num: 100
        }, {
            name: 'sq',
            kemu: '化学',
            num: 76
        }, {
            name: 'cc',
            kemu: '物理',
            num: 98
        }];
        // 获取tbody
        var tb = $id('tb');
        //获取全部的删除单元格
        var del = document.getElementsByClassName('del');
        // 循环遍历数组,添加tr行
        for (let i = 0; i < data.length; i++) {
            // 创建新标签tr
            var tr = document.createElement('tr');
            // 数据内部以对象形式存储数据,遍历对象
            for (let j in data[i]) {
                //对象每一对数据都单独创建一个td标签
                var td = document.createElement('td');
                // td标签添加内容
                td.innerText = data[i][j];
                //将创建的td添加到tr中
                tr.appendChild(td);
            }
            // 最后添加最后一行的删除单元格,并给定类名,这里采用直接新增innerHTML的方式进行添加,也可以使用createElement新建标签方式
            tr.innerHTML += '<td class="del"><a href="#">删除</a></td>';
            // 将tr添加到tb中
            tb.appendChild(tr);
        }
        //循环遍历数组给每个删除单元格添加click事件
        for (let i = 0; i < del.length; i++) {
            // 添加事件
            del[i].onclick = function () {
                // 获取父元素
                var fater = this.parentNode;
                // 删除父元素
                tb.removeChild(fater);
            }
        }
        // 这里使用遍历循环添加事件,如果在之前使用createElement方法新建删除标签,可以在标签内部直接添加onclick
    </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>
        select {
            width: 200px;
            height: 200px;
            background-color: #33cccc;
            font-size: 20px;
        }
    </style>
    <script>
        // 功能:获取指定id名的元素
        function $id(x) {
            return document.getElementById(x);
        }
    </script>
</head>

<body>
    <select id="all" size="5" multiple="multiple">
        <option>苹果</option>
        <option>橘子</option>
        <option></option>
        <option>西瓜</option>
        <option>水蜜桃</option>
    </select>

    <input type="button" value=">>" id="btn1">
    <input type="button" value="<<" id="btn2">
    <input type="button" value=">" id="btn3">
    <input type="button" value="<" id="btn4">

    <select id="choose" multiple="multiple">
    </select>

    <script>
        // 获取元素
        var all = $id('all'),
            choose = $id('choose'),
            //4个按钮
            btn1 = $id('btn1'),
            btn2 = $id('btn2'),
            btn3 = $id('btn3'),
            btn4 = $id('btn4'),
            allopts = all.children;
        //第一个按钮点击事件
        btn1.onclick = function () {
            //获取左侧列表的所有子元素,注意,这里的opts数组是动态变化的,后面移动元素会导致这数组发生变化
            var opts = all.children;
            //循环遍历将子元素移动到右侧列表,因为opts长度是动态变化的,所以我们使用固定值size当作判断条件
            for (let i = 0; i < all.size; i++) {
                //把元素移动到右侧,appendChild属于移动元素方法
                choose.appendChild(opts[0]);
            }
            //更改右侧列表的size属性,加上移动过来的个数
            choose.size += all.size;
            //重置左侧列表size属性为0
            all.size = 0;
        }
        //第二个按钮点击事件,原理同上
        btn2.onclick = function () {
            var opts = choose.children;
            for (let i = 0; i < choose.size; i++) {
                all.appendChild(opts[0]);
            }
            all.size += choose.size;
            choose.size = 0;
        }
        // 第三个按钮点击事件
        btn3.onclick = function () {
            // 获取左侧列表所有子元素,注意这里和上面一样,数组都是动态变化的
            var childs = all.children;
            //循环遍历每个子元素,一旦发现被选中移动到右边,因为数组是动态变化的,所以我们这里从后往前遍历,可以避免受到数组长度的影响(这里也可以先新建一个数组,把所有选中的元素显添加到数组中在移动的方式)
            for (let i = all.size - 1; i >= 0; i--) {
                //判断元素是否被选中,如果被选中,立即移动到左侧
                if (childs[i].selected) {
                    //移动过去之后不需要选中了,所以先取消选中状态
                    childs[i].selected = false;
                    //移动
                    choose.appendChild(childs[i]);
                    //修改移动后两侧的size值
                    all.size--;
                    choose.size++;
                }
            }
        }
        // 第四个按钮的点击事件,原理同上
        btn4.onclick = function () {
            var childs = choose.children;
            for (let i = choose.size - 1; i >= 0; i--) {
                if (childs[i].selected) {
                    childs[i].selected = false;
                    all.appendChild(childs[i]);
                    choose.size--;
                    all.size++;
                }
            }
        }
    </script>
</body>

</html>

addEventListener绑定事件

on+事件名的方法无法多次绑定,多次绑定会被覆盖,addEventListener绑定可以多次添加事件,都可以执行

注册事件的其他方法1

元素.addEventListener(事件类型,事件函数)

注意:

  • 第一个参数为字符串类型的事件类型,例如:'ciick'
  • 同一个元素可以绑定多个事件,同一个事件类型可以注册多个事件函数
  • IE9以下浏览器不支持
<body>
    <button id="btn">点我</button>
    <script>
        //获取元素
        var btn = $id('btn');
        //注册第一个click事件
        btn.addEventListener('click', function () {
            alert('1');
        })
        //注册第二个click事件,这两个事件都可以执行
        btn.addEventListener('click', function () {
            alert('2');
        })
        // 注册第三个事件,事件函数使用外部函数,注意,函数不要加小括号,否则会立即执行
        btn.addEventListener('click', btnClick);

        //在外部定义一个函数,可以在注册函数的时候调用
        function btnClick() {
            alert('3')
        }
    </script>
</body>

注册事件的其他方法2

元素.attachEvent('on事件类型',事件函数)

注意事项

  • 第一个参数为字符串类型的,需要加on
  • 和上面一样,同一个元素可以绑定多事件,同一类型事件可以注册多个事件函
  • 只支持IE10及以下浏览器(IE8及以下可能会出现事件顺序错乱的问题)
<body>
    <button id="btn">点我</button>
    <script>
        //获取元素
        var btn = $id('btn');
        // 注册三个事件,但是由于attachEvent只支持IE10及以下浏览器,所以新浏览器可能不支持
        btn.attachEvent('onclick', function () {
            alert('1')
        })
        btn.attachEvent('onclick', function () {
            alert('2')
        })
        // 事件函数也可以调用外部函数
        btn.attachEvent('onclick', clixkevent)

        function clixkevent() {
            alert(3);
        }
    </script>
</body>

注册事件兼容写法

方案:自定义一个注册事件函数

注意事项:

  • 参数:(事件源,事件类型(不加on),事件函数){}
  • 方法:判断浏览器版本,IE9及以上使用addEventListener方法,以下使用attachEvent方法
  • 判断方法:不直接判断版本,我们检测浏览器的能力(将某个方法的调用作为判断条件,浏览器认识该方法返回true,不认识返回flase)
<body>
    <button id="btn">点我</button>
    <script>
        //获取元素
        var btn = $id('btn');
        // 给元素绑定事件,通过调用注册事件函数
        addEvent(btn, 'click', ret);


        //一个兼容所有浏览器的事件绑定函数
        //参数:元素,事件类型字符串,事件函数
        function addEvent(ys, lx, fn) {
            //判断浏览器是否认识某个方法
            //判断浏览器是否认识addEventListener方法,如果认识,使用addEventListener注册事件
            if (ys.addEventListener) {
                //注册事件
                btn.addEventListener(lx, fn);
            } else if (ys.attachEvent) { //如果浏览器不认识addEventListener,那么判断是否认识attachEvent
                //如果认识,使用attachEvent注册事件
                btn.attachEvent('on' + lx, fn);
            }
        }
        //新建一个事件函数
        function ret() {
            alert('事件');
        }
    </script>
</body>

事件解绑

on开头的事件解除

直接使用on+事件类型 = null即可

移除其他事件方法1

使用元素.removeEventListener('事件类型',事件函数名)

注意事项

  • 第一个参数和注册时候一样,使用字符串事件类型,不需要加on
  • 第二个参数是事件函数的引用名(我们没有办法移除一个匿名函数,所以我们只能在注册事件的时候给一个有函数名的事件函数)
  • 移除事件需要写在书写注册事件之后
  • 不兼容IE9以下浏览器
<body>
    <button id="btn">点我</button>
    <script>
        //获取元素
        var btn = $id('btn');


        // 注册事件
        btn.addEventListener('click', clickbtn);
        //移除事件
        btn.removeEventListener('click', clickbtn);

        //事件函数
        function clickbtn() {
            alert(1);
        }
    </script>
</body>

移除其他事件的方法2

使用元素.detachEvent('事件类型',事件函数名)

注意事项

  • 第一个参数字符串类型的事件类型,需要加on
  • 第二个参数同上个方法一样,只能移除有名字的事件函数
  • 只兼容IE10及以下浏览器
<body>
    <button id="btn">点我</button>
    <script>
        //获取元素
        var btn = $id('btn');


        // 注册事件
        btn.attachEvent('click', clickbtn);
        //移除事件
        btn.detachEvent('click', clickbtn);

        //事件函数
        function clickbtn() {
            alert(1);
        }
    </script>
</body>

解除事件兼容写法

和绑定事件一样,我们可以自己自定义一个解除事件函数

注意事项

参数:(事件源,事件类型(不加on),事件函数)

方法:判断浏览器能力(IE9及以上判断removeEventListener方法,以下判断detachEvent方法)

// 自定义解绑事件函数
//参数:事件源,事件类型,事件函数
function removeClick(ele, lx, fn) {
    //判断浏览器能力,来判断浏览器版本
    // 判断是否认识removeEventListener方法
    if (btn.removeEventListener) {
        btn.removeEventListener(lx, fn);
    } else if (btn.detachEvent) { 
      //如果不认识removeEventListener那么判断是否认识detachEvent方法
        btn.detachEvent('on' + lx, fn);
    }
}

建议:建议把我们自己封装的一些函数放在单独的js文件内

DOM事件流

rnrJLd.png

小案例:体验事件流向

<body>
    <!-- 三个依次包含的div -->
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        //获取元素
        var box1 = document.getElementById('box1'),
            box2 = document.getElementById('box2'),
            box3 = document.getElementById('box3');
        //给三个元素添加事件
        box1.addEventListener('click', function () {
            console.log(this.id);
        })
        box2.addEventListener('click', function () {
            console.log(this.id);
        })
        box3.addEventListener('click', function () {
            console.log(this.id);
        })
    </script>
</body>

我们点击box3可以发现,控制台依次输出了box3、box2、box1,可以发现,事件的流向是从小到大的(box3➡️box2➡️box1)我们管这种方式叫做事件冒泡

事件冒泡和时间捕获过程

  • 事件冒泡:事件触发从子元素向上(父元素)执行的过程
  • 事件捕获:和事件冒泡刚好相反,事件从外向内依次执行

控制事件流方法:addEventListener方法有第三个参数,默认为flase(事件冒泡),如果设置为true则流向为事件捕获

<body>
    <!-- 三个依次包含的div -->
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        //获取元素
        var box1 = document.getElementById('box1'),
            box2 = document.getElementById('box2'),
            box3 = document.getElementById('box3');
        //给三个元素添加事件,默认事件冒泡,默认第三个参数为flase
        box1.addEventListener('click', function () {
            console.log(this.id);
        })
        box2.addEventListener('click', function () {
            console.log(this.id);
        })
        box3.addEventListener('click', function () {
            console.log(this.id);
        })
        //三个元素再添加事件捕获流程,添加第三个参数,把事件流改为时间捕获
        box1.addEventListener('click', function () {
            console.log(1);
        }, true)
        box2.addEventListener('click', function () {
            console.log(2);
        }, true)
        box3.addEventListener('click', function () {
            console.log(3);
        }, true)


        //当我们点击box3可以发现,控制台打印的效果依次是1、2、box3、3、box2、box1,可以发现整体上来看捕获比冒泡先执行了,但是box3的两个事件却是冒泡先执行,这是因为对于box3自身来说,同时存在冒泡和捕获哪个在前哪个先执行
    </script>
</body>

如果两者同时存在:先执行事件捕获,再执行事件冒泡

事件流的三个阶段

在整个的事件流过程中,我们触发事件可能经历下面的三个阶段

  • 第一阶段:事件捕获
  • 第二阶段:事件执行
  • 第三阶段:事件冒泡

addEventListener方法第三个参数作用:(事件一定会执行,但是执行的顺序可以有不同)

  • 默认为flase:会按照冒泡的过程执行事件(从向外执行)
  • 设置值为true:会按照事件捕获的过程执行(从外向内执行)
  • 如果两者都设置了,优先执行事件捕获过程

注意:on+事件类型attachEvent()方法只能进行事件冒泡过程,没有捕获阶段

事件委托

利用事件冒泡的特性,将子元素的公共事件委托给父元素加载,同时利用事件函数的一个e参数,内部存储事件对象

<body>
    <ul id="ul">
        <li>1111</li>
        <li>1111</li>
        <li>1111</li>
        <li>1111</li>
        <li>1111</li>
        <li>1111</li>
    </ul>
    <script>
        //需求:点击li标签高亮背景色

        //获取外层ul
        var ul = document.getElementById('ul');
        //获取内部li标签
        var lis = ul.children;
        //事件委托:把子元素公共的事件委托给父元素,在父元素中借用参数e获取触发事件的子元素
        ul.onclick = function (e) {
            //我们需要在内部寻找到真正触发事件的子元素
            //参数e为事件对象,只要触发事件,函数内部都会产生一个事件对象

            //排他思想,清除全部的样式
            for (let i = 0; i < lis.length; i++) {
                lis[i].style.backgroundColor = '';
            }
            //e.target就记录的真正触发事件的事件源头
            e.target.style.backgroundColor = 'pink';
        }
    </script>
</body>

事件对象

只要触发事件,就会有一个事件对象,内部存储了与事件相关的数据信息

e在低版本浏览器中有兼容问题,低版本浏览器中需要使用window.event

事件对象常用属性

属性说明
e.eventPhase插件事件触发所处阶段
e.target获取真正触发事件的元素
e.srcElement获取真正触发事件的元素(低版本用法)
e.currentTarget用于获取绑定事件的事件源
e.type获取事件类型
e.clientX / e.clientY鼠标距离浏览器窗口左上角的距离(坐标)
e.pageX / e.pageY鼠标距离整个HTML页面坐上顶点距离(IE8-不支持)
<body>
    <!-- 三个依次包含的div -->
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        //获取元素
        var box1 = document.getElementById('box1'),
            box2 = document.getElementById('box2'),
            box3 = document.getElementById('box3');
        box1.onclick = function (e) {
            //兼容写法,低版本浏览器使用window.event
            var e = e || window.event;
            // 查看事件触发阶段
            console.log(e.eventPhase);
            //兼容写法,低版本浏览器使用e.srcElement获取真正触发事件的元素
            var target = e.target || e.srcElement;
            //查看真正触发事件的事件元素
            console.log(target);
            //查看绑定事件的事件元素(返回的是绑定的事件源,不会根据点击的元素不同发生改变),另外this指向绑定的事件源,和上面相等 
            console.log(e.currentTarget);

            console.log(this);
            //查看触发的事件类型
            console.log(e.type);
            //查看鼠标距离浏览器窗口左上角的距离
            console.log(e.clientX, e.clientY);
            //查看鼠标距离HTML页面左上角的距离
            console.log(e.pageX, e.pageY);
        }


      
      
      
        // 给box1添加事件
        box1.onmouseover = fn;
        box1.onmouseout = fn;

        // 扩展:利用e.type动态添加事件
        //事件动态添加函数
        //参数e为事件对象
        function fn(e) {
            //兼容写法
            var e = e || window.event;
            //条件分支语句,根据e.type判断
            switch (e.type) {
                //如果是鼠标移入
                case 'mouseover':
                    //执行代码
                    this.style.backgroundColor = 'skyblue';
                    break;
                    //如果是鼠标移出
                case 'mouseout':
                    //执行代码
                    this.style.backgroundColor = 'pink';
                    break;
            }
        }
    </script>
</body>

案例:图片随鼠标移动

<!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>
        /* 设置图片样式,固定定位 */
        img {
            position: fixed;
            width: 30px;
            height: 30px;
        }
    </style>
    <script>
        // 功能:获取指定id名的元素
        function $id(x) {
            return document.getElementById(x);
        }
    </script>
</head>

<body>
    <!-- //图片 -->
    <img id="m" src="./images/tianshi.gif" alt="">

    <script>
        // 获取图片元素
        var m = document.getElementById('m');
        //直接给document添加鼠标移动事件,这里也可以给window
        document.addEventListener('mousemove', function () {
            //兼容写法
            var e = e || window.event;
            //把e.client值给图片的left和top值
            m.style.left = e.clientX + 'px';
            m.style.top = e.clientY + 'px';
        });
    </script>
</body>

</html>

取消默认行为和阻止冒泡

取消默认行为

  • e.preventDefault():取消默认行为
  • e.returnValue = flase:取消默认行为(低版本浏览器使用)
<body>
    <!-- //图片 -->
    <a id="a" href="./images/tianshi.gif">点我点我</a>

    <script>
        // 获取元素
        var a = $id('a');
        // 点击事件
        a.onclick = function (e) {
            // 兼容写法
            e = e || window.event;
            //事件执行体
            alert(1);
            // 取消默认行为(这里取消了a标签的默认跳转行为)
            e.preventDefault();
            // 低版本浏览器使用
            // e.returnvalue = false;
          
          // 当然我们也可以使用之前的return flase方法阻止a标签的跳转
        }
    </script>
</body>

阻止冒泡

  • e.stopPropagation():阻止冒泡
  • e.cancelBubble = true:阻止冒泡,IE低版本浏览器使用,标准中已放弃
<!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: 300px;
            height: 300px;
            background: pink;
        }

        #box2 {
            width: 200px;
            height: 200px;
            background: skyblue;
        }

        #box3 {
            width: 100px;
            height: 100px;
            background: #ccc;
        }
    </style>
    <script>
        // 功能:获取指定id名的元素
        function $id(x) {
            return document.getElementById(x);
        }
    </script>
</head>

<body>
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        // 获取元素
        var box1 = $id('box1'),
            box2 = $id('box2'),
            box3 = $id('box3');
        //添加事件
        box3.onclick = function (e) {
            console.log(this.id);
            //阻止冒泡,点击box3时就不会触发外层元素的事件
            e.stopPropagation();
            //低版本浏览器使用
            // e.cancelBubble = true;
        }
        box2.onclick = function () {
            console.log(this.id);
        }
        box1.onclick = function () {
            console.log(this.id);
        }
    </script>
</body>

</html>

DOM特效属性

偏移量属性

rKNCTK.png

  • offsetParent:偏移参考父级--距离自己最近的有定位的父级(类似于css绝对定位),没有则参考body(html)
  • offsetLeft/offsetTop:偏移位置 -- 距离偏移父级元素左上顶点距离(类似于css绝对定位left和top)
  • offsetWidth/offsetHeight:偏移大小 -- 元素大小(除margin以外)
<body>
    <div id="box1">
        <div id="box3">
        </div>
    </div>
    <script>
        // 获取元素
        var box3 = $id('box3');
        // 偏移父元素(因为外部box1没有设置定位,所以就参考了body),元素天生就认识
        console.log(box3.offsetParent);
        // 偏移距离,参考点位body左上顶点
        console.log(box3.offsetLeft);
        console.log(box3.offsetTop);
        // 偏移大小,就是元素盒子大小(不算margin)
        console.log(box3.offsetWidth);
        console.log(box3.offsetHeight);
    </script>
</body>

客户端大小相关属性

rKgtGn.png

client没有参考父级元素

clientLeft / clienTop: 左边框、上边框大小(不常用)

clientWidth / clientHeight:边框内部大小(padding + 内容)

<body>
    <div id="box1">
        <div id="box3">
        </div>
    </div>
    <script>
        // 获取元素
        var box3 = $id('box3');
        //客户端大小有关属性
        //获取左边和上边框大小
        console.log(box3.clientLeft);
        console.log(box3.clientTop);
        // 获取元素内部大小(内容区域+padding)
        console.log(box3.clientWidth);
        console.log(box3.clientHeight);
    </script>
</body>

滚动偏移相关属性

rKh9BV.png

scrollLeft / scrollTop :盒子内部滚动出去的尺寸 -- 已滚动的,未滚动为0

scrollWidth / scrollHeight:盒子内部的宽度和高度 -- 实际上就是盒子内部可供滚动的像素值

<!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: 300px;
            height: 300px;
            padding: 50px;
            border: 20px solid green;
            background: pink;
            overflow: auto;
        }

        #box3 {
            width: 500px;
            height: 500px;
            background: skyblue;
        }
    </style>
    <script>
        // 功能:获取指定id名的元素
        function $id(x) {
            return document.getElementById(x);
        }
    </script>
</head>

<body>
    <div id="box1">
        <div id="box3">
        </div>
    </div>
    <script>
        var box1 = $id('box1');
        var box3 = $id('box3');
        // 获取元素的滚动偏移属性

        //滚动偏移量:指的是滚动出去的距离,如果没有滚动两者都是0
        console.log(box1.scrollLeft);
        console.log(box1.scrollTop);

        //元素滚动偏移大小:实际上就是盒子内部可以滚动的距离
        console.log(box1.scrollWidth); //550(box1左内边距 + box3盒子宽度)
        console.log(box1.scrollHeight); //600(box1上内边距 + 盒子高度)
        //这里不同是因为浏览器加载机制的问题,纵向滚动会多加载一个父元素的下内边距


        //这两个属性都不会因为溢出而发生影响
        console.log(box1.clientWidth); //385(本应该是400,但是滚动条占用了15px)
        console.log(box1.offsetWidth); //440

        //添加事件查看一下滚动偏移,onscroll事件为滚动事件
        box1.onscroll = function () {
            //滚动的话输出偏移量
            console.log(box1.scrollLeft, box1.scrollTop);
        }
    </script>
</body>

</html>

案例:拖拽

<!DOCTYPE html>
<html>

<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .nav {
            height: 30px;
            background: #036663;
            border-bottom: 1px solid #369;
            line-height: 30px;
            padding-left: 30px;
        }

        .nav a {
            color: #fff;
            text-align: center;
            font-size: 14px;
            text-decoration: none;

        }

        .d-box {
            width: 400px;
            height: 300px;
            border: 5px solid #eee;
            box-shadow: 2px 2px 2px 2px #666;
            position: absolute;
            top: 40%;
            left: 40%;
            background-color: white;

            /* 不让文字被选中 */
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }

        .hd {
            width: 100%;
            height: 25px;
            background-color: #7c9299;
            border-bottom: 1px solid #369;
            line-height: 25px;
            color: white;
            cursor: move;
        }

        #box_close {
            float: right;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <div class="nav">
        <a href="javascript:;" id="register">注册信息</a>
    </div>
    <div class="d-box" id="d_box">
        <div class="hd" id="drop">注册信息 (可以拖拽)
            <span id="box_close">【关闭】</span>
        </div>
        <div class="bd"></div>
    </div>
    <script src="common.js"></script>
    <script>
        // 获取元素
        var d_box = document.getElementById('d_box'),
            drop = document.getElementById('drop'),
            box_close = document.getElementById('box_close');
        //点击关闭按钮关闭,添加click事件
        box_close.onclick = function () {
            //修改css样式
            d_box.style.display = 'none';
        }
        //鼠标按下事件
        //思路:
        //需要先获取鼠标位置和box左上顶点的坐标距离
        //鼠标移动的时候使用鼠标位置减掉这两个距离就可以算出box应该在的实际位置
        //再把这个定位赋值给box的css定位即可
        drop.onmousedown = function (e) {
            //兼容写法
            var e = e || window.event,
                //获取鼠标位置和box左上顶点的差值
                x = e.pageX - d_box.offsetLeft,
                y = e.pageY - d_box.offsetTop;
            // var me = true;
            //嵌套鼠标移动事件
            drop.onmousemove = function (e) {
                //兼容写法
                var e = e || window.event;
                // if (me) {
                //将鼠标位置减掉与左上顶点的差值赋值给css定位属性
                d_box.style.top = e.clientY - y + 'px';
                d_box.style.left = e.clientX - x + 'px';
                // }
            }
            // drop.onmouseup = function () {
            //     me = false;
            // }
        }
        // 当鼠标抬起后,我们清空鼠标移动事件,让box不再跟着鼠标移动
        drop.onmouseup = function () {
            this.onmousemove = null;
        }


        // 思路2:(上面注释掉的部分)
        //前面同思路1,但是我们可以把onmouseup事件写在onmousedown事件内部,我们可以在onmousedown中创造一个值,当这个值为true是才能执行鼠标移动事件,为flase就让鼠标移动事件不触发,我们只需要在鼠标抬起事件内部把这个值设置为flase即可
    </script>
</body>

</html>   

案例:点击弹层(遮罩)

<!DOCTYPE html>
<html>

<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        .login-header {
            width: 100%;
            text-align: center;
            height: 30px;
            font-size: 24px;
            line-height: 30px;
        }

        ul,
        li,
        ol,
        dl,
        dt,
        dd,
        div,
        p,
        span,
        h1,
        h2,
        h3,
        h4,
        h5,
        h6,
        a {
            padding: 0px;
            margin: 0px;
        }

        .login {
            width: 512px;
            position: absolute;
            border: #ebebeb solid 1px;
            height: 280px;
            left: 50%;
            right: 50%;
            background: #ffffff;
            box-shadow: 0px 0px 20px #ddd;
            z-index: 9999;
            margin-left: -256px;
            margin-top: 140px;
            display: none;
        }

        .login-title {
            width: 100%;
            margin: 10px 0px 0px 0px;
            text-align: center;
            line-height: 40px;
            height: 40px;
            font-size: 18px;
            position: relative;
            cursor: move;
            -moz-user-select: none;
            /*火狐*/
            -webkit-user-select: none;
            /*webkit浏览器*/
            -ms-user-select: none;
            /*IE10*/
            -khtml-user-select: none;
            /*早期浏览器*/
            user-select: none;
        }

        .login-input-content {
            margin-top: 20px;
        }

        .login-button {
            width: 50%;
            margin: 30px auto 0px auto;
            line-height: 40px;
            font-size: 14px;
            border: #ebebeb 1px solid;
            text-align: center;
        }

        .login-bg {
            width: 100%;
            height: 100%;
            position: fixed;
            top: 0px;
            left: 0px;
            background: #000000;
            filter: alpha(opacity=30);
            -moz-opacity: 0.3;
            -khtml-opacity: 0.3;
            opacity: 0.3;
            display: none;
        }

        a {
            text-decoration: none;
            color: #000000;
        }

        .login-button a {
            display: block;
        }

        .login-input input.list-input {
            float: left;
            line-height: 35px;
            height: 35px;
            width: 350px;
            border: #ebebeb 1px solid;
            text-indent: 5px;
        }

        .login-input {
            overflow: hidden;
            margin: 0px 0px 20px 0px;
        }

        .login-input label {
            float: left;
            width: 90px;
            padding-right: 10px;
            text-align: right;
            line-height: 35px;
            height: 35px;
            font-size: 14px;
        }

        .login-title span {
            position: absolute;
            font-size: 12px;
            right: -20px;
            top: -30px;
            background: #ffffff;
            border: #ebebeb solid 1px;
            width: 40px;
            height: 40px;
            border-radius: 20px;
        }
    </style>
</head>

<body>
    <div class="login-header"><a id="link" href="javascript:void(0);">点击,弹出登录框</a></div>
    <div id="login" class="login">
        <div id="title" class="login-title">登录会员
            <span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span>
        </div>
        <div class="login-input-content">
            <div class="login-input">
                <label>用户名:</label>
                <input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="list-input">
            </div>
            <div class="login-input">
                <label>登录密码:</label>
                <input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="list-input">
            </div>
        </div>
        <div id="loginBtn" class="login-button"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div>
    </div>
    <!-- 遮盖层 -->
    <div id="bg" class="login-bg"></div>
    <script>
        // 获取元素
        var link = document.getElementById('link'),
            login = document.getElementById('login'),
            closeBtn = document.getElementById('closeBtn'),
            bg = document.getElementById('bg');

        //a标签点击事件,点击后显示表单和背景遮罩
        //修改行内css样式,因为行内会覆盖内嵌样式,所以会显示出来
        link.onclick = function () {
            login.style.display = 'block';
            bg.style.display = 'block';
        }
        //点击关闭按钮修改行内的display为空,这样css样式覆盖就消失了,就会显示默认内嵌式样式
        closeBtn.onclick = function () {
            login.style.display = '';
            bg.style.display = '';
        }
    </script>
</body>

</html>