概述
文档对象模型,将网页转为一个js对象,实现各种操作。
Document节点
document.open() document.close()
document.open清除当前文档所有内容,使文档处于可写状态document.close关闭document.open()打开的文档
document.querySelector() document.querySelectorAll()
接受CSS选择器作为参数,返回匹配该选择器的元素节点。
document.addEventListener() document.removeEventListener() document.dispatchEvent()
// 添加事件监听函数
document.addEventListener('click', listener, false);
// 移除事件监听函数
document.removeEventListener('click', listener, false);
// 触发事件
var event = new Event('click');
document.dispatchEvent(event);
Element节点
Element.clientHeight Element.clientWidth
// 视口高度
document.documentElement.clientHeight
// 网页总高度
document.body.clientHeight
Element.clientLeft Element.clientTop
-
元素节点左边框的宽度,不包括左侧的
padding和margin -
网页元素顶部边框的宽度
Element.scrollHeight Element.scrollWidth
// 网页总高度
document.documentElement.scrollHeight
document.body.scrollHeight
Element.scrollLeft Element.scrollTop
// 整张网页水平垂直的滚动距离
document.documentElement.scrollLeft
document.documentElement.scrollTop
Element.offsetHeight Element.offsetWidth
元素CSS垂直高度,包括元素本身的高度、padding和 border,以及水平滚动条的高度,如果存在的话。
元素CSS水平宽度。
Element.offsetLeft Element.offsetTop
返回当前元素左上角相对于Element.offsetParent节点的水平位移,Element.offsetTop返回垂直位移,单位为像素。
Element.focus() Element.blur()
将当前页面的焦点,转移到指定元素上。
将焦点从当前元素移除。
document.getElementById('my-span').focus();
属性操作
Element.getAttribute()
返回当前元素节点的指定属性。
Element.setAttribute()
为当前元素节点新增属性。
Element.hasAttribute()
当前元素节点是否包含指定属性。
Element.removeAttribute()
从当前元素节点移除属性。
事件模型
EventTarget.addEventListener()
EventTarget.removeEventListener()
EventTarget.dispatchEvent()
在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true。
监听函数
js有三种形式为事件绑定监听函数:
- 1
<body onload = "doSomething()">
<div onclick = "console.log('触发事件')">
- 2
window.onload = doSomething;
div.onclick = function(event) {
console.log('触发事件');
};
- 3 推荐
window.addEventListener('load', doSomething, false);
事件传播
事件发生之后,会在子元素和父元素之间进行传播,共有三个阶段:
- 第一阶段(捕获):从
window对象传导到目标节点,上层传到底层 - 第二阶段(目标):在目标节点上触发
- 第三阶段(冒泡):从目标节点传导回
window对象,从底层传回上层
<div>
<p>点击</p>
</div>
var phases = {
1: 'capture',
2: 'target',
3: 'bubble'
};
var div = document.querySelector('div');
var p = document.querySelector('p');
div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);
function callback(event) {
var tag = event.currentTarget.tagName;
var phase = phases[event.eventPhase];
console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}
// 点击结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'
上例事件的传播顺序:
-
捕获阶段:
window、document、html、body、div、p -
冒泡阶段:
p、div、body、html、document、window
事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.body)。
事件代理
由于事件会在冒泡阶段向上传播到父节点,因此可把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素事件。
优点:只要定义一个监听函数,就能处理多个子节点事件。
var ul = document.querySelector('ul');
ul.addEventListener('click', function(event) {
if(event.target.tagName.toLowerCase() === 'li') {
// some code
}
});
stopPropagation
阻止事件的传播,只是阻止传播,并没有彻底取消click事件。
// 事件传播到`p`元素后就不再向下传播
p.addEventListener('click', function(event) {
event.stopPropagation();
}, true);
// 事件冒泡到`p`元素后,就不再向上冒泡
p.addEventListener('click', function(event) {
event.stopPropagation();
}, false);
Event对象
事件发生后,会产生一个事件对象,作为参数传给监听函数。
浏览器提供了一个Event对象,所有事件都是这个对象的实例。
Event.preventDefault()
取消浏览器对当前事件的默认行为(a标签跳转事件)。
注意:
-
并不会阻止事件的传播
-
若要阻止传播,使用
stopPropagation()或stopImmediatePropagation()
Event.stopPropagation()
阻止事件在DOM中继续传播。
function stopEvent(e) {
e.stopPropagation();
}
el.addEventListener('click', stopEvent, false);
// click事件将不会进一步冒泡到el的父节点上
Event.currentTarget Event.target
-
返回事件当前所在的节点,即正在执行的监听函数所绑定的那个节点
-
返回原始触发事件的那个节点,即事件最初发生的节点
<p id="para">
Hello <em>World</em>
</p>
function hide(e) {
console.log(this === e.currentTarget); // 总是 true
console.log(this === e.target); // 有可能不是 true
e.target.style.visibility = 'hidden';
}
para.addEventListener('click', hide, false);
事件类型
鼠标事件
clickdblclickmousedownmouseupmousemove鼠标在一个节点内部移动时触发,当鼠标持续移动时会连续触发mouseenter鼠标进入一个节点时触发,进入子节点不会触发这个事件mouseover鼠标进入一个节点时触发,进入子节点会再一次触发这个事件mouseout鼠标离开一个节点时触发,离开父节点也会触发这个事件mouseleave鼠标离开一个节点时触发,离开父节点不会触发这个事件wheel
完成mousedown动作,再完成mouseup动作,
触发顺序是: mousedown,mouseup,click。
-
mouseenter事件只触发一次。 -
只要鼠标在节点内部移动,
mouseover会触发多次。 -
在父元素内部离开一个子元素时,
mouseleave不会触发,mouseout事件会触发。
MouseEvent.clientX MouseEvent.clientY
返回鼠标位置相对于浏览器窗口左上角的水平坐标和垂直坐标。
MouseEvent.screenX MouseEvent.screenY
返回鼠标位置相对于屏幕左上角的水平和垂直坐标。
MouseEvent.offsetX MouseEvent.offsetY
返回鼠标位置与目标节点左侧的padding边缘的水平和垂直距离。
键盘事件
-
keydown -
keypress -
keyup
如果用户一直按键不松开,就会连续触发键盘事件,触发的顺序如下:
keydownkeypresskeydownkeypress- …(重复以上过程)
keyup
进度事件
描述资源加载进度及文件上传。
error:由于错误导致外部资源无法加载时触发load:外部资源加载成功时触发loadstart:外部资源开始加载时触发loadend:外部资源停止加载时触发progress:外部资源加载过程中不断触发timeout:加载超时时触发
拖拉事件
<div draggable="true">
此区域可拖拉
</div>
drag:拖拉过程中dragstart:用户开始拖拉时,在被拖拉的节点上触发dragend:拖拉结束时,在被拖拉的节点上触发dragenter:拖拉进入当前节点时,在当前节点上触发一次dragover:拖拉到当前节点上方,只要没有离开这个节点,dragover事件会持续触发dragleave:拖拉操作离开当前节点范围时drop:被拖拉的节点或选中的文本,释放到目标节点时,在目标节点上触发
div.addEventListener('dragstart', function (e) {
this.style.backgroundColor = 'red';
}, false);
div.addEventListener('dragend', function (e) {
this.style.backgroundColor = 'green';
}, false);
// 将一个节点从当前父节点,拖拉到另一个父节点中
/* HTML 代码如下
<div class="dropzone">
<div id="draggable" draggable="true">
该节点可拖拉
</div>
</div>
<div class="dropzone"></div>
<div class="dropzone"></div>
<div class="dropzone"></div>
*/
// 被拖拉节点
var dragged;
document.addEventListener('dragstart', function (event) {
// 保存被拖拉节点
dragged = event.target;
// 被拖拉节点的背景色变透明
event.target.style.opacity = 0.5;
}, false);
document.addEventListener('dragend', function (event) {
// 被拖拉节点的背景色恢复正常
event.target.style.opacity = '';
}, false);
document.addEventListener('dragover', function (event) {
// 防止拖拉效果被重置,允许被拖拉的节点放入目标节点
event.preventDefault();
}, false);
document.addEventListener('dragenter', function (event) {
// 目标节点的背景色变紫色
// 由于该事件会冒泡,所以要过滤节点
if (event.target.className === 'dropzone') {
event.target.style.background = 'purple';
}
}, false);
document.addEventListener('dragleave', function( event ) {
// 目标节点的背景色恢复原样
if (event.target.className === 'dropzone') {
event.target.style.background = '';
}
}, false);
document.addEventListener('drop', function( event ) {
// 防止事件默认行为(比如某些元素节点上可以打开链接),
event.preventDefault();
if (event.target.className === 'dropzone') {
// 恢复目标节点背景色
event.target.style.background = '';
// 将被拖拉节点插入目标节点
dragged.parentNode.removeChild(dragged);
event.target.appendChild( dragged );
}
}, false);
注意:
- 拖拉过程只触发拖拉事件,不触发鼠标事件
dragenter和dragover事件的监听函数,用来取出拖拉数据
DataTransfer.files
// 接收拖拉文件的例子
// HTML 代码如下
// <div id="output" style="min-height: 200px;border: 1px solid black;">
// 文件拖拉到这里
// </div>
var div = document.getElementById('output');
div.addEventListener("dragenter", function( event ) {
div.textContent = '';
event.stopPropagation();
event.preventDefault();
}, false);
div.addEventListener("dragover", function( event ) {
event.stopPropagation();
event.preventDefault();
}, false);
div.addEventListener("drop", function( event ) {
event.stopPropagation();
event.preventDefault();
var files = event.dataTransfer.files;
for (var i = 0; i < files.length; i++) {
div.textContent += files[i].name + ' ' + files[i].size + '字节\n';
}
}, false);
表单事件
input
select
<input id="test" type="text" value="Select me!" />
var elem = document.getElementById('test');
elem.addEventListener('select', function (e) {
console.log(e.type); // "select"
}, false);
submit事件
表单数据向服务器提交时触发。
窗口事件
scroll事件
缺点:用户拖动滚动条时触发,会连续大量触发,所以它的监听函数不应该有耗费计算的操作。
推荐使用requestAnimationFrame或setTimeout控制该事件的触发频率,然后结合customEvent抛出一个新事件。
- 1
(function () {
var throttle = function (type, name, obj) {
var obj = obj || window;
var running = false;
var func = function () {
if (running) { return; }
running = true;
requestAnimationFrame(function() {
obj.dispatchEvent(new CustomEvent(name));
running = false;
});
};
obj.addEventListener(type, func);
};
// 将 scroll 事件重定义为 optimizedScroll 事件
throttle('scroll', 'optimizedScroll');
})();
window.addEventListener('optimizedScroll', function() {
console.log('Resource conscious scroll callback!');
});
上面代码中,throttle函数用于控制事件触发频率,requestAnimationFrame方法保证每次页面重绘(每秒60次),只会触发一次scroll事件的监听函数。
- 2
改用setTimeout方法,可以放置更大的时间间隔。
(function() {
window.addEventListener('scroll', scrollThrottler, false);
var scrollTimeout;
function scrollThrottler() {
if (!scrollTimeout) {
scrollTimeout = setTimeout(function () {
scrollTimeout = null;
actualScrollHandler();
}, 66);
}
}
function actualScrollHandler() {
// ...
}
}());
上面代码中,每次scroll事件都会执行scrollThrottler函数。
该函数里面有一个定时器setTimeout,每66毫秒触发一次(每秒15次)真正执行的任务actualScrollHandler。
- 3
下面是一个更一般的throttle函数的写法:
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}
window.addEventListener('scroll', throttle(callback, 1000));
上面代码将scroll事件的触发频率,限制在一秒一次。
- 4
lodash函数库提供了现成的throttle函数,可以直接使用。
window.addEventListener('scroll', _.throttle(callback, 1000));
剪贴板事件
cutcopypaste
焦点事件
focusblur
由于focus和blur事件不会冒泡,只能在捕获阶段触发,所以addEventListener方法的第三个参数需要设为true。
CSS操作
getAttribute、setAttribute、removeAttribute
div.setAttribute(
'style',
'background-color:red;' + 'border:1px solid black;'
);
// 相当于
<div style="background-color:red; border:1px solid black;" />
var divStyle = document.querySelector('div').style;
divStyle.backgroundColor = 'red';
divStyle.border = '1px solid black';
divStyle.width = '100px';
divStyle.height = '100px';
divStyle.fontSize = '10em';