DayNote(JS2-WebAPI)

374 阅读32分钟

基础概念

  • 浏览器读取文件内容,不会立即解析绘制界面,是先把标签解析为成对象,构造一颗 dom 树,再按照dom树+css呈现树,绘制界面。(只要dom树中的元素对象发现改变,就会立即重绘页面)。
  • Dom对象是根据html代码中的标签创建的,每个标签都会创建一个dom对象,一起构成一颗dom树。
  • webAIP:BOM和DOM,浏览器提供的一套操作修改和操作dom树里的对象的API,进而控制页面的交互。(浏览器提供给我们使用的一系列方法)
  • 事件三要素:事件源、事件类型、事件处理函数

DOM

获取元素

// 获取元素,没找到返回null
document.querySelector('.box');// 类
document.querySelector('#nav');// id
document.querySelector('li');// 第一个li
document.querySelectorAll('li');// 所有li
document.querySelector('.box').querySelectorAll('li');

// 获取body标签
document.body;
// 获取html标签
document.documentElement;

操作元素

改变元素内容

// 可以修改,也可以读取
div.innerText='返回的是标签内部的内容(不包含html标签)不识别HTML标签';
div.innerHTML='识别HTML标签(推荐)';

<div><p>里面的东西</p></div>

div.innerText; // 里面的东西
div.innerHTML; // <p>里面的东西</p>

普通属性操作

// 可以修改,也可以读取
src/href
id/alt/title

img.src='/images/zxy.jpg';

表单属性操作

// 可以修改,也可以读取
#input/textarea/select

type/value/checked/selected/disabled

<input type="checkbox" checked>
input.checked;//选中 则为true,未选中 则为false

样式属性操作

只能拿到行内样式表

// 只能修改
#1.div.style 行内样式操作
// 采取驼峰命名法
- div.style.backgroundColor='blue';
- div.style.color='blue';
- div.style.display='none';

#2.div.className  类名样式操作
// 会直接更改其类名,覆盖原先的类名
- div.className='first change';
- div.className='';

排他思想

// 排他思想 优化      
let lis = document.querySelectorAll('li');
for (let i = 0; i < lis.length; i++) {
	// 所有按钮.onclick共享一个方法!!
	lis[i].onclick = click;
}

// 排他函数(公用)
function click() {
	// 干掉所有人
	for (let i = 0; i < lis.length; i++) {
		lis[i].style.backgroundColor = 'lightcoral';
	}
	// 留下我自己
	this.style.backgroundColor = 'lightblue';
}

classlist 属性

classList属性是HTML5新增的一个属性,返回元素的类名。但是ie10以上版本支持。 添加类:

element.classList.add(’类名’);

focus.classList.add('current');

移除类:

element.classList.remove(’类名’);

focus.classList.remove('current');

切换类:

element.classList.toggle(’类名’);

focus.classList.toggle('current');

注意:以上方法里面,所有类名都不带点

属性操作

dom元素属性

  1. 标准属性
    • 浏览器根据html标签 会创建dom对象,对象中自带的属性 就是标准属性
    • 如:id,name,style,type....注意:获取style.属性,只能获取行内样式(写在样式表里面的获取不到)
    • 操作方式:直接通过对象.属性名 的方式来操作
      • 获取属性值:dom.name
      • 设置属性值:dom.checked=true
      • 无法 删除dom的标准属性 delete dom.name//删除不了
let o={a:'123',b:'456'};
delete o.a;
console.log(o);//{b:'456'};
  1. 自定义属性

    • 因为自定义属性并没有加到dom对象中,而是放在dom对象里的attribute属性中,所以不能直接通过 dom.自定义属性来获取

    • 程序员通过html标签语法 或dom语法 向dom添加的属性

    • <a href="1.html" mydog="ricky"></a>

    • 操作方式:必须通过 dom.getArrtribute()dom.setArrtribute()去操作

      • 获取属性值:dom.getArrtribute('mydog');// ricky
      • 设置属性值:dom.setArrtribute('mydog','motty')
      • 添加自定义属性值:dom.setAttribute('mycat','miao')
      • 删除自定义属性:dom.removeAttribute('mydog');
  2. h5自定义属性(data-)

    • 操作方式:
      • 获取属性值:dom.getArrtribute('属性') 或者 dom.dataset.属性
      • 设置/添加属性:dom.setArrtribute('mydog','motty') ; 或者 dom.dataset.mycat='miao';
      • 删除自定义属性:dom.removeAttribute('mydog'); 或者 delete dom.dataset.mycat;

注意:

  • getAttribute和setAttribute是万能的,各种属性都可以获取到或操作
  • 自己添加自定义属性尽量以 data- 开头

补充说明

  1. 通过打点设置自定义属性

    ① 可以设置属性成功,但是没有设置到标签内,不会显示在标签里(想要显示在标签里 请使用setAttribute方法)

    ② 打点调用可以获取属性值,getAttribute方法不能获取属性值

  2. 通过setAttribute方法设置属性

    ① 可以设置属性成功,并且设置到标签内

    ② 必须通过getAttribute方法才能获取属性值,打点调用获取不到

节点操作

万物皆节点

  • 浏览器会解析html页面,将页面中的内容都创建成节点对象
  • 节点对象 有很多类型,都有相同的三个属性:nodeType、nodeName、nodeValue
  • 常见节点类型:
    • 元素节点:根据html标签生成
    • 文本节点:标签内部或外部的文本
    • 注释节点

截屏2021-05-31 上午9.26.37.png

获取节点的标签名

e.target.nodeName=='A';//标签名要大写
e.target.tagName=='A'

ol.addEventListener('click', function(e) {
            if (e.target.nodeName == 'A') {
                let li = e.target.parentNode;
                ol.removeChild(li);
                count--;
                todocount.innerHTML = count;
            }
})

父子节点

  1. 父节点

    • dom.parentNode会找到父亲所有的节点(包含文本节点等)

    • dom.parentElement只会找到父亲的元素节点

    • 两个只在删除html父标签时不一样,其他情况下都是删除父元素,效果一样。

      // document.documentElement为html标签,其的父元素为document,不是标签
      - document.documentElement.parentNode;// #document 
      - document.documentElement.parentElement;// null
      
  2. 子节点

    • ul.childNodes,找到所有的子节点 (包含 元素节点 文本节点等等)
    • ul.children,获取所有的子元素节点 也是我们实际开发常用的
    • ol.children[0] / ol.children[ol.children.length - 1]实际开发的写法 既没有兼容性问题又返回第一个子元素
    • ol.firstChild / ol.lastChild,第一个/最后一个子节点 不管是文本节点还是元素节点
    • ol.firstElementChild / ol.lastElementChild,返回第一个子元素节点 ie9才支持

兄弟节点

  1. nextSibling 下一个兄弟节点 包含元素节点或者 文本节点等等

    • div.nextSibling
    • div.previousSibling
  2. nextElementSibling 得到下一个兄弟元素节点,推荐(不兼容ie9之前)

    • div.nextElementSibling
    • div.previousElementSibling
  3. 获取所有的兄弟节点

    for(let li of lis){
      li.onclick= liClick;
    }
    
    function liClick(){
      let arrNext=[];
      let nextE=this.nextElementSibling;
      // 当元素的下个兄弟元素还能找到
      while(nextE){
        arrNext.push(nextE);
        nextE=nextE.nextElementSibling;
      }
      console.log(arrNext);
    }
    

创建/添加/删除元素节点

动态创建/添加的元素节点,注意会出现在外部获取不到该节点的问题

// 1. 创建节点元素节点(存在于内容中)
var li = document.createElement('li');

// 2. 添加节点(在后面) node.appendChild(child)  node 父级  child 是子级 后面追加元素  类似于数组中的push
ul.appendChild(li);

// 3. 添加节点(在前面) 父元素.insertBefore(创建的dom, 指定元素);
var lili = document.createElement('li');
ul.insertBefore(lili, ul.children[0]);

// 4.删除元素 父元素.removeChild(子元素);
ul.removeChild(ul.children[0]);

复制节点

// 将node节点复制一份,都会复制当前节点所有的属性(包含里面的事件等)

1. 浅拷贝()/(false) 只复制当前元素本身,不克隆里面的子节点 
node.clonNode();

2. 深拷贝(true) 复制当前元素所有内容,包含里面的子元素
node.clonNode(true);

获得焦点input.focus()

// 1.autofocus,页面以加载就自动聚焦
<textarea name="" id="" autofocus></textarea>
<button disabled>发布</button>

// 2.text.focus(),点击button时 ,让文本框聚焦;
button.onclick=function(){
   text.focus();
}

// 3.<label for="male">,点击lable,就聚焦对应的input
<label for="male">Male</label>
<input type="radio" name="sex" id="male" />

留言发布案例

<body>
    <textarea name="" id="" autofocus></textarea>
    <button disabled>发布</button>
    <ul>

    </ul>
    <script>
        let text = document.querySelector('textarea');
        let btn = document.querySelector('button');
        let ul = document.querySelector('ul');

        text.onkeyup = textKeyup;
				// 按键抬起时,判断文本框是否有内容,进而判断按钮是否要禁用
        function textKeyup() {
            if (!this.value.trim()) {
                btn.disabled = true;
            } else {
                btn.disabled = false;
            }
        }

        btn.onclick = btnClick;
        function btnClick() {
            let li = document.createElement('li');
            li.innerHTML = text.value;
            // li.innerHTML = text.value + '<span>X</span>';
            // 给li添加删除按钮
            let del = document.createElement('span');
            del.innerHTML = 'X';
            li.appendChild(del);
            // 给ul添加li
            ul.appendChild(li);
            // 将文本框的内容清空,并聚焦
            text.value = '';
            text.focus();

            // 给删除按钮添加点击事件
            #注意不要写到外面去,因为在外面还获取不到当前动态创建的删除按钮!!因为要点击btn才会动态创建li
            del.onclick = delCLick;
        }

        function delCLick() {
            let isOk = confirm('确认要删除吗?');
            // 如果点击确认
            if (isOk) {
                // 就删除当前li
                ul.removeChild(this.parentElement);
            } else {
                // 如果点击取消,就退出函数
                return;
            }
        }
    </script>
</body>

动态生成单元格案例

<body>
    <table cellspacing="0">
        <thead>
            <tr>
                <th>姓名</th>
                <th>科目</th>
                <th>成绩</th>
                <th>操作</th>

            </tr>
        </thead>
        <tbody>

        </tbody>
    </table>
    <script>
        // 1.先去准备好学生的数据
        var datas = [{
            name: '魏璎珞',
            subject: 'JavaScript',
            score: 100
        }, {
            name: '弘历',
            subject: 'JavaScript',
            score: 98
        }, {
            name: '傅恒',
            subject: 'JavaScript',
            score: 99
        }, {
            name: '明玉',
            subject: 'JavaScript',
            score: 88
        }, {
            name: '大猪蹄子',
            subject: 'JavaScript',
            score: 0
        }];
        let tbody = document.querySelector('tbody')

        // 遍历数组,生成数据行
        for (let stu of datas) {
            let tr = document.createElement('tr');
            tbody.appendChild(tr);
            // 遍历学员对象,生成单元格
            for (let k in stu) {
                let td = document.createElement('td');
                td.innerHTML = stu[k];
                tr.appendChild(td);
            }
            // 生成删除单元格
            let del = document.createElement('td');
            del.innerHTML = '<a href="javascript:;">删除</a>';
            tr.appendChild(del);

            // 给删除按钮 添加点击事件
            del.onclick = delClick;
        }

        function delClick() {
            let isOk = confirm('确认要删除吗?');
            if (isOk) {
                tbody.removeChild(this.parentNode);
            } else {
                return;
            }
        }
    </script>
</body>

三种动态创建元素区别

  1. document.write() 创建元素 如果页面文档流加载完毕,再调用这句话会导致页面重绘

    // 如果页面已经加载完毕了,再点击btn,页面原有的内容都没有了,只有<div>123</div>
    var btn = document.querySelector('button');
    btn.onclick = function() {
    	document.write('<div>123</div>');
    }
    
  2. innerHTML 创建元素

    var inner = document.querySelector('.inner');
    
    // 方法一:字符串拼接(最慢)
    // 慢的原因:
    // 1.每次要解析 html代码,生成dom对象,加入dom树,再重绘页面(最耗时)
    // 2.每次循环都 重新设置了body的innerHTML,从而引发了页面重绘1000次
    console.time();// 开始计时
    for (var i = 0; i <= 1000; i++) { 
    	inner.innerHTML += '<a href="#">百度</a>'
    }
    console.timeEnd();// 结束计时
    
    
    
    // 方法二:数组形式拼接(最快)
    // 快的原因:4ms
    // 一次性将所有生成的标签设置个innerHTML,创建1000个dom对象
    // 只需要解析一次代码和重绘一次页面
    var arr = [];
    for (var i = 0; i <= 1000; i++) {
    	arr.push('<a href="#">百度</a>');
    }
    inner.innerHTML = arr.join('');
    
  3. document.createElement() 创建元素

    for (var i = 0; i <= 1000; i++) {
    	var a = document.createElement('a');
    	document.body.appendChild(a);
    }
    // 速度居中
    // 慢的原因:17ms
    // 1.创建了1000次对象
    // 2.重绘了页面了1000次
    
  4. document.createDocumentFragment()

    // 创建代码片段(用来 临时存放 新创建的dom对象)
    var frag = document.createDocumentFragment();
    
    for (var i = 0; i <= 1000; i++) {
    	var a = document.createElement('a');
    	frag.appendChild(a);
    }
    // 将代码片段 添加到body中(一次性将内部的1000个div加到body)
    document.body.appendChild(frag);
    
    // 慢的原因:7ms
    // 1.创建了1000次对象
    // 2.一次性将1000个对象 添加到页面,重绘页面了1次
    

事件高级

事件本质是对象的属性,添加的函数则为其属性值。

注册事件(2种方式)

  1. 传统注册方式(以on开头)

    • 注册事件的唯一性
    • 同一个元素同一个事件只能设置一个处理函数,如果有多个,后面的注册函数会覆盖前面的
    • 0级dom事件
    • 属性是直接加给dom对象的
  2. 监听注册方式(addEventListener())

    • w3c标准 推荐(IE9之前不支持,可用attachEvent()-不推荐 代替)

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

    • 默认按注册顺序依次执行

    • 2级dom事件

    • 属性不会加给dom对象,而是加到内存里的2级dom事件表里(虚拟)

截屏2021-06-02 上午10.13.40.png

<script>
    var btns = document.querySelectorAll('button');
    // 1. 传统方式注册事件
		// hao a u
    btns[0].onclick = function() {
        alert('hi');
    }
    btns[0].onclick = function() {
            alert('hao a u');
    }
   // 2. 事件侦听注册事件 addEventListener 
   // (1) 里面的事件类型是字符串 必定加引号 而且不带on
   // (2) 同一个元素 同一个事件可以添加多个侦听器(事件处理程序)
   //  22 33
    btns[1].addEventListener('click', function() {
        alert(22);
    })
    btns[1].addEventListener('click', function() {
            alert(33);
    })
</script>

删除事件(解绑事件)

  1. 传统注册方式

    • eventTarget.onclick=null;
  2. 方法监听注册方式

    • eventTarget.removeEventListener(type, listener[, useCapture ]);
    • eventTarget.detachEvent( eventNameWithOn , callback );(不推荐)
    <script>
        var divs = document.querySelectorAll('div');
        divs[0].onclick = function() {
            alert(11);
            // 1. 传统方式删除事件
            divs[0].onclick = null;
        }
        // 2. removeEventListener 删除事件
        divs[1].addEventListener('click', fn) // 里面的fn 不需要调用加小括号
        function fn() {
            alert(22);
            divs[1].removeEventListener('click', fn);
        }

        // 3. detachEvent
        divs[2].attachEvent('onclick', fn1);

        function fn1() {
            alert(33);
            divs[2].detachEvent('onclick', fn1);
        }
    </script>

DOM事件流

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

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

DOM 事件流会经历3个阶段:

  1. 捕获阶段

  2. 当前目标阶段

  3. 冒泡阶段

我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点( 最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。

1551169007768.png 注意:

  • js只能执行 捕获 或 冒泡 其中一个阶段。

  • onclick 和 attachEvent 只能得到冒泡阶段

  • add的第三个参数useCapture

    • true,捕获阶段
    • false(不写默认值),冒泡阶段
  • 更关注事件冒泡

  • 有些事件是没有冒泡的,如onblur, unfocus, onmouseenter, onmouseleave

事件冒泡

// 点击son,会先执行son的点击事件,。。。,最后是document

		<div class="father">
        <div class="son">son盒子</div>
    </div>
    <script>
        // onclick 和 attachEvent(ie) 在冒泡阶段触发
        // 冒泡阶段 如果addEventListener 第三个参数是 false 或者 省略 
        // son -> father ->body -> html -> document
        var son = document.querySelector('.son');
		// 给son注册单击事件
        son.addEventListener('click', function() {
            alert('son');
        }, false);
		// 给father注册单击事件
        var father = document.querySelector('.father');
        father.addEventListener('click', function() {
            alert('father');
        }, false);
		// 给document注册单击事件,省略第3个参数
        document.addEventListener('click', function() {
            alert('document');
        })
    </script>

事件捕获

// 点击son,会先执行document的点击事件,。。。,最后是son

		<div class="father">
        <div class="son">son盒子</div>
    </div>
    <script>
        // 如果addEventListener() 第三个参数是 true 那么在捕获阶段触发
        // document -> html -> body -> father -> son
         var son = document.querySelector('.son');
		// 给son注册单击事件,第3个参数为true
         son.addEventListener('click', function() {
             alert('son');
         }, true);
         var father = document.querySelector('.father');
		// 给father注册单击事件,第3个参数为true
         father.addEventListener('click', function() {
             alert('father');
         }, true);
		// 给document注册单击事件,第3个参数为true
        document.addEventListener('click', function() {
            alert('document');
        }, true)
    </script>

事件对象

事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象。

如:谁绑定了这个事件,鼠标位置,按了哪个键等

事件触发发生时就会产生事件对象,并且系统会以实参的形式传给事件处理函数。

截屏2021-06-02 上午11.28.04.png

事件对象的兼容性处理

在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。

    <div>123</div>
    <script>
        var div = document.querySelector('div');
        div.onclick = function(e) {
                // 事件对象
                e = e || window.event;
                console.log(e);
        }
    </script>

事件对象的属性和方法

1551169931778.png

e.target 和 this 的区别

  • this 是事件绑定的元素(绑定这个事件处理函数的元素) 。

  • e.target 是事件触发的元素。

通常情况下terget 和 this是一致的, 但有一种情况不同,那就是在事件冒泡时(父子元素有相同事件,单击子元素,父元素的事件处理函数也会被触发执行), 这时候this指向的是父元素,因为它是绑定事件的元素对象, 而target指向的是子元素,因为他是触发事件的那个具体元素对象。

#事件冒泡下的e.targetthis

    <ul>
        <li>abc</li>
        <li>abc</li>
        <li>abc</li>
    </ul>
    <script>
        var ul = document.querySelector('ul');
				// 点击了li标签,事件会冒泡到ul上
        ul.addEventListener('click', function(e) {
              // 我们给ul 绑定了事件  那么this 就指向ul  
              console.log(this); // ul

              // e.target 触发了事件的对象 我们点击的是li e.target 指向的就是li
              console.log(e.target); // li
        });

阻止默认行为

html中一些标签有默认行为,例如a标签被单击后,默认会进行页面跳转。

# e.preventDefault();    

		<a href="http://www.baidu.com">百度</a>
    <script>
      
        // 2. 阻止默认行为 让链接不跳转 
        var a = document.querySelector('a');
        a.addEventListener('click', function(e) {
             e.preventDefault(); //  dom 标准写法
        });

        // 3. 传统的注册方式
        a.onclick = function(e) {
            // 普通浏览器 e.preventDefault(); 方法##推荐##
            e.preventDefault();
          
            // 低版本浏览器 ie678  returnValue 属性
            e.returnValue = false;
          
            // 我们可以利用return false 也能阻止默认行为 没有兼容性问题 
            // 注意:return后面的代码不执行了,而且只限于传统的注册方式
            return false;
        }
    </script>
// 表单点击提交后,默认会提交表单的数据。
// 可以给 表单 阻止提交事件,也可以给 提交按钮 添加

## 推荐通过 表单 提交事件,因为表单可能有多个提交按钮,都会提交表单,所以应该在表单提交事件中统一阻止表单的提交

let fNode=document.querySelector('form');
fNode.addEventListener('submit',function(e){
  e.preventDefault();
});
// 阻止页面最开始选择文字的默认事件
document.addEventListener('selectstart', function(e){
      e.preventDefault();
})

阻止事件冒泡

  • 标准写法

    e.stopPropagation(); // stop 停止  Propagation 传播
    
  • 非标准写法 IE678

    window.event.cancelBubble = true; // 非标准 cancel 取消 bubble 泡泡
    
    <div class="father">
        <div class="son">son儿子</div>
    </div>
    <script>
        var son = document.querySelector('.son');
		// 给son注册单击事件
        son.addEventListener('click', function(e) {
            alert('son');
            e.stopPropagation(); // stop 停止  Propagation 传播
            window.event.cancelBubble = true; // 非标准 cancel 取消 bubble 泡泡
        }, false);

        var father = document.querySelector('.father');
		// 给father注册单击事件
        father.addEventListener('click', function() {
            alert('father');
        }, false);
		// 给document注册单击事件
        document.addEventListener('click', function() {
            alert('document');
        })
    </script>

事件委托

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

说白了就是,不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行。

事件委托的原理

  • 父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素。

  • 即把子元素的事件 通过冒泡 委托给父元素

  • 委托给 已经存在的元素

事件委托的作用

  • 我们只操作了一次 DOM ,提高了程序的性能。
  • 动态新创建的子元素,也拥有事件。
  • 应用:通过给 ul 注册点击事件,通过 事件委托,可以实现给 未来新创建的li 添加点击事件
<button>新增</button>
<ul>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
</ul>
<script>
	let btnAdd = document..querySelector('button');
	var ul = document.querySelector('ul');

	// 动态添加li
	btnAdd.addEventListener('click', function(e) {
		let newLi = document.createElement('li');
    newLi.innerHTML = '我是新加的li'+Math.radom();
    ul.appendChild(li);
})	
	
			
	// 事件委托的核心原理:给父节点添加侦听器, 利用事件冒泡影响每一个子节点
	// 可以给 未来新创建的li 也会添加点击事件
	ul.addEventListener('click', function(e) {
		// e.target 这个可以得到我们点击的对象
    e.target.style.backgroundColor = 'pink';
})
</script>

常用鼠标事件

1551172699854.png

案例:禁止选中文字和禁止右键菜单

// 禁用一般是为了阻止浏览器显示默认的右键菜单,因为需要自己写右键菜单的样式功能等

<body>
    我是一段不愿意分享的文字
    <script>
        // 1. contextmenu 我们可以禁用右键菜单
        document.addEventListener('contextmenu', function(e) {
                e.preventDefault();
        })
        // 2. 禁止选中文字 selectstart
        document.addEventListener('selectstart', function(e) {
            e.preventDefault();
        })
    </script>
</body>

mouseout

注意:mouseout必须是 鼠标 移除目标元素的边界才会触发,而“踩着”其他元素 移出 是不会触发的

注意点

  1. mouseover和mouseout在父元素和子元素中都可以触发,当鼠标经过某个元素时,触发的次数取决于子元素的个数;
  2. mouseenter和mouseleave只在父元素触发,当鼠标经过某个元素时,只会触发一次;
  3. 当四个函数都在父子元素中使用时,mouseover和mouseout比mouseenter和mouseleave先触发;
  • 一般来说:
    • mouseover和mouseout一起使用,mouseenter和mouseleave一起使用
    • 如果元素内部没有子元素影响,可以考虑用mouseover和mouseout;
    • 如果元素内有子元素影响,可以采用mouseenter和mouseleave,防止事件冒泡;

鼠标事件对象

1551173103741.png

获取鼠标在页面的坐标(pageX)

// 鼠标事件对象 MouseEvent
document.addEventListener('click', function(e) {
    // 1. client 鼠标在可视区的x和y坐标
    console.log(e.clientX);
    console.log(e.clientY);
    console.log('---------------------');

    // 2. page 鼠标在页面文档的x和y坐标 !!重点!!
    console.log(e.pageX);
    console.log(e.pageY);
    console.log('---------------------');

    // 3. screen 鼠标在电脑屏幕的x和y坐标
    console.log(e.screenX);
    console.log(e.screenY);

})

案例:跟随鼠标的天使

1551173186812.png

<img src="images/angel.gif" alt="">
    <script>
        var pic = document.querySelector('img');
        document.addEventListener('mousemove', function(e) {
        	// 1. mousemove只要我们鼠标移动1px 就会触发这个事件
        	// 2.核心原理: 每次鼠标移动,我们都会获得最新的鼠标坐标, 
            // 把这个x和y坐标做为图片的top和left 值就可以移动图片
          // 注意:top left是相对于 文档的 坐标
        	var x = e.pageX;
        	var y = e.pageY;
        	console.log('x坐标是' + x, 'y坐标是' + y);
        	//3 . 千万不要忘记给left 和top 添加px 单位
        	pic.style.left = x - 50 + 'px';
        	pic.style.top = y - 40 + 'px';
    	});
    </script>

常用的键盘事件

键盘事件

1551318122855.png

三个事件的执行顺序:keydown -- keypress -- keyup

keyup和keydown的区别

截屏2021-06-02 下午4.38.01.png

文本框的内容输入 和 keydown 和 keyup 的顺序

  1. 先触发 keydown事件,此时 文本框 还没有输入 被按下的按钮内容

  2. 将按键 添加到文本框中

  3. 按键抬起,触发keyup事件

    文本框中,一般用keyup事件

键盘事件对象

keyCode -- 返回该值的ASCII值 e.keycode===65 //判断是否按下a键

注意:

  • onkeydown 和 onkeyup不区分字母大小写,onkeypress区分
  • 实际开发中,更多使用onkeydown 和 onkeyup

案例:模拟京东按键输入内容

// 如果添加事件为keydown
// 1. 触发文档的keydown事件,事件中判断 如果按的是s键,就让文本框获取焦点
// 2. 文本框获取焦点
// 3. 浏览器 会接着将按下的s键,输入到 当前获取焦点的元素中

// 如果添加事件为keyup
// 1. 触发文档的keydown事件,但是 这个案例中 没有为文档添加keydown事件,所以什么都不会发生
// 2. 浏览器会接着将按下的s键,输入到当前焦点元素中,但是此时页面上没有焦点元素,所以什么都没有发生
// 3. 触发文档的keyup事件,让文本框获取焦点

		<input type="text">
    <script>
        // 获取输入框
        var search = document.querySelector('input');
		// 给document注册keyup事件
        document.addEventListener('keyup', function(e) {
            // 判断keyCode的值
            if (e.keyCode === 83) {
                // 触发输入框的获得焦点事件
                search.focus();
              	search.
            }
        })
    </script>

案例:模拟京东快递单号查询

判断文本框是否为空:this.value.trim().length === 0

// 要求:当我们在文本框中输入内容时,文本框上面自动显示大字号的内容。
<div class="search">
    <div class="con">123</div>
    <input type="text" placeholder="请输入您的快递单号" class="jd">
</div>

<script>
    // 获取要操作的元素
    var con = document.querySelector('.con');
    var jd_input = document.querySelector('.jd');

    // 给输入框注册keyup事件
    jd_input.addEventListener('keyup', function() 		// 判断输入框内容是否为空
    	if (this.value.trim().length === 0) {
    		// 为空,隐藏放大提示盒子
      	con.style.display = 'none';
    	} else {
       	// 不为空,显示放大提示盒子,设置盒子的内容
       	con.style.display = 'block';
       	con.innerText = this.value;
    	}
    })

    // 给输入框注册失去焦点事件,隐藏放大提示盒子
    jd_input.addEventListener('blur', function() {
    	con.style.display = 'none';
     })

     // 给输入框注册获得焦点事件
     jd_input.addEventListener('focus', function() {
     	// 判断输入框内容是否为空
     	if (this.value !== '') {
     		// 不为空则显示提示盒子
     		con.style.display = 'block';
     	}
    })
</script>

BOM

BOM概述

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

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

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

BOM 比 DOM 更大,它包含 DOM。

1551319264407.png

1551319344183.png

顶级对象window

  • 是js访问浏览器的一个接口

  • 是全局对象。定义在全局作用域中的变量、函数都会变成window对象的属性和方法

    • 调用时可以省略window。alert()、prompt()都是window对象方法
  • window的下一个特殊属性 window.name,所以不要在全局作用域下声明name

window对象的常见事件

页面(窗口)加载事件(2种)

让js不用必须写在html文件的下面

1.第一种

  • window.onload 是窗口 (页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS 文件等), 就调用的处理函数。
  • 缺点:如果网速慢的话,用户 先看到了按钮,但是点击后 没有作用
// 传统注册方式,只能写一次 ##平常代码使用##
window.onload = function(){}

// 没有限制
window.addEventListener("load",function(){})

2.第二种

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

  • IE9以上才支持!!!

  • 只能用2级注册方式

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

document.addEventListener('DOMContentLoaded',function(){})

调整窗口大小事件

window.addEventListener('resize', function(){}) 

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

注意:

  1. 只要窗口大小发生像素变化,就会触发这个事件。

  2. 我们经常利用这个事件完成响应式布局。 window.innerWidth 当前屏幕的宽度

    <script>
        // 注册页面加载事件
        window.addEventListener('load', function() {
            var div = document.querySelector('div');
        	// 注册调整窗口大小事件
            window.addEventListener('resize', function() {
                // window.innerWidth 获取窗口大小
                console.log('变化了');
                if (window.innerWidth <= 800) {
                    div.style.display = 'none';
                } else {
                    div.style.display = 'block';
                }
            })
        })
    </script>
    <div></div>

定时器

炸弹定时器

setTimeout() 炸弹 开启定时器

window.setTimeout(调用函数,[延迟的毫秒数]);
// setTimeout这调用函数称为 回调函数 callback
// window可以省略
// 延迟的毫秒数 省略默认为0

// 调用函数可以 直接写函数,或者写函数名,或者'函数名()'不推荐
    <script>
        // 回调函数是一个匿名函数
         setTimeout(function() {
             console.log('时间到了');

         }, 2000);
        function callback() {
            console.log('爆炸了');
        }
		// ##因为定时器可能有很多,所以经常给定时器赋值一个标识符##
		// 回调函数是一个有名函数
        var timer1 = setTimeout(callback, 3000);//返回的是一个1个id
        var timer2 = setTimeout(callback, 5000);
    </script>

停止定时器

window.clearTimeout(timeoutID);// timeoutID即为定时器的标识符

移动div盒子案例

    <style>
        * {
            padding: 0;
            margin: 0;
        }
        
        div {
            position: absolute;
            width: 100px;
            height: 100px;
            background-color: lightcoral;
        }
    </style>    

		<script>
        window.onload = function() {
            let div = document.querySelector('div');
            let start = document.querySelector('.start');
            let stop = document.querySelector('.stop');
            let timeId = null;
            start.addEventListener('click', function() {
                if (timeId) return;
                timeId = setInterval(function() {
                    let windowW = window.innerWidth - 100;
                    let xNum = parseInt(div.style.left);
                    console.log(xNum);
                    if (xNum >= windowW) {
                        clearInterval(timeId);
                        // timeId = null;
                        // return;
                    }
                    div.style.left = xNum + 10 + 'px';

                }, 20);
                stop.onclick = function() {
                    clearInterval(timeId);
                    timeId = null;
                }
            })
        }
    </script>
    <button class="start">移动</button>
    <button class="stop">停止</button>
    <div style="left: 100px"></div>

闹钟定时器

setInterval() 闹钟 开启定时器

// 每隔间隔时间,就去调用一次回调函数
window.setInterval(调用函数,[间隔的毫秒数]);

停止计时器

window.clearInterval(intervalID);// timeoutID即为定时器的标识符

倒计时案例

1551321298787.png

        // 1. 获取元素 
        var hour = document.querySelector('.hour'); // 小时的黑色盒子
        var minute = document.querySelector('.minute'); // 分钟的黑色盒子
        var second = document.querySelector('.second'); // 秒数的黑色盒子
        var inputTime = +new Date('2019-10-25 18:00:00'); // 返回的是用户输入时间总的毫秒数
				// 先调用一次,这样页面才能一加载出来就有内容
        countDown();
        // 2. 开启定时器
        setInterval(countDown, 1000);
        function countDown() {
            var nowTime = +new Date(); // 返回的是当前时间总的毫秒数
            var times = (inputTime - nowTime) / 1000; // times是剩余时间总的秒数 
            var h = parseInt(times / 60 / 60 % 24); //时
            h = h < 10 ? '0' + h : h;
            hour.innerHTML = h; // 把剩余的小时给 小时黑色盒子
            var m = parseInt(times / 60 % 60); // 分
            m = m < 10 ? '0' + m : m;
            minute.innerHTML = m;
            var s = parseInt(times % 60); // 当前的秒
            s = s < 10 ? '0' + s : s;
            second.innerHTML = s;
        }

this指向问题

  1. 全局作用域或者普通函数中this指向全局对象window(注意定时器里面的this指向window)

  2. 方法调用中谁调用this指向谁

  3. 构造函数中this指向构造函数的实例

// 1. 全局作用域或者普通函数中this指向全局对象window( 注意定时器里面的this指向window)
function a(){
  console.log(this);//window
}
a();// window.a();

window.setTimeout(function() {
	console.log(this);// window
}, 1000);}


// 2. 方法调用中谁调用this指向谁
var o = {
	sayHi: function() {
		console.log(this); // this指向的是 o 这个对象
	}
}
o.sayHi();

// 3. 构造函数中this指向构造函数的实例
function Fun() {
	console.log(this); // this 指向的是fun 实例对象
}
var fun = new Fun();

location对象

location概念

1551322091638.png

URL

1551322373704.png

1551322387201.png

location对象的属性

1551322416716.png

重点:href 和 search

href案例

    // 5s后自动跳转页面
		<button>点击</button>
    <div></div>
    <script>
        var btn = document.querySelector('button');
        var div = document.querySelector('div');
        btn.addEventListener('click', function() {
            // console.log(location.href);
            location.href = 'http://www.itcast.cn';
        })
        var timer = 5;
        setInterval(function() {
            if (timer == 0) {
                location.href = 'http://www.itcast.cn';
            } else {
                div.innerHTML = '您将在' + timer + '秒钟之后跳转到首页';
                timer--;
            }
        }, 1000);
    </script>

search案例

# login.html

<form action="index.html">
  用户名: <input type="text" name="uname"> 密码:
  <input type="password" name="password" id="">
  <input type="submit" value="登录">
</form>

# index.html
<div></div>
<script>
	let div = document.querySelector('div');
	let data = UrlToObj(location.search);
  //  decodeURIComponent(data.uname) 解码中文
  div.innerHTML = `${decodeURIComponent(data.uname)},欢迎您,您的密码是:${data.password}`;

	// 将location.search 转换为 对象
	function UrlToObj(urlParams) {
		let strParams = urlParams.substr(1); // uname=jean&password=1234
    let obj = {};
    let arrParams = strParams.split('&') // ["uname=jean","password=1234"]
    for (let p of arrParams) {
    	let key = p.split('='); // ['uname','jean']
      obj[key[0]] = key[1]; // { uname: 'jean'}
    }
    return obj;
	}
</script>

location对象的常见方法

1551322750241.png

    <button>点击</button>
    <script>
        var btn = document.querySelector('button');
        btn.addEventListener('click', function() {
            // 记录浏览历史,所以可以实现后退功能
            // location.assign('http://www.itcast.cn');
            // 不记录浏览历史,所以不可以实现后退功能
            // location.replace('http://www.itcast.cn');
            location.reload(true);
        })
    </script>

navigator对象

​ navigator 对象包含有关浏览器的信息,它有很多属性,我们最常用的是 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 = "";     //手机
 } else {
    window.location.href = "";     //电脑
 }

history对象

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

1551322885216.png

history对象一般在实际开发中比较少用,但是会在一些 OA 办公系统中见到。

本地存储

只能存储字符串,可以将对象JSON.stringify() 编码后存储

window.sessionStorage

1、生命周期为关闭浏览器窗口

2、在同一个窗口(页面)下数据可以共享

3、以键值对的形式存储使用

存储数据:

sessionStorage.setItem(key, value)

获取数据:

sessionStorage.getItem(key)

删除数据:

sessionStorage.removeItem(key)

清空数据:(所有都清除掉)

sessionStorage.clear()

window.localStorage

1、声明周期永久生效,除非手动删除 否则关闭页面也会存在

2、可以多窗口(页面)共享(同一浏览器可以共享)

3.  以键值对的形式存储使用

存储数据:

localStorage.setItem(key, value)

获取数据:

localStorage.getItem(key)

删除数据:

localStorage.removeItem(key)

清空数据:(所有都清除掉)

localStorage.clear()

JS执行机制

JS 是单线程

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
	这样所导致的问题是: 如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

同步任务和异步任务

JS中所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务指的是:
	在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  
异步任务指的是:
	不进入主线程、而进入”任务队列”的任务,当主线程中的任务运行完了,才会从”任务队列”取出异步任务放入主线程执行。

1551434972778.png

回调函数:函数作为参数传入

事件循环(event loop)

1551435335464.png

1551435369570.png

link和script标签

<!--link标签 会 异步加载文件-->
<!--浏览器遇到 link 标签的外部文件时,会 另外去请求,自己继续执行后面的代码-->
<!--目的:先把页面结构显示给用户,然后再把样式加入,美化界面-->
<link rel="stylesheet" href="01.css">
  
<!--script 标签 会同步加载文件-->
<!--浏览器遇到 script标签 的外部文件时,会等 外部js文件加载完毕后,自己继续执行后面的代码-->
<!--目的:当前页面中的js代码 有可能会 访问/调用 外部的js文件,所以必须先执行完外部的js文件-->
<script src="01.js"></script>
<!--所以 外部js的函数 如果内部要访问 ,就要把函数写在window.onload=function(){}的外面-->

PC端网页特效

元素偏移量 offset 系列

offset概述

  1. 获得元素距离带有定位父元素的位置
    • 直接获取 最后样式位置运算(合编样式表=行内样式+内嵌样式+外链样式)的结果
  2. 获得元素自身的大小(宽度高度)
  3. 注意:返回的数值都不带单位

图片1.png

<div class="father">
    <div class="son"></div>
</div>
<div class="w"></div>
<script>
    // offset 系列
    var father = document.querySelector('.father');
    var son = document.querySelector('.son');
    // 1.可以得到元素的偏移 位置 返回的不带单位的数值  
    console.log(father.offsetTop);
    console.log(father.offsetLeft);
    // 它以带有定位的父亲为准  如果么有父亲或者父亲没有定位 则以 body 为准
    console.log(son.offsetLeft);
    var w = document.querySelector('.w');
    // 2.可以得到元素的大小 宽度和高度 是包含padding + border + width 
    console.log(w.offsetWidth);
    console.log(w.offsetHeight);
    // 3. 返回带有定位的父亲 否则返回的是body
    console.log(son.offsetParent); // 返回带有定位的父亲 否则返回的是body
    console.log(son.parentNode); // 返回父亲 是最近一级的父亲 亲爸爸 不管父亲有没有定位

offset 与 style 区别

offset

  • offset 可以得到任意样式表中的样式值

  • offset 系列获得的数值是没有单位的

  • offsetWidth 包含padding+border+width

  • offsetWidth 等属性是只读属性,只能获取不能赋值

  • 所以,我们想要获取元素大小位置,用offset更合适

style

  • style 只能得到行内样式表中的样式值

  • style.width 获得的是带有单位的字符串

  • style.width 获得不包含padding和border 的值

  • style.width 是可读写属性,可以获取也可以赋值

  • 所以,我们想要给元素更改值,则需要用style改变

因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes

06求鼠标在盒子里的坐标.png

模态框拖拽

 // 1. 获取元素
        var login = document.querySelector('.login');
        var mask = document.querySelector('.login-bg');
        var link = document.querySelector('#link');
        var closeBtn = document.querySelector('#closeBtn');
        var title = document.querySelector('#title');
				// 阻止页面最开始选择文字的默认事件
				document.addEventListener('selectstart', function(e){
          		e.preventDefault();
        })
        // 2. 点击弹出层这个链接 link  让mask 和login 显示出来
        link.addEventListener('click', function() {
                mask.style.display = 'block';
                login.style.display = 'block';
            })
            // 3. 点击 closeBtn 就隐藏 mask 和 login 
        closeBtn.addEventListener('click', function() {
                mask.style.display = 'none';
                login.style.display = 'none';
            })
            // 4. 开始拖拽
            // (1) 当我们鼠标按下, 就获得鼠标在盒子内的坐标
        title.addEventListener('mousedown', function(e) {
            var x = e.pageX - login.offsetLeft;
            var y = e.pageY - login.offsetTop;
            // (2) 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
          	// ###因为只有鼠标按下时,才能触发鼠标移动事件,主要鼠标可以在任意位置移动,所以事件源为document
            document.addEventListener('mousemove', move)

            function move(e) {
                login.style.left = e.pageX - x + 'px';
                login.style.top = e.pageY - y + 'px';
            }
            // (3) 鼠标弹起,就让鼠标移动事件移除
            document.addEventListener('mouseup', function() {
                document.removeEventListener('mousemove', move);
            })
        })


放大镜效果

截屏2021-06-05 上午10.36.45.png

  window.onload = function() {
      // 预览图
      let preview_img = document.querySelector('.preview_img');
      // 遮罩层
      let mask = document.querySelector('.mask');
      // 大图盒子
      let big = document.querySelector('.big');
      // 大图
      let bigImg = document.querySelector('.bigImg');

      // 鼠标移入,mask和大图显示
      preview_img.addEventListener('mouseover', function(e) {
          mask.style.display = 'block';
          big.style.display = 'block';

      })

      // 鼠标移出,mask和大图隐藏
      // !!mouseout必须是 鼠标 移除目标元素的边界才会触发,而“踩着”其他元素 移出 是不会触发的
      preview_img.addEventListener('mouseout', function(e) {
          mask.style.display = 'none';
          big.style.display = 'none';
      })

      // 鼠标移动
      preview_img.addEventListener('mousemove', function(e) {
          // 鼠标在盒子内部的坐标
          let x = e.pageX - this.offsetLeft;
          let y = e.pageY - this.offsetTop;
          // 遮罩层的坐标=鼠标在盒子内部的坐标-遮罩层大小的一半
          let maskX = x - mask.offsetWidth / 2;
          let maskY = y - mask.offsetHeight / 2;

          // 遮罩层在盒子内移动的最大距离=盒子的宽度-遮罩层的宽度
          let maskMax = this.offsetWidth - mask.offsetWidth;
          // 限制mask移动的距离
          if (maskX < 0) {
              maskX = 0;
          } else if (maskX > maskMax) {
              maskX = maskMax;
          }
          if (maskY < 0) {
              maskY = 0;
          } else if (maskY > maskMax) {
              maskY = maskMax;
          }
          // 遮挡层的移动距离
          mask.style.left = maskX + 'px';
          mask.style.top = maskY + 'px';


          // 3. 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
          // 大图片最大移动距离
          bigMax = bigImg.offsetWidth - big.offsetWidth;
          let bigX = maskX * bigMax / maskMax;
          let bigY = maskY * bigMax / maskMax;
          // 大图片的移动距离 X Y
          bigImg.style.left = -bigX + 'px';
          bigImg.style.top = -bigY + 'px';
      })
  }

元素可视区 client 系列

图片3.png

图片4.png

淘宝 flexible.js 源码分析

// flexible.js

(function flexible(window, document) {
    // 获取的html 的根元素
    var docEl = document.documentElement
        // dpr 物理像素比
    var dpr = window.devicePixelRatio || 1

    // adjust body font size  设置我们body 的字体大小
    function setBodyFontSize() {
        // 如果页面中有body 这个元素 就设置body的字体大小
        if (document.body) {
            document.body.style.fontSize = (12 * dpr) + 'px'
        } else {
            // 如果页面中没有body 这个元素,则等着 我们页面主要的DOM元素加载完毕再去设置body
            // 的字体大小
            document.addEventListener('DOMContentLoaded', setBodyFontSize)
        }
    }
    setBodyFontSize();

    // set 1rem = viewWidth / 10    设置我们html 元素的文字大小
    function setRemUnit() {
        var rem = docEl.clientWidth / 10
        docEl.style.fontSize = rem + 'px'
    }

    setRemUnit()

    // reset rem unit on page resize  当我们页面尺寸大小发生变化的时候,要重新设置下rem 的大小
    window.addEventListener('resize', setRemUnit)
        // pageshow 是我们重新加载页面触发的事件
    window.addEventListener('pageshow', function(e) {
        // e.persisted 返回的是true 就是说如果这个页面是从缓存取过来的页面,也需要从新计算一下rem 的大小
        if (e.persisted) {
            setRemUnit()
        }
    })

    // detect 0.5px supports  有些移动端的浏览器不支持0.5像素的写法
    if (dpr >= 2) {
        var fakeBody = document.createElement('body')
        var testElement = document.createElement('div')
        testElement.style.border = '.5px solid transparent'
        fakeBody.appendChild(testElement)
        docEl.appendChild(fakeBody)
        if (testElement.offsetHeight === 1) {
            docEl.classList.add('hairlines')
        }
        docEl.removeChild(fakeBody)
    }
}(window, document))

立即执行函数

主要作用: 创建一个独立的作用域。 避免了命名冲突问题

解决问题:引入多个文件时,不同文件中 使用了相同的变量或函数名,从而导致 变量污染

方案:在每个j文件中 使用 自执行函数 生成独立的 作用域,避免变量污染的问题

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

pageshow

下面三种情况都会刷新页面都会触发 load 事件。

1.a标签的超链接

2.F5或者刷新按钮(强制刷新)

3.前进后退按钮

但是 火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。

所以此时后退按钮不能刷新页面。

此时可以使用 pageshow事件来触发。这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中,pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件注意这个事件给window添加。

元素滚动 scroll 系列

scroll 概述

scroll 翻译过来就是滚动的,我们使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。 图片5.png

图片6.png

页面被卷去的头部

如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发 onscroll事件。

仿淘宝固定右侧侧边栏(window.pageYOffset)

  1. 原先侧边栏是绝对定位
  2. 当页面滚动到一定位置,侧边栏改为固定定位
  3. 页面继续滚动,会让 返回顶部显示出来

注意:

  • 元素被卷去的头部是element.scrollTop  , 如果是页面被卷去的头部 则是 window.pageYOffset
    • 页面被卷去的头部:可以通过window.pageYOffset 获得 如果是被卷去的左侧window.pageXOffset
    • 其实这个值 可以通过盒子的 offsetTop可以得到,如果大于等于这个值,就可以让盒子固定定位了(注意:offsetTop是相对于 body(整个页面的顶部) ,页面滚动时,会也来越小)
    • 固定定位是相对于 窗口(浏览器可视区) 而言的,绝对定位是相对于 定位父盒子来说的,如果没有父盒子,则相对于body(整个页面的顶部)
        let sliderbar = document.querySelector('.slider-bar');
        let goBack = document.querySelector('.goBack');
        let banner = document.querySelector('.banner');
        let main = document.querySelector('.main');

        // banner.offestTop 就是被卷去头部的大小 页面滚动时,该值不会变
        let bannerTop = banner.offsetTop;
        // 侧边栏一开始绝对定位时的 top值
        let sliderbarTop = sliderbar.offsetTop;
        // 当我们侧边栏固定定位之后应该变化的数值
        let sliderbarTop1 = sliderbarTop - bannerTop;
        // 记录main最开始的top值
        let mainTop = main.offsetTop;
        document.addEventListener('scroll', function() {
          	// 当我们页面被卷去的头部大于等于了 bannerTop 此时 侧边栏就要改为固定定位
            if (window.pageYOffset >= bannerTop) {
                sliderbar.style.position = 'fixed';
                // 记得要加px单位
                sliderbar.style.top = sliderbarTop1 + 'px';
            } else {
                sliderbar.style.position = 'absolute';
                sliderbar.style.top = sliderbarTop + 'px';
            }

            // 当我们页面滚动到main盒子,就显示 goback模块
            if (window.pageYOffset >= mainTop) {
                goBack.style.display = 'block';
            } else {
                goBack.style.display = 'none';
            }
        })

各系列总结-待补充

图片7.png

他们主要用法:

1.offset系列 经常用于获得元素位置    offsetLeft  offsetTop

2.client经常用于获取元素大小  clientWidth clientHeight

3.scroll 经常用于获取滚动距离 scrollTop  scrollLeft  

4.注意页面滚动的距离通过 window.pageXOffset  获得 系列总结.jpg

5.鼠标系列

08鼠标坐标.png

window

window.pageYOffset

// 页面滚动事件 scroll
document.addEventListener('scroll', function() {
			// 当我们页面滚动到main盒子,就显示 goback模块
			if (window.pageYOffset >= mainTop) {
					goBack.style.display = 'block';
			} else {
					goBack.style.display = 'none';
			}
})

window.scroll()

window.scroll(x-coord, y-coord)
window.scroll(options)

// x-coord 值表示你想要置于左上角的像素点的横坐标。
// y-coord 值表示你想要置于左上角的像素点的纵坐标。

// 示例
<!-- 把纵轴上第 100 个像素置于窗口顶部 -->
<button onclick="window.scroll(0, 100);">点击以向下滚动 100 像素</button>

// 使用 options:
window.scroll({
  top: 100,
  left: 100,
  behavior: 'smooth'
});

mouseenter 和mouseover的区别

mouseover和mouseenter.jpg

  • mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发。mouseenter  只会经过自身盒子触发
    • mouseover-----只要鼠标在盒子上面(盒子上面有子盒子也会)就会触发
    • mousenter------进入自身盒子(包含子盒子的边界)会触发,此时子盒子和父盒子可以看作为一体
  • 之所以这样,就是因为mouseenter不会冒泡
  • 跟mouseenter搭配鼠标离开 mouseleave  同样不会冒泡
  • 当四个函数都在父子元素中使用时,mouseover和mouseout比mouseenter和mouseleave先触发;

动画函数

动画实现原理

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

实现步骤:

  1. 获得盒子当前位置
  2. 让盒子在当前位置加上1个移动距离
  3. 利用定时器不断重复这个操作
  4. 加一个结束定时器的条件
  5. 注意此元素需要添加定位,才能使用element.style.left

动画函数封装

如果多个元素都使用这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。

核心原理:利用 JS 是一门动态语言,可以很方便的给当前对象添加属性。

 function animate(dom, target) {
            // 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
            // 解决方案就是 让我们元素只有一个定时器执行
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(dom.timerId);
            dom.timerId = setInterval(function() {
                if (dom.offsetLeft >= target) {
                    // 停止动画 本质是停止定时器
                  	// 加 return 是为了当前计时器不要再向下执行
                    return clearInterval(dom.timerId);
                }
                dom.style.left = dom.offsetLeft + 1 + 'px';

            }, 20);
}

// 调用函数
animate(div, 300);
// 点击btn1 启动span的动画
btn1.addEventListener('click', function() {
     animate(span, 200);
})
// 点击btn2 停止span的动画
btn2.addEventListener('click', function() {
     clearInterval(span.timerId)
})

缓动效果原理

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

思路:

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
  2. 核心算法: (目标值 - 现在的位置)   /  10    做为每次移动的距离步长
  3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器 
  4. 注意步长值需要取整  
function animate(dom, target) {
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(dom.timerId);
  
            dom.timerId = setInterval(function() {
              	// 因为最后面几步都是1px地移动,所以肯定能到达目标位置
                if (dom.offsetLeft == target) {
                    // 停止动画 本质是停止定时器
                  	// 加 return 是为了当前计时器不要再向下执行
                    return clearInterval(dom.timerId);
                }
              	// 步长值写到定时器的里面 步长=(目标值 - 现在的位置) / 10
                var step = (target - dom.offsetLeft) / 10;
                // 如果step>0,说明是向右移动,需要向上取整(0.2=>1);否则,是向右移动,需要向下取整(-1.2 => -2)
              	step= step > 0 ? Math.ceil(step) : Math.floor(step);
              	// 把每次加1 这个∫步长值改为一个慢慢变小的值  
                dom.style.left = dom.offsetLeft + step + 'px';

            }, 20);
}

动画函数多个目标值之间移动

可以让动画函数从 800 移动到 500。

当我们点击按钮时候,判断步长是正值还是负值

​ 1.如果是正值,则步长往大了取整

​ 2.如果是负值,则步长 向小了取整

var step = (target - dom.offsetLeft) / 10;
// 如果step>0,说明是向右移动,需要向上取整(0.2=>1);否则,是向右移动,需要向下取整(-1.2 => -2)
step= step > 0 ? Math.ceil(step) : Math.floor(step);

动函数添加回调函数

回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。

回调函数写的位置:定时器结束的位置。

callback && callback instanceof Function && callback();

function animate(dom, target, callback){
		if (dom.offsetLeft == target) {
				// 如果callback存在 且 callback为函数,就调用callback
        callback instanceof Function && callback();
				// 停止动画 本质是停止定时器
				// 加 return 是为了当前计时器不要再向下执行
				return clearInterval(dom.timerId);
     }
}

网页轮播图

轮播图也称为焦点图,是网页中比较常见的网页特效。

功能需求:

​ 1.鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。

​ 2.点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理。

​ 3.图片播放的同时,下面小圆圈模块跟随一起变化。

​ 4.点击小圆圈,可以播放相应图片。

​ 5.鼠标不经过轮播图,轮播图也会自动播放图片。

  • 自动播放功能:相当于 手动调用 点击右侧按钮事件arrow_r.click();

​ 6.鼠标经过,轮播图模块, 自动播放停止。

    let timerF = false;
    focus.addEventListener('mouseenter', function() {
        // 暂停轮播图的自动播放
        timerF = true;
    });

    focus.addEventListener('mouseleave', function() {
        // 是轮播图 继续自动播放
        timerF = false;
    });    

		setInterval(function() {
        // 如果为true,则暂停播放
        if (timerF) return;
        // 手动调用 右键点击事件
        arrow_r.click();
		}, 2000)

完整代码

window.addEventListener('load', function() {
    // 左箭头
    let arrow_l = document.querySelector('.arrow-l');
    // 右箭头
    let arrow_r = document.querySelector('.arrow-r');
    // 轮播图盒子
    let focus = this.document.querySelector('.focus');
    let focusWidth = focus.offsetWidth;
    // 判断是否暂停 自动播放
    let timerF = false;
    // 1.鼠标移入轮播图时,显示箭头
    focus.addEventListener('mouseenter', function() {
        arrow_l.style.display = 'block';
        arrow_r.style.display = 'block';
        // 暂停轮播图的自动播放
        timerF = true;
    });

    // 2.鼠标移出轮播图时,隐藏箭头
    focus.addEventListener('mouseleave', function() {
        arrow_l.style.display = 'none';
        arrow_r.style.display = 'none';
        // 是轮播图 继续自动播放
        timerF = false;
    });

    // 获取底部小圆点 的父亲 ol
    let ol = this.document.querySelector('.circle');
    // 获取焦点图 的父亲 ul
    let ul = focus.querySelector('ul');
    // 3.动态生成小圆点,有几张图片,就生成几个小圆点
    for (let i = 0; i < ul.children.length; i++) {
        let li = document.createElement('li');
        // 记录当前小圆点的索引号
        li.setAttribute('index', i);
        ol.appendChild(li);
    }

    // 4.通过事件委托,将小圆点的点击事件 绑定到 父亲ol上
    ol.addEventListener('click', function(e) {
        // 记住要判断当前点击的节点 是否为li(有可能为文本节点)
        if (e.target.nodeName == 'LI') {
            // 排他思想 设置current类名
            for (let li of this.children) {
                li.className = '';
            }
            e.target.className = 'current';
            // 拿到当前li的索引号
            let index = e.target.getAttribute('index');
            // 当我们点击了某个小li 就要把这个li 的索引号给 num 和circle 需要同步
            num = index;
            circle = index;
            // 5. 点击小圆圈,移动图片 当然移动的是 ul 
            // ul 的移动距离 小圆圈的索引号 乘以 图片的宽度 注意是负值
            animate(ul, -index * focusWidth);
        }
    });
    // 默认给第一个小圆点 添加current类名
    ol.children[0].className = 'current';

    // 6. 克隆第一张图片(li)放到ul 最后面
    let first = ul.children[0].cloneNode(true);
    ul.appendChild(first);

    // 7. 点击右侧按钮, 图片滚动一张
    // 记录图片的索引
    let num = 0;
    // 记录小圆圈的索引
    let circle = 0;
    // 节流阀
    let flag = true;
    arrow_r.addEventListener('click', function() {
        // 10.只有当一个图片动画做完,才打开节流阀,进行下一个动画
        if (flag) {
            // 关闭节流阀
            flag = false;
            // 如果走到了最后复制的一张图片,此时 我们的ul 要快速复原 left 改为 0
            if (num == ul.children.length - 1) {
                ul.style.left = 0;
                num = 0;
            }
            num++;
            animate(ul, -num * focusWidth, function() {
                // 打开节流阀
                flag = true;
            });
            // 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
            circle++;
            // 如果circle == 4 说明走到最后我们克隆的这张图片了 我们就复原
            if (circle == ol.children.length) {
                circle = 0;
            }
            circleChange();
        }
    })

    arrow_l.addEventListener('click', function() {
        // 10.只有当一个图片动画做完,才打开节流阀,进行下一个动画
        if (flag) {
            // 关闭节流阀
            flag = false;
            // 如果走到了第一张图片,此时 我们的ul 要快速到 最后一张图片位置处
            if (num == 0) {
                num = ul.children.length - 1;
                ul.style.left = -num * focusWidth + 'px';
            }
            num--;
            animate(ul, -num * focusWidth, function() {
                // 打开节流阀
                flag = true;
            });
            // 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
            circle--;
            // 如果circle <0 说明已经到了第一张图片,需要跳到最后一个小圆圈处
            if (circle < 0) {
                circle = ol.children.length - 1;
            }
            circleChange();
        }
    })

    // 图片改变,小圆圈随之改变
    function circleChange() {
        for (let li of ol.children) {
            li.className = '';
        }
        ol.children[circle].className = 'current';
    }

    // 10. 自动播放轮播图
    setInterval(function() {
        // 如果为true,则暂停播放
        if (timerF) return;
        // 手动调用 右键点击事件
        arrow_r.click();
    }, 2000)
})

节流阀

防止轮播图按钮连续点击造成播放过快。

节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。

核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。

 开始设置一个变量var flag= true;

If(flag){flag = false; do something}       关闭水龙头

利用回调函数动画执行完毕, flag = true     打开水龙头

arrow_l.addEventListener('click', function() {
        // 只有当一个图片动画做完,才打开节流阀,进行下一个动画
        if (flag) {
            // 关闭节流阀
            flag = false;
            .
            .
            animate(ul, -num * focusWidth, function() {
                // 打开节流阀
                flag = true;
            });
            .
            .
        }
    })

触屏事件

触屏事件

图片1.png

触摸事件对象

因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes

图片2.png

移动端拖动元素

  1. touchstart、touchmove、touchend 可以实现拖动元素
  2. 但是拖动元素需要当前手指的坐标值 我们可以使用  targetTouches[0] 里面的pageX 和 pageY
  3. 移动端拖动的原理:    手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
  4. 手指移动的距离:  手指滑动中的位置 减去  手指刚开始触摸的位置

拖动元素三步曲:

(1) 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置

(2) 移动手指 touchmove: 计算手指的滑动距离,并且移动盒子

(3) 离开手指 touchend:

注意: 手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动 e.preventDefault();

click 延时解决方案

移动端 click 事件会有 300ms 的延时,原因是移动端屏幕双击会缩放(double tap to zoom) 页面。

解决方案:

​ 1. 禁用缩放。 浏览器禁用默认的双击缩放行为并且去掉300ms 的点击延迟。

  <meta name="viewport" content="user-scalable=no">

​ 2.利用touch事件自己封装这个事件解决300ms 延迟。

​ 原理就是:

  1. 当我们手指触摸屏幕,记录当前触摸时间
  2. 当我们手指离开屏幕, 用离开的时间减去触摸的时间
  3. 如果时间小于150ms,并且没有滑动过屏幕, 那么我们就定义为点击

代码如下:

//封装tap,解决click 300ms 延时
function tap (obj, callback) {
        var isMove = false;
        var startTime = 0; // 记录触摸时候的时间变量
        obj.addEventListener('touchstart', function (e) {
            startTime = Date.now(); // 记录触摸时间
        });
        obj.addEventListener('touchmove', function (e) {
            isMove = true;  // 看看是否有滑动,有滑动算拖拽,不算点击
        });
        obj.addEventListener('touchend', function (e) {
            if (!isMove && (Date.now() - startTime) < 150) {  // 如果手指触摸和离开时间小于150ms 算点击
                callback && callback(); // 执行回调函数
            }
            isMove = false;  //  取反 重置
            startTime = 0;
        });
}
//调用  
  tap(div, function(){   // 执行代码  });

  1. 使用插件。fastclick 插件解决300ms 延迟。 GitHub官网
if ('addEventListener' in document) {
            document.addEventListener('DOMContentLoaded', function() {
                       FastClick.attach(document.body);
            }, false);
}