基础概念
- 浏览器读取文件内容,不会立即解析绘制界面,是先把标签解析为成对象,构造一颗 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元素属性
- 标准属性
- 浏览器根据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'};
-
自定义属性
-
因为自定义属性并没有加到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');
- 获取属性值:
-
-
h5自定义属性(data-)
- 操作方式:
- 获取属性值:
dom.getArrtribute('属性')或者dom.dataset.属性 - 设置/添加属性:
dom.setArrtribute('mydog','motty'); 或者dom.dataset.mycat='miao'; - 删除自定义属性:
dom.removeAttribute('mydog');或者delete dom.dataset.mycat;
- 获取属性值:
- 操作方式:
注意:
- getAttribute和setAttribute是万能的,各种属性都可以获取到或操作
- 自己添加自定义属性尽量以 data- 开头
补充说明
-
通过打点设置自定义属性
① 可以设置属性成功,但是没有设置到标签内,不会显示在标签里(想要显示在标签里 请使用setAttribute方法)
② 打点调用可以获取属性值,getAttribute方法不能获取属性值
-
通过setAttribute方法设置属性
① 可以设置属性成功,并且设置到标签内
② 必须通过getAttribute方法才能获取属性值,打点调用获取不到
节点操作
万物皆节点
- 浏览器会解析html页面,将页面中的内容都创建成节点对象
- 节点对象 有很多类型,都有相同的三个属性:nodeType、nodeName、nodeValue
- 常见节点类型:
- 元素节点:根据html标签生成
- 文本节点:标签内部或外部的文本
- 注释节点
获取节点的标签名
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;
}
})
父子节点
-
父节点
-
dom.parentNode会找到父亲所有的节点(包含文本节点等) -
dom.parentElement只会找到父亲的元素节点 -
两个只在删除html父标签时不一样,其他情况下都是删除父元素,效果一样。
// document.documentElement为html标签,其的父元素为document,不是标签 - document.documentElement.parentNode;// #document - document.documentElement.parentElement;// null
-
-
子节点
ul.childNodes,找到所有的子节点 (包含 元素节点 文本节点等等)ul.children,获取所有的子元素节点 也是我们实际开发常用的ol.children[0] / ol.children[ol.children.length - 1],实际开发的写法 既没有兼容性问题又返回第一个子元素ol.firstChild / ol.lastChild,第一个/最后一个子节点 不管是文本节点还是元素节点ol.firstElementChild / ol.lastElementChild,返回第一个子元素节点 ie9才支持
兄弟节点
-
nextSibling 下一个兄弟节点 包含元素节点或者 文本节点等等
- div.nextSibling
- div.previousSibling
-
nextElementSibling得到下一个兄弟元素节点,推荐(不兼容ie9之前)- div.nextElementSibling
- div.previousElementSibling
-
获取所有的兄弟节点
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>
三种动态创建元素区别
-
document.write()创建元素 如果页面文档流加载完毕,再调用这句话会导致页面重绘// 如果页面已经加载完毕了,再点击btn,页面原有的内容都没有了,只有<div>123</div> var btn = document.querySelector('button'); btn.onclick = function() { document.write('<div>123</div>'); } -
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(''); -
document.createElement()创建元素for (var i = 0; i <= 1000; i++) { var a = document.createElement('a'); document.body.appendChild(a); } // 速度居中 // 慢的原因:17ms // 1.创建了1000次对象 // 2.重绘了页面了1000次 -
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种方式)
-
传统注册方式(以on开头)
- 注册事件的唯一性
- 同一个元素同一个事件只能设置一个处理函数,如果有多个,后面的注册函数会覆盖前面的
- 0级dom事件
- 属性是直接加给dom对象的
-
监听注册方式(addEventListener())
-
w3c标准 推荐(IE9之前不支持,可用attachEvent()-不推荐 代替)
-
同一个元素同一个事件可以注册多个监听器
-
默认按注册顺序依次执行
-
2级dom事件
-
属性不会加给dom对象,而是加到内存里的2级dom事件表里(虚拟)
-
<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>
删除事件(解绑事件)
-
传统注册方式
- eventTarget.onclick=null;
-
方法监听注册方式
- 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个阶段:
-
捕获阶段
-
当前目标阶段
-
冒泡阶段
我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点( 最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。
注意:
-
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>
事件对象
事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象。
如:谁绑定了这个事件,鼠标位置,按了哪个键等
事件触发发生时就会产生事件对象,并且系统会以实参的形式传给事件处理函数。
事件对象的兼容性处理
在 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>
事件对象的属性和方法
e.target 和 this 的区别
-
this 是事件绑定的元素(绑定这个事件处理函数的元素) 。
-
e.target 是事件触发的元素。
通常情况下terget 和 this是一致的, 但有一种情况不同,那就是在事件冒泡时(父子元素有相同事件,单击子元素,父元素的事件处理函数也会被触发执行), 这时候this指向的是父元素,因为它是绑定事件的元素对象, 而target指向的是子元素,因为他是触发事件的那个具体元素对象。
#事件冒泡下的e.target和this
<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>
常用鼠标事件
案例:禁止选中文字和禁止右键菜单
// 禁用一般是为了阻止浏览器显示默认的右键菜单,因为需要自己写右键菜单的样式功能等
<body>
我是一段不愿意分享的文字
<script>
// 1. contextmenu 我们可以禁用右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})
// 2. 禁止选中文字 selectstart
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
</script>
</body>
mouseout
注意:mouseout必须是 鼠标 移除目标元素的边界才会触发,而“踩着”其他元素 移出 是不会触发的
注意点
- mouseover和mouseout在父元素和子元素中都可以触发,当鼠标经过某个元素时,触发的次数取决于子元素的个数;
- mouseenter和mouseleave只在父元素触发,当鼠标经过某个元素时,只会触发一次;
- 当四个函数都在父子元素中使用时,mouseover和mouseout比mouseenter和mouseleave先触发;
- 一般来说:
- mouseover和mouseout一起使用,mouseenter和mouseleave一起使用
- 如果元素内部没有子元素影响,可以考虑用mouseover和mouseout;
- 如果元素内有子元素影响,可以采用mouseenter和mouseleave,防止事件冒泡;
鼠标事件对象
获取鼠标在页面的坐标(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);
})
案例:跟随鼠标的天使
<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>
常用的键盘事件
键盘事件
三个事件的执行顺序:keydown -- keypress -- keyup
keyup和keydown的区别
文本框的内容输入 和 keydown 和 keyup 的顺序
-
先触发 keydown事件,此时 文本框 还没有输入 被按下的按钮内容
-
将按键 添加到文本框中
-
按键抬起,触发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。
顶级对象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 是调整窗口大小加载事件, 当触发时就调用的处理函数。
注意:
-
只要窗口大小发生像素变化,就会触发这个事件。
-
我们经常利用这个事件完成响应式布局。 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即为定时器的标识符
倒计时案例
// 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指向问题
-
全局作用域或者普通函数中this指向全局对象window(注意定时器里面的this指向window)
-
方法调用中谁调用this指向谁
-
构造函数中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概念
URL
location对象的属性
重点: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对象的常见方法
<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。
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)。
同步任务指的是:
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是:
不进入主线程、而进入”任务队列”的任务,当主线程中的任务运行完了,才会从”任务队列”取出异步任务放入主线程执行。
回调函数:函数作为参数传入
事件循环(event loop)
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概述
- 获得元素距离带有定位父元素的位置
- 直接获取 最后样式位置运算(合编样式表=行内样式+内嵌样式+外链样式)的结果
- 获得元素自身的大小(宽度高度)
- 注意:返回的数值都不带单位
<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
模态框拖拽
// 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);
})
})
放大镜效果
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 系列
淘宝 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 系列的相关属性可以动态的得到该元素的大小、滚动距离等。
页面被卷去的头部
如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发 onscroll事件。
仿淘宝固定右侧侧边栏(window.pageYOffset)
- 原先侧边栏是绝对定位
- 当页面滚动到一定位置,侧边栏改为固定定位
- 页面继续滚动,会让 返回顶部显示出来
注意:
- 元素被卷去的头部是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';
}
})
各系列总结-待补充
他们主要用法:
1.offset系列 经常用于获得元素位置 offsetLeft offsetTop
2.client经常用于获取元素大小 clientWidth clientHeight
3.scroll 经常用于获取滚动距离 scrollTop scrollLeft
4.注意页面滚动的距离通过 window.pageXOffset 获得
5.鼠标系列
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 只会经过自身盒子触发
- mouseover-----只要鼠标在盒子上面(盒子上面有子盒子也会)就会触发
- mousenter------进入自身盒子(包含子盒子的边界)会触发,此时子盒子和父盒子可以看作为一体
- 之所以这样,就是因为
mouseenter不会冒泡 - 跟mouseenter搭配鼠标离开 mouseleave 同样不会冒泡
- 当四个函数都在父子元素中使用时,mouseover和mouseout比mouseenter和mouseleave先触发;
动画函数
动画实现原理
核心原理:通过定时器 setInterval() 不断移动盒子位置。
实现步骤:
- 获得盒子当前位置
- 让盒子在当前位置加上1个移动距离
- 利用定时器不断重复这个操作
- 加一个结束定时器的条件
- 注意此元素需要添加定位,才能使用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)
})
缓动效果原理
缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来
思路:
- 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
- 核心算法:
(目标值 - 现在的位置) / 10做为每次移动的距离步长 - 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
- 注意步长值需要取整
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;
});
.
.
}
})
触屏事件
触屏事件
触摸事件对象
因为平时我们都是给元素注册触摸事件,所以重点记住 targetTocuhes
移动端拖动元素
- touchstart、touchmove、touchend 可以实现拖动元素
- 但是拖动元素需要当前手指的坐标值 我们可以使用 targetTouches[0] 里面的pageX 和 pageY
- 移动端拖动的原理: 手指移动中,计算出手指移动的距离。然后用盒子原来的位置 + 手指移动的距离
- 手指移动的距离: 手指滑动中的位置 减去 手指刚开始触摸的位置
拖动元素三步曲:
(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 延迟。
原理就是:
- 当我们手指触摸屏幕,记录当前触摸时间
- 当我们手指离开屏幕, 用离开的时间减去触摸的时间
- 如果时间小于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(){ // 执行代码 });
- 使用插件。fastclick 插件解决300ms 延迟。 GitHub官网
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}