浏览器环境,规格
平台可以是一个浏览器,一个 Web 服务器,或其他 主机(host),甚至可以是一个“智能”咖啡机,如果它能运行 JavaScript 的话。它们每个都提供了特定于平台的功能。JavaScript 规范将其称为 主机环境。
- 有一个叫做
window
的“根”对象。它有两个角色:- 首先,它是 JavaScript 代码的全局对象,如 全局对象 一章所述。
- 其次,它代表“浏览器窗口”,并提供了控制它的方法。
- 文档对象模型(DOM)
- 文档对象模型(Document Object Model),简称 DOM,将所有页面内容表示为可以修改的对象。
document
对象是页面的主要“入口点”。我们可以使用它来更改或创建页面上的任何内容。- DOM 规范解释了文档的结构,并提供了操作文档的对象。有的非浏览器设备也使用 DOM。
- 浏览器对象模型(BOM)
DOM 树
HTML 文档的主干是标签(tag)。 根据文档对象模型(DOM),每个 HTML 标签都是一个对象。嵌套的标签是闭合标签的“子标签(children)”。标签内的文本也是一个对象。 所有这些对象都可以通过 JavaScript 来访问,我们可以使用它们来修改页面。
- 一些标签
document.body
是表示<body>
标签的对象。innerHTML
—— 节点的 HTML 内容。
offsetWidth
—— 节点宽度(以像素度量)
- 等等
- DOM的一些知识点
- 标签被称为 元素节点(或者仅仅是元素),并形成了树状结构:
<html>
在根节点,<head>
和<body>
是其子项,等。 - 元素内的文本形成 文本节点,被标记为
#text
。一个文本节点只包含一个字符串。它没有子项,并且总是树的叶子。 - 字符串开头/结尾处的空格,以及只有空格的文本节点,通常会被工具隐藏
- 标签被称为 元素节点(或者仅仅是元素),并形成了树状结构:
- 自动修正
- 如果浏览器遇到格式不正确的 HTML,它会在形成 DOM 时自动更正它。
- 表格永远有
<tbody>
- 其他节点类型
- 除了元素和文本节点外,还有一些其他的节点类型。,例如注释
- HTML 中的所有内容,甚至注释,都会成为 DOM 的一部分。
- 甚至 HTML 开头的
<!DOCTYPE...>
指令也是一个 DOM 节点 document
—— DOM 的“入口点”。- 元素节点 —— HTML 标签,树构建块。
- 本节点 —— 包含文本。
- 注释 —— 有时我们可以将一些信息放入其中,它不会显示,但 JS 可以从 DOM 中读取它。
- 查看DOM结构
- 可以打开这个网页 elks.html,然后打开浏览器开发工具,并切换到元素(Elements)选项卡。
- Styles —— 我们可以看到按规则应用于当前元素的 CSS 规则,包括内建规则(灰色)。几乎所有内容都可以就地编辑,包括下面的方框的 dimension/margin/padding。
- Computed —— 按属性查看应用于元素的 CSS:对于每个属性,我们可以都可以看到赋予它的规则(包括 CSS 继承等)。
- Event Listeners —— 查看附加到 DOM 元素的事件侦听器(我们将在本教程的下一部分介绍它们)。
- 与控制台交互
- 在我们处理 DOM 时,我们可能还希望对其应用 JavaScript。例如:获取一个节点并运行一些代码来修改它,以查看结果。以下是在元素(Elements)选项卡和控制台(Console)之间切换的一些技巧。
- 在元素(Elements)选项卡中选择第一个
<li>
。
- 在元素(Elements)选项卡中选择第一个
- 按下 Esc —— 它将在元素(Elements)选项卡下方打开控制台(Console)。
- 在我们处理 DOM 时,我们可能还希望对其应用 JavaScript。例如:获取一个节点并运行一些代码来修改它,以查看结果。以下是在元素(Elements)选项卡和控制台(Console)之间切换的一些技巧。
遍历 DOM
DOM 让我们可以对元素和它们中的内容做任何事,但是首先我们需要获取到对应的 DOM 对象。 对 DOM 的所有操作都是以 document
对象开始。它是 DOM 的主“入口点”。从它我们可以访问任何节点。
- 在最顶层:documentElement 和 body
- 最顶层的树节点可以直接作为
document
的属性来使用:<html>
=document.documentElement
- 最顶层的 document 节点是
document.documentElement
。这是对应<html>
标签的 DOM 节点。 <body>
=document.body
- 另一个被广泛使用的 DOM 节点是
<body>
元素 ——document.body
。 <head>
=document.head
<head>
标签可以通过document.head
访问。
- 这里有个问题:
document.body
的值可能是null
- 脚本无法访问在运行时不存在的元素。
- 尤其是,如果一个脚本是在
<head>
中,那么脚本是访问不到document.body
元素的,因为浏览器还没有读到它。
- 最顶层的树节点可以直接作为
- 子节点:childNodes,firstChild,lastChild
- 子节点(或者叫作子) —— 对应的是直系的子元素。换句话说,它们被完全嵌套在给定的元素中。例如,
<head>
和<body>
就是<html>
元素的子元素。
- 子节点(或者叫作子) —— 对应的是直系的子元素。换句话说,它们被完全嵌套在给定的元素中。例如,
- 子孙元素 —— 嵌套在给定元素中的所有元素,包括子元素,以及子元素的子元素等。
childNodes
集合列出了所有子节点,包括文本节点。firstChild
和lastChild
属性是访问第一个和最后一个子元素的快捷方式。
- DOM 集合
- 正如我们看到的那样,
childNodes
看起来就像一个数组。但实际上它并不是一个数组,而是一个 集合 —— 一个类数组的可迭代对象。- 我们可以使用
for..of
来迭代它 - 无法使用数组的方法,因为它不是一个数组
- 我们可以使用
- DOM 集合是只读的
- DOM 集合,甚至可以说本章中列出的 所有 导航(navigation)属性都是只读的。
- 我们不能通过类似
childNodes[i] = ...
的操作来替换一个子节点。
- DOM 集合是实时的
- 除小部分例外,几乎所有的 DOM 集合都是 实时 的。换句话说,它们反映了 DOM 的当前状态。
- 如果我们保留一个对
elem.childNodes
的引用,然后向 DOM 中添加/移除节点,那么这些节点的更新会自动出现在集合中。
- 不要使用
for..in
来遍历集合- 可以使用
for..of
对集合进行迭代。但有时候人们会尝试使用for..in
来迭代集合。
- 可以使用
- 正如我们看到的那样,
- 兄弟节点和父节点
- 兄弟节点(sibling) 是指有同一个父节点的节点。
<head>
和<body>
就是兄弟节点。
- 兄弟节点(sibling) 是指有同一个父节点的节点。
- 纯元素导航
- 上面列出的导航(navigation)属性引用 所有 节点。例如,在
childNodes
中我们可以看到文本节点,元素节点,甚至包括注释节点(如果它们存在的话)。 parentElement
属性返回的是“元素类型”的父节点,而parentNode
返回的是“任何类型”的父节点。这些属性通常来说是一样的:它们都是用于获取父节点。
- 上面列出的导航(navigation)属性引用 所有 节点。例如,在
- 更多链接:表格
<table>
元素支持 (除了上面给出的,之外) 以下属性 -table.rows
——<tr>
元素的集合。table.caption/tHead/tFoot
—— 引用元素<caption>
,<thead>
,<tfoot>
。table.tBodies
——<tbody>
元素的集合(根据标准还有很多元素,但是这里至少会有一个 —— 即使没有被写在 HTML 源文件中,浏览器也会将其放入 DOM 中)。
<thead>
,<tfoot>
,<tbody>
元素提供了rows
属性:tbody.rows
—— 表格内部<tr>
元素的集合。
<tr>
:tr.cells
—— 在给定<tr>
中的<td>
和<th>
单元格的集合。tr.sectionRowIndex
—— 给定的<tr>
在封闭的<thead>/<tbody>/<tfoot>
中的位置(索引)。tr.rowIndex
—— 在整个表格中<tr>
的编号(包括表格的所有行)。
<td>
和<th>
:td.cellIndex
—— 在封闭的<tr>
中单元格的编号。
搜索:getElement,querySelector
- document.getElementById 或者只使用 id
- 如果一个元素有
id
特性(attribute),那我们就可以使用document.getElementById(id)
方法获取该元素,无论它在哪里 - 请不要使用以 id 命名的全局变量来访问元素
id
必须是唯一的
- 如果一个元素有
- querySelectorAll
- 到目前为止,最通用的方法是
elem.querySelectorAll(css)
,它返回elem
中与给定 CSS 选择器匹配的所有元素。 - CSS 选择器的伪类,例如
:hover
和:active
也都是被支持的。
- 到目前为止,最通用的方法是
- querySelector
elem.querySelector(css)
调用会返回给定 CSS 选择器的第一个元素。- 换句话说,结果与
elem.querySelectorAll(css)[0]
相同,但是后者会查找 所有 元素,并从中选取一个,而elem.querySelector
只会查找一个。因此它在速度上更快,并且写起来更短。
- matches
- elem.matches(css) 不会查找任何内容,它只会检查
elem
是否与给定的 CSS 选择器匹配。它返回true
或false
。 - 当我们遍历元素(例如数组或其他内容)并试图过滤那些我们感兴趣的元素时,这个方法会很有用。
- elem.matches(css) 不会查找任何内容,它只会检查
- closest
- 元素的祖先(ancestor)是:父级,父级的父级,它的父级等。祖先们一起组成了从元素到顶端的父级链。
elem.closest(css)
方法会查找与 CSS 选择器匹配的最近的祖先。elem
自己也会被搜索。- 换句话说,方法
closest
在元素中得到了提升,并检查每个父级。如果它与选择器匹配,则停止搜索并返回该祖先。
- getElementsBy
*
elem.getElementsByTagName(tag)
查找具有给定标签的元素,并返回它们的集合。tag
参数也可以是对于“任何标签”的星号"*"
。elem.getElementsByClassName(className)
返回具有给定CSS类的元素。document.getElementsByName(name)
返回在文档范围内具有给定name
特性的元素。很少使用。
- 不要忘记字母
"s"
!- 调用
getElementByTagName
而不是getElement**s**ByTagName
。
- 调用
- 它返回的是一个集合,不是一个元素!
- 实时的集合
- 所有的
"getElementsBy*"
方法都会返回一个 实时的(live) 集合。这样的集合始终反映的是文档的当前状态,并且在文档发生更改时会“自动更新”。 querySelectorAll
返回的是一个 静态的 集合。就像元素的固定数组。
- 所有的
节点属性:type,tag 和 content
- DOM 节点类
- 不同的 DOM 节点可能有不同的属性。例如,标签
<a>
相对应的元素节点具有链接相关的(link-related)属性,标签<input>
相对应的元素节点具有与输入相关的属性,等。 - 每个 DOM 节点都属于相应的内建类。
- 层次结构(hierarchy)的根节点是 EventTarget,Node 继承自它,其他 DOM 节点继承自 Node。
- 类:
- EventTarget —— 是一切的根“抽象(abstract)”类。 该类的对象从未被创建。它作为一个基础,以便让所有 DOM 节点都支持所谓的“事件(event)”,我们会在之后学习它。
- Node —— 也是一个“抽象”类,充当 DOM 节点的基础。 它提供了树的核心功能:
parentNode
,nextSibling
,childNodes
等(它们都是 getter)。Node
类的对象从未被创建。但是还有一些继承自它的其他类(因此继承了Node
的功能)。 - Document 由于历史原因通常被
HTMLDocument
继承(尽管最新的规范没有规定)—— 是一个整体的文档。 全局变量document
就是属于这个类。它作为 DOM 的入口。 - CharacterData —— 一个“抽象”类,被下述类继承:
- Element —— 是 DOM 元素的基础类。 它提供了元素级导航(navigation),如
nextElementSibling
,children
,以及搜索方法,如getElementsByTagName
和querySelector
。 浏览器不仅支持 HTML,还支持 XML 和 SVG。因此,Element
类充当的是更具体的类的基础:SVGElement
,XMLElement
(我们在这里不需要它)和HTMLElement
。 - 最后,HTMLElement —— 是所有 HTML 元素的基础类。我们大部分时候都会用到它。 它会被更具体的 HTML 元素继承:
- HTMLInputElement ——
<input>
元素的类, - HTMLBodyElement ——
<body>
元素的类, - HTMLAnchorElement ——
<a>
元素的类, - ……等。
- HTMLInputElement ——
console.dir(elem)
与console.log(elem)
- 它们是不同的:
console.log(elem)
显示元素的 DOM 树。console.dir(elem)
将元素显示为 DOM 对象,非常适合探索其属性。
- 它们是不同的:
- 规范中的 IDL
- 在规范中,DOM 类不是使用 JavaScript 来描述的,而是一种特殊的 接口描述语言(Interface description language),简写为 IDL,它通常很容易理解。
- 不同的 DOM 节点可能有不同的属性。例如,标签
- “nodeType” 属性
nodeType
属性提供了另一种“过时的”用来获取 DOM 节点类型的方法。- 它有一个数值型值(numeric value):
- 对于元素节点
elem.nodeType == 1
, - 对于文本节点
elem.nodeType == 3
, - 对于 document 对象
elem.nodeType == 9
, - 在 规范 中还有一些其他值。
- 对于元素节点
- 标签:nodeName 和 tagName
- 给定一个 DOM 节点,我们可以从
nodeName
或者tagName
属性中读取它的标签名 - tagName 和 nodeName 之间有什么不同吗?
tagName
属性仅适用于Element
节点。nodeName
是为任意Node
定义的:- 对于元素,它的意义与
tagName
相同。 - 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串。
tagName
仅受元素节点支持(因为它起源于Element
类),而nodeName
则可以说明其他节点类型。
- 标签名称始终是大写的,除非是在 XML 模式下
- 给定一个 DOM 节点,我们可以从
- innerHTML:内容
- innerHTML 属性允许将元素中的 HTML 获取为字符串形式。
- 如果
innerHTML
将一个<script>
标签插入到 document 中 —— 它会成为 HTML 的一部分,但是不会执行。 - 小心:“innerHTML+=” 会进行完全重写
- 我们可以使用
elem.innerHTML+="more html"
将 HTML 附加到元素上。 innerHTML+=
的作用- 移除旧的内容。
- 然后写入新的
innerHTML
(新旧结合)。 - 因为内容已“归零”并从头开始重写,因此所有的图片和其他资源都将重写加载。
- 我们可以使用
- outerHTML:元素的完整 HTML
outerHTML
属性包含了元素的完整 HTML。就像innerHTML
加上元素本身一样。- 与
innerHTML
不同,写入outerHTML
不会改变元素。而是在 DOM 中替换它。
- nodeValue/data:文本节点内容
innerHTML
属性仅对元素节点有效。- 其他节点类型,例如文本节点,具有它们的对应项:
nodeValue
和data
属性。这两者在实际使用中几乎相同,只有细微规范上的差异。因此,我们将使用data
,因为它更短。
- textContent:纯文本
textContent
提供了对元素内的 文本 的访问权限:仅文本,去掉所有<tags>
。- 写入
textContent
要有用得多,因为它允许以“安全方式”写入文本。 - 使用
textContent
,我们将其“作为文本”插入,所有符号(symbol)均按字面意义处理。
- 使用
- “hidden” 属性
- “hidden” 特性(attribute)和 DOM 属性(property)指定元素是否可见。
- 从技术上来说,
hidden
与style="display:none"
做的是相同的事。但hidden
写法更简洁。
- 更多属性
- DOM 元素还有其他属性,特别是那些依赖于 class 的属性:
value
——<input>
,<select>
和<textarea>
(HTMLInputElement
,HTMLSelectElement
……)的 value。href
——<a href="...">
(HTMLAnchorElement
)的 href。id
—— 所有元素(HTMLElement
)的 “id” 特性(attribute)的值。- ……以及更多其他内容……
- DOM 元素还有其他属性,特别是那些依赖于 class 的属性:
特性和属性(Attributes and properties)
当浏览器加载页面时,它会“读取”(或者称之为:“解析”)HTML 并从中生成 DOM 对象。对于元素节点,大多数标准的 HTML 特性(attributes)会自动变成 DOM 对象的属性(properties)。
- DOM 属性
- DOM 节点是常规的 JavaScript 对象。我们可以更改它们。
- DOM 属性和方法的行为就像常规的 Javascript 对象一样
- 它们可以有很多值。
- 它们是大小写敏感的(要写成
elem.nodeType
,而不是elem.NoDeTyPe
)。
- 它们是大小写敏感的(要写成
- HTML 特性
- 在 HTML 中,标签可能拥有特性(attributes)。当浏览器解析 HTML 文本,并根据标签创建 DOM 对象时,浏览器会辨别 标准的 特性并以此创建 DOM 属性。
- 所以,当一个元素有
id
或其他 标准的 特性,那么就会生成对应的 DOM 属性。但是非 标准的 特性则不会。 - 所有特性都可以通过使用以下方法进行访问
elem.hasAttribute(name)
—— 检查特性是否存在。elem.getAttribute(name)
—— 获取这个特性值。elem.setAttribute(name, value)
—— 设置这个特性值。elem.removeAttribute(name)
—— 移除这个特性。
- HTML 特性有以下几个特征
- 它们的名字是大小写不敏感的(
id
与ID
相同)。 - 它们的值总是字符串类型的。
- 它们的名字是大小写不敏感的(
- 属性—特性同步
- 当一个标准的特性被改变,对应的属性也会自动更新,(除了几个特例)反之亦然。
- DOM 属性是多类型的
- DOM 属性不总是字符串类型的。
- 非标准的特性,dataset
- 有时,非标准的特性常常用于将自定义的数据从 HTML 传递到 JavaScript,或者用于为 JavaScript “标记” HTML 元素。
- 所有以 “data-” 开头的特性均被保留供程序员使用。它们可在
dataset
属性中使用。
修改文档(document)
DOM 修改是创建“实时”页面的关键。
例子:展示一条消息
JavaScript
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert">
<strong>Hi there!</strong> You've read an important message.
</div>
创建一个元素
JavaScript
let div = document.createElement('div');
//用给定的文本创建一个 文本节点
let textNode = document.createTextNode('Here I am');
创建一条消息
JavaScript
// 1. 创建 <div> 元素
let div = document.createElement('div');
// 2. 将元素的类设置为 "alert"
div.className = "alert";
// 3. 填充消息内容
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";
- 插入方法
- 元素插入方法,指明了不同的插入位置
node.append(...nodes or strings)
—— 在node
末尾 插入节点或字符串,node.prepend(...nodes or strings)
—— 在node
开头 插入节点或字符串,node.before(...nodes or strings)
—— 在node
前面 插入节点或字符串,node.after(...nodes or strings)
—— 在node
后面 插入节点或字符串,node.replaceWith(...nodes or strings)
—— 将node
替换为给定的节点或字符串。
- 元素插入方法,指明了不同的插入位置
- insertAdjacentHTML/Text/Element
- 一个非常通用的方法:
elem.insertAdjacentHTML(where, html)
。"beforebegin"
—— 将html
插入到elem
之前,"afterbegin"
—— 将html
插入到elem
开头,"beforeend"
—— 将html
插入到elem
末尾,"afterend"
—— 将html
插入到elem
之后。
- 这个方法有两个兄弟
elem.insertAdjacentText(where, text)
—— 语法一样,但是将text
字符串“作为文本”插入而不是作为 HTML,
elem.insertAdjacentElement(where, elem)
—— 语法一样,但是插入的是一个元素。
- 一个非常通用的方法:
- 节点移除
- 想要移除一个节点,可以使用
node.remove()
。 - 如果我们要将一个元素 移动 到另一个地方,则无需将其从原来的位置中删除。
- 所有插入方法都会自动从旧位置删除该节点。
- 想要移除一个节点,可以使用
- 克隆节点:cloneNode
- 调用
elem.cloneNode(true)
来创建元素的一个“深”克隆 —— 具有所有特性(attribute)和子元素。如果我们调用elem.cloneNode(false)
,那克隆就不包括子元素。
- 调用
- DocumentFragment
DocumentFragment
是一个特殊的 DOM 节点,用作来传递节点列表的包装器(wrapper)。
- 老式的 insert/remove 方法
- 聊一聊 “document.write”
- 还有一个非常古老的向网页添加内容的方法:
document.write
。 - 调用
document.write(html)
意味着将html
“就地马上”写入页面。html
字符串可以是动态生成的,所以它很灵活。我们可以使用 JavaScript 创建一个完整的页面并对其进行写入。 document.write
调用只在页面加载时工作。- 运行起来出奇的快,因为它 不涉及 DOM 修改。
- 还有一个非常古老的向网页添加内容的方法:
样式和类
通常有两种设置元素样式的方式: 1. 在 CSS 中创建一个类,并添加它:<div class="...">
2. 将属性直接写入 style
:<div style="...">
。
- className 和 classList
- 更改类是脚本中最常见的操作之一。
"className"
:elem.className
对应于"class"
特性(attribute)。elem.classList
是一个特殊的对象,它具有add/remove/toggle
单个类的方法。classList
的方法elem.classList.add/remove(class)
—— 添加/移除类。elem.classList.toggle(class)
—— 如果类不存在就添加类,存在就移除它。elem.classList.contains(class)
—— 检查给定类,返回true/false
。
- 元素样式
elem.style
属性是一个对象,它对应于"style"
特性(attribute)中所写的内容。elem.style.width="100px"
的效果等价于我们在style
特性中有一个width:100px
字符串。
- 重置样式属性
- 有时我们想要分配一个样式属性,稍后移除它。
- 这里不应该使用
delete elem.style.display
,而应该使用elem.style.display = ""
将其赋值为空。 - 用
style.cssText
进行完全的重写- 我们使用
style.*
来对各个样式属性进行赋值。我们不能像这样的div.style="color: red; width: 100px"
设置完整的属性,因为div.style
是一个对象,并且它是只读的。
- 我们使用
- 注意单位
- px,rem,vh等
- 计算样式:getComputedStyle
style
属性仅对"style"
特性(attribute)值起作用,而没有任何 CSS 级联(cascade)。- element
- 需要被读取样式值的元素。
- pseudo
- 伪元素(如果需要),例如
::before
。空字符串或无参数则意味着元素本身。
- 伪元素(如果需要),例如
- 计算值和解析值
- 计算 (computed) 样式值是所有 CSS 规则和 CSS 继承都应用后的值,这是 CSS 级联(cascade)的结果。它看起来像
height:1em
或font-size:125%
。 - 解析 (resolved) 样式值是最终应用于元素的样式值。诸如
1em
或125%
这样的值是相对的。浏览器将使用计算(computed)值,并使所有单位均为固定的,且为绝对单位,例如:height:20px
或font-size:16px
。对于几何属性,解析(resolved)值可能具有浮点,例如:width:50.5px
。
- 计算 (computed) 样式值是所有 CSS 规则和 CSS 继承都应用后的值,这是 CSS 级联(cascade)的结果。它看起来像
getComputedStyle
需要完整的属性名- 要的确切的属性,例如
paddingLeft
、marginTop
或borderTopWidth
。否则,就不能保证正确的结果。
- 要的确切的属性,例如
- 应用于
:visited
链接的样式被隐藏了!- 可以使用 CSS 伪类
:visited
对被访问过的链接进行着色。 - 但
getComputedStyle
没有给出访问该颜色的方式,因为如果允许的话,任意页面都可以通过在页面上创建它,并通过检查样式来确定用户是否访问了某链接。 - JavaScript 看不到
:visited
所应用的样式。
- 可以使用 CSS 伪类
元素大小和滚动
- offsetParent,offsetLeft/Top
offsetParent
是最接近的祖先(ancestor),在浏览器渲染期间,它被用于计算坐标。- 最近的祖先为下列之一:
- CSS 定位的(
position
为absolute
、relative
、fixed
或sticky
),
- CSS 定位的(
- 或
<td>
,<th>
,<table>
, - 或
<body>
。 - 属性
offsetLeft/offsetTop
提供相对于offsetParent
左上角的 x/y 坐标。 - 有以下几种情况下,
offsetParent
的值为null
:- 对于未显示的元素(
display:none
或者不在文档中)。 - 对于
<body>
与<html>
。 - 对于带有
position:fixed
的元素。
- 对于未显示的元素(
- offsetWidth/Height
- 提供了元素的“外部” width/height
offsetWidth = 390
—— 外部宽度(width),可以计算为内部 CSS-width(300px
)加上 padding(2 * 20px
)和 border(2 * 25px
)。
offsetHeight = 290
—— 外部高度(height)。
- 对于未显示的元素,几何属性为 0/null
- 如果一个元素(或其任何祖先)具有
display:none
或不在文档中,则所有几何属性均为零(或offsetParent
为null
)。 - 当我们创建了一个元素,但尚未将其插入文档中,或者它(或它的祖先)具有
display:none
时,offsetParent
为null
,并且offsetWidth
和offsetHeight
为0
。
- 如果一个元素(或其任何祖先)具有
- 提供了元素的“外部” width/height
- clientTop/Left
- 为了测量它们,可以使用
clientTop
和clientLeft
。clientLeft = 25
—— 左边框宽度
clientTop = 25
—— 上边框宽度
- 为了测量它们,可以使用
- clientWidth/Height
- “content width” 和 “padding”,但不包括滚动条宽度(scrollbar)
- scrollWidth/Height
- 属性就像
clientWidth/clientHeight
,但它们还包括滚动出(隐藏)的部分 - 是内容区域的完整内部高度,包括滚动出的部分。
- 是完整的内部宽度,这里我们没有水平滚动,因此它等于
clientWidth
。
- 属性就像
- scrollLeft/scrollTop
- 属性
scrollLeft/scrollTop
是元素的隐藏、滚动部分的 width/height。 - scrollLeft/scrollTop 是可修改的
- 大多数几何属性是只读的,但是
scrollLeft/scrollTop
是可修改的,并且浏览器会滚动该元素。
- 属性
- 不要从 CSS 中获取 width/height
- 首先,CSS
width/height
取决于另一个属性:box-sizing
,它定义了“什么是” CSS 宽度和高度。出于 CSS 的目的而对box-sizing
进行的更改可能会破坏此类 JavaScript 操作。
- 首先,CSS
- 其次,CSS 的
width/height
可能是auto
,例如内联(inline)元素
Window 大小和滚动
- 窗口的 width/height
- 为了获取窗口(window)的宽度和高度,我们可以使用
document.documentElement
的clientWidth/clientHeight
- 不是
window.innerWidth/innerHeight
- 如果这里存在一个滚动条,并且滚动条占用了一些空间,那么
clientWidth/clientHeight
会提供没有滚动条(减去它)的 width/height。换句话说,它们返回的是可用于内容的文档的可见部分的 width/height。window.innerWidth/innerHeight
包括了滚动条。
- 如果这里存在一个滚动条,并且滚动条占用了一些空间,那么
DOCTYPE
很重要- 当 HTML 中没有
<!DOCTYPE HTML>
时,顶层级(top-level)几何属性的工作方式可能就会有所不同。可能会出现一些稀奇古怪的情况。
- 当 HTML 中没有
- 为了获取窗口(window)的宽度和高度,我们可以使用
- 文档的 width/height
- 由于根文档元素是
document.documentElement
,并且它包围了所有内容,因此我们可以通过使用documentElement.scrollWidth/scrollHeight
来测量文档的完整大小。
- 由于根文档元素是
- 获得当前滚动
- DOM 元素的当前滚动状态在其
scrollLeft/scrollTop
属性中。 - 对于文档滚动,在大多数浏览器中,我们可以使用
document.documentElement.scrollLeft/scrollTop
,但在较旧的基于 WebKit 的浏览器中则不行,例如在 Safari(bug 5991)中,我们应该使用document.body
而不是document.documentElement
。
- DOM 元素的当前滚动状态在其
- 我们也可以从
window
的scrollX
和scrollY
属性中获取滚动信息window.pageXOffset
是window.scrollX
的别名。
window.pageYOffset
是window.scrollY
的别名。
- 滚动:scrollTo,scrollBy,scrollIntoView
- 可以通过更改
scrollTop/scrollLeft
来滚动常规元素- 方法
scrollBy(x,y)
将页面滚动至 相对于当前位置的(x, y)
位置。例如,scrollBy(0,10)
会将页面向下滚动10px
。 - 方法
scrollTo(pageX,pageY)
将页面滚动至 绝对坐标,使得可见部分的左上角具有相对于文档左上角的坐标(pageX, pageY)
。就像设置了scrollLeft/scrollTop
一样。
- 方法
- 可以通过更改
- scrollIntoView
elem.scrollIntoView(top)
的调用将滚动页面以使elem
可见。它有一个参数- 如果
top=true
(默认值),页面滚动,使elem
出现在窗口顶部。元素的上边缘将与窗口顶部对齐。
- 如果
- 如果
top=false
,页面滚动,使elem
出现在窗口底部。元素的底部边缘将与窗口底部对齐。
- 如果
- 禁止滚动
- 要使文档不可滚动,只需要设置
document.body.style.overflow = "hidden"
。
- 要使文档不可滚动,只需要设置
坐标
移动页面的元素,我们应该先熟悉坐标
- 大多数 JavaScript 方法处理的是以下两种坐标系中的一个
- 相对于窗口 —— 类似于
position:fixed
,从窗口的顶部/左侧边缘计算得出。- 我们将这些坐标表示为
clientX/clientY
,当我们研究事件属性时,就会明白为什么使用这种名称来表示坐标。
- 我们将这些坐标表示为
- 相对于文档 —— 与文档根(document root)中的
position:absolute
类似,从文档的顶部/左侧边缘计算得出。- 我们将它们表示为
pageX/pageY
。
- 我们将它们表示为
- 相对于窗口 —— 类似于
- 元素坐标:getBoundingClientRect
- 方法
elem.getBoundingClientRect()
返回最小矩形的窗口坐标,该矩形将elem
作为内建 DOMRect 类的对象。 - 主要的
DOMRect
属性x/y
—— 矩形原点相对于窗口的 X/Y 坐标,
width/height
—— 矩形的 width/height(可以为负)。
- 派生(derived)属性
top/bottom
—— 顶部/底部矩形边缘的 Y 坐标,
left/right
—— 左/右矩形边缘的 X 坐标。
- 为什么需要派生(derived)属性?如果有了
x/y
,为什么还要还会存在top/left
?- 从技术上讲,
width/height
可能为负数,从而允许“定向(directed)”矩形,例如代表带有正确标记的开始和结束的鼠标选择。 - 负的
width/height
值表示矩形从其右下角开始,然后向左上方“增长”。 elem.getBoundingClientRect()
总是返回正数的 width/height,这里我们提及负的width/height
只是为了帮助你理解,为什么这些看起来重复的属性,实际上并不是重复的。
- 从技术上讲,
- IE 浏览器不支持
x/y
- 由于历史原因,IE 浏览器不支持
x/y
属性。
- 由于历史原因,IE 浏览器不支持
- 坐标的 right/bottom 与 CSS position 属性不同
- 相对于窗口(window)的坐标和 CSS
position:fixed
之间有明显的相似之处。 - 在 CSS 定位中,
right
属性表示距右边缘的距离,而bottom
属性表示距下边缘的距离
- 相对于窗口(window)的坐标和 CSS
- elementFromPoint(x, y)
- 对
document.elementFromPoint(x, y)
的调用会返回在窗口坐标(x, y)
处嵌套最多(the most nested)的元素。
- 对
- 对于在窗口之外的坐标,
elementFromPoint
返回null
- 方法
document.elementFromPoint(x,y)
只对在可见区域内的坐标(x,y)
起作用。 - 如果任何坐标为负或者超过了窗口的 width/height,那么该方法就会返回
null
。
- 方法
- 用于 “fixed” 定位
- 想要在某元素附近展示内容,我们可以使用
getBoundingClientRect
来获取这个元素的坐标,然后使用 CSSposition
以及left/top
(或right/bottom
)。
- 想要在某元素附近展示内容,我们可以使用
- 文档坐标
- 在 CSS 中,窗口坐标对应于
position:fixed
,而文档坐标与顶部的position:absolute
类似。 - 我们可以使用
position:absolute
和top/left
来把某些内容放到文档中的某个位置,以便在页面滚动时,元素仍能保留在该位置。但是我们首先需要正确的坐标。 pageY
=clientY
+ 文档的垂直滚动出的部分的高度。
pageX
=clientX
+ 文档的水平滚动出的部分的宽度。
- 在 CSS 中,窗口坐标对应于
- 方法