body 元素
<body> 元素是 HTML 文档中所有用户可见内容的容器。页面中显示的文字、图片、链接、表格、表单、音视频等所有内容,都必须放在 <body> 内部。
前置知识
阅读本节前,建议先了解:head 元素
基础概念
body 的作用
<body> 元素定义了 HTML 文档的内容区域。如果说 <head> 是文档的"幕后配置",那么 <body> 就是文档的"舞台"——所有用户能看到的、能交互的内容都在这里呈现。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>body 元素演示</title>
</head>
<body>
<!-- 用户能看到的所有内容都在这里 -->
<header>
<h1>网站标题</h1>
<nav>导航菜单</nav>
</header>
<main>
<p>这是页面的主要内容区域。</p>
</main>
<footer>
<p>页脚信息</p>
</footer>
</body>
</html>body 的层级关系
Document
└── html (根元素)
├── head (元信息)
│ ├── meta
│ ├── title
│ └── link
└── body (内容区域) ← 本节重点
├── header
│ ├── h1
│ └── nav
├── main
│ ├── article
│ ├── section
│ └── aside
└── footerbody 与 document.body
在 JavaScript 中,document.body 是访问 <body> 元素的快捷方式:
// 两种方式等价
var body1 = document.body;
var body2 = document.getElementsByTagName('body')[0];
var body3 = document.querySelector('body');注意
如果脚本在 <head> 中同步执行(没有 defer 或 async),此时 document.body 可能为 null,因为 <body> 还未被解析。
语法
基本语法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>页面标题</title>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>body 的全局属性
<body> 元素除了支持所有 HTML 全局属性外,还支持以下专属的事件属性:
窗口事件属性
| 属性 | 说明 | 触发时机 |
|---|---|---|
onload | 页面加载完成 | 所有资源(图片、样式表、脚本等)加载完毕 |
onunload | 页面卸载 | 用户离开页面时 |
onbeforeunload | 页面即将卸载 | 在 unload 之前触发,可用于提示用户保存 |
onpageshow | 页面显示 | 页面加载或从缓存中恢复时 |
onpagehide | 页面隐藏 | 用户离开页面或页面被缓存时 |
onresize | 窗口大小改变 | 浏览器窗口尺寸变化时 |
onscroll | 页面滚动 | 用户滚动页面时 |
onerror | 加载错误 | 资源加载失败时 |
表单事件属性
| 属性 | 说明 |
|---|---|
onfocus | 元素获得焦点 |
onblur | 元素失去焦点 |
onreset | 表单重置 |
onsubmit | 表单提交 |
onchange | 元素值改变 |
oninput | 用户输入时 |
onselect | 文本被选中 |
oninvalid | 表单验证失败 |
拖拽事件属性
| 属性 | 说明 |
|---|---|
ondrag | 元素被拖动 |
ondragstart | 拖动开始 |
ondragend | 拖动结束 |
ondragover | 拖过目标元素 |
ondragenter | 拖入目标元素 |
ondragleave | 拖离目标元素 |
ondrop | 在目标元素上放下 |
onscroll | 滚动事件 |
剪贴板事件属性
| 属性 | 说明 |
|---|---|
oncopy | 复制内容 |
oncut | 剪切内容 |
onpaste | 粘贴内容 |
键盘事件属性
| 属性 | 说明 |
|---|---|
onkeydown | 按下键盘按键 |
onkeypress | 按下字符键(已废弃) |
onkeyup | 释放键盘按键 |
鼠标事件属性
| 属性 | 说明 |
|---|---|
onclick | 单击 |
ondblclick | 双击 |
onmousedown | 鼠标按下 |
onmouseup | 鼠标释放 |
onmouseover | 鼠标移入 |
onmousemove | 鼠标移动 |
onmouseout | 鼠标移出 |
onmouseenter | 鼠标进入(不冒泡) |
onmouseleave | 鼠标离开(不冒泡) |
onwheel | 滚轮滚动 |
oncontextmenu | 右键菜单 |
关于内联事件属性
虽然在 <body> 标签上使用事件属性(如 onload="init()")是合法的,但不推荐这种写法。推荐使用 JavaScript 的 addEventListener 方法来绑定事件,以实现关注点分离。
详细说明
body 的默认样式
浏览器为 <body> 元素提供了默认样式,不同浏览器的默认值略有差异:
/* 大多数浏览器的 body 默认样式 */
body {
display: block;
margin: 8px; /* 浏览器默认有 8px 的外边距 */
/* Chrome/Safari 的默认值 */
}通常在开发中,我们会重置 body 的默认样式:
/* 常见的 CSS 重置 */
body {
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
color: #333;
background-color: #fff;
}body 的背景与前景
<body> 元素是设置页面背景色和默认文字颜色的标准位置:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>body 背景</title>
<style>
/* 通过 CSS 设置 body 背景 */
body {
background-color: #f5f5f5;
color: #333;
background-image: url('background.jpg');
background-size: cover;
background-position: center;
background-attachment: fixed;
min-height: 100vh;
}
</style>
</head>
<body>
<h1>页面内容</h1>
</body>
</html>补充说明
根据 HTML 规范,如果 <body> 没有设置背景,浏览器会将 <html> 元素的背景应用到整个画布。但为了清晰和可预测性,建议直接在 <body> 上设置背景样式。
onload 事件详解
onload 是 <body> 最常用的事件属性,它在页面所有资源加载完成后触发。
使用内联事件属性(不推荐)
<!-- 不推荐:内联事件处理器 -->
<body onload="init()">使用 JavaScript addEventListener(推荐)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>onload 事件</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 700px;
margin: 40px auto;
padding: 0 20px;
}
</style>
</head>
<body>
<h1>页面加载事件</h1>
<p id="status">加载中...</p>
<img src="https://picsum.photos/800/400" alt="演示图片" id="demo-img">
<script>
// 推荐:使用 addEventListener
window.addEventListener('load', function() {
document.getElementById('status').textContent =
'所有资源已加载完成!(包括图片)';
});
// DOMContentLoaded:DOM 解析完成即触发,不等待图片等资源
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM 已解析完成,但图片可能还在加载。');
});
</script>
</body>
</html>load 与 DOMContentLoaded 的区别
| 特性 | load | DOMContentLoaded |
|---|---|---|
| 触发时机 | 所有资源加载完成(图片、CSS、JS、iframe) | DOM 解析完成(不等待图片和样式表) |
| 触发对象 | window | document |
| 等待图片 | 是 | 否 |
| 等待 CSS | 是 | 否(但同步脚本会阻塞解析) |
| 等待 iframe | 是 | 否 |
| 适用场景 | 需要操作图片尺寸等依赖资源就绪的场景 | 需要尽早操作 DOM 的场景 |
| 性能 | 触发较晚 | 触发较早 |
// load 事件:等待所有资源
window.addEventListener('load', function() {
var img = document.getElementById('my-image');
// 此时可以安全地获取图片的宽高
console.log('图片宽度:', img.naturalWidth);
console.log('图片高度:', img.naturalHeight);
});
// DOMContentLoaded 事件:DOM 解析完成
document.addEventListener('DOMContentLoaded', function() {
// 此时 DOM 已可操作,但图片可能未加载
var list = document.getElementById('user-list');
// 可以安全地操作 DOM 元素
list.innerHTML = '<li>用户 1</li><li>用户 2</li>';
});body 中的内容组织
<body> 内部通常按照语义化标签来组织内容:
<body>
<!-- 页面头部 -->
<header>
<h1>网站名称</h1>
<nav>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/about">关于</a></li>
<li><a href="/contact">联系</a></li>
</ul>
</nav>
</header>
<!-- 主要内容 -->
<main>
<article>
<h2>文章标题</h2>
<p>文章内容...</p>
</article>
<aside>
<h3>侧边栏</h3>
<p>相关链接和推荐内容</p>
</aside>
</main>
<!-- 页面底部 -->
<footer>
<p>© 2024 网站名称. 保留所有权利。</p>
</footer>
</body>body 与 iframe
在 iframe 中,内部页面也有自己的 <body> 元素:
<!-- 父页面 -->
<body>
<h1>父页面</h1>
<iframe src="child.html" id="my-iframe"></iframe>
<script>
var iframe = document.getElementById('my-iframe');
// 访问 iframe 内部的 body
iframe.addEventListener('load', function() {
var innerBody = iframe.contentDocument.body;
innerBody.style.backgroundColor = '#ffffcc';
});
</script>
</body>跨域限制
如果 iframe 加载的是跨域页面,由于同源策略限制,父页面无法访问 iframe 内部的 DOM。
实战示例
页面加载进度指示器
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加载进度指示器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
}
/* 加载遮罩 */
#loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
transition: opacity 0.5s ease;
}
#loading-overlay.hidden {
opacity: 0;
pointer-events: none;
}
.progress-bar {
width: 300px;
height: 6px;
background: #e0e0e0;
border-radius: 3px;
overflow: hidden;
margin-top: 20px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #0066cc, #00aaff);
border-radius: 3px;
width: 0%;
transition: width 0.3s ease;
}
.loading-text {
margin-top: 12px;
color: #666;
font-size: 14px;
}
/* 主内容 */
#main-content {
text-align: center;
display: none;
}
#main-content h1 {
font-size: 2em;
margin-bottom: 10px;
}
</style>
</head>
<body>
<!-- 加载遮罩 -->
<div id="loading-overlay">
<div class="progress-bar">
<div class="progress-fill" id="progress-fill"></div>
</div>
<p class="loading-text" id="loading-text">正在加载资源...</p>
</div>
<!-- 主内容(初始隐藏) -->
<div id="main-content">
<h1>页面加载完成</h1>
<p>所有资源已成功加载,欢迎访问!</p>
<img src="https://picsum.photos/600/300" alt="演示图片">
</div>
<script>
var progressFill = document.getElementById('progress-fill');
var loadingText = document.getElementById('loading-text');
var overlay = document.getElementById('loading-overlay');
var mainContent = document.getElementById('main-content');
// 模拟加载进度
var progress = 0;
var timer = setInterval(function() {
progress += Math.random() * 15 + 5;
if (progress > 90) progress = 90; // 停在 90% 等待真实加载
progressFill.style.width = progress + '%';
}, 200);
// DOM 加载完成
document.addEventListener('DOMContentLoaded', function() {
loadingText.textContent = '正在渲染页面...';
});
// 所有资源加载完成
window.addEventListener('load', function() {
clearInterval(timer);
progressFill.style.width = '100%';
loadingText.textContent = '加载完成!';
setTimeout(function() {
overlay.classList.add('hidden');
mainContent.style.display = 'block';
}, 500);
});
</script>
</body>
</html>实时显示滚动信息
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>滚动信息</title>
<style>
body {
font-family: system-ui, sans-serif;
margin: 0;
padding: 0;
line-height: 1.6;
}
/* 固定信息栏 */
#scroll-info {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: rgba(0, 0, 0, 0.85);
color: white;
padding: 10px 20px;
font-size: 14px;
font-family: monospace;
z-index: 1000;
}
#scroll-info span {
margin-right: 20px;
}
/* 生成足够多的内容以便滚动 */
.content-section {
min-height: 100vh;
padding: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5em;
color: white;
}
.section-1 { background: linear-gradient(135deg, #667eea, #764ba2); }
.section-2 { background: linear-gradient(135deg, #f093fb, #f5576c); }
.section-3 { background: linear-gradient(135deg, #4facfe, #00f2fe); }
.section-4 { background: linear-gradient(135deg, #43e97b, #38f9d7); }
.section-5 { background: linear-gradient(135deg, #fa709a, #fee140); }
</style>
</head>
<body>
<div id="scroll-info">
<span>scrollY: <b id="scroll-y">0</b>px</span>
<span>窗口高度: <b id="win-height">0</b>px</span>
<span>文档高度: <b id="doc-height">0</b>px</span>
<span>滚动百分比: <b id="scroll-percent">0</b>%</span>
</div>
<div class="content-section section-1"><h1>第一部分</h1></div>
<div class="content-section section-2"><h1>第二部分</h1></div>
<div class="content-section section-3"><h1>第三部分</h1></div>
<div class="content-section section-4"><h1>第四部分</h1></div>
<div class="content-section section-5"><h1>第五部分 - 到底了!</h1></div>
<script>
var scrollYEl = document.getElementById('scroll-y');
var winHeightEl = document.getElementById('win-height');
var docHeightEl = document.getElementById('doc-height');
var scrollPercentEl = document.getElementById('scroll-percent');
// 使用 passive 事件监听器提高滚动性能
window.addEventListener('scroll', function() {
var scrollY = window.scrollY || window.pageYOffset;
var winHeight = window.innerHeight;
var docHeight = document.documentElement.scrollHeight;
var maxScroll = docHeight - winHeight;
var percent = maxScroll > 0 ? Math.round((scrollY / maxScroll) * 100) : 0;
scrollYEl.textContent = scrollY;
winHeightEl.textContent = winHeight;
docHeightEl.textContent = docHeight;
scrollPercentEl.textContent = percent;
}, { passive: true });
</script>
</body>
</html>注意事项
1. body 中不应放 meta 和 link 等元信息元素
<!-- 错误!meta 和 link 应该在 head 中 -->
<body>
<meta charset="UTF-8">
<title>错误示例</title>
<link rel="stylesheet" href="style.css">
<h1>内容</h1>
</body>
<!-- 正确 -->
<head>
<meta charset="UTF-8">
<title>正确示例</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>内容</h1>
</body>虽然浏览器能容错处理这种情况,但这是不规范的写法。
2. 避免在 body 上直接设置样式属性
<!-- 不推荐:使用 HTML 属性设置样式 -->
<body bgcolor="#ffffff" text="#333333" link="#0066cc" vlink="#663399" alink="#ff0000">
<!-- 推荐:使用 CSS 设置样式 -->
<body>
<style>
body {
background-color: #ffffff;
color: #333333;
}
a { color: #0066cc; }
a:visited { color: #663399; }
a:active { color: #ff0000; }
</style>bgcolor、text、link、vlink、alink 等 body 属性已在 HTML5 中被废弃。
3. 避免过深的嵌套
虽然 <body> 可以包含任意深度的嵌套元素,但过深的嵌套会影响性能和可读性:
<!-- 不推荐:过深的嵌套 -->
<body>
<div>
<div>
<div>
<div>
<div>
<p>内容</p>
</div>
</div>
</div>
</div>
</div>
</body>建议嵌套层级不超过 5~7 层。
4. 确保 body 有足够的可访问性
<!-- 推荐:使用语义化标签 -->
<body>
<a href="#main-content" class="skip-link">跳转到主要内容</a>
<header>...</header>
<main id="main-content">...</main>
<footer>...</footer>
</body>最佳实践
1. 使用 CSS 重置 body 默认样式
/* 推荐:CSS 重置或 Normalize.css */
body {
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.6;
color: #333;
background-color: #fff;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}2. 使用语义化标签组织内容
<body>
<header>
<nav>...</nav>
</header>
<main>
<article>...</article>
<aside>...</aside>
</main>
<footer>...</footer>
</body>3. 使用 JavaScript 事件监听器,而非内联事件
<!-- 不推荐 -->
<body onload="init()" onscroll="handleScroll()">
<!-- 推荐 -->
<script>
window.addEventListener('load', init);
window.addEventListener('scroll', handleScroll, { passive: true });
</script>4. 为 body 设置 min-height
/* 确保页面内容不足时,背景色仍覆盖整个视口 */
body {
min-height: 100vh;
}5. 使用 skip link 提升无障碍性
<body>
<a href="#main" style="position:absolute; left:-9999px;">
跳转到主要内容
</a>
<main id="main">
<!-- 主要内容 -->
</main>
</body>下一节
继续学习:title 标题