JS Web API(汇总2)

273 阅读37分钟

DOM 操作

什么是DOM?

文档对象模型(Document Object Model),是 W3C 组织推荐的处理可拓展标记语言(HTML或者XML)的编程接口。

W3C 已经定义了一系列的 DOM 接口,通过这些接口可以改变网页的内容、结构和样式。

DOM 树

image-20220615154553565

  • 文档:一个页面就是一个文档,DOM 中使用 document 表示
  • 元素:页面中的所有标签都是元素,DOM中使用 element 表示
  • 节点:网页中的所有内容都是节点(标签、属性、文本、注释等),DOM 中使用 node 表示

DOM 把以上内容都看成是对象

获取元素

代码归属描述
document.getElementById(‘nav’)DOM 老方法不推荐通过元素的 id 属性值获取元素,只返回第一个元素
document.getElementByTagName(‘div’)DOM 老方法不推荐通过元素的标签名获取元素,将选择到的所有对象存入伪数组并返回
document.querySelector(‘.nav li’)H5 新增方法推荐通过选择器的方法获取元素,只返回第一个对象
document.querySelectorAll(‘.nav li’)H5 新增方法推荐通过选择器的方法获取元素,将选择到的所有对象存入伪数组并返回
document.getElementByClassName(‘nav’)H5 新增方法通过元素的类名获取元素,将选择到的所有对象存入伪数组并返回,不兼容ie
document.body获取整个文档的body部分
document.documentElement获取整个文档,即html部分

根据元素 ID 获取 getElementById()

  • 此方法为 DOM 老 API,兼容性好,但是不推荐开发时使用。

  • 此方法只返回获取到的第一个元素对象。

  • 语法:

// 获取 id 为 time 的第一个元素
var timer = document.getElementById('time');
// 返回获取到的整个元素标签
console.log(timer);
// 返回获取到的元素类型
console.log(typeof timer);
// 在控制台中显示指定 JavaScript 对象的属性,并通过类似文件树样式的交互列表显示, 更好的查看属性和方法
console.dir(timer);

通过标签名获取 getElementByTagName()

  • 此方法为 DOM 老 API,兼容性好,但是不推荐开发时使用。

  • 此方法将获取到的对象存放于伪数组,以伪数组的形式返回所有获取的元素。

  • 如果获取到的元素只有一个,依然返回伪数组;如果没有获取到元素,返回的是一个空的伪数组、

  • 语法:

// 获取页面中所有 li
var lis = document.getElementsByTagName('li');
// 输出伪数组
console.log(lis);
// 输出伪数组中的第2个对象
console.log(lis[1]);
// 遍历
for (i = 0; i < lis.length; i++){
    console.log(lis[i]);
}

根据类名来获取 getElementByClassName()

  • 此方法为 HTML5 新增的方法,仅仅兼容ie9+,平时不常用。
  • 此方法将获取到的对象存放于伪数组,以伪数组的形式返回所有获取的元素。
  • 如果获取到的元素只有一个,依然返回伪数组;如果没有获取到元素,返回的是一个空的伪数组。
  • 语法:
var box = document.getElementsByClassName('box');
console.log(box);

根据选择器获取 querySelector()

  • 此方法为 HTML5 新增的方法,仅仅兼容ie9+,推荐使用。
  • 此方法只返回获取到的第一个元素对象。
var box = document.querySelector('.box');
console.log(box);

根据选择器获取 querySelectorAll()

  • 此方法为 HTML5 新增的方法,仅仅兼容ie9+,推荐使用。
  • 此方法将获取到的对象存放于伪数组,以伪数组的形式返回所有获取的元素。
  • 如果获取到的元素只有一个,依然返回伪数组;如果没有获取到元素,返回的是一个空的伪数组
var box =  document.querySelectorAll('.box');
console.log(box);

获取 body 和 html

  • 在文档中元素的获取中,有专门获取 body 和 html 的方法
  • 语法:
// 获取body
var body = document.body;
console.log(body);
console.dir(body);
// 获取整个html
var htmlEle = document.documentElement;
console.log(htmlEle);

事件操作

网页中的每个元素都可以产生某些可以触发 JS 的事件,例如,我们可以在用户点击某按钮时产生一个事件,然后去执行某些操作

事件有三部分组成:事件源、事件类型、事件处理程序,我们将其称为事件三要素

  1. 事件源:事件的触发对象(谁触发了事件?)
  2. 事件类型:事件如何被触发 什么事件 比如鼠标点击(onclick)还是鼠标经过 还是键盘按下
  3. 事件处理程序:事件被触发要执行的操作

注册事件

给元素添加事件,称为注册事件或者绑定事件

注册事件有两种方式:传统方式方法监听注册方式

  1. 传统注册方式

    • 使用 on 开头的事件 onclick

    • <button onclick="alert('hi')"></button>

    • btn.onclock = function(){}

    • 特点:注册事件的唯一性

  • 同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数。
  1. 监听注册方式

    • w3c 标准 推荐方式

    • addEventListener() 他是一个方法

    • IE9之前的IE版本不支持此方法,可以使用attachEvent() 代替

    • 特点:同一个元素同一个事件可以注册多个监听器

传统方式注册事件

事件描述
onclick点击事件
onfocus获得焦点事件
onblur失去焦点事件
onmouseover鼠标经过
onmouseout鼠标离开
onmousemove鼠标移动
onmouseup鼠标弹起
onmousedown鼠标按下

语法:

// 获取按钮元素
var btn = document.querySelector('button');
// 给按钮元素添加点击事件
btn.onclick = function(){
    // 事件处理程序,但被点击时执行
    console.log('按钮被点击');
}

addEventListener 事件监听方式

eventTarget.addEventListener(type, listener, useCapture)

eventTarget.addEventListener() 方法将指定的监听器注册到 evenTarget (目标对象) 上,当该对象触发指定事件时,就会执行事件处理函数。

该方法有三个参数:

  • type:事件类型字符串,比如 click、mouseover,注意这里并没有on
  • listener:事件处理函数,事件发生时,会调用该监听函数
  • useCapture:事件流,默认为false。flase为冒泡阶段,true为捕获阶段。

代码实例:

// 给元素btn添加两个监听鼠标点击事件
btn.addEvenListener('click',function(){
    alert(22);
})
btn.addEvenListener('click',function(){
    alert(33);
})

attachEvent 事件监听方式

eventTarget.attachEvent(type, callback)

该方法将指定的监听器注册到指定的对象上,当对象被触发,就会执行指定的回调函数。

该方法只支持ie9之前的ie浏览器,并不被其他浏览器支持。

  • type:事件类型字符串,比如 onclick、onmouseover,这里要带有 on
  • callback:事件处理函数,当目标触发事件时回调函数被调用

代码实例:

btn.attachEvent('onclick', function(){
    alert(11)
})

对不同浏览器的兼容性处理

这里写一个兼容性函数,来适配不同浏览器的兼容性问题(PS:IE就是个毒瘤)

兼容性要首先照顾大多数浏览器,再处理特殊浏览器

function addEventListener(element, eventName, fn){
    // 判断当前浏览器是否支持 addEventListener
    if(element.addEvenListener){
        element.addEventListener(eventName, fn);
    }else if(element.attachEvent){
        element.attachEvent('on' + eventName, fn);
    }else{
        // 相当于 element.onclick = fn
        element['on' + eventName] = fn;
    }
}

删除事件(解绑事件)

传统方式删除事件

直接给指定的对象的相应的事件赋值为null,即可删除该事件。

eventTarget.onclick = null;

监听方式删除事件 removeEventListener

eventTarget.removeEventListener(type, listener, useCapture)
  • type:事件类型字符串,比如 click、mouseover,注意这里并没有on

  • listener:需要解绑的事件处理函数

  • useCapture:事件流,默认为false。flase为冒泡阶段,true为捕获阶段。

代码实例:

btn.addEventListener('click', fn);
function fn() {
    alert(22);
    btn.removeEventListener('click', fn);
}

注意:如果要删除监听事件,那么添加监听事件时一定要用具体的函数名,不能使用匿名函数。使用匿名函数时,再删除事件时候,无法指定事件触发函数。

监听方式删除事件 detachEvent

此方法对应 attackEvent

eventTarget.detachEvent(type, callback)
  • type:事件类型字符串,比如 onclick、onmouseover,这里要带有 on
  • callback:要删除的事件处理函数

代码实例:

btn.attachEvent('onclick', fn);
function fn() {
    alert(33);
    btn.detachEvent('onclick', fn);
}

对不同浏览器的兼容性处理

function removeEventListener(element, eventName, fn){
    // 判断当前浏览器是否支持 removeEventListener
    if(element.removeEvenListener){
        element.removeEventListener(eventName, fn);
    }else if(element.detachEvent){
        element.detachEvent('on' + eventName, fn);
    }else{
        // 相当于 element.onclick = fn
        element['on' + eventName] = null;
    }
}

DOM 事件流

事件流描述的是从页面接收事件的顺序

事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程称为 DOM 事件流。

DOM 事件流分为3个阶段:

  1. 捕获阶段
  2. 当前目标阶段
  3. 冒泡阶段

事件冒泡:IE最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到 DOM 最顶层节点的过程。

事件捕获:网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到最具体的元素接收的过程。

代码验证

  1. JS 代码只能执行捕获或者冒泡的其中一个阶段。
  2. onclick 和 attachEvent 只能得到冒泡阶段。
  3. addEventListener中的第三个参数如果是 true,表示再事件捕获阶段调用事件处理程序;如果是false(不写默认时false)则表示再事件冒泡阶段调用事件处理程序。

演示地址

<!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>事件流</title>
    <style>
        .father {
            width: 300px;
            height: 300px;
            margin: 10px auto;
            background-color: antiquewhite;
            overflow: hidden;
        }

        .son {
            width: 200px;
            height: 200px;
            margin: 50px auto;
            background-color: aliceblue;
        }
    </style>
</head>

<body>
    <div class="father">father
        <div class="son">
            son 冒泡阶段 触发事件处理程序
        </div>
    </div>
    <div class="father">father2
        <div class="son">
            son2 捕获阶段 触发事件处理程序
        </div>
    </div>
    <script>
        var son = document.querySelectorAll('.son');
        // 指定事件在冒泡阶段触发事件处理程序
        son[0].addEventListener('click', function () {
            alert('son');
        })
        son[0].parentNode.addEventListener('click', function () {
            alert('fatehr');
        })

        // 指定事件在捕获阶段触发事件处理程序
        son[1].addEventListener('click', function () {
            alert('son');
        }, true)
        son[1].parentNode.addEventListener('click', function () {
            alert('fatehr');
        }, true)
    </script>
</body>

</html>

事件对象

事件对象只有有了事件才会存在,他是系统给我们自动创建的,不需要我们传递参数。

事件对象是我们事件的一系列的相关数据的集合,跟事件相关的,比如鼠标点击里面就包含了鼠标的相关信息,如鼠标坐标等。如果是键盘事件,那么里面就包含的键盘事件的信息,比如判断用户按下了哪个键。

事件对象可以由我们自己命名,比如 event、evt、e。

对 ie678 有兼容性问题,ie678 不可自己对事件对象命名,只能使用window.event

代码:

div.onclick = function(event) {
    console.log(event);
}

兼容性处理:

div.onclick = function(e) {
    e = e || window.event;
    console.log(e);
}

常见事件对象的属性和方法

属性描述
e.target返回触发事件的对象 不兼容ie678
this返回的是绑定事件的对象
e.srcElement返回触发事件的对象 兼容ie678
e.currentTarget和this相似 但是不兼容ie678
e.type返回事件的类型,比如 click、mouseover 不带on
e.cancelBubbel阻止冒泡 ie678不兼容
e.returnValue阻止默认事件(默认行为)非标准 比如不让链接跳转 ie678不兼容
e.preventDefault()阻止默认事件(默认行为)标准
e.stopPropagation()阻止冒泡 标准

this 和 target

如果我们有一个 ul>li,给 ul 绑定一个点击事件。

那么当我们点击 li 的时候:

e.target 返回的是触发事件的对象,也就是 li,e.target 不兼容ie678

this 返回的是绑定事件的对象,也就是 ul

对于这两个方法我们还有两个额外的相似的方法:

e.srcElement 对应 e.target,但是前者兼容了 ie678

e.currentTarget 对应 this,但是前者不兼容ie678

阻止默认行为

有些标签默认就带有一些行为,比如 a 链接跳转等,我们可以使用如下方法阻止其默认行为。

阻止行为的方法有两种:

  • e.preventDefault():标准的阻止默认事件方法,推荐使用,但是不兼容ie678
  • e.returnValue:这是一个属性,兼容ie678。
  • return false:这种方法也可以阻止默认事件,而且没有兼容性问题。需要注意的是,用这种方法后,return之后的代码将不会执行。
a.onclick = function(e) {
    e.preventDefault();
    e.returnValue;
    return false;
}

阻止冒泡(重点)

我们已经知道 dom 事件流包括:捕获,目标,冒泡三个阶段。

事件开始冒泡时,事件将会由具体的目标元素向上逐级传递到dom最顶层节点。

阻止冒泡就是阻止事件向上传播。

阻止冒泡的方法有两种:

  • e.stopPropagation():标准阻止冒泡的方法,推荐使用,但是不兼容ie678
  • e.cancelBubbel:这是一个属性,用于阻止冒泡,兼容ie678
div.onclick = function(e) {
    e.stopPropagation();
    e.cancelBubbel = True;
}

事件委托

事件委托也称为事件代理,在jQuery里面也叫事件委派。

其思想就是,当一个父节点中的所有子节点都需要点击事件时,我们不是给每个子节点单独设置事件监听器,而是将事件监听器设置到父亲节点上,然后利用冒泡原理来影响每个子节点,这样点击子节点就可以触发父节点的监听器。

这样我们只需要操作一次DOM,提高了程序的性能。

<body>
    <ul>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
    <script>
        var ul = document.querySelector('ul');
        ul.addEventListener('click', function(e) {
            e.target.style.backgroundColor = 'pink';
        })
    </script>
</body>

常用的鼠标事件

禁止鼠标右键菜单(contextmenu)

contextmenu 主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单。

document.addEventListener('contextmenu', function(e) {
    e.preventDefault();
})

禁止鼠标选中(selectstart 开始选中)

document.addEventListener('selectstart', function(e) {
    e.preventDefault();
})

鼠标事件对象

event 对象代表了事件的状态,跟事件相关的一些列信息的集合。其中就包裹键盘事件信息和鼠标事件信息。鼠标事件对象是 MouseEvent;键盘事件对象是 KeyboardEvent。

鼠标事件对象描述
e.clientX返回鼠标相对于浏览器窗口可视区的 X 坐标
e.clientY返回鼠标相对于浏览器窗口可视区的 Y 坐标
e.pageX返回鼠标相对于文档页面的 X 坐标 IE9+支持
e.pageY返回鼠标相对于文档页面的 Y 坐标 IE9+支持
e.screenX返回鼠标相对于电脑屏幕的 X 坐标
e.screenY返回鼠标相对于电脑屏幕·的 Y 坐标

常用的键盘事件

键盘事件描述
onkeyup某个键盘按键被松开时触发
onkeydown某个键盘按键被按下时触发
onkeypress某个键盘按键被按下时触发 但是这个方法不识别功能键,比如 ctrl shift等

当三个事件同时存在时,执行顺序永远为:keydown --> keypress --> keyup

键盘事件对象

事件对象描述
key记录用户按下的键盘按键,除了ie678,还有些其他浏览器不兼容
keyCode记录用户按下键的 ASCLL 值

操作元素

JavaScript 的 DOM 操作可以改变网页内容、结构和样式,我们可以利用 DOM 操作元素来改变元素里面的内容、属性等。

改变元素内容

innerText

  • innerText 只获取元素中的文本内容
  • innerText 会删除空格和换行,只保留文本

​ 语法:

var div = document.querySelector('div');
div.innerText = 'brokyz'

innerHTML

  • innerHTML 会保留空格和换行
  • innerHTML 修改时支持 HTML 标签,在内容中添加标签将会被识别
  • 推荐使用 innerHTML
var div = document.querySelector('div');
div.innerHTML = '<strong>brokyz</strong>'

修改或添加属性内容

  • 当获取到元素时,我们对元素中的属性进行添加和更改。
  • 本方法对 html 中已经有定义的属性进行操作
  • 修改属性时会直接覆盖掉原来的属性值。

语法:

// 获取a标签
var a = document.querySelector('a');
// 更改或添加a的属性
a.href = 'https://bilibili.com';
a.id = 'bilibili';
// 修改类名属性值
a.className = 'iama';

表单元素的属性操作

利用 DOM 可以操作如下表单元素属性:

type、value、checked、selected、disabled

修改或添加CSS样式属性

  • 我们可以通过 JS 修改元素的大小、颜色、位置等 CSS 样式。
  • JS 里面的样式采用驼峰命名法,比如 fontSize、backgroundColor
  • JS 修改 style 样式操作,产生的是行内样式,修改后的 CSS 权重更高

语法:

var div = document.querySelector('div');
this.style.backgroundColor = 'antiquewhite';
this.style.width = '400px';

使用 className 修改元素类名的方式修改CSS样式属性

  • 使用 className 修改样式属性会直接覆盖掉原来的 class 值
<!DOCTYPE html>
<head>
    <style>
        .text {
            width: 400px;
            height: 400px;
            background-color: antiquewhite;
        }

        .change {
            background-color: aliceblue;
        }
    </style>
</head>

<body>
    <div class=”text“>文本</div>

    <script>
        var div = document.querySelector('div');
        div.onclick = function(){
            // 此方法会直接覆盖掉原来class属性中的text
            this.className = 'change';
            // 如果不覆盖原来的class属性,只是追加的话可以这样使用
            this.className += ' change';
        }
    </script>
</body>
</html>

对自定义属性值的操作

  • 在标签中,有些属性是内置的,比如 a标签的hreftarget等,这些都可以直接获取元素a,然后使用a.href对属性进行操作。
  • 在标签中,也有些属性是我们自定义的,比如我们自己给a标签定义一个index = '1',这时我们就不能使用a.index对自定义属性进行操作。
  • 以下我们将讲解对自定义属性的操作方法:

获取自定义属性值 getAttribute()

  • getAttribute()既可以获取自定义的属性值,也可以获取自带的属性值。

语法:

var div = document.querySelector('div');
// 获取内置属性值
console.log(div.getAttribute('id'));
// 获取自定义属性值
console.log(div.getAttribute('index'));

添加和修改自定义属性值

  • div.setAttribute('自定义属性','要修改的值');既可以修改自定义的属性值,也可以修改自带的属性值。
  • 但是用div.setAttribute('自定义属性','要修改的值')修改元素的类名时,只需要指定class即可,不需要自带方法那样使用className

语法:

var div = document.querySelector('div');
div.setAttribute('index','2');
div.setAttribute('class','box');
  • 当使用setAttribute设置 class 时就是使用 class 而不是使用 className

移除自定义属性值

  • div.removeAttribute()既可以删除自定义的属性值,也可以删除自带的属性值。

语法:

div.removeAttribute('index');

H5自定义属性新规范

由于自定义属性,无法使用自带的方法div.index调用,所以html5新增了对自定义属性的调用支持

  • h5标准要求在自定义属性前加data-,当调用的时候使用dataset调用
  • 当自定义属性中有多个 - 连接时,调用时需要使用驼峰命名法调用
  • 此方法存在兼容性问题,使用 dataset 获取时,仅对ie11+支持

语法:

<body> 
    <div data-listname="123" data-list-name="456"></div>
    <script>
        var div = document.querySelector('div');
        console.log(div.dataset.listname);
        console.log(div.dataset.listName);
    </script>
</body>

节点操作

节点操作可以利用层级关系获取元素,为我们开发提供了一种更加方便的获取元素的方法。

节点概述

网页中的所有内容都是节点(标签、属性、文本、注释等),在 DOM 中,节点使用 node 来表示。

HTML DOM树中的所有节点均可以通过 JavaScript 进行访问,所有 HTML 元素(节点)均可被修改,也可以创建或删除。

一般地,节点至少拥有node Type(节点类型)、nodeName(节点名称)和 nodeValue(节点值)这三个基本属性。

  • 元素节点 nodeType 为 1
  • 属性节点 nodeType 为 2
  • 文本节点 nodeType 为 3 (文本节点包含文字、空格、换行等)

实际开发中,节点操作主要操作的是元素节点

节点的使用

代码使用频率描述
ul.parentNode常用获取父亲节点,如果找不到返回空
ul.childNodes不常用得到所有子节点,包含 元素节点 文本节点等等
ul.children常用获取所有的子元素节点 实际开发常用
ul.firstChild不常用获取第一个子节点,不管是文本节点和元素节点都可以被拿到
ul.lastChild不常用获取最后一个子节点,不管是文本节点和元素节点都可以被拿到
ul.firstElementChild常用获取第一个元素节点,仅支持ie9+
ul.lastElementChild常用获取最后一个元素节点,仅支持ie9+
li.nextSibling不常用返回下一个兄弟节点 包含 元素节点 文本节点等等
ul.nextElementSibling常用返回下一个元素兄弟节点,仅支持ie9+
ul.previousSibling不常用返回上一个兄弟节点 包含 元素节点 文本节点等等
ul.previousElementSibling常用返回上一个元素兄弟节点,仅支持ie9+
        // 1.父节点 parentNode
        var erweima = document.querySelector('.erweima');
        console.log(erweima.parentNode); // 如果找不到父节点就返回为空

        // 2.子节点 childNodes (集合) 得到所有子节点,包含 元素节点 文本节点等等
        var ul = document.querySelector('ul');
        console.log(ul.childNodes);
        // 注意:返回值里面包含了所有的子节点
        // 如果只想获得里面的元素节点,则需要专门处理。所以我们一般不提倡使用childNodes
        for (let i = 0; i < ul.childNodes.length; i++) {
            const element = ul.childNodes[i];
            if (element.nodeType == 1){
                console.log(element);
            }
        }

        // children 获取所有的子元素节点 实际开发常用
        console.log(ul.children);

        // firstchild 获取第一个子节点,不管是文本节点和元素节点都可以被拿到
        console.log(ul.firstChild);
        // lastchild 获取最后一个子节点,不管是文本节点和元素节点都可以被拿到
        console.log(ul.lastChild);

        // firstElementChild 获取第一个元素节点
        console.log(ul.firstElementChild);
        // lastElementChild 获取最后一个元素节点
        console.log(ul.lastElementChild);
        // 注意:这几个返回第一个和最后一个存在兼容性问题,不支持ie9
        // 实际开发中,兼顾兼容性
        console.log(ul.children[0]);

        // 3.兄弟节点
        // nextSibling 返回下一个兄弟节点 包含 元素节点 文本节点等等
        console.log(ul.nextSibling);
        // nextElement 返回下一个元素兄弟节点
        console.log(ul.nextElementSibling);
        // prevousSibling 返回上一个兄弟节点 包含 元素节点 文本节点等等
        console.log(ul.previousSibling);
        // prevousElementSibling 返回上一个兄弟节点 包含 元素节点 文本节点等等
        console.log(ul.previousElementSibling);

创建节点并添加节点

代码描述
ul.appendChild()在孩子的最后面添加元素
ul.insertBefore(要添加的元素,指定的孩子元素)在指定孩子元素的前面添加要添加的元素

语法:

var li = document.createElement('li');
var ul = document.querySelector('ul');
// 在ul的孩子后面追加元素
ul.appendChild(li);
// 在ul的指定孩子前面插入元素
ul.insertBefore(li,ul.children[0])

删除节点

var ul = document.querySelector('ul');
// 删除父节点里面的孩子
ul.removeChild(ul.children[0]); // 删除ul中的第一个孩子

复制节点

var ul = document.querySelector('ul');
//复制节点
var cloned = ul.children[0].cloneNode(true); //括号为空或者false,是浅拷贝, 则只复制标签不复制内容,如果为true,复制内容,深拷贝
ul.appendChild(cloned);

三种创建元素方式的区别

  • document.write()

    • document.write('<div>123<div>')==文档执行完毕,他会导致页面全部重绘==
  • innerHTML = ‘ ’

    • div.innerHTML += '<div>123<div>'使用拼接的方式创建多个元素时,耗时非常大
    • 当将要创建的元素插入数组中,再通过innerHTML插入时,效率大大提成,为最优
  • document.createElement()

    • 使用这种方法创建,并使用节点操作添加到页面中时,方法效率高,但是没有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>三种创建元素的区别</title>
</head>

<body>
    <button>文档流执行完毕,测试document.write</button>
    <button>测试innerHTML执行效率</button>
    <button>测试innerHTML结合数组执行效率</button>
    <button>测试creatElement执行效率</button>
    <div class="test"></div>
    <script>
        // document.write 当所有文档流执行完毕时,执行这段代码会导致页面重绘
        var btn = document.querySelectorAll('button');
        btn[0].onclick = function () {
            document.write('<div>当所有文档流执行完毕时,执行这段代码会导致页面重绘 document.write</div>');
        }


        // 测试innerHTML执行效率
        var test = document.querySelector('.test');
        btn[1].onclick = function () {
            if (test.children[0]) {
                test.removeChild(test.children[0]);
            }
            var div = document.createElement('div');
            var d1 = +new Date();
            for (var i = 0; i < 2000; i++) {
                div.innerHTML += "<div style='width:100px;border:1px solid blue;'></div>"
            }
            var d2 = +new Date();
            console.log(d2 - d1);
            test.appendChild(div);

        }

        // 测试innerHTML加数组效率
        btn[2].onclick = function () {
            if (test.children[0]) {
                test.removeChild(test.children[0]);
            }

            var div = document.createElement('div');
            var d1 = +new Date();
            var arr = [];
            for (var i = 0; i < 2000; i++) {
                arr.push("<div style='width:100px;border:1px solid blue;'></div>");
            }
            div.innerHTML = arr.join('');
            test.appendChild(div);
            var d2 = +new Date();
            console.log(d2 - d1);


        }

        // creatElement效率测试
        btn[3].onclick = function () {
            if (test.children[0]) {
                test.removeChild(test.children[0]);
            }
            var div = document.createElement('div');
            var d1 = +new Date();
            // 注意创建要添加的元素一定要写在循环内,因为如果写在外面,只创建一次,append添加之后就没有了。
            for (var i = 0; i < 2000; i++) {
                var divs = document.createElement('div');
                divs.style.border = '1px solid blue';
                divs.style.width = '100px';
                div.appendChild(divs);
            }
            var d2 = +new Date();
            test.appendChild(div)
            console.log(d2 - d1);
        }
    </script>
</body>
</html>

DOM 重点核心(复习)

DOM 就是文档对象模型(Document Object Model),是w3c组织推荐的处理可拓展标记语言(HTML或者XML)的标准编程接口。

W3C已经定义了一系列的 DOM 接口,通过这些接口 DOM 接口可以改变网页的内容、结构和样式。

  1. 对于 JavaScript,为了能够使 JavaScript 操作 HTML,JavaScript就有了一套自己的 DOM 编程接口。
  2. 对于 HTML,DOM 使得 HTML 形成了一棵 DOM 树。其中包含了文档(整个页面)、元素(页面中所有的标签)、节点(页面中所有的内容,文档是节点,元素是节点,属性也是节点)。
  3. 我们获取来的 DOM 元素是一个对象(object),所以称为文档对象模型。

关于 DOM 操作,我们主要针对于元素的操作。主要有创建、增、删、改、查、属性操作、事件操作。

创建

  1. document.write
  2. innerHTML
  3. createElement

  1. appendChild
  2. insertBefore

  1. removeChild

主要是修改 DOM 元素的属性、内容、表单值等

  1. 修改元素属性:src、href、title等
  2. 修改普通元素内容:innerHTML、innerText
  3. 修改表单元素:value、type、disable等
  4. 修改元素样式:style、className

主要获取查询 DOM 元素

  1. DOM 提供的 API 方法:getElementById、getElementByTagName 古老用法,不推荐
  2. H5 提供的新方法:querySelector、querySelectorAll 推荐
  3. 利用节点操作获取元素:父(parentNode)、子(children)、兄(previousElementSibling、nextElementSibling)推荐

属性操作

主要针对于自定义属性

  1. setAttribute:设置dom的属性值
  2. getAttribute:得到dom的属性值
  3. removeAttribute:移除属性

事件操作

给元素注册事件、采取事件源.事件类型 = 事件处理程序

  1. onclick:鼠标点击左键触发
  2. onmouseover:鼠标经过触发
  3. onmouseout:鼠标离开触发
  4. onfocus:获得鼠标焦点触发
  5. onblur:失去鼠标焦点触发
  6. onmousemove:鼠标移动触发
  7. onmouseup:鼠标弹起触发
  8. onmousedown:鼠标按下触发

BOM 操作

什么是 BOM

BOM (Browser Object Model)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是 window。

BOM 由一系列相关的对象构成,并且每个对象都提供了很多方法与属性。

BOM 缺乏标准,JavaScript 语法的标准化组织是 ECMA,DOM 的标准化组织是 W3C,BOM 最初是 Netspace 浏览器标准的一部分。

DOMBOM
文档对象模型浏览器对象模型
DOM 就是把 [文档] 当作一个 [对象] 来看待把 [浏览器] 当做一个 [对象] 来看待
DOM 的顶级对象是 documentBOM 的顶级对象是 window
DOM 主要学习的是操作页面元素BOM 学习的是浏览器窗口交互的一些对象
DOM 是 W3C 标准规范DOM 是浏览器厂商在各自浏览器上定义的,兼容性比较差

BOM 的构成

window 对象是浏览器的顶级对象,它具有双重角色。

  1. 它是 JS 访问浏览器的一个接口。
  2. 它是一个全局对象。定义在全局作用域中的变量、函数都会变成 window 对象的属性和方法。

在调用的时候可以省略 window,前面学习的对话框都属于 window 对象方法,如 alert()、prompt()等。

注意:window下的一个特殊属性 window.name

页面加载事件

window.onload = function() {}
或者
window.addEventListener("load",function(){})

window.onload 是窗口(页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS文件等)才调用处理函数。

注意:

  1. 有了 window.onload 就可以把 JS 代码写到页面元素的上方,因为 onload 是等页面内容全部加载完毕,再去执行处理函数。
  2. window.onload 传统注册事件方式只能写一次,如果有多个,会以最后一个 window.onload 为准。
  3. 如果使用 addEventListener 则没有限制,写多少个都行。
document.addEventListener("DOMcontentLoaded",function(){})

DOMContentLoaded 事件触发时,仅当 DOM 加载完成,不包括样式表,图片,flash等等。

ie9以上才支持。

如果页面有很多图图片,从用户访问到 onload 触发可能需要较长的时间,交互效果就不能实现,必然影响用户体验,此时用DOMContentLoaded 事件比较合适。

调整窗口大小事件

window.onresize = function(){}
window.addEventListener("resize",function(){})

window.onresize 是调整窗口大小加载事件,当触发时就调用的处理函数。

注意:

  1. 只要窗口大小发生像素变化,就会触发这个事件。
  2. 我们经常利用这个事件完成响应式布局。window.innerWidth 当前屏幕的宽度。
// 浏览器窗口的当前宽度
window.innerWidth

定时器

window 对象给我们提供了 2 种非常好的方法 - 定时器

setTimeout()

window.setTimeout(调用函数, [延迟的毫秒数])
// 在调用的时候 window 可以省略
setTimeout(function() {
    console.log('三秒过去了');
}, 3000)
// 延迟时间单位是毫秒,如果不写默认为0
function callback(){
    console.log('三秒过去了');
}
setTimeout(callback, 3000);
setTimeout('callback()', 3000); // 不提倡这种写法
// 页面中可能有多个定时器,我们经常给定时器加标识符(名字)
var timer1 = setTimeout(callback, 3000);
var timer2 = setTimeout(callback, 5000);

setTimeout() 方法用于设置一个定时器,该定时器在定时器到期后执行调用函数。

注意:

  1. window 可以省略。
  2. 这个调用函数可以直接写函数,或者写函数名或者采取字符串 ’函数名()‘三种形式。
  3. 延迟的毫秒数省略默认是0,如果写,必须是毫秒。
  4. 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。

setTimeout() 这个函数我们也叫做回调函数 callback

普通函数是按照代码顺序直接调用。

而这个函数,需要等待时间,时间到了才去调用这个函数,因此称为回调函数。

简单理解,回调,就是回头调用。上一件事情干完,再回头去调用这个函数。

之前我们学的 element.onclick = function() {} 或者 element.addEventListener("click",fn) 里面的函数也是回调函数。

停止 setTimeout() 定时器

window.clearTimeout(timeoutID)
// 可以省略window
clearTimeout()

setInterval() 定时器

window.setInterval(回调函数, 间隔毫秒数)

setInterval() 方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。

注意:

  1. window 可以省略。
  2. 这个调用函数可以直接写函数,或者写函数名或者采取字符串 ’函数名()‘三种形式。
  3. 延迟的毫秒数省略默认是0,如果写,必须是毫秒。
  4. 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。

停止 clearInterval() 定时器

window.clearInterval(timeoutID)
// 可以省略window
clearInterval()

this

在我们使用停止定时器时,不能在定时器中使用 this 停止定时器。

因为定时器的时 window 对象的函数,this 指向当前对象,所以指向的是 windows。

所以 this 指向的是调用函数的对象

JS 执行机制

JS 老版本是单线程的

JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这是因为 JavaScript 这门脚本语言的诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作 DOM 而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。应该先进行添加,之后再删除。

比如以下代码:

console.log(1);
setTimeout(function(){
    console.log(3);
},1000);
console.log(2);

这段代码需要等定时器执行完,才会向下执行代码,如果定时器设置时间较长,那么就会等待很久。

现在新版 Js 支持同步和异步

为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许JavaScript 脚本创建多个线程。于是,JS中出现了同步异步

同步

前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。

异步

在做一件事情时,因为这件事情需要花费很长时间,在做这件事情的同时,你还可以去处理其他的事情。比如做饭的异步做法,在我们烧水的同时,利用这10分钟,去切菜,炒菜。

同步和异步的本质区别:这条流水线上各个流程的执行顺序不同

JS 执行机制的本质

有如下代码:

console.log(1);
setTimeout(function(){
    console.log(3);
},0);
console.log(2);

以上代码的输出结果为 1,2,3。

在 JS 中有两种任务:

  1. 同步任务:所有的同步任务都在主线程上执行,形成一个执行栈

  2. 异步任务:异步任务是通过回调函数来实现的,在执行栈执行同步任务时,其中的回调函数会被放到任务队列(消息队列)里面等待主线程执行结束再执行。

    一般来说异步任务有如下三种类型:

    • 普通事件,如 click、resize等
    • 资源加载,如 load、error等
    • 定时器,包括 setInterval、setTimeout等
主线程执行栈任务队列(消息队列)
console.log(1);function(){ console.log(3); }
setTimeout(fn, 0);
console.log(2);

当执行栈执行到setTimeout(fn, 0);时,定时器本身是同步任务,但是定时器里面的回调函数是异步任务,被放到任务队列中,等待执行栈执行完毕,再去执行任务队列里面的任务。

在执行栈和任务队列之间,又有一个异步进程处理的步骤,这一步骤将决定,回调函数在何时被加入任务队列。

以如下代码举例:

console.log(1);
document.onclick = function() {
    console.log('click');
}
console.log(2);
setTimeout(function() {
    console.log(3);
},3000)
  1. 首先执行栈会按照顺序执行同步任务。
  2. 当执行到包含异步任务的同步任务时,这个同步任务就会被提交到异步进程中进行处理。
  3. 在异步进程中,其中待处理的任务只有满足了条件时,才会被放到任务队列中。比如点击事件,只有被点击时,其中的异步进程才会被送到任务队列。
  4. 当执行栈中的同步任务运行完毕,执行栈会将任务队列中的异步任务按顺序拿到执行栈进行执行。
  5. 然后运行完成后,执行栈并没有停止,它还会不断地去获得任务,执行任务,这种机制叫做循环机制。比如这时我们再次点击页面,异步进程会再次将异步任务放到任务队列,由于执行栈会重复获取任务,执行栈又会将任务队列中的任务拿到执行栈进行执行。

重点:由于主线程不断的重复获取任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环

location 对象

window 对象给我们提供了一个 location 属性,用于获取设置窗体的 url,并且可以用于解析 url。因为这个属性返回的是一个对象,所以我们将这个属性也成为 location 对象。

URL

统一资源定位符(Uniform Resource Locator,URL)是互联网上标准资源的地址。互联网上每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

URL 的一般格式为:

protocol://host[:port]/path/[?query]#fragment
http://www.brokyz.tk/index.html?name=andy&age=18#link
组成说明
protocol通信协议 常用的 http、ftp、maito等
host主机(域名)
port端口号 可选,省略时使用方案默认端口,如http默认为80
path路径 有 零或多个/符号隔开的字符串,一般用来表示主机上的一个目录或文件地址
query参数 以键值对的形式,通过&符号分隔
fragment片段 #后面内容 常见于链接 锚点

location 对象的属性

属性返回值
location.href获取或者设置 整个 URL,如果不打印设置url会实现页面跳转
location.host返回主机(域名)
location.port返回端口号 如果未写则返回空字符串
location.pathname返回路径
location.search返回参数
location.hash返回片段 #后面内容 常用于链接 锚点
// 5秒后页面跳转
var t = 5;
setTimeout(function() {
    if (t == 0){
        location.href = 'https://baidu.com';
    }else{
        t--;
    }
})

获取 URL 参数

<body>
    <form action="获取URL参数.html">
        <input type="text" name="uname">
        <input type="submit" value="提交">
    </form>
    <div></div>
    <script>
        console.log(location.search)
        var params = location.search.substring(1);
        console.log(params);
        var arr = params.split('=');
        console.log(arr);
        var div = document.querySelector('div');
        if(arr[1]){
            div.innerHTML = '这里是' + arr[1];
        }
    </script>
</body>
  • substring(1):输入第1位之后的字符
  • split('='):通过=拆分字符,拆分返回拆分后的数组

location 常用方法

location方法返回值
location.assign(url)跟 href 一样,可以跳转页面(也称为重定向页面)
location.replace(url)跳转替换当前页面,因为不记录历史,所以不能后退页面
location.reload()重新加载页面,相当于刷新按钮或者 f5 如果参数为true 强制刷新 ctrl+f5(无视浏览器缓存)

navigator 对象

navgator 包含了有关浏览器的相关信息,他有很多属性,我们最常用的时 userAgent,该属性可以返回由客户机发送服务器的 user-agent 头部的值。

下面前端代码可以判断用户哪个终端打开页面,实现跳转。

//
if ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
      window.location.href = "../H5/index.html"; //手机
}

history 对象

window 对象给我们提供了一个 hostory 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的 URL。

history对象方法作用
history.back()可以后退功能
history.forward()前进功能
history.go(参数)前进后退功能 参数如果是1 前进1 个页面,2是前进2,如果时-1 后退1 个页面

元素偏移量 offset 系列

offset 翻译过来就是偏移量,我们使用 offset 系列相关属性可以动态的得到该原告诉的位置(偏移)、大小等。

  • 获得元素距离带有定位父元素的位置
  • 获得元素自身的大小(宽度高度)
  • 注意:返回的数值都不带单位

offset 系列常用属性:

offset系列属性作用
element.offsetParent返回作为该元素带有定位的父级元素 如果父级没有定位则返回body
element.offsetTop返回元素相对带有定位父元素上方的偏移
emement.offsetLeft返回元素相对带有定位父元素左边框的偏移
element.offsetWidth返回自身包括padding、边框、内容区的宽度,返回数值不带单位
element.offsetHeight返回自身包括padding、边框、内容区的高度、返回数值不带单位

offset 和 style 的区别

offsetstyle
offset 可以得到任意样式表中的样式值style 只能获得行内样式表中的样式值
offset 系列获得的数值是没有单位的style.width 获得的是带有单位的字符串
offsetWidth 包含 padding、border、widthstyle.width 获得不包含 padding、border 的值
offsetWidth 等属性是只读属性,只能获取不能赋值style.width 是可读写属性,可以获取也可以赋值
所以,我们想要获取元素大小位置,用offset更合适所以,我们想要给元素赋值,用style更合适

获取鼠标在盒子内的距离

我们可以通过事件对象的属性e.pageXe.pageY来获取我们鼠标在浏览器中的坐标。

然后,我们通过得到的鼠标距离浏览器的距离,减去,盒子距离浏览器的距离,就是我们鼠标在盒子内的距离。

元素可视区 client 系列

client 翻译是客户端的意思,我们可以使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到该元素的边框大小、元素大小等。

client 系列属性作用
element.clientTop返回元素上边框的大小
element.clientLeft返回元素左边框的大小
element.Width返回自身包括padding、内容区的宽度,不含边框,返回数值不带单位
element.clientHeight返回自身包括padding、内容区的高度,不含边框,返回数值不带单位

立即执行函数

立即执行函数不需要调用,立马能够自己执行。

主要作用:创建一个独立的作用域,里面所有的变量都是局部变量,避免了命名冲突的问题。

(function() {})()
// 或者
(function(){}())

案例:淘宝flexible.js

元素滚动 scroll 系列

使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。

scroll 属性作用
element.scrollTop返回被卷上去的上侧距离,返回数值不带单位
element.scrollLeft返回被卷上去的左侧距离,返回数值不带单位
element.scrollWidth返回自身实际宽度,不含边框,但是包含padding,返回数值不带单位
element.scrollHeight返回自身实际高度,不含边框,但是包含padding,返回数值不带单位
window.pageYOffset获得页面被卷去的头部距离,不支持ie9以下,修改此值不会影响页面
window.pageXOffset获得页面被卷去的左侧距离,不支持ie9以下,修改此值不会影响页面

当盒子内内容超出后,scroll返回的是超出之后的大小,而之前的client返回的则是固定指定的大小。

页面被卷去头部兼容性解决方案

被卷去的头部,存在兼容性问题:

  1. 声明了 DTD,使用 document.documentElement.scrollTop

  2. 未声明 DTD,使用document.body.scrollTop

  3. 新方法 window.pageYOffsetwindow.pageXOffset,IE9 才开始支持,修改这个值,并不会影响页面位置。

兼容性函数:

function getScroll() {
    return {
        left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
        top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0,
    };
}
getScroll().left;
getScroll().top;

三大系列总结

三大系列对比作用
element.offsetWidth返回自身包括padding、边框、内容区的宽度,返回值不带px
element.clientWidth返回自身包括padding、内容区域的宽度,不含边框,返回值不带px
element.scrollWidth返回自身的实际宽度(比如字太多被滚动撑开),不含边框,返回数值不带单位

主要用法:

  1. offset系列经常用于获得元素位置:offsetTop、offsetLeft
  2. client系列经常用于获得元素大小:clientWidth、clientHeight
  3. scroll系列经常用于获取滚动距离:scrollTop、scrollLeft

mouseover 和 mouseenter 区别

mouseenter 鼠标事件

  • 当鼠标移动到元素上时就会触发 mouseenter 事件
  • 类似 mouseover,他们两者之间的差别是
  • mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发。mouseenter 只会经过自身盒子触发
  • 出现这个现象的原因是因为,mouseover会冒泡,mouseenter不会冒泡

动画函数封装

核心原理:通过定时器 setInterval() 不断移动盒子的位置

实现步骤

  1. 获得盒子当前的位置
  2. 让盒子在当前位置加上1个距离
  3. 利用定时器不断重复这个操作
  4. 添加结束定时器的条件
  5. 注意此元素需要添加定位,才能使用element.style.left
        // 1. 获得盒子当前位置
        var div = document.querySelector('div');
        // 2. 让盒子在当前位置上加上1个距离
        div.style.left = div.offsetLeft + 1 + 'px';
        // 3. 用定时器不断的重复这个操作
        var timer = setInterval(function () {
            if (div.offsetLeft >= 400) {
                // 4. 停止动画的本质是停止定时器
                clearInterval(timer);
            }
            div.style.left = div.offsetLeft + 1 + 'px';
        }, 1)

动画函数的封装

        function animate(obj, target) {
            var timer = setInterval(function () {
                if (obj.offsetLeft >= target) {
                    clearInterval(timer);
                }
                obj.style.left = obj.offsetLeft + 1 + 'px';
            }, 1)
        }

        animate(div,300);

动画封装函数优化

问题:

  1. 每一个对象调用函数都会声明var timer开辟一个内存空间,这样调用对象一多影响性能
  2. 每一个对象调用函数时,函数里面的定时器都叫timer,可能会引起歧义
  3. 当同一个对象多次调用定时器时,动画会加快,因为定时器同时运行出现叠加

解决办法:

  • 将定时器看成当前对象的属性obj.timer,这样就避免声明新对象,也避免了定时器名字相同引起的歧义。
  • 在调用函数的时候,首先清除一下定时器,这时候在运行代码,就保证了一直是一个定时器运行。
        function animate(obj, target) {
            clearInterval(obj.timer);
            obj.timer = setInterval(function () {
                if (obj.offsetLeft >= target) {
                    clearInterval(obj.timer);
                }
                obj.style.left = obj.offsetLeft + 1 + 'px';
            }, 1)
        }
        animate(div,300);

缓动动画

缓动动画就是让元素运动速度有所变化,最常见的就是让速度慢慢停下来。

原理:

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
  2. 核心算法:(目标值 - 现在的位置)/ 10 作为每次的移动距离。
  3. 停止的条件是:让当前的盒子位置等于目标的位置就停止计时器。
function animateAvg(obj, target, callback) {
            clearInterval(obj.timer);
            obj.timer = setInterval(function () {
                // 把步长值改为整数,来防止小数计算,使得目标到不了指定位置而无法停止
                // 当step是正数向上取整(取大),当是负数向下取整(取小)   
                var step = (target - obj.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                if (obj.offsetLeft == target) {
                    clearInterval(obj.timer);
                    // 回调函数写到定时器结束的时候调用
                    if (callback) {
                        callback();
                    }
                }
                obj.style.left = obj.offsetLeft + step + 'px';
                // console.log('stop');
            }, 20)
        }

轮播图

demo

HTML部分

  • slideshow: 整个轮播图的框架

  • prev: 上一页按钮

  • next: 下一页按钮

  • promo: 轮播图促销图片

  • circle: 促销图片对应的按钮

  • promo_now: 当前促销图片对应的按钮

<body>
    <div class="slideshow">
        <a src="" alt="" class="prev iconfont"><</a>
        <a src="" alt="" class="next iconfont">></a>
        <ul class="promo">
            <li>
                <img src="../../res/taobao-promo.png" alt="" class="promo_img" />
            </li>
            <li>
                <img src="../../res/taobao-promo1.png" alt="" class="promo_img" />
            </li>
            <li>
                <img src="../../res/taobao-promo2.png" alt="" class="promo_img" />
            </li>
            <li>
                <img src="../../res/taobao-promo3.png" alt="" class="promo_img" />
            </li>
        </ul>
        <ol class="circle">
        </ol>
    </div>
</body>

CSS3部分

  • 注意: 后续需要添加动画的元素, 需要加上绝对定位.
<style>
    * {
        margin: 0;
        padding: 0;
    }

    li {
        list-style: none;
    }

    a {
        text-decoration: none;
    }

    .slideshow {
        position: relative;
        width: 600px;
        height: 300px;
        margin: 200px auto;
        background-color: aliceblue;
        border-radius: 10px;
        overflow: hidden;
    }

    .prev,
    .next {
        display: none;
        position: absolute;
        top: 50%;
        width: 20px;
        height: 30px;
        margin-top: -15px;
        display: inline-block;
        color: white;
        background-color: rgba(0, 0, 0, .3);
        text-align: center;
        line-height: 30px;
        z-index: 2;
    }

    .prev:hover,
    .next:hover {
        cursor: pointer;
    }

    .prev {
        left: 0;
        border-radius: 0 15px 15px 0;
    }

    .next {
        right: 0;
        border-radius: 15px 0 0 15px;
    }

    .promo {
        position: absolute;
    }

    .promo li {
        float: left;
    }

    .promo li .promo_img {
        width: 600px;
        height: 300px;
        cursor: pointer;
    }

    .circle {
        position: absolute;
        right: 5%;
        bottom: 5px;
        height: 15px;
        /* width: 50px; */
        /* background-color: rgba(255, 255, 255, .7); */
        border-radius: 10px;
    }

    .circle li {
        float: left;
        margin-top: 2px;
        margin-left: 5px;
        width: 10px;
        height: 10px;
        background-color: rgba(255, 255, 255, 1);
        border-radius: 10px;
    }

    .circle li:hover {
        background-color: coral;
        cursor: pointer;
    }

    .circle .promo_now {
        background-color: coral;
    }
</style>

JS部分

  1. 获取所需要操作的元素.

    • slideshow: 轮播图整体框架.

    • prev: 轮播图上一页按钮.

    • next: 轮播图下一页按钮.

    • promo: 轮播图促销海报主体.

    • circle: 轮播图促销海报对应的按钮.

    • imgWidth: 轮播图一张促销海报的宽度.

  2. 鼠标经过轮播图显示上一页和下一页的图标, 鼠标离开轮播图隐藏上一页和下一页的图标.

    • 给轮播图整体框架添加鼠标进入和离开事件.
  3. 动态设定轮播图促销海报主体的宽度.

    • 由于后面为了优化第一张和最后一张的过渡动画, 所以我们这里动态调整的时候应该比length多一张.

    • 促销海报的总数 * 促销海报单张的宽度 = 轮播图促销海报主体的宽度

  4. 动态生成轮播图对应的显示按钮.

    • 通过循环遍历, 动态生成按钮. 这样按钮数和促销海报数就可一一对应.

    • 创建按钮的时候, 给每个按钮添加上点击事件, 让点击哪个按钮哪个按钮就以类名标记为当前按钮promo_now. 在添加前要使用排他思想, 清除所有按钮的promo_now标记.

  5. 复制在轮播图中复制并尾插入轮播图的第一张图片,为了优化第一页和最后一页之间的切换动画.

    • 克隆促销海报的第一张海报, 将其复制到所有海报的最后.

    • 这样一来, 最后一张切换到第一张时, 我们可以先让其滚动到我们克隆的最后一张图片上. 等动画结束, 我们让轮播图迅速切换到第一张图片位置. 这样人眼无法发觉, 利用了一个小障眼法, 优化了动画效果.

    • 第一张切换到最后一张同理, 先让其无动画迅速切换到最后一个克隆的轮播图位置. 之后再向前翻页, 切换到实际的最后一张海报. 优化动画效果.

  6. 实现轮播图下一页的逻辑.

    • 获取当前按钮promo_now, 以及其编号index, 以此来判断当前位于第几个轮播图.

    • 计算下一张轮播图所需要的移动坐标target.

    • 需要获取的信息完毕后, 清除当前轮播图的标记promo_now.

    • 使用封装好的动画函数移动轮播图, 当轮播图移动到最后一张时候, 让其施展障眼法瞬间移动到第一张.

    • 给移动到的下一张轮播图添加当前轮播图标记promo_now.

  7. 实现轮播图上一页的逻辑.

    • 获取当前按钮promo_now, 以及其编号index, 以此来判断当前位于第几个轮播图.

    • 点击后判断, 当为第一张轮播图时, 施展障眼法瞬间移动到最后一张轮播图.

    • 计算上一张轮播图所需要的移动坐标target.

    • 需要获取的信息完毕后, 清除当前轮播图的标记promo_now.

    • 使用封装好的动画函数移动轮播图.

    • 给移动到的上一张轮播图添加当前轮播图标记promo_now.

  8. 播图点击按钮移动到相对应的轮播图。

    • 获取当前点击按钮的index.

    • 计算移动到当前点击按钮位置轮播图所需要移动到的位置target.

    • 通过动画函数, 移动到计算好的位置.

  9. 实现轮播图定时自动切换下一页.

    • 设置定时函数, 指定时间间隔调用以此, 下一页按钮.

    • 当鼠标进入轮播图时, 定时器.

    • 当鼠标离开时重新加载定时器.

<script>
    // 1. 获取所需要操作的元素。
    var slideshow = document.querySelector(".slideshow");
    var prev = document.querySelector(".prev");
    var next = document.querySelector(".next");
    var ul = slideshow.querySelector(".promo");
    var ol = slideshow.querySelector(".circle");
    var imgWidth = slideshow.offsetWidth;
    // 2. 鼠标经过轮播图显示上一页下一页
    slideshow.addEventListener("mouseenter", function () {
        prev.style.display = "block";
        next.style.display = "block";
        // 9. 实现轮播图定时自动切换下一页。
        clearInterval(promo_auto);
    })
    // 2. 鼠标离开轮播图隐藏上一页下一页
    slideshow.addEventListener("mouseleave", function () {
        prev.style.display = "none";
        next.style.display = "none";
        // 9. 实现轮播图定时自动切换下一页。
        promo_auto = setInterval(function () {
            next.click();
        }, 3000);
    })
    // 3. 动态设定轮播图本质主体的宽度。
    ul.style.width = (ul.children.length + 1) * imgWidth + "px";

    // 4. 动态生成轮播图对应的显示按钮。
    for (var i = 0; i < ul.children.length; i++) {
        var li = document.createElement("li");
        li.setAttribute("index", i);
        ol.appendChild(li);
        li.addEventListener("click", function () {
            for (var i = 0; i < ol.children.length; i++) {
                ol.children[i].className = "";
            }
            this.className = "promo_now";
            // 8. 实现轮播图点击按钮移动到相对应的轮播图。
            var promo_index = this.getAttribute("index");
            target = promo_index * imgWidth;
            animate(ul, -target);
        })
    }
    // 设置第一个按钮为当前按钮
    ol.children[0].className = "promo_now";
    // 5. 复制在轮播图中复制并尾插入轮播图的第一张图片,为了优化第一页和最后一页之间的切换动画。
    var ul0 = ul.children[0].cloneNode(true);
    ul.appendChild(ul0);
    // 6. 实现轮播图下一页的逻辑。
    next.addEventListener("click", function () {
        var promo_now = document.querySelector(".promo_now");
        var promo_index = promo_now.getAttribute("index");
        target = (promo_index - 0 + 1) * imgWidth;
        promo_now.className = "";
        animate(ul, -target, function () {
            if ((promo_index - 0 + 1) == ol.children.length) {
                ul.style.left = "0px";
            }
        });
        if ((promo_index - 0 + 1) == ol.children.length) {
            ol.children[0].className = "promo_now";
        } else {
            ol.children[promo_index - 0 + 1].className = "promo_now";
        }
    })
    // 7. 实现轮播图上一页的逻辑。
    prev.addEventListener("click", function () {
        var promo_now = document.querySelector(".promo_now");
        var promo_index = promo_now.getAttribute("index");
        if (promo_index == 0) {
            ul.style.left = -(ul.children.length - 1) * imgWidth + "px";
            promo_index = (ul.children.length - 1);
        }
        target = (promo_index - 1) * imgWidth;
        promo_now.className = "";
        animate(ul, -target);
        console.log("target:" + target);
        console.log("promo_index:" + (promo_index - 1));
        ol.children[promo_index - 1].className = "promo_now";
    })

    // 9. 实现轮播图定时自动切换下一页。
    var promo_auto = setInterval(function () {
        next.click();
    }, 3000);

</script>
function animate(obj, target, callback) {
    clearInterval(obj.timer);
    obj.timer = setInterval(function () {
        // 把步长值改为整数,来防止小数计算,使得目标到不了指定位置而无法停止
        // 当step是正数向上取整(取大),当是负数向下取整(取小)   
        var step = (target - obj.offsetLeft) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if (obj.offsetLeft == target) {
            clearInterval(obj.timer);
            // 回调函数写到定时器结束的时候调用
            // if (callback) {
            //     callback();
            // }
            // 高级写法
            callback && callback();
        }
        obj.style.left = obj.offsetLeft + step + 'px';
        // console.log('stop');
    }, 10)
}