JavaScript DOM 操作完全指南(上篇):基础 API、尺寸位置与节点操作
本篇聚焦 DOM 的基础知识体系,系统讲解节点类型、元素获取、内容读写、尺寸位置、滚动控制、节点增删改查和 document 对象,并通过全选、半选、倒计时等案例完成基础能力闭环。
前言
DOM 是 JavaScript 操作页面结构、内容与样式的核心接口。上篇的目标不是罗列 API,而是把常用 DOM 能力按真实开发路径串起来:先理解节点和元素,再掌握内容、尺寸、位置、滚动、节点操作,最后用典型案例完成实战落地。
阅读完本篇,你将能够独立完成页面元素选择、内容更新、尺寸位置计算、滚动控制、节点动态创建,以及常见表单交互和倒计时功能。
目录
- 核心名词解释
- [1. DOM 概述](#1. DOM 概述)
- [1.1 什么是 DOM](#1.1 什么是 DOM)
- [1.2 DOM 节点类型](#1.2 DOM 节点类型)
- [1.3 获取元素的方式](#1.3 获取元素的方式)
- [1.4 DOM 树关系属性](#1.4 DOM 树关系属性)
- [1.5 属性与样式操作](#1.5 属性与样式操作)
- [2. 读写元素内容](#2. 读写元素内容)
- [2.1 核心属性对比](#2.1 核心属性对比)
- [2.2 innerHTML - 读写内部 HTML](#2.2 innerHTML - 读写内部 HTML)
- [2.3 outerHTML - 读写包含自身的 HTML](#2.3 outerHTML - 读写包含自身的 HTML)
- [2.4 innerText - 渲染后的文本内容](#2.4 innerText - 渲染后的文本内容)
- [2.5 textContent - 所有文本内容](#2.5 textContent - 所有文本内容)
- [2.6 内容属性选择决策图](#2.6 内容属性选择决策图)
- [2.7 实际应用场景](#2.7 实际应用场景)
- [3. 获取元素尺寸](#3. 获取元素尺寸)
- [3.1 尺寸属性全景图](#3.1 尺寸属性全景图)
- [3.2 offsetWidth / offsetHeight](#3.2 offsetWidth / offsetHeight)
- [3.3 clientWidth / clientHeight](#3.3 clientWidth / clientHeight)
- [3.4 scrollWidth / scrollHeight](#3.4 scrollWidth / scrollHeight)
- [3.5 尺寸属性对比表](#3.5 尺寸属性对比表)
- [3.6 getBoundingClientRect() - 获取完整尺寸和位置](#3.6 getBoundingClientRect() - 获取完整尺寸和位置)
- [3.7 视口尺寸获取](#3.7 视口尺寸获取)
- [4. 获取元素位置](#4. 获取元素位置)
- [4.1 位置属性概览](#4.1 位置属性概览)
- [4.2 offsetLeft / offsetTop](#4.2 offsetLeft / offsetTop)
- [4.3 clientLeft / clientTop](#4.3 clientLeft / clientTop)
- [4.4 位置属性关系图](#4.4 位置属性关系图)
- [4.5 获取元素在页面中的绝对位置](#4.5 获取元素在页面中的绝对位置)
- [5. 滚动位置控制](#5. 滚动位置控制)
- [5.1 滚动属性](#5.1 滚动属性)
- [5.2 基础滚动控制](#5.2 基础滚动控制)
- [5.3 页面滚动控制](#5.3 页面滚动控制)
- [5.4 检测滚动到底部](#5.4 检测滚动到底部)
- [6. 节点增删改查](#6. 节点增删改查)
- [6.1 创建节点](#6.1 创建节点)
- [6.2 添加节点](#6.2 添加节点)
- [6.3 删除节点](#6.3 删除节点)
- [6.4 替换节点](#6.4 替换节点)
- [6.5 克隆节点](#6.5 克隆节点)
- [6.6 节点操作关系图](#6.6 节点操作关系图)
- [7. document 对象详解](#7. document 对象详解)
- [7.1 document 核心属性](#7.1 document 核心属性)
- [7.2 document 核心方法](#7.2 document 核心方法)
- [7.3 实战示例:标题滚动效果](#7.3 实战示例:标题滚动效果)
- [8. 实战案例](#8. 实战案例)
- [8.1 案例一(基础):三按钮全选/取消全选/反选](#8.1 案例一(基础):三按钮全选/取消全选/反选)
- [8.2 案例二(增强):父级全选与半选状态](#8.2 案例二(增强):父级全选与半选状态)
- [8.3 案例三:目标日期倒计时](#8.3 案例三:目标日期倒计时)
核心名词解释
| 名词 | 英文 | 解析 |
|---|---|---|
| DOM | Document Object Model | 将 HTML/XML 文档抽象为可编程的对象树,脚本可通过 API 读写结构、内容与样式。 |
| 节点 | Node | DOM 树中的基本单位,包括元素、文本、注释、属性等类型。 |
| 元素节点 | Element | 对应 HTML 标签的节点,如 <div> 在 DOM 中即元素节点。 |
| 文档节点 | Document | 整份页面的根,document 即其 JavaScript 入口。 |
| 回流 | Reflow | 布局相关属性变化后,浏览器重新计算几何信息并绘制,频繁触发影响性能。 |
| 重绘 | Repaint | 仅视觉样式变化、不影响布局时的绘制,开销通常小于回流。 |
| 视口 | Viewport | 浏览器可见区域;getBoundingClientRect() 返回的坐标均相对视口。 |
| 定位祖先 | Offset Parent | offsetLeft/Top 参照的第一个 position 非 static 的祖先,若无则参照文档。 |
| 事件委托 | Event Delegation | 在父元素统一监听子元素事件,利用冒泡减少监听器数量。 |
| 懒加载 | Lazy Loading | 图片等资源进入可视区域后再赋值 src,降低首屏请求与流量。 |
| XSS | Cross-Site Scripting | 将不可信字符串写入 innerHTML 可能执行恶意脚本,需用 textContent 或消毒。 |
| HTMLCollection | --- | getElementsByTagName 等返回的动态集合,DOM 变化时自动更新。 |
| NodeList | --- | querySelectorAll 返回的静态快照,后续 DOM 变化不会自动反映。 |
| DocumentFragment | --- | 轻量文档片段,批量插入子节点时减少回流次数。 |
| indeterminate | --- | 复选框的「半选」视觉状态,表示子项部分选中,需配合 JS 维护。 |
1. DOM 概述
1.1 什么是 DOM
DOM(Document Object Model,文档对象模型)是 HTML 和 XML 文档的编程接口。它将文档表示为一个树形结构,其中每个节点都是文档的一部分,允许程序和脚本动态地访问和更新文档的内容、结构和样式。
Document
html
head
body
meta
title
div
ul
li
li
1.2 DOM 节点类型
| 节点类型 | 说明 | 常见示例 |
|---|---|---|
| Document | 文档根节点 | document |
| Element | 元素节点 | <div>, <p>, <span> |
| Attribute | 属性节点 | class, id, src |
| Text | 文本节点 | 元素内的文本内容 |
| Comment | 注释节点 | <!-- 注释 --> |
1.3 获取元素的方式
javascript
// 1. 通过 ID 获取
const elem1 = document.getElementById('myId');
// 2. 通过标签名获取
const elem2 = document.getElementsByTagName('div');
// 3. 通过类名获取
const elem3 = document.getElementsByClassName('myClass');
// 4. 通过 name 属性获取
const elem4 = document.getElementsByName('username');
// 5. 使用 CSS 选择器(推荐)
const elem5 = document.querySelector('#myId');
const elem6 = document.querySelectorAll('.myClass');
// 6. 快捷方法
const html = document.documentElement; // html 元素
const body = document.body; // body 元素
const head = document.head; // head 元素
代码解释:
getElementById(): 通过元素的唯一 ID 获取单个元素,效率最高getElementsByTagName(): 通过标签名获取元素集合(HTMLCollection),返回动态集合getElementsByClassName(): 通过类名获取元素集合getElementsByName(): 通过 name 属性获取元素集合,常用于表单元素querySelector(): 使用 CSS 选择器获取第一个匹配的元素,支持复杂选择器querySelectorAll(): 使用 CSS 选择器获取所有匹配的元素,返回静态 NodeListdocumentElement/body/head: 快捷访问页面根元素的属性,省去选择器操作
1.4 DOM 树关系属性
除「查询选择器」外,还可通过节点关系在已获取的元素上继续查找,适合列表、菜单、表格等层级结构。
| 属性 | 说明 |
|---|---|
children |
子元素节点集合(不含文本节点) |
childElementCount |
子元素个数 |
firstElementChild / lastElementChild |
第一个 / 最后一个子元素 |
parentElement |
父元素节点 |
previousElementSibling / nextElementSibling |
前一个 / 后一个同级元素 |
parentElement
previousElementSibling
nextElementSibling
ul#list
li 第一项
li 第二项
li 第三项
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>DOM 树关系示例</title>
</head>
<body>
<ul id="list">
<li>任务 A</li>
<li id="current">任务 B(当前)</li>
<li>任务 C</li>
</ul>
<button id="prevBtn">上一项</button>
<button id="nextBtn">下一项</button>
<p id="info"></p>
<script>
const current = document.querySelector('#current');
const info = document.querySelector('#info');
document.querySelector('#prevBtn').onclick = function () {
const prev = current.previousElementSibling;
if (prev) {
info.textContent = '上一项:' + prev.textContent;
}
};
document.querySelector('#nextBtn').onclick = function () {
const next = current.nextElementSibling;
if (next) {
info.textContent = '下一项:' + next.textContent;
}
};
// 父元素与子元素数量
info.textContent = '父元素标签:' + current.parentElement.tagName
+ ',子元素数:' + current.parentElement.childElementCount;
</script>
</body>
</html>
代码解释 :previousElementSibling / nextElementSibling 只匹配元素节点 ,会跳过文本节点;firstElementChild 常用于 insertBefore(newNode, parent.firstElementChild) 在列表头部插入。
经典场景:评论回复链定位、表格行高亮切换、轮播图上一张/下一张、面包屑导航回溯。
1.5 属性与样式操作
DOM 不仅能改内容,还能读写属性 与样式,常与动态 UI 配合使用。
1.5.1 内置属性与自定义属性
javascript
// 读写内置属性(属性名与 DOM 属性一致)
img.src = 'images/db01.jpg';
input.value = 'hello';
link.href = 'https://example.com';
// 通用 attribute API
el.setAttribute('data-id', '1001');
el.getAttribute('data-id'); // '1001'
// data-* 自定义属性 → dataset(驼峰)
// HTML: <div data-user-id="8">
el.dataset.userId = '8';
console.log(el.dataset.userId);
1.5.2 行内样式与计算样式
javascript
box.style.width = '200px';
box.style.backgroundColor = 'tomato'; // 驼峰写法
// 读取最终生效样式(只读)
const styles = getComputedStyle(box);
console.log(styles.width);
1.5.3 className 与 classList
javascript
// 覆盖式,易误删其他类名
el.className = 'active';
// 推荐:逐项增删
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('expanded'); // 有则删、无则加
el.classList.contains('active'); // true / false
是
否
修改外观
改单条样式?
style.xxx
classList
add / remove / toggle
需精确像素/颜色时
网站应用 :京东商品卡片 hover 高亮(classList)、GitHub 暗色模式切换(dataset + classList)、Ant Design 表单校验状态(setAttribute('aria-invalid'))。
1.5.4 本文高频 CSS 样式速查
DOM 案例中的 CSS 不只是装饰,它经常承担「布局」「状态反馈」「交互动效」三类职责。下面列出本文示例高频样式的作用、最小例子和常见网站场景。
| CSS 样式 | 作用 | 最小例子 | 常见网站场景 |
|---|---|---|---|
display: flex |
一维布局,让子元素横向或纵向排列 | .toolbar { display: flex; gap: 12px; } |
电商筛选栏、后台操作按钮组 |
flex-wrap: wrap |
子元素空间不足时自动换行 | .gallery { display: flex; flex-wrap: wrap; } |
图片墙、商品列表 |
gap |
控制 flex/grid 子项间距 | .list { display: flex; gap: 16px; } |
卡片列表、导航菜单 |
position: sticky |
滚动到指定位置后吸顶 | .header { position: sticky; top: 0; } |
文档目录、商品详情页顶部栏 |
position: fixed |
固定在视口某处 | .badge { position: fixed; right: 20px; bottom: 20px; } |
返回顶部按钮、加载提示 |
overflow: hidden |
隐藏溢出内容 | .marquee { overflow: hidden; } |
无缝滚动、轮播容器 |
overflow: auto |
内容超出时出现滚动条 | .panel { height: 300px; overflow: auto; } |
聊天记录、侧边列表 |
box-shadow |
添加阴影,形成层次 | .card { box-shadow: 0 4px 16px rgba(0,0,0,.12); } |
商品卡片、弹窗、仪表盘 |
border-radius |
设置圆角 | .btn { border-radius: 8px; } |
按钮、头像、卡片 |
transition |
属性变化时平滑过渡 | .btn { transition: background .3s; } |
hover 效果、状态切换 |
transform |
位移、缩放、旋转 | .item:hover { transform: translateY(-2px); } |
卡片悬浮、轮播动画 |
animation |
绑定关键帧动画 | .track { animation: scroll 20s linear infinite; } |
公告滚动、加载动画 |
object-fit: cover |
图片裁剪填满容器 | img { width: 100%; height: 180px; object-fit: cover; } |
商品图、头像封面、图库 |
linear-gradient |
线性渐变背景 | .banner { background: linear-gradient(135deg, #667eea, #764ba2); } |
活动横幅、按钮背景 |
z-index |
控制定位元素层级 | .header { position: sticky; z-index: 100; } |
吸顶导航、浮层、遮罩 |
归纳 :布局类样式优先决定结构(display、flex-wrap、gap),状态类样式表达用户反馈(hover、transition、box-shadow),动效类样式用于连续运动(animation、transform)。DOM 操作通常通过 classList 切换这些样式,而不是频繁直接写入多个 style 属性。
2. 读写元素内容
2.1 核心属性对比
| 属性 | 读写 | 返回内容 | 解析HTML | 受CSS影响 | 标准状态 |
|---|---|---|---|---|---|
| innerHTML | ✅ | 内部HTML+文本 | ✅ | ❌ | 标准属性 |
| outerHTML | ✅ | 包含自身的HTML | ✅ | ❌ | 标准属性 |
| innerText | ✅ | 渲染后的文本 | ❌ | ✅ | HTML 标准,历史兼容性复杂 |
| textContent | ✅ | 所有文本内容 | ❌ | ❌ | 标准属性 |
2.2 innerHTML - 读写内部 HTML
功能:获取或设置元素内部的 HTML 内容(从起始标签到结束标签之间的内容)。
特点:
- 包含所有子元素和 HTML 标签
- 赋值时会解析 HTML 字符串
- 符合现代 DOM 标准
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>innerHTML 示例</title>
<style>
.wrapper {
width: 600px;
padding: 20px;
border: 2px dashed #999;
}
.item {
padding: 20px;
background: #ccc;
}
</style>
</head>
<body>
<button id="getBtn">读取内容</button>
<button id="setBtn">设置内容</button>
<br><br>
<div id="box" class="wrapper">
<p>原始内容</p>
<ul>
<li>列表项 1</li>
<li>列表项 2</li>
</ul>
</div>
<script>
const box = document.querySelector('#box');
const getBtn = document.querySelector('#getBtn');
const setBtn = document.querySelector('#setBtn');
// 读取内容
getBtn.onclick = function() {
console.log(box.innerHTML);
// 输出: <p>原始内容</p><ul><li>列表项 1</li><li>列表项 2</li></ul>
};
// 设置内容 - 会解析 HTML 标签
setBtn.onclick = function() {
box.innerHTML = '<h1 class="item">Hello World</h1><p>新段落</p>';
};
</script>
</body>
</html>
代码解释:
- CSS 部分 :定义了容器样式
.wrapper(虚线边框、内边距)和内容项样式.item(灰色背景) - HTML 部分:创建了两个按钮和一个包含列表的容器元素
- JavaScript 部分 :
- 使用
querySelector获取元素引用 getBtn.onclick: 点击"读取内容"按钮时,innerHTML返回元素内部的所有 HTML 代码(包括标签)setBtn.onclick: 点击"设置内容"按钮时,innerHTML会解析传入的 HTML 字符串,将<h1>和<p>标签渲染为实际的 HTML 元素
- 使用
- 关键点 :
innerHTML既能读取也能写入,写入时会解析 HTML 标签,这是它与textContent的核心区别
⚠️ 安全警告 :使用 innerHTML 插入用户输入的内容存在 XSS 攻击风险。
javascript
// 危险示例
const userInput = '<img src="x" onerror="alert(1)">';
element.innerHTML = userInput; // 会执行恶意代码
// 安全替代方案
element.textContent = userInput; // 不会解析 HTML
代码解释:
- 危险示例 :当用户输入包含恶意代码(如
onerror事件处理器)时,innerHTML会解析并执行这段代码,导致 XSS 攻击 - 安全方案 :使用
textContent代替innerHTML,它会将内容作为纯文本处理,不会解析 HTML 标签 - 最佳实践 :处理用户输入时,优先使用
textContent;如果必须使用innerHTML,先对输入进行消毒(sanitize)处理
2.3 outerHTML - 读写包含自身的 HTML
功能:获取或设置包含元素自身的完整 HTML。
特点:
- 包含元素自身的起始和结束标签
- 赋值时会替换整个元素
- 符合现代 DOM 标准
html
<div id="container">
<div id="box">原始内容</div>
</div>
<script>
const box = document.querySelector('#box');
// 读取 outerHTML
console.log(box.outerHTML);
// 输出: <div id="box">原始内容</div>
// 设置 outerHTML - 会替换整个元素
box.outerHTML = '<span id="box">新内容</span>';
// 此时 box 变量指向的元素已被移除
// 需要重新获取
const newBox = document.querySelector('#box');
console.log(newBox.tagName); // SPAN
</script>
代码解释:
- outerHTML 读取:返回包含元素自身在内的完整 HTML,包括起始标签、内容和结束标签
- outerHTML 写入 :与
innerHTML不同,设置outerHTML会替换整个元素,而不是只修改内部内容 - 变量引用失效 :执行
outerHTML赋值后,原box变量仍指向被移除的元素,需要重新查询获取新元素 - 实际应用 :适合需要完全替换某个元素(包括其标签类型)的场景,如将
<div>替换为<span>
2.4 innerText - 渲染后的文本内容
功能:获取元素渲染后的可见文本内容。
特点:
- 受 CSS 样式影响 (
display: none的元素不会返回) - 触发回流,性能较差
- 只能 HTMLElement 调用
- 兼容性复杂:早期并非 DOM 标准属性,现代浏览器已广泛支持,使用时仍要理解它会受 CSS 与布局影响
html
<div id="box">
Hello <span style="display:none">Hidden</span>
<style>.ignore { display: none; }</style>
<span class="ignore">Ignored</span>
</div>
<script>
const box = document.querySelector('#box');
console.log(box.innerText);
// 输出: "Hello "(不包含 Hidden 和 Ignored)
</script>
代码解释:
- 受 CSS 影响 :
innerText会考虑 CSS 样式,display: none的元素内容不会被返回 - 触发回流:浏览器需要计算元素的渲染结果才能确定可见文本,这会触发页面回流(reflow)
- 与 textContent 的区别 :
textContent会返回所有文本内容,包括隐藏元素的文本 - 使用场景:需要获取用户实际能看到的文本内容时使用
2.5 textContent - 所有文本内容
功能:获取节点及其后代的纯文本内容。
特点:
- 不受 CSS 影响
- 包括隐藏元素的文本
- 任意 Node 节点都可调用
- 标准属性
- 性能更好(不触发回流)
html
<div id="box">
Hello <span style="display:none">Hidden</span>
</div>
<script>
const box = document.querySelector('#box');
console.log(box.textContent);
// 输出: "Hello Hidden"(包含隐藏元素)
// 设置纯文本(推荐用于用户输入)
box.textContent = '<script>alert("XSS")<\/script>';
// 不会被解析为 HTML,而是作为纯文本显示
</script>
代码解释:
- 不受 CSS 影响 :
textContent会返回所有文本,包括隐藏元素的文本 - 标准属性 :属于现代 DOM 标准,性能优于
innerText(不触发回流) - 防 XSS :设置
textContent时,HTML 标签会被转义为纯文本,不会执行脚本 - 推荐使用 :处理用户输入、需要设置纯文本内容时,优先使用
textContent
2.6 内容属性选择决策图
是
否
是
否
需要
不需要
是
需要修改元素内容
是否需要插入HTML?
使用 innerHTML
内容是否来自用户输入?
使用 textContent
是否需要隐藏元素?
使用 innerText
使用 textContent
是否需要替换自身?
使用 outerHTML
2.7 实际应用场景
| 场景 | 推荐属性 | 原因 |
|---|---|---|
| 富文本编辑器 | innerHTML | 需要解析 HTML 标签 |
| 显示用户评论 | textContent | 防止 XSS 攻击 |
| 获取可见文本 | innerText | 自动排除隐藏元素 |
| 批量文本操作 | textContent | 性能更优 |
3. 获取元素尺寸
3.1 尺寸属性全景图
元素尺寸
offsetWidth/Height
clientWidth/Height
scrollWidth/Height
getBoundingClientRect
内容+padding+border
内容+padding
内容+padding+溢出
完整尺寸+位置信息
3.2 offsetWidth / offsetHeight
功能:获取元素的总宽度和总高度。
计算公式:
offsetWidth = content + padding + border
offsetHeight = content + padding + border
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>offset 尺寸示例</title>
<style>
.box {
width: 300px;
padding: 20px;
border: 5px solid #333;
margin: 10px;
}
</style>
</head>
<body>
<div class="box" id="demo">内容</div>
<script>
const box = document.querySelector('#demo');
console.log('offsetWidth:', box.offsetWidth); // 350 = 300 + 20*2 + 5*2
console.log('offsetHeight:', box.offsetHeight);
</script>
</body>
</html>
代码解释:
- offsetWidth 计算公式:内容宽度(300) + 左右内边距(20×2) + 左右边框(5×2) = 350px
- offsetHeight 计算公式:内容高度 + 上下内边距 + 上下边框
- 注意:不包括外边距(margin),因为 margin 是元素外部的空间
- 只读属性:offsetWidth/offsetHeight 是只读的,不能通过赋值来改变元素尺寸
3.3 clientWidth / clientHeight
功能:获取元素内容区域的宽度和高度。
计算公式:
clientWidth = content + padding
clientHeight = content + padding
注意:不包括边框(border)和滚动条。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>client 尺寸示例</title>
<style>
.box {
width: 300px;
padding: 20px;
border: 5px solid #333;
}
</style>
</head>
<body>
<div class="box" id="demo">内容</div>
<script>
const box = document.querySelector('#demo');
console.log('clientWidth:', box.clientWidth); // 340 = 300 + 20*2
console.log('clientHeight:', box.clientHeight);
</script>
</body>
</html>
代码解释:
- clientWidth 计算公式:内容宽度(300) + 左右内边距(20×2) = 340px
- 不包括边框:与 offsetWidth 不同,clientWidth 不包含边框宽度
- 实际意义:表示元素内容区域(可绘制内容的区域)的宽度
- 应用场景:计算元素实际可用空间、检测内容是否溢出等
3.4 scrollWidth / scrollHeight
功能:获取元素完整的滚动宽度和高度。
计算公式:
scrollWidth = content + padding + 溢出部分
scrollHeight = content + padding + 溢出部分
应用场景:检测内容是否溢出、实现自定义滚动条等。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>scroll 尺寸示例</title>
<style>
.box {
width: 300px;
height: 100px;
padding: 20px;
border: 2px solid #333;
overflow: auto;
}
.content {
width: 500px;
height: 300px;
background: #f0f0f0;
}
</style>
</head>
<body>
<div class="box" id="demo">
<div class="content">超长内容</div>
</div>
<script>
const box = document.querySelector('#demo');
console.log('clientWidth:', box.clientWidth); // 340
console.log('scrollWidth:', box.scrollWidth); // 540 (包含溢出)
console.log('clientHeight:', box.clientHeight); // 140
console.log('scrollHeight:', box.scrollHeight); // 340 (包含溢出)
</script>
</body>
</html>
代码解释:
- clientWidth:300(内容宽) + 20×2(左右内边距) = 340px,表示可见区域宽度
- scrollWidth:340(clientWidth) + 200(溢出部分) = 540px,表示完整滚动宽度
- scrollHeight:140(clientHeight) + 200(溢出部分) = 340px,表示完整滚动高度
- 关键区别:scrollWidth/scrollHeight 包含因内容溢出而不可见的部分
- 应用场景 :
- 判断内容是否溢出(scrollWidth > clientWidth)
- 计算滚动条位置
- 实现"滚动到底部"功能
- 检测是否还有更多内容可滚动
3.5 尺寸属性对比表
| 属性 | 内容 | padding | border | 溢出部分 | 滚动条 |
|---|---|---|---|---|---|
| offsetWidth | ✅ | ✅ | ✅ | ❌ | ❌ |
| clientWidth | ✅ | ✅ | ❌ | ❌ | ❌ |
| scrollWidth | ✅ | ✅ | ❌ | ✅ | ❌ |
3.6 getBoundingClientRect() - 获取完整尺寸和位置
功能:返回一个 DOMRect 对象,包含元素的大小及其相对于视口的位置。
javascript
const rect = element.getBoundingClientRect();
// 返回对象包含以下属性
console.log(rect.width); // 元素宽度(同 offsetWidth)
console.log(rect.height); // 元素高度(同 offsetHeight)
console.log(rect.top); // 元素顶部到视口顶部的距离
console.log(rect.left); // 元素左侧到视口左侧的距离
console.log(rect.right); // 元素右侧到视口左侧的距离
console.log(rect.bottom); // 元素底部到视口顶部的距离
console.log(rect.x); // 同 left
console.log(rect.y); // 同 top
代码解释:
- getBoundingClientRect():返回一个 DOMRect 对象,包含元素的尺寸和位置信息
- 相对于视口:所有位置属性都是相对于浏览器视口(viewport)的,不是相对于页面
- width/height:元素的完整宽高,与 offsetWidth/offsetHeight 相同
- left/top:元素左上角相对于视口左上角的坐标
- right/bottom:元素右下角相对于视口左上角的坐标
- 滚动影响:页面滚动时,这些值会实时变化
- 常用场景:检测元素是否在视口中可见、实现懒加载、计算元素位置用于定位其他元素
实际应用示例:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>getBoundingClientRect 示例</title>
<style>
.box {
width: 200px;
height: 150px;
padding: 20px;
border: 5px solid #333;
margin: 50px;
background: #f0f0f0;
}
</style>
</head>
<body>
<div class="box" id="demo">内容</div>
<script>
const box = document.querySelector('#demo');
const rect = box.getBoundingClientRect();
console.log('尺寸信息:');
console.log(' 宽度:', rect.width, 'px');
console.log(' 高度:', rect.height, 'px');
console.log('位置信息(相对于视口):');
console.log(' 左侧:', rect.left, 'px');
console.log(' 顶部:', rect.top, 'px');
console.log(' 右侧:', rect.right, 'px');
console.log(' 底部:', rect.bottom, 'px');
// 判断元素是否在视口中可见
const isVisible = (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
console.log('元素完全可见:', isVisible);
</script>
</body>
</html>
代码解释:
- 元素尺寸:width 和 height 包含 content + padding + border
- 位置信息:left/top 是元素相对于视口左上角的坐标
- right/bottom 计算:right = left + width,bottom = top + height
- 可见性检测:通过比较元素边界与视口大小来判断元素是否完全可见
- 实际应用:懒加载(元素进入视口时加载)、吸顶效果(元素离开视口时固定)、动画触发(元素进入视口时播放)
3.7 视口尺寸获取
javascript
// 方法一:包含滚动条
const viewportWidth1 = window.innerWidth;
const viewportHeight1 = window.innerHeight;
// 方法二:不包含滚动条
const viewportWidth2 = document.documentElement.clientWidth;
const viewportHeight2 = document.documentElement.clientHeight;
console.log('视口尺寸:', viewportWidth1, 'x', viewportHeight1);
代码解释:
- window.innerWidth/innerHeight:获取浏览器窗口的内部宽高,包含滚动条的宽度
- documentElement.clientWidth/clientHeight:获取文档根元素的宽高,不包含滚动条
- 选择建议 :
- 需要精确计算可用空间时使用
clientWidth/clientHeight(不包含滚动条) - 需要获取窗口总尺寸时使用
innerWidth/innerHeight(包含滚动条)
- 需要精确计算可用空间时使用
- 响应式设计:常用于检测屏幕宽度、实现响应式布局、计算弹窗位置等
4. 获取元素位置
4.1 位置属性概览
| 属性 | 只读 | 参照物 | 说明 |
|---|---|---|---|
| offsetLeft | ✅ | 定位祖先元素 | 到第一个定位祖先的左侧距离 |
| offsetTop | ✅ | 定位祖先元素 | 到第一个定位祖先的顶部距离 |
| clientLeft | ✅ | 元素自身 | 左边框宽度 |
| clientTop | ✅ | 元素自身 | 上边框宽度 |
4.2 offsetLeft / offsetTop
功能:获取元素相对于第一个定位祖先元素的位置。
注意:如果没有定位的祖先元素,则相对于页面(document)。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>offset 位置示例</title>
<style>
.parent {
position: relative;
width: 400px;
height: 300px;
padding: 20px;
border: 2px dashed #999;
margin: 50px;
}
.child {
position: relative;
width: 100px;
height: 100px;
padding: 20px;
margin: 30px;
border: 10px solid #099;
background: #f0f0f0;
}
</style>
</head>
<body>
<div class="parent" id="parent">
<div class="child" id="child">子元素</div>
</div>
<script>
const child = document.querySelector('#child');
const parent = document.querySelector('#parent');
// child 到 parent 的距离(不包含 parent 的 padding)
console.log('offsetLeft:', child.offsetLeft); // 70 (margin 30 + border 2 + padding 20 + margin 30)
console.log('offsetTop:', child.offsetTop); // 70
</script>
</body>
</html>
代码解释:
- 定位参照物 :由于父元素设置了
position: relative,子元素的 offsetLeft/offsetTop 相对于父元素计算 - 计算细节:offsetLeft = 子元素外边距(30) + 父元素边框(2) + 父元素内边距(20) + 子元素外边距(30)
- 定位祖先 :
offsetParent返回最近的定位祖先元素(position 不为 static) - 无定位祖先:如果所有祖先元素都没有定位,则相对于 document(页面)
- 实际应用:计算元素相对位置、实现拖拽效果、元素碰撞检测等
4.3 clientLeft / clientTop
功能:获取元素左边框和上边框的宽度。
html
<div style="border-left: 10px solid red; border-top: 5px solid blue;">
内容
</div>
<script>
const div = document.querySelector('div');
console.log('左边框宽度:', div.clientLeft); // 10
console.log('上边框宽度:', div.clientTop); // 5
</script>
代码解释:
- clientLeft:返回元素的左边框宽度,本例中为 10px
- clientTop:返回元素的上边框宽度,本例中为 5px
- 实际意义:通常用于计算元素内容区域的精确位置
- 注意事项:如果滚动条在左侧(某些从右到左的语言环境),clientLeft 还会包含滚动条宽度
- 应用场景:精确定位、边框计算、元素位置调整等
4.4 位置属性关系图
页面
定位祖先元素
父元素 padding-left
子元素 margin-left
offsetLeft 起点
子元素 border-left
clientLeft 值
4.5 获取元素在页面中的绝对位置
javascript
function getAbsolutePosition(element) {
let x = 0;
let y = 0;
while (element) {
x += element.offsetLeft;
y += element.offsetTop;
element = element.offsetParent;
}
return { x, y };
}
// 使用 getBoundingClientRect 的现代方法
function getPagePosition(element) {
const rect = element.getBoundingClientRect();
return {
x: rect.left + window.scrollX,
y: rect.top + window.scrollY
};
}
代码解释:
-
传统方法(getAbsolutePosition):
- 通过循环遍历元素的 offsetParent 链
- 累加每个 offsetLeft 和 offsetTop
- 最终得到元素相对于页面的绝对位置
- 缺点:需要遍历 DOM 树,性能相对较低
-
现代方法(getPagePosition):
- 使用 getBoundingClientRect() 获取元素相对于视口的位置
- 加上页面的滚动距离(scrollX/scrollY)
- 得到元素相对于页面的绝对位置
- 优点:代码简洁,性能更好
-
应用场景:元素绝对定位、拖拽效果保存、滚动后恢复位置等
5. 滚动位置控制
5.1 滚动属性
| 属性 | 读写 | 说明 |
|---|---|---|
| scrollLeft | ✅ | 内容向左滚动的距离 |
| scrollTop | ✅ | 内容向上滚动的距离 |
5.2 基础滚动控制
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>滚动控制示例</title>
<style>
.container {
width: 300px;
height: 200px;
padding: 20px;
border: 2px solid #999;
overflow: hidden;
}
.content {
width: 800px;
padding: 20px;
background: #f0f0f0;
}
.controls {
position: fixed;
right: 10px;
bottom: 20px;
}
.controls button {
display: block;
margin: 5px;
padding: 10px 20px;
}
</style>
</head>
<body>
<div class="container" id="box">
<div class="content">
<p>长内容区域...</p>
<p>可以滚动的内容...</p>
</div>
</div>
<div class="controls">
<button id="leftBtn">← 左移</button>
<button id="rightBtn">→ 右移</button>
<button id="upBtn">↑ 上移</button>
<button id="downBtn">↓ 下移</button>
</div>
<script>
const box = document.querySelector('#box');
// 监听滚动事件
box.addEventListener('scroll', function() {
console.log('滚动位置:', box.scrollLeft, box.scrollTop);
});
// 控制按钮
document.querySelector('#leftBtn').onclick = () => {
box.scrollLeft += 20;
};
document.querySelector('#rightBtn').onclick = () => {
box.scrollLeft -= 20;
};
document.querySelector('#upBtn').onclick = () => {
box.scrollTop += 20;
};
document.querySelector('#downBtn').onclick = () => {
box.scrollTop -= 20;
};
</script>
</body>
</html>
代码解释:
- scrollLeft:内容向左滚动的距离(正值表示内容向左移动,显示右侧内容)
- scrollTop:内容向上滚动的距离(正值表示内容向上移动,显示下方内容)
- 滚动监听 :通过
scroll事件实时获取滚动位置 - 按钮控制:点击按钮修改 scrollLeft/scrollTop 值,实现内容滚动
- 应用场景:自定义滚动条、图片预览、幻灯片导航等
5.3 页面滚动控制
javascript
// 获取页面滚动距离
const scrollX = window.scrollX || document.documentElement.scrollLeft;
const scrollY = window.scrollY || document.documentElement.scrollTop;
// 设置页面滚动位置
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth' // 平滑滚动
});
// 相对滚动
window.scrollBy({
top: 100,
behavior: 'smooth'
});
// 滚动到指定元素
element.scrollIntoView({
behavior: 'smooth',
block: 'start' // 'start', 'center', 'end', 'nearest'
});
代码解释:
- 获取滚动距离 :
scrollX/scrollY是现代 API,兼容写法使用documentElement.scrollLeft/scrollTop - scrollTo() :滚动到指定位置,
behavior: 'smooth'实现平滑滚动效果 - scrollBy():相对当前滚动位置进行滚动,正值向下/向右滚动
- scrollIntoView() :滚动页面使指定元素可见
block: 'start':元素顶部与视口顶部对齐block: 'center':元素垂直居中block: 'end':元素底部与视口底部对齐
- 应用场景:返回顶部、页面导航、锚点跳转、表单验证滚动到错误位置等
5.4 检测滚动到底部
javascript
function isScrollToBottom(element) {
return element.scrollHeight - element.scrollTop === element.clientHeight;
}
// 页面滚动到底部检测
window.addEventListener('scroll', function() {
const scrollTop = document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
if (scrollTop + clientHeight >= scrollHeight - 10) {
console.log('已滚动到底部');
// 触发加载更多内容
}
});
代码解释:
-
isScrollToBottom():判断元素是否滚动到底部
- 公式:
scrollHeight - scrollTop === clientHeight - 当已滚动距离 + 可见高度 ≥ 总高度时,表示已到底部
- 公式:
-
页面滚动检测:
scrollTop:已滚动的距离scrollHeight:内容总高度clientHeight:视口可见高度- 10:留出 10px 的容差,提前触发加载
-
应用场景:
- 无限滚动加载更多内容
- 滚动到底部显示相关推荐
- 滚动到一定位置显示"返回顶部"按钮
- 懒加载触发时机判断
6. 节点增删改查
6.1 创建节点
javascript
// 创建元素节点
const div = document.createElement('div');
const img = document.createElement('img');
// 创建文本节点
const text = document.createTextNode('文本内容');
// 创建图片快捷方式
const image1 = new Image();
const image2 = new Image(200); // 指定宽度
const image3 = new Image(200, 150); // 指定宽度和高度
代码解释:
- createElement():创建指定类型的元素节点,参数为标签名(如 'div'、'img')
- createTextNode():创建文本节点,常用于添加纯文本内容
- new Image() :创建图片元素的快捷方式,可选择性传入宽高参数
new Image():创建默认尺寸的图片new Image(200):创建宽度为 200px 的图片new Image(200, 150):创建宽度 200px、高度 150px 的图片
- 注意:创建的节点需要通过 appendChild() 等方法添加到文档中才会显示
6.2 添加节点
| 方法 | 说明 | 返回值 |
|---|---|---|
| appendChild(child) | 在末尾添加子节点 | 添加的节点 |
| insertBefore(new, ref) | 在参考节点前插入 | 新节点 |
| prepend() | 在开头添加子节点 | - |
| append() | 在末尾添加子节点 | - |
| after() | 在元素后插入 | - |
| before() | 在元素前插入 | - |
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>节点操作示例</title>
<style>
.todolist {
margin: 50px auto;
width: 700px;
}
.todo-header input {
width: 200px;
padding: 10px;
border: 1px solid #ccc;
}
.todo-header button {
padding: 10px 20px;
background-color: #f5f5f5;
border: 1px solid #ccc;
cursor: pointer;
}
.todo-header button:hover {
background-color: #e0e0e0;
}
.todo-body ul {
padding: 0;
list-style: none;
}
.todo-body li {
margin: 15px 0;
padding: 10px;
border: 1px solid #ccc;
background: #fff;
}
</style>
</head>
<body>
<div class="todolist">
<div class="todo-header">
<input type="text" id="input" placeholder="输入任务">
<button id="addBtn">添加到末尾</button>
<button id="insertBtn">插入到开头</button>
<button id="deleteBtn">删除末尾</button>
<button id="replaceBtn">替换末尾</button>
</div>
<div class="todo-body">
<ul id="todoContent">
<li>学习 JavaScript</li>
<li>学习 DOM 操作</li>
<li>练习项目实战</li>
</ul>
</div>
</div>
<script>
const todoContent = document.querySelector('#todoContent');
const inputBox = document.querySelector('#input');
// 添加到末尾
document.querySelector('#addBtn').onclick = function() {
if (!inputBox.value.trim()) {
alert('请输入任务内容');
return;
}
const newLi = document.createElement('li');
newLi.textContent = inputBox.value;
todoContent.appendChild(newLi);
inputBox.value = '';
};
// 插入到开头
document.querySelector('#insertBtn').onclick = function() {
if (!inputBox.value.trim()) {
alert('请输入任务内容');
return;
}
const newLi = document.createElement('li');
newLi.textContent = inputBox.value;
todoContent.insertBefore(newLi, todoContent.firstElementChild);
inputBox.value = '';
};
// 删除末尾
document.querySelector('#deleteBtn').onclick = function() {
if (todoContent.lastElementChild) {
todoContent.removeChild(todoContent.lastElementChild);
}
};
// 替换末尾
document.querySelector('#replaceBtn').onclick = function() {
if (!inputBox.value.trim()) {
alert('请输入任务内容');
return;
}
if (todoContent.lastElementChild) {
const newLi = document.createElement('li');
newLi.textContent = inputBox.value;
todoContent.replaceChild(newLi, todoContent.lastElementChild);
inputBox.value = '';
}
};
</script>
</body>
</html>
6.3 删除节点
javascript
// 方法一:通过父节点删除
parent.removeChild(child);
// 方法二:直接删除(现代浏览器)
child.remove();
// 删除所有子节点
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
// 清空子节点(更高效)
parent.innerHTML = '';
代码解释:
- removeChild():传统方法,通过父节点删除指定子节点
- remove():现代方法,元素可以直接调用自身删除,代码更简洁
- 循环删除:使用 while 循环逐个删除子节点,直到没有子节点为止
- innerHTML 清空:最快速的方法,但存在性能和安全问题
- 推荐做法:根据场景选择,少量节点用 remove(),大量节点考虑批量操作
6.4 替换节点
javascript
// replaceChild(newNode, oldNode)
const newLi = document.createElement('li');
newLi.textContent = '新任务';
todoContent.replaceChild(newLi, todoContent.lastElementChild);
// 现代方法
oldElement.replaceWith(newElement);
代码解释:
- replaceChild():传统方法,用新节点替换旧节点,参数顺序为(新节点,旧节点)
- replaceWith():现代方法,旧节点调用自身来替换为新节点,代码更直观
- DOM 变化:旧节点从文档树中移除,新节点占据相同位置
- 应用场景:更新列表项、替换错误提示、动态内容刷新等
6.5 克隆节点
javascript
// cloneNode(deep)
// deep: true - 深度克隆(包含所有后代)
// deep: false - 浅克隆(只克隆节点本身)
const original = document.querySelector('#original');
const deepClone = original.cloneNode(true);
const shallowClone = original.cloneNode(false);
代码解释:
- cloneNode(true):深度克隆,复制元素及其所有后代节点
- cloneNode(false):浅克隆,只复制元素本身,不包含子节点
- 克隆内容:复制元素的标签、属性、样式,但不复制事件监听器
- 注意事项:克隆的节点需要手动添加到文档中,克隆的 id 重复可能导致问题
- 应用场景:复制模板、批量创建相似元素、实现撤销/重做功能等
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>克隆节点示例</title>
<style>
.original {
padding: 20px;
background: #f0f0f0;
margin: 10px;
}
.clone {
padding: 20px;
background: #e0e0e0;
margin: 10px;
border: 2px dashed #999;
}
</style>
</head>
<body>
<div id="original" class="original">
<h3>原始元素</h3>
<p>这是原始内容</p>
</div>
<button id="cloneDeep">深度克隆</button>
<button id="cloneShallow">浅克隆</button>
<div id="container"></div>
<script>
const original = document.querySelector('#original');
const container = document.querySelector('#container');
// 深度克隆
document.querySelector('#cloneDeep').onclick = function() {
const clone = original.cloneNode(true);
clone.classList.add('clone');
clone.querySelector('h3').textContent = '深度克隆副本';
container.appendChild(clone);
};
// 浅克隆
document.querySelector('#cloneShallow').onclick = function() {
const clone = original.cloneNode(false);
clone.classList.add('clone');
clone.innerHTML = '<h3>浅克隆副本</h3><p>只包含元素本身</p>';
container.appendChild(clone);
};
</script>
</body>
</html>
6.6 节点操作关系图
添加
插入
替换
删除
克隆
document.createElement
创建新节点
操作类型
appendChild
insertBefore
replaceChild
removeChild
cloneNode
添加到父节点末尾
插入到指定位置
替换旧节点
从父节点移除
复制节点
7. document 对象详解
7.1 document 核心属性
javascript
// 文档元素
document.documentElement // html 元素
document.body // body 元素
document.head // head 元素
document.all // 所有元素的集合
// 文档信息
document.title // 读写页面标题
document.cookie // 读写 cookie
document.URL // 当前页面 URL
document.domain // 页面域名
document.referrer // 来源页面 URL
// 文档状态
document.readyState // 'loading', 'interactive', 'complete'
document.compatMode // 渲染模式
代码解释:
-
文档元素快捷访问:
documentElement:返回<html>根元素body:返回<body>元素head:返回<head>元素all:返回文档中所有元素的集合(非标准但广泛支持)
-
文档信息:
title:可读写,用于获取或修改浏览器标签页标题cookie:用于读写 HTTP CookieURL:当前页面的完整地址domain:当前页面的域名referrer:链接到当前页面的来源页面 URL
-
文档状态:
readyState:文档加载状态,'loading'(加载中)、'interactive'(可交互)、'complete'(完成)compatMode:渲染模式,'CSS1Compat'(标准模式)、'BackCompat'(怪异模式)
7.2 document 核心方法
javascript
// 获取元素
document.getElementById(id)
document.getElementsByTagName(name)
document.getElementsByClassName(name)
document.getElementsByName(name)
document.querySelector(selectors)
document.querySelectorAll(selectors)
// 创建节点
document.createElement(tagName)
document.createTextNode(data)
document.createDocumentFragment()
// 文档写入(解析阶段)
document.write(markup)
document.writeln(markup)
代码解释:
-
元素获取方法:
getElementById():通过 ID 获取单个元素,效率最高getElementsByTagName():通过标签名获取元素集合getElementsByClassName():通过类名获取元素集合getElementsByName():通过 name 属性获取元素集合(常用于表单)querySelector():使用 CSS 选择器获取第一个匹配元素querySelectorAll():使用 CSS 选择器获取所有匹配元素
-
节点创建方法:
createElement():创建元素节点createTextNode():创建文本节点createDocumentFragment():创建文档片段(用于批量操作)
-
文档写入方法:
write():向文档写入内容writeln():写入内容并添加换行符- 注意:这些方法只在文档解析阶段有效,页面加载完成后调用会清空整个文档
7.3 实战示例:标题滚动效果
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>标题滚动示例</title>
</head>
<body>
<h1>请查看浏览器标题栏</h1>
<script>
(function() {
// 设置初始标题
const title = '欢迎访问我的网站 - 新消息提醒';
document.title = title;
// 标题滚动效果
let position = 0;
setInterval(function() {
document.title = title.slice(position) + title.slice(0, position);
position = (position + 1) % title.length;
}, 300);
})();
</script>
</body>
</html>
代码解释:
- 初始设置 :使用
document.title设置页面标题 - 滚动逻辑 :
title.slice(position):获取从 position 位置到字符串末尾的子串title.slice(0, position):获取从开头到 position 位置的子串- 将两部分拼接,实现标题字符循环移动的效果
- position 更新 :每次增加 1,使用取模运算
% title.length保证在 0 到标题长度之间循环 - setInterval:每 300ms 执行一次,形成连续滚动效果
- 实际应用:新消息提醒、活动公告、重要通知等场景
8. 实战案例
本章覆盖目录中全部交互案例:三按钮全选 、父级全选框 、目标日期倒计时 、图片懒加载 、scrollLeft 无缝滚动 、TodoList 节点增删改 (见第 6 节)、标题滚动 (见第 7.3 节),并补充随机点名器、选项卡、HTML DOM 与事件衔接。示例图片统一使用同目录下 images/db01.jpg~db10.jpg 资源。
8.1 案例一(基础):三按钮全选/取消全选/反选
应用场景:批量勾选列表项、简易权限勾选,逻辑直观,适合入门。
核心 API :querySelectorAll、checked 属性、forEach。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>三按钮全选示例</title>
<style>
ul { list-style: none; padding-left: 0; }
li { margin: 8px 0; }
button { margin-right: 8px; padding: 6px 12px; cursor: pointer; }
</style>
</head>
<body>
<ul id="checkboxList">
<li><input type="checkbox"> 待办事项 1</li>
<li><input type="checkbox"> 待办事项 2</li>
<li><input type="checkbox"> 待办事项 3</li>
<li><input type="checkbox"> 待办事项 4</li>
<li><input type="checkbox"> 待办事项 5</li>
<li><input type="checkbox"> 待办事项 6</li>
</ul>
<hr>
<button id="selectAllBtn">全选</button>
<button id="selectNotAllBtn">取消全选</button>
<button id="reverseBtn">反选</button>
<script>
const checkboxItems = document.querySelectorAll('#checkboxList input');
document.querySelector('#selectAllBtn').onclick = function () {
checkboxItems.forEach(function (item) { item.checked = true; });
};
document.querySelector('#selectNotAllBtn').onclick = function () {
checkboxItems.forEach(function (item) { item.checked = false; });
};
document.querySelector('#reverseBtn').onclick = function () {
checkboxItems.forEach(function (item) {
item.checked = !item.checked;
});
};
</script>
</body>
</html>
代码解释:
querySelectorAll('#checkboxList input')一次性拿到列表内所有复选框引用。- 全选/取消全选 :遍历集合并统一设置
checked为true或false。 - 反选 :对每个复选框取反
!item.checked,未选中变选中,反之亦然。 - 网站应用:后台批量审核勾选、网盘多文件操作栏、简易问卷多选。
8.2 案例二(增强):父级全选与半选状态
应用场景:邮箱批量操作、购物车商品选择、权限管理等(含「全选框 + 子项联动」)。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>复选框全选示例</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 600px;
margin: 0 auto;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #f0f0f0;
border-radius: 5px;
margin-bottom: 15px;
}
.checkbox-list {
list-style: none;
}
.checkbox-list li {
padding: 15px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
}
.checkbox-list li:hover {
background: #f9f9f9;
}
.checkbox-list input[type="checkbox"] {
width: 18px;
height: 18px;
margin-right: 10px;
cursor: pointer;
}
.buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
.buttons button {
flex: 1;
padding: 12px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.btn-select {
background: #4CAF50;
color: white;
}
.btn-select:hover {
background: #45a049;
}
.btn-deselect {
background: #f44336;
color: white;
}
.btn-deselect:hover {
background: #da190b;
}
.btn-reverse {
background: #2196F3;
color: white;
}
.btn-reverse:hover {
background: #0b7dda;
}
.status {
text-align: center;
padding: 10px;
margin-top: 15px;
color: #666;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="checkAll">
<span style="margin-left: 8px;">全选 / 取消全选</span>
</label>
<span id="selectedCount">已选: 0 / 6</span>
</div>
<ul id="checkboxList" class="checkbox-list">
<li><label><input type="checkbox"> HTML5 语义化标签</label></li>
<li><label><input type="checkbox"> CSS3 Flexbox 布局</label></li>
<li><label><input type="checkbox"> CSS3 Grid 网格布局</label></li>
<li><label><input type="checkbox"> JavaScript ES6+ 新特性</label></li>
<li><label><input type="checkbox"> DOM 操作与事件处理</label></li>
<li><label><input type="checkbox"> 前端工程化与构建工具</label></li>
</ul>
<div class="buttons">
<button class="btn-select" id="selectAllBtn">全选</button>
<button class="btn-deselect" id="deselectAllBtn">取消全选</button>
<button class="btn-reverse" id="reverseBtn">反选</button>
</div>
<div class="status" id="status"></div>
</div>
<script>
(function() {
// 获取元素
const checkboxList = document.querySelector('#checkboxList');
const checkboxes = checkboxList.querySelectorAll('input[type="checkbox"]');
const checkAll = document.querySelector('#checkAll');
const selectAllBtn = document.querySelector('#selectAllBtn');
const deselectAllBtn = document.querySelector('#deselectAllBtn');
const reverseBtn = document.querySelector('#reverseBtn');
const selectedCount = document.querySelector('#selectedCount');
const status = document.querySelector('#status');
// 更新选中数量和全选状态
function updateStatus() {
const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
selectedCount.textContent = `已选: ${checkedCount} / ${checkboxes.length}`;
checkAll.checked = checkedCount === checkboxes.length && checkboxes.length > 0;
checkAll.indeterminate = checkedCount > 0 && checkedCount < checkboxes.length;
}
// 全选复选框监听
checkAll.addEventListener('change', function() {
checkboxes.forEach(cb => cb.checked = this.checked);
updateStatus();
showStatus(this.checked ? '已全选' : '已取消全选');
});
// 单个复选框监听
checkboxes.forEach(cb => {
cb.addEventListener('change', updateStatus);
});
// 全选按钮
selectAllBtn.addEventListener('click', function() {
checkboxes.forEach(cb => cb.checked = true);
updateStatus();
showStatus('已全选');
});
// 取消全选按钮
deselectAllBtn.addEventListener('click', function() {
checkboxes.forEach(cb => cb.checked = false);
updateStatus();
showStatus('已取消全选');
});
// 反选按钮
reverseBtn.addEventListener('click', function() {
checkboxes.forEach(cb => cb.checked = !cb.checked);
updateStatus();
showStatus('已反选');
});
// 显示状态提示
function showStatus(message) {
status.textContent = message;
status.style.opacity = '1';
setTimeout(() => {
status.style.opacity = '0.5';
}, 2000);
}
// 初始化状态
updateStatus();
})();
</script>
</body>
</html>
代码解释:
-
HTML 结构:
- 顶部全选框:控制所有复选框的状态
- 复选框列表:6个技术主题选项
- 操作按钮:全选、取消全选、反选
- 状态显示:实时显示已选数量和提示信息
-
CSS 样式:
- 响应式布局,卡片式设计
- 悬停效果和按钮过渡动画
- 清晰的视觉层次和色彩区分
-
JavaScript 核心功能:
updateStatus():更新选中数量和全选框状态,使用indeterminate属性表示部分选中状态- 全选框监听:点击全选框时,同步所有子复选框状态
- 单个复选框监听:每个子复选框变化时,更新全选框状态
- 三个操作按钮:全选、取消全选、反选(取反操作)
showStatus():显示操作反馈,2秒后淡出
-
实际应用:邮箱批量删除、购物车全选、权限批量设置、数据批量导出等场景
8.3 案例三:目标日期倒计时
应用场景:活动开始提醒、课程开课提醒、考试时间提醒、任务截止时间提醒。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>目标日期倒计时</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #43cea2, #185a9d);
font-family: Arial, sans-serif;
}
#box {
padding: 40px;
border-radius: 16px;
background: rgba(255, 255, 255, .95);
color: #185a9d;
text-align: center;
font-size: 42px;
font-weight: 700;
line-height: 1.8;
box-shadow: 0 20px 60px rgba(0, 0, 0, .2);
}
</style>
</head>
<body>
<div id="box"></div>
<script>
(function () {
const box = document.querySelector('#box');
// 目标时间:当前时间 3 天后。实际项目中可替换为后端返回的截止时间。
const targetDate = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000 + 2 * 60 * 60 * 1000);
let timerId = null;
function pad(num) {
return num < 10 ? '0' + num : String(num);
}
function render() {
const diff = targetDate.getTime() - Date.now();
if (diff <= 0) {
clearInterval(timerId);
box.innerHTML = '活动已开始';
return;
}
const days = Math.floor(diff / (24 * 60 * 60 * 1000));
const hours = Math.floor(diff % (24 * 60 * 60 * 1000) / (60 * 60 * 1000));
const minutes = Math.floor(diff % (60 * 60 * 1000) / (60 * 1000));
const seconds = Math.floor(diff % (60 * 1000) / 1000);
box.innerHTML = '距离活动开始还有<br>' +
pad(days) + ' 天 ' + pad(hours) + ' 时 ' +
pad(minutes) + ' 分 ' + pad(seconds) + ' 秒';
}
render();
timerId = setInterval(render, 1000);
})();
</script>
</body>
</html>
代码解释:
targetDate.getTime() - Date.now()得到目标时间与当前时间的毫秒差。- 通过取整与取余依次拆出天、小时、分钟、秒。
pad()负责补零,让视觉格式稳定。- 当差值小于等于 0 时,必须
clearInterval()清除定时器,避免持续执行。 - 网站应用:课程预约、直播开始提醒、限时表单提交、任务截止页。