DOM
DOM(Document Object Model,文档对象模型)是 HTML 和 XML 文档的编程接口。它提供了对文档的结构化表示,并定义了一种方式使程序可以访问和操作文档的结构、样式和内容。
核心概念:
- 树状结构:DOM 将文档表示为节点和对象的树状结构
- 平台和语言中立:虽然通常通过 JavaScript 操作,但 DOM 是独立于语言的
- W3C 标准:由万维网联盟(W3C)制定和维护
DOM 节点类型
1. 主要节点类型
// 常见节点类型及其 nodeType 值
ELEMENT_NODE // 1 - 元素节点
TEXT_NODE // 3 - 文本节点
COMMENT_NODE // 8 - 注释节点
DOCUMENT_NODE // 9 - 文档节点
DOCUMENT_TYPE_NODE // 10 - 文档类型节点
DOCUMENT_FRAGMENT_NODE // 11 - 文档片段节点
2. 节点关系
// DOM 树中的关系
parentNode // 父节点
childNodes // 所有子节点的列表
firstChild // 第一个子节点
lastChild // 最后一个子节点
nextSibling // 下一个兄弟节点
previousSibling // 上一个兄弟节点
DOM操作API
1.选择元素
// 根据 ID 选择
const element = document.getElementById('myId');// 根据类名选择(返回 HTMLCollection)
const elements = document.getElementsByClassName('myClass');// 根据标签名选择(返回 HTMLCollection)
const divs = document.getElementsByTagName('div');// CSS 选择器选择(返回第一个匹配元素)
const firstMatch = document.querySelector('.container p');// CSS 选择器选择所有(返回 NodeList)
const allMatches = document.querySelectorAll('.item');// 表单元素选择
const form = document.forms['myForm'];
const input = form.elements['username'];
2.创建和添加元素
const menu = document.createElement("ul");
const li_1 = document.createElement("li");
li_1.textContent = "产品";
const li_2 = document.createElement("li");
li_2.textContent = "服务";
const li_3 = document.createElement("li");
li_3.textContent = "联系我";
const li_4 = document.createElement("li");
li_4.textContent = "博客";
const li_5 = document.createElement("li");
li_5.textContent = "码云";
document.body.appendChild(menu); // 添加到子节点末尾
menu.prepend(li_1); // 添加到子节点开头
li_1.after(li_3); // 在元素后插入
li_3.before(li_2); //在元素前插入
menu.append(li_4, li_5); //添加多个节点let li = document.createElement("li");
li.textContent = "首页";
menu.insertBefore(li, menu.firstElementChild); // 在指定节点前插入const oldDiv = document.createElement("div");
oldDiv.textContent = "oldDiv";
document.body.appendChild(oldDiv); // 添加到子节点末尾
const newDiv = document.createElement("div");
newDiv.textContent = "newDiv";
const anotherDiv = document.createElement("div");
anotherDiv.textContent = "anotherDiv";//替换元素
document.body.replaceChild(newDiv, oldDiv);
newDiv.replaceWith(anotherDiv);
在添加多个元素时,可能会造成性能问题。需要尽量减少DOM操作:
// 不好:多次重排
for (let i = 0; i < 100; i++) {document.body.appendChild(document.createElement('div'));
}// 好:使用文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {fragment.appendChild(document.createElement('div'));
}
document.body.appendChild(fragment);// 好:使用 innerHTML 批量更新
const items = ['item1', 'item2', 'item3'];
list.innerHTML = items.map(item => `<li>${item}</li>`).join('');
3.删除元素
//移除元素
document.body.removeChild(menu);
anotherDiv.remove();
4.克隆元素
// 克隆元素
// 浅克隆(不克隆子节点)
const shallowClone = menu.cloneNode(false);
// 深克隆(克隆所有子节点)
const deepClone = menu.cloneNode(true);document.body.appendChild(shallowClone);
document.body.appendChild(deepClone);
5.修改元素属性和样式
const element = document.createElement("div");
// 操作属性
element.setAttribute("id", "newId");
element.getAttribute("id");
element.removeAttribute("id");
element.hasAttribute("class");// classList API(推荐用于类操作)
element.classList.add("active");
element.classList.remove("hidden");
element.classList.toggle("visible");
element.classList.contains("className");// 操作样式
element.style.color = "red";
element.style.backgroundColor = "#fff";
element.style.cssText = "color: red; background: blue;";document.body.appendChild(element);
// 获取计算样式
const computedStyle = window.getComputedStyle(element);
const color = computedStyle.color;console.log(computedStyle, color);
DOM事件
1.事件处理/监听
// 三种添加事件的方式
// 1. HTML 属性
// <button onclick="handleClick()">点击</button>// 2. DOM 属性
button.onclick = function() {console.log('点击事件');
};// 3. addEventListener(推荐)
element.addEventListener('click', handler, options);
element.removeEventListener('click', handler);
// 一次性事件监听
element.addEventListener('click', function(event) {console.log('只执行一次');
}, { once: true });
2.事件对象
element.addEventListener('click', function(event) {// 常用属性console.log(event.target); // 触发事件的元素console.log(event.currentTarget); // 绑定事件的元素console.log(event.type); // 事件类型console.log(event.clientX); // 鼠标X坐标console.log(event.clientY); // 鼠标Y坐标console.log(event.key); // 按键值// 事件方法event.preventDefault(); // 阻止默认行为event.stopPropagation(); // 阻止事件冒泡event.stopImmediatePropagation(); // 阻止其他监听器执行
});
3.事件流
事件流描述了页面接收事件的顺序。结果非常有意思,IE和 Netscape 开发团队提出了几乎完全相反的事件流方案。IE将支持事件冒泡流,而 Netscape Communicator 将支持事件捕获流。
事件冒泡
IE事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触 发,然后向上传播至没有那么具体的元素(文档)。比如有如下HTML页面:
<!DOCTYPE html>
<html>
<head> <title>Event Bubbling Example</title>
</head>
<body> <div id="myDiv">Click Me</div>
</body>
</html>
在点击页面中的
(1)<div>
(2)<body>
(3)<html>
(4)document
也就是说,

所有现代浏览器都支持事件冒泡,只是在实现方式上会有一些变化。IE5.5及早期版本会跳过元素(从直接到document)。现代浏览器中的事件会一直冒泡到window对象。
事件捕获
Netscape Communicator 团队提出了另一种名为事件捕获的事件流。事件捕获的意思是最不具体的节 点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标 前拦截事件。如果前面的例子使用事件捕获,则点击
元素会以下列顺序触发click事件:
(1) document
(2) <html>
(3) <body>
(4) <div>
在事件捕获中,click事件首先由document元素捕获,然后沿DOM树依次向下传播,直至到达实际的目标元素div。这个过程如图所示。

由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获。通常建议使用事件冒泡,特殊情况下可以使用事件捕获。
事件流
DOM2 Events规范规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生, 为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。

在DOM事件流中,实际的目标(div元素)在捕获阶段不会接收到事件。这是因为捕获阶段从 document 到再到就结束了。下一阶段,即会在元素上触发事件的“到达目标” 阶段,通常在事件处理时被认为是冒泡阶段的一部分。然后,冒泡阶段开始,事件反向传 播至文档。
常用事件类型
// 鼠标事件
click, dblclick, mousedown, mouseup, mousemove, mouseover, mouseout// 键盘事件
keydown, keyup, keypress// 表单事件
submit, change, input, focus, blur// 窗口事件
load, resize, scroll// 触摸事件(移动端)
touchstart, touchmove, touchend
DOM3 Events定义了如下事件类型。
- 用户界面事件(UIEvent):涉及与BOM交互的通用浏览器事件。
- 焦点事件(FocusEvent):在元素获得和失去焦点时触发。
- 鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
- 滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。
- 输入事件(InputEvent):向文档中输入文本时触发。
- 键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。
- 合成事件(CompositionEvent):在使用某种IME(Input Method Editor,输入法编辑器)输入字符时触发。
用户界面事件
定义:用户界面事件是一类与浏览器用户界面交互相关的事件,通常涉及窗口、文档、表单元素等界面的变化。
常见事件:
-
load:当页面(包括所有资源)完全加载后触发。通常用于初始化操作。window.addEventListener("load",(event)=>{console.log("Loaded!"); }) -
unload:当页面卸载(例如关闭页面或跳转到其他页面)时触发。可用于清理资源。 -
abort:当资源加载被中止时触发(如图像加载被用户取消)。 -
error:当资源加载失败时触发(如图像加载错误)。 -
resize:当窗口或框架大小改变时触发。 -
scroll:当用户滚动包含滚动条的元素时触发。
典型应用场景:
- 在
load事件中执行页面初始化,如获取数据、绑定事件。 - 在
resize事件中调整页面布局,实现响应式设计。 - 在
scroll事件中实现无限滚动或动态加载内容。
注意事项:
load事件可能会因为资源过多而延迟,建议将非必要的初始化操作放在DOMContentLoaded事件中(DOM 解析完成后立即触发,无需等待样式表、图像等资源)。unload事件中不宜进行耗时操作,因为页面即将卸载,浏览器可能会限制某些操作。
焦点事件
定义:焦点事件在元素获得或失去焦点时触发,通常用于表单元素(如 input、textarea)或可聚焦元素(如链接、按钮)。
常见事件:
focus:当元素获得焦点时触发(不会冒泡)。blur:当元素失去焦点时触发(不会冒泡)。focusin:当元素即将获得焦点时触发(会冒泡)。focusout:当元素即将失去焦点时触发(会冒泡)。
典型应用场景:
- 表单验证:在
blur事件中验证输入内容,及时给出提示。 - 增强用户体验:在
focus事件中高亮显示当前输入框,在blur时取消高亮。
注意事项:
focus和blur事件不会冒泡,但可以通过事件捕获阶段处理,或者使用focusin和focusout(它们会冒泡)。- 并非所有元素都可以获得焦点,只有可聚焦元素(如表单元素、带
tabindex属性的元素)才会触发焦点事件。
鼠标事件
定义:鼠标事件是由用户操作鼠标(或类似设备,如触摸板)时触发的事件。
常见事件:
click:单击鼠标左键(或按下回车键)时触发。dblclick:双击鼠标左键时触发。mousedown:按下任意鼠标按钮时触发。mouseup:释放任意鼠标按钮时触发。mousemove:鼠标在元素内移动时持续触发。mouseover:鼠标进入元素时触发(会冒泡)。mouseout:鼠标离开元素时触发(会冒泡)。mouseenter:鼠标进入元素时触发(不会冒泡)。mouseleave:鼠标离开元素时触发(不会冒泡)。
4个点击事件顺序触发:
- mousedown
- mouseup
- click
- mousedown
- mouseup
- click
- dbclick
click和dbclick在触发前都依赖其他事件触发,mousedown和mouseup则不会受其他事件影响。
典型应用场景:
- 交互式元素:如按钮点击、拖拽、右键菜单等。
- 游戏开发:通过
mousemove跟踪鼠标位置,实现游戏控制。
注意事项:
mouseover和mouseout会冒泡,而mouseenter和mouseleave不会冒泡。根据是否需要事件冒泡来选择使用。- 鼠标事件包含丰富的信息,如鼠标位置(
clientX,clientY)、按下的按钮(button)、修饰键状态(ctrlKey,shiftKey等)。
滚轮事件
定义:滚轮事件是鼠标事件的一个子类,专门处理鼠标滚轮(或类似设备,如触摸板滚动)的交互。
常见事件:
wheel:滚动鼠标滚轮时触发。
典型应用场景:
- 页面缩放:通过滚轮事件控制图片或地图的缩放。
- 自定义滚动:实现自定义滚动条或水平滚动。
注意事项:
- 滚轮事件提供了
deltaX、deltaY、deltaZ属性,分别表示滚轮在 X、Y、Z 轴上的滚动量。正值表示向右、向下、向外滚动。 - 可以通过
event.preventDefault()阻止默认的滚动行为,但需谨慎使用,以免影响用户体验。
键盘事件
定义:键盘事件由键盘操作触发,例如按下或释放按键。
常见事件:
keydown:按下任意按键时触发。keypress:按下并释放一个字符键(如字母、数字)时触发(已废弃,不推荐使用)。keyup:释放按键时触发。
典型应用场景:
- 快捷键:通过监听
keydown事件实现快捷键功能(如 Ctrl+S 保存)。 - 游戏控制:使用键盘控制游戏角色移动(如方向键)。
注意事项:
keypress事件已被废弃,因为它只能报告产生字符的按键,而keydown和keyup可以报告所有按键。建议使用keydown或keyup代替。- 键盘事件包含按键信息,如
key属性表示按下的键值,code表示物理按键代码,ctrlKey、shiftKey等表示修饰键状态。
合成事件
定义:合成事件是在使用输入法(IME)输入字符时触发的事件。例如,在中文、日文等需要多次击键才能组成一个字符的输入法中,合成事件用于管理输入过程中间状态。
常见事件:
compositionstart:当输入法编辑器开始一次新的合成时触发(例如,用户开始输入拼音)。compositionupdate:在合成过程中每次输入新字符时触发(例如,拼音串的变化)。compositionend:当合成完成,用户确认输入一个字符时触发(例如,从候选词中选择一个汉字)。
典型应用场景:
- 输入法兼容:在需要精确控制输入过程的应用中(如实时翻译、输入验证),可以通过合成事件区分用户是在输入过程中还是已完成输入。
注意事项:
- 合成事件通常与输入事件(
input)结合使用。在输入法输入过程中,可能会触发多次compositionupdate和input事件,但只有compositionend之后才表示最终输入完成。 - 在处理输入内容时,需要注意合成事件的状态,避免在输入过程中进行不必要的处理(如实时验证)。
鼠标事件坐标
客户端坐标(clientX, clientY)
是相对于当前视口(viewport)的坐标,即浏览器窗口中可见区域(不包括工具栏、滚动条等)的左上角为原点(0,0)。
特性
- 当用户滚动页面时,视口的位置会改变,但客户端坐标的原点(视口左上角)始终不变。
- 因此,如果你在页面最顶部点击一个元素,然后滚动页面并再次点击同一位置(相对于视口),那么客户端坐标是相同的。
使用场景
- 当你需要知道鼠标在用户当前可见区域内的位置时,例如实现拖拽功能,通常使用客户端坐标。
element.addEventListener('click', function(event) {console.log('Client X: ' + event.clientX + ', Client Y: ' + event.clientY);
});
页面坐标(pageX, pageY)
是相对于整个文档(document)的坐标,即文档的左上角为原点(0,0)。
特性
- 当页面没有滚动时,页面坐标和客户端坐标是相同的。
- 当页面滚动时,页面坐标会包括被滚动隐藏的部分,即文档的左上角(包括滚动隐藏的部分)作为原点。
使用场景
- 当你需要知道鼠标在整个文档中的位置时,例如记录点击位置在文档中的绝对位置,或者需要将元素定位到鼠标点击的位置(考虑滚动偏移)。
element.addEventListener('click', function(event) {console.log('Page X: ' + event.pageX + ', Page Y: ' + event.pageY);
});
屏幕坐标(screenX, screenY)
屏幕坐标是相对于用户屏幕的坐标,即整个屏幕的左上角为原点(0,0)。
特性
- 屏幕坐标不考虑浏览器窗口的位置,而是整个屏幕。
- 如果你将浏览器窗口移动到屏幕的不同位置,然后在同一位置点击,屏幕坐标会发生变化。
使用场景
- 当你需要知道鼠标在用户屏幕上的绝对位置时,例如在多个显示器环境中定位,或者与操作系统级别交互时。
element.addEventListener('click', function(event) {console.log('Screen X: ' + event.screenX + ', Screen Y: ' + event.screenY);
});
坐标之间的关系
pageX=clientX+ 文档水平滚动距离pageY=clientY+ 文档垂直滚动距离
文档水平滚动距离可以通过window.scrollX(或window.pageXOffset)获取,垂直滚动距离可以通过window.scrollY(或window.pageYOffset)获取。
element.addEventListener('click', function(event) {var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;console.log('Client: (' + event.clientX + ', ' + event.clientY + ')');console.log('Page: (' + event.pageX + ', ' + event.pageY + ')');console.log('Screen: (' + event.screenX + ', ' + event.screenY + ')');console.log('Calculated page from client and scroll: (' + (event.clientX + scrollLeft) + ', ' + (event.clientY + scrollTop) + ')');
});
注意事项
- 兼容性:这些属性在主流浏览器中都得到了很好的支持,但在旧版IE(IE8及以下)中,
pageX和pageY不可用。在旧版IE中,可以通过clientX和clientY加上文档的滚动距离来计算。 - 坐标系:这些坐标都是基于CSS像素,并且受CSS缩放影响。
- 事件类型:这些坐标属性通常存在于鼠标事件(如click, mousemove)和触摸事件(触摸事件有类似属性,但可能不同)中。
实际应用
- 拖拽:通常使用客户端坐标,因为拖拽元素时,我们关心的是在视口中的位置。
- 定位元素:如果要将一个元素定位到鼠标点击的位置,并且希望即使页面滚动也保持在同一文档位置,那么使用页面坐标。
- 全屏应用:如果应用是全屏的,或者需要跨窗口交互,可能会使用屏幕坐标。
修饰键
虽然鼠标事件主要是通过鼠标触发的,但有时候要确定用户想实现的操作,还要考虑键盘按键的状态。键盘上的修饰键Shift、Ctrl、Alt和Meta经常用于修改鼠标事件的行为。DOM规定了4个属性来表 示这几个修饰键的状态:shiftKey、ctrlKey、altKey 和 metaKey。这几属性会在各自对应的修饰 键被按下时包含布尔值true,没有被按下时包含false。在鼠标事件发生的,可以通过这几个属性来检测修饰键是否被按下。
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {let keys = new Array();if (event.shiftKey) { keys.push("shift"); } if (event.ctrlKey) { keys.push("ctrl"); } if (event.altKey) { keys.push("alt"); } if (event.metaKey) { keys.push("meta"); } console.log("Keys: " + keys.join(","));
});
HTML5事件
1.contextmenu事件
contextmenu事件以专门用 于表示何时该显示上下文菜单,从而允许开发者取消默认的上下文菜单并提供自定义菜单。
contextmenu 事件冒泡,因此只要给document 指定一个事件处理程序就可以处理页面上的所有同类事件。事件目标是触发操作的元素。这个事件在所有浏览器中都可以取消,在DOM合规的浏览器中使用event.preventDefault(),在 IE8 及更早版本中将 event.returnValue 设置为 false。
<body> <div id="myDiv">Right click</div> <ul id="myMenu" style="position:absolute;visibility:hidden;background-color: silver"> <li>菜单一</li> <li>菜单二</li> <li>菜单三</li> </ul>
</body>
<script>
let div = document.getElementById("myDiv");
div.addEventListener("contextmenu", (event) => {event.preventDefault();let menu = document.getElementById("myMenu"); menu.style.left = event.clientX + "px"; menu.style.top = event.clientY + "px"; menu.style.visibility = "visible";
});
</script>
2.beforeunload事件
beforeunload 事件会在window 上触发,用意是给开发者提供阻止页面被卸载的机会。这个事件会在页面即将从浏览器中卸载时触发,如果页面需要继续使用,则可以不被卸载。这个事件不能取消, 否则就意味着可以把用户永久阻拦在一个页面上。
window.addEventListener('beforeunload', function (event) {// 取消事件,现代浏览器会忽略自定义消息,但可以设置returnValue来触发确认对话框event.preventDefault();event.returnValue = ''; // 现代浏览器要求设置returnValue属性
});
触发时机:当窗口、文档或资源即将被卸载时触发。例如:
- 关闭浏览器标签页或窗口
- 点击链接跳转到其他页面
- 在地址栏输入新的URL并回车
- 刷新页面
- 调用
window.location进行导航 - 调用
window.close()关闭窗口
事件行为:如果事件处理函数为 event.returnValue 属性设置了非空字符串,或者从事件处理函数中返回一个非空字符串,那么浏览器会弹出一个确认对话框,询问用户是否确认离开。但是,请注意,现代浏览器(如Chrome、Firefox等)会忽略自定义消息,而使用默认的提示消息。
3.DOMContentLoaded事件
window 的 load 事件会在页面完全加载后触发,因为要等待很多外部资源加载完成,所以会花费较长时间。而DOMContentLoaded 事件会在DOM树构建完成后立即触发,而不用等待图片、JavaScript 文件、CSS文件或其他资源加载完成。相对于load事件,DOMContentLoaded可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能够更快地与页面交互。
要处理DOMContentLoaded 事件,需要给document 或window 添加事件处理程序(实际的事件 目标是document,但会冒泡到window)。
document.addEventListener("DOMContentLoaded", (event) => { console.log("Content loaded");
});
4.readystatechange 事件
IE 首先在DOM文档的一些地方定义了一个名为readystatechange 事件。这个事件旨在提供文档或元素加载状态的信息,但行为有时候并不稳定。支持readystatechange 事件的每个 对象都有一个readyState属性,该属性具有一个以下列出的可能的字符串值。
- uninitialized:对象存在并尚未初始化。
- loading:对象正在加载数据。
- loaded:对象已经加载完数据。
- interactive:对象可以交互,但尚未加载完成。 文档已被解析,但是像图片、样式表和框架等子资源仍在加载。在这个状态,用户可以与页面进行交互(例如,点击链接、输入表单等),但是页面可能还没有完全显示出来。
- complete:对象加载完成。所有资源都已完成加载,表示
load事件即将被触发。
// 监听文档的 readystatechange 事件
document.addEventListener('readystatechange', function(event) {console.log('当前 readyState:', document.readyState);switch (document.readyState) {case 'loading':console.log('文档正在加载中...');break;case 'interactive':console.log('文档已加载并解析完毕,但子资源(如图片、样式表)仍在加载中。');// 此时可以安全地操作DOM(除了依赖图片、样式表等资源的操作)initApp();break;case 'complete':console.log('文档和所有子资源已完全加载。');// 相当于 window.onload 事件触发finalizeApp();break;}
});
DOM 遍历和操作
1.遍历DOM树
// 父级遍历
const parent = element.parentNode;
const parentElement = element.parentElement;// 子级遍历
const children = element.children; // 只包含元素节点
const childNodes = element.childNodes; // 包含所有节点类型
const firstChild = element.firstElementChild;
const lastChild = element.lastElementChild;// 兄弟节点遍历
const next = element.nextElementSibling;
const prev = element.previousElementSibling;
2.元素尺寸和位置
元素尺寸
-
偏移尺寸
偏移尺寸包含元素在屏幕上占用的所有视觉空间,包括内容、内边距、边框和滚动条(如果存在)。偏移尺寸有以下四个属性:(其中,
offsetLeft和offsetTop是相对于包含元素(offsetParent)的。)- offsetWidth:元素在水平方向占用的像素尺寸,包括内容宽度、水平内边距和垂直滚动条宽度(如果存在)以及左右边框的宽度。
- offsetHeight:元素在垂直方向占用的像素尺寸,包括内容高度、垂直内边距和水平滚动条高度(如果存在)以及上下边框的高度。
- offsetLeft:元素左边框外侧距离包含元素左边框内侧的像素数。
- offsetTop:元素上边框外侧距离包含元素上边框内侧的像素数。
-
客户端尺寸
客户端尺寸包含元素内容及其内边距所占用的空间,不包括边框、外边距和滚动条。客户端尺寸有以下两个属性:
- clientWidth:内容宽度加左右内边距。
- clientHeight:内容高度加上下内边距。
-
滚动尺寸
滚动尺寸包含元素内容的实际大小,包括因溢出而不可见的部分。滚动尺寸有以下四个属性:
- scrollWidth:元素内容的总宽度,包括因溢出而不可见的部分。如果没有水平滚动条,则等于clientWidth。
- scrollHeight:元素内容的总高度,包括因溢出而不可见的部分。如果没有垂直滚动条,则等于clientHeight。
- scrollLeft:元素内容向左滚动的距离(像素),可读写。
- scrollTop:元素内容向上滚动的距离(像素),可读写。
注意:
- 上述属性都是只读的,除了
scrollLeft和scrollTop。 - 所有尺寸属性返回的都是整数,没有小数(四舍五入)。
元素位置
-
相对于包含元素的位置
包含元素(offsetParent)是距离当前元素最近的定位祖先元素(position不为static)或最近的
<table>,<td>,<th>,<body>元素。我们可以通过offsetLeft和offsetTop获取相对于包含元素的位置。 -
相对于视口的位置
可以使用
getBoundingClientRect()方法获取元素相对于视口的位置。该方法返回一个DOMRect对象,包含以下属性:x:元素左边相对于视口左边的距离。y:元素上边相对于视口上边的距离。width:元素的宽度(同offsetWidth)。height:元素的高度(同offsetHeight)。top:元素上边相对于视口上边的距离(同y)。right:元素右边相对于视口左边的距离。bottom:元素下边相对于视口上边的距离。left:元素左边相对于视口左边的距离(同x)。
注意:
getBoundingClientRect()返回的值是相对于视口的,并且会随着滚动而改变。 -
获取相对于文档的位置
由于
getBoundingClientRect()是相对于视口的,要获取相对于文档的位置,需要加上当前的滚动距离。我们可以使用window.scrollX和window.scrollY(或window.pageXOffset和window.pageYOffset)来获取文档的滚动距离。
其他相关属性和方法
-
确定包含元素(offsetParent)
通过元素的
offsetParent属性可以获取其包含元素。如果元素被隐藏(display:none)或者元素是<body>或<html>,则offsetParent可能为null。 -
视口尺寸
window.innerWidth和window.innerHeight:视口的宽度和高度,包括滚动条(如果存在)。document.documentElement.clientWidth和document.documentElement.clientHeight:视口的宽度和高度,不包括滚动条。
-
滚动元素
element.scrollIntoView():滚动元素至视口中。
-
元素是否在视口中
可以利用
getBoundingClientRect()来判断元素是否在视口中。示例:
function isElementInViewport(el) {const rect = el.getBoundingClientRect();return (rect.top >= 0 &&rect.left >= 0 &&rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&rect.right <= (window.innerWidth || document.documentElement.clientWidth)); }
总结
// 元素尺寸
const width = element.offsetWidth; // 包含边框和内边距
const height = element.offsetHeight;
const clientWidth = element.clientWidth; // 不包含边框
const clientHeight = element.clientHeight;
const scrollWidth = element.scrollWidth; // 包含滚动内容
const scrollHeight = element.scrollHeight;// 元素位置
const rect = element.getBoundingClientRect();
rect.left, rect.top, rect.right, rect.bottom, rect.width, rect.height// 视口相对位置
const scrollTop = element.scrollTop;
const scrollLeft = element.scrollLeft;// 窗口滚动
window.scrollX, window.scrollY
window.pageXOffset, window.pageYOffset// 滚动控制
element.scrollIntoView({ behavior: 'smooth' });
window.scrollTo({ top: 100, behavior: 'smooth' });
设备事件
设备事件主要涉及与设备相关的硬件事件,包括设备方向、设备运动、电池状态等。这些事件可以让Web应用响应用户设备的各种状态变化。
方向事件deviceorientation
当设备的朝向发生变化时触发,提供设备在三维空间中的朝向角度。
window.addEventListener('deviceorientation', function(event) {// alpha: 设备绕Z轴旋转的角度,范围0到360// beta: 设备绕X轴旋转的角度,范围-180到180(前后倾斜)// gamma: 设备绕Y轴旋转的角度,范围-90到90(左右倾斜)console.log('朝向角度 - alpha: ' + event.alpha + ', beta: ' + event.beta + ', gamma: ' + event.gamma);// absolute: 如果设备可以提供绝对方向(例如,通过指南针),则为trueconsole.log('是否绝对方向: ' + event.absolute);
});
运动事件devicemotion
提供设备的加速度和旋转速率信息。
window.addEventListener('devicemotion', function(event) {// 加速度(不含重力加速度),单位 m/s^2const acceleration = event.acceleration;console.log('加速度 x: ' + acceleration.x + ', y: ' + acceleration.y + ', z: ' + acceleration.z);// 包含重力加速度的加速度const accelerationIncludingGravity = event.accelerationIncludingGravity;console.log('含重力的加速度 x: ' + accelerationIncludingGravity.x + ', y: ' + accelerationIncludingGravity.y + ', z: ' + accelerationIncludingGravity.z);// 旋转速率,单位 deg/sconst rotationRate = event.rotationRate;console.log('旋转速率 alpha: ' + rotationRate.alpha + ', beta: ' + rotationRate.beta + ', gamma: ' + rotationRate.gamma);// 事件发生的时间间隔,单位秒console.log('时间间隔: ' + event.interval);
});
此事件可以用于摇一摇检测。
横屏/竖屏变化事件
通常使用 orientationchange 事件来监听设备屏幕方向的变化,但是要注意,orientationchange 事件是 window 对象上的事件,而不是 screen.orientation 的变化事件。
实际上,在移动设备中,当设备方向发生变化时(横屏或竖屏),会触发 window 上的 orientationchange 事件。
同时,我们也可以使用 window.orientation 属性(已废弃,但仍有浏览器支持)或者 screen.orientation 来获取更详细的信息。
但是,请注意:orientationchange 事件并不是所有浏览器都支持,尤其是在一些旧版本的浏览器中。因此,我们通常需要结合使用 resize 事件作为备选方案。
// 方法1:直接监听 orientationchange 事件
window.addEventListener('orientationchange', function() {console.log('设备方向发生变化');// 获取当前方向const orientation = window.orientation;console.log('当前方向角度:', orientation);// 或者通过屏幕尺寸判断if (window.innerHeight > window.innerWidth) {console.log('竖屏模式 (Portrait)');} else {console.log('横屏模式 (Landscape)');}
});// 方法2:使用 screen.orientation(更现代的方法)
if (screen.orientation) {screen.orientation.addEventListener('change', function() {console.log('屏幕方向变化:', screen.orientation.type);console.log('角度:', screen.orientation.angle);});
}// 方法3:同时监听 resize 事件作为后备方案
window.addEventListener('resize', function() {// 防抖处理,避免频繁触发clearTimeout(this.resizeTimer);this.resizeTimer = setTimeout(() => {detectOrientation();}, 250);
});function detectOrientation() {const isPortrait = window.innerHeight > window.innerWidth;console.log(isPortrait ? '竖屏' : '横屏');
}
触摸及手势事件
触摸事件
触摸事件有三个基本类型:
touchstart:当手指触摸屏幕时触发。touchmove:当手指在屏幕上滑动时触发。touchend:当手指从屏幕上移开时触发
此外,还有一个touchcancel,当触摸被意外中断时触发(如来电)。
每个触摸事件都包含了三个触摸列表:
touches:当前屏幕上所有手指的列表。targetTouches:当前元素上所有手指的列表。changedTouches:涉及当前事件的手指的列表。
这些列表中的每个对象都包含触摸的详细信息,如位置、目标元素等。
触摸事件对象包含以下重要属性:
touches:当前位于屏幕上的所有触摸点的数组。targetTouches:位于当前事件目标元素上的触摸点的数组。changedTouches:在本次事件中发生改变的触摸点的数组。
每个触摸点(Touch对象)包含:
identifier:触摸点的唯一标识符。target:触摸点起始时的目标元素。clientX, clientY:相对于视口的坐标。pageX, pageY:相对于页面的坐标。screenX, screenY:相对于屏幕的坐标。radiusX, radiusY:触摸点椭圆半径(椭圆与触摸区域近似)。rotationAngle:旋转角度(椭圆区域)。force:压力大小(0.0-1.0)。
const touchable = document.getElementById('touchable');touchable.addEventListener('touchstart', function(event) {// 阻止默认行为,比如滚动event.preventDefault();const touch = event.touches[0];console.log(`触摸开始:(${touch.clientX}, ${touch.clientY})`);
});touchable.addEventListener('touchmove', function(event) {event.preventDefault();const touch = event.touches[0];console.log(`触摸移动:(${touch.clientX}, ${touch.clientY})`);
});touchable.addEventListener('touchend', function(event) {console.log('触摸结束');
});
手势事件
手势事件是苹果公司为Safari浏览器引入的,用于处理捏合、旋转等复杂手势。主要包括:
gesturestart:当两个或更多手指触摸屏幕时触发。gesturechange:当手指在屏幕上移动导致手势变化时触发。gestureend:当手指离开屏幕,手势结束时触发。
手势事件对象包含以下属性:
rotation:从gesturestart开始旋转的角度(正值表示顺时针)。scale:从gesturestart开始的缩放比例(基于1开始增长或减少)。
注意:手势事件在非Safari浏览器中可能不被支持,因此通常使用触摸事件模拟手势。
const gestureElement = document.getElementById('gestureElement');gestureElement.addEventListener('gesturestart', function(event) {event.preventDefault();console.log('手势开始');
});gestureElement.addEventListener('gesturechange', function(event) {event.preventDefault();console.log(`手势变化:缩放 ${event.scale}, 旋转 ${event.rotation}`);
});gestureElement.addEventListener('gestureend', function(event) {console.log('手势结束');
});
触摸事件和手势事件存在一定的关系。当一个手指放在屏幕上时,会触发touchstart事件。当另一个手指放到屏幕上时,gesturestart 事件会首先触发,然后紧接着触发这个手指的 touchstart 事件。如果两个手指或其中一个手指移动,则会触发gesturechange事件。只要其中一个手指离开屏 幕,就会触发gestureend事件,紧接着触发该手指的touchend事件。
内存与性能
在JavaScript中,页面中事件处理程序的数量与页面整体性能直接相关。
事件委托
“过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。例如,click事件冒泡到document。这意味着可以为整个页面指定 一个onclick 事件处理程序,而不用为每个可点击元素分别指定事件处理程序。比如有以下HTML:
<ul id="myLinks"> <li id="goSomewhere">Go somewhere</li> <li id="doSomething">Do something</li> <li id="sayHi">Say hi</li>
</ul>
这里的HTML包含3个列表项,在被点击时应该执行某个操作。对此,通常的做法是像这样指定3 个事件处理程序。
如果对页面中所有需要使用onclick 事件处理程序的元素都如法炮制,结果就会出现大片雷同的 只为指定事件处理程序的代码。使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序, 就可以解决问题。比如:
let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => { let target = event.target; switch(target.id) { case "doSomething":
document.title = "I changed the document's title";
break; case "goSomewhere":
location.href = "http:// www.wrox.com";
break; case "sayHi":
console.log("hi");
break; }
});
删除事件处理程序
很多Web应用性能不佳都是由于无用的事件处理程序长驻内存导致的,应该及时删除不用的事件处理程序。
删除事件处理程序主要有以下几种方法:
-
使用removeEventListener删除通过addEventListener添加的事件处理程序
function handleClick(event) {console.log('Clicked!'); }// 添加事件监听器 element.addEventListener('click', handleClick, false);// 移除事件监听器 element.removeEventListener('click', handleClick, false);注意:如果添加事件时使用了
捕获(useCapture为true),那么移除时也需要指定为true。另外,如果事件处理函数是
匿名函数,那么将无法移除,因为我们需要传递相同的函数引用。因此,为了能够移除事件,最好使用命名函数。 -
将事件处理程序设置为null(适用于通过on事件属性添加的处理程序)
如果我们是通过元素的on事件属性(如onclick、onload等)来绑定事件处理程序的,那么可以通过将其设置为null来移除。
element.onclick = function() {console.log('Clicked!'); };// 移除事件处理程序 element.onclick = null; -
使用removeAttribute删除通过HTML属性指定的事件处理程序
如果我们是通过HTML属性(如onclick="handleClick()")来指定事件处理程序的,那么可以通过removeAttribute来移除。
<button id="myBtn" onclick="handleClick()">Click me</button> // 移除onclick属性 document.getElementById('myBtn').removeAttribute('onclick'); -
使用jQuery的off方法(如果之前是用jQuery的on方法绑定的)
如果项目中使用jQuery,并且使用on方法绑定事件,那么可以使用off方法来移除。
// 绑定事件 $('#myElement').on('click', handleClick);// 移除事件 $('#myElement').off('click', handleClick);如果需要移除一个元素上的所有事件,可以使用不带参数的off方法,但要注意这可能会移除其他代码绑定的事件,需谨慎使用。
注意事项:
-
内存泄漏:在单页应用或长时间运行的页面中,如果不及时移除不再需要的事件处理程序,可能会导致内存泄漏。特别是当元素被从DOM中移除时,如果绑定的事件处理程序没有被移除,那么这些处理函数和相关的元素可能无法被垃圾回收。
-
动态移除:有时候我们需要在事件执行一次后就移除事件处理程序,这种情况下可以在事件处理函数内部移除自身。
示例:
function handleClickOnce(event) {console.log('This will only run once.');event.target.removeEventListener('click', handleClickOnce, false); }element.addEventListener('click', handleClickOnce, false);