Skip to content

浏览器环境,规格

平台可以是一个浏览器,一个 Web 服务器,或其他 主机(host),甚至可以是一个“智能”咖啡机,如果它能运行 JavaScript 的话。它们每个都提供了特定于平台的功能。JavaScript 规范将其称为 主机环境

  1. 有一个叫做 window 的“根”对象。它有两个角色:
      1. 首先,它是 JavaScript 代码的全局对象,如 全局对象 一章所述。
      1. 其次,它代表“浏览器窗口”,并提供了控制它的方法。
  2. 文档对象模型(DOM)
    1. 文档对象模型(Document Object Model),简称 DOM,将所有页面内容表示为可以修改的对象。
    2. document 对象是页面的主要“入口点”。我们可以使用它来更改或创建页面上的任何内容。
    3. DOM 规范解释了文档的结构,并提供了操作文档的对象。有的非浏览器设备也使用 DOM。
  3. 浏览器对象模型(BOM)
    1. 浏览器对象模型(Browser Object Model),简称 BOM,表示由浏览器(主机环境)提供的用于处理文档(document)之外的所有内容的其他对象。
        • navigator 对象提供了有关浏览器和操作系统的背景信息。navigator 有许多属性,但是最广为人知的两个属性是:navigator.userAgent —— 关于当前浏览器,navigator.platform —— 关于平台(有助于区分 Windows/Linux/Mac 等)。
        • location 对象允许我们读取当前 URL,并且可以将浏览器重定向到新的 URL。
    2. BOM 是通用 HTML 规范 的一部分。

DOM 树

HTML 文档的主干是标签(tag)。 根据文档对象模型(DOM),每个 HTML 标签都是一个对象。嵌套的标签是闭合标签的“子标签(children)”。标签内的文本也是一个对象。 所有这些对象都可以通过 JavaScript 来访问,我们可以使用它们来修改页面。

  1. 一些标签
    1. document.body 是表示 <body> 标签的对象。
      • innerHTML —— 节点的 HTML 内容。
      • offsetWidth —— 节点宽度(以像素度量)
    2. 等等
  2. DOM的一些知识点
    1. 标签被称为 元素节点(或者仅仅是元素),并形成了树状结构:<html> 在根节点,<head> 和 <body> 是其子项,等。
    2. 元素内的文本形成 文本节点,被标记为 #text。一个文本节点只包含一个字符串。它没有子项,并且总是树的叶子。
    3. 字符串开头/结尾处的空格,以及只有空格的文本节点,通常会被工具隐藏
  3. 自动修正
    1. 如果浏览器遇到格式不正确的 HTML,它会在形成 DOM 时自动更正它。
    2. 表格永远有 <tbody>
  4. 其他节点类型
    1. 除了元素和文本节点外,还有一些其他的节点类型。,例如注释
    2. HTML 中的所有内容,甚至注释,都会成为 DOM 的一部分。
    3. 甚至 HTML 开头的 <!DOCTYPE...> 指令也是一个 DOM 节点
    4. document —— DOM 的“入口点”。
    5. 元素节点 —— HTML 标签,树构建块。
    6. 本节点 —— 包含文本。
    7. 注释 —— 有时我们可以将一些信息放入其中,它不会显示,但 JS 可以从 DOM 中读取它。
  5. 查看DOM结构
    1. 可以打开这个网页 elks.html,然后打开浏览器开发工具,并切换到元素(Elements)选项卡。
      • Styles —— 我们可以看到按规则应用于当前元素的 CSS 规则,包括内建规则(灰色)。几乎所有内容都可以就地编辑,包括下面的方框的 dimension/margin/padding。
      • Computed —— 按属性查看应用于元素的 CSS:对于每个属性,我们可以都可以看到赋予它的规则(包括 CSS 继承等)。
      • Event Listeners —— 查看附加到 DOM 元素的事件侦听器(我们将在本教程的下一部分介绍它们)。
  6. 与控制台交互
    1. 在我们处理 DOM 时,我们可能还希望对其应用 JavaScript。例如:获取一个节点并运行一些代码来修改它,以查看结果。以下是在元素(Elements)选项卡和控制台(Console)之间切换的一些技巧。
        • 在元素(Elements)选项卡中选择第一个 <li>
        • 按下 Esc —— 它将在元素(Elements)选项卡下方打开控制台(Console)。

遍历 DOM

DOM 让我们可以对元素和它们中的内容做任何事,但是首先我们需要获取到对应的 DOM 对象。 对 DOM 的所有操作都是以 document 对象开始。它是 DOM 的主“入口点”。从它我们可以访问任何节点。

  1. 在最顶层:documentElement 和 body
    1. 最顶层的树节点可以直接作为 document 的属性来使用:
      1. <html> = document.documentElement
      2. 最顶层的 document 节点是 document.documentElement。这是对应 <html> 标签的 DOM 节点。
      3. <body> = document.body
      4. 另一个被广泛使用的 DOM 节点是 <body> 元素 —— document.body
      5. <head> = document.head
      6. <head> 标签可以通过 document.head 访问。
    2. 这里有个问题:document.body 的值可能是 null
      1. 脚本无法访问在运行时不存在的元素。
      2. 尤其是,如果一个脚本是在 <head> 中,那么脚本是访问不到 document.body 元素的,因为浏览器还没有读到它。
  2. 子节点:childNodes,firstChild,lastChild
      • 子节点(或者叫作子) —— 对应的是直系的子元素。换句话说,它们被完全嵌套在给定的元素中。例如,<head> 和 <body> 就是 <html> 元素的子元素。
      • 子孙元素 —— 嵌套在给定元素中的所有元素,包括子元素,以及子元素的子元素等。
    1. childNodes 集合列出了所有子节点,包括文本节点。
    2. firstChild 和 lastChild 属性是访问第一个和最后一个子元素的快捷方式。
  3. DOM 集合
    1. 正如我们看到的那样,childNodes 看起来就像一个数组。但实际上它并不是一个数组,而是一个 集合 —— 一个类数组的可迭代对象。
      1. 我们可以使用 for..of 来迭代它
      2. 无法使用数组的方法,因为它不是一个数组
    2. DOM 集合是只读的
      1. DOM 集合,甚至可以说本章中列出的 所有 导航(navigation)属性都是只读的。
      2. 我们不能通过类似 childNodes[i] = ... 的操作来替换一个子节点。
    3. DOM 集合是实时的
      1. 除小部分例外,几乎所有的 DOM 集合都是 实时 的。换句话说,它们反映了 DOM 的当前状态。
      2. 如果我们保留一个对 elem.childNodes 的引用,然后向 DOM 中添加/移除节点,那么这些节点的更新会自动出现在集合中。
    4. 不要使用 for..in 来遍历集合
      1. 可以使用 for..of 对集合进行迭代。但有时候人们会尝试使用 for..in 来迭代集合。
  4. 兄弟节点和父节点
    1. 兄弟节点(sibling) 是指有同一个父节点的节点。<head> 和 <body> 就是兄弟节点。
  5. 纯元素导航
    1. 上面列出的导航(navigation)属性引用 所有 节点。例如,在 childNodes 中我们可以看到文本节点,元素节点,甚至包括注释节点(如果它们存在的话)。
    2. parentElement 属性返回的是“元素类型”的父节点,而 parentNode 返回的是“任何类型”的父节点。这些属性通常来说是一样的:它们都是用于获取父节点。
  6. 更多链接:表格
    1. <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

  1. document.getElementById 或者只使用 id
    1. 如果一个元素有 id 特性(attribute),那我们就可以使用 document.getElementById(id) 方法获取该元素,无论它在哪里
    2. 请不要使用以 id 命名的全局变量来访问元素
    3. id 必须是唯一的
  2. querySelectorAll
    1. 到目前为止,最通用的方法是 elem.querySelectorAll(css),它返回 elem 中与给定 CSS 选择器匹配的所有元素。
    2. CSS 选择器的伪类,例如 :hover 和 :active 也都是被支持的。
  3. querySelector
    1. elem.querySelector(css) 调用会返回给定 CSS 选择器的第一个元素。
    2. 换句话说,结果与 elem.querySelectorAll(css)[0] 相同,但是后者会查找 所有 元素,并从中选取一个,而 elem.querySelector 只会查找一个。因此它在速度上更快,并且写起来更短。
  4. matches
    1. elem.matches(css) 不会查找任何内容,它只会检查 elem 是否与给定的 CSS 选择器匹配。它返回 true 或 false
    2. 当我们遍历元素(例如数组或其他内容)并试图过滤那些我们感兴趣的元素时,这个方法会很有用。
  5. closest
    1. 元素的祖先(ancestor)是:父级,父级的父级,它的父级等。祖先们一起组成了从元素到顶端的父级链。
    2. elem.closest(css) 方法会查找与 CSS 选择器匹配的最近的祖先。elem 自己也会被搜索。
    3. 换句话说,方法 closest 在元素中得到了提升,并检查每个父级。如果它与选择器匹配,则停止搜索并返回该祖先。
  6. getElementsBy*
    • elem.getElementsByTagName(tag) 查找具有给定标签的元素,并返回它们的集合。tag 参数也可以是对于“任何标签”的星号 "*"
    • elem.getElementsByClassName(className) 返回具有给定CSS类的元素。
    • document.getElementsByName(name) 返回在文档范围内具有给定 name 特性的元素。很少使用。
    1. 不要忘记字母 "s"
      1. 调用 getElementByTagName 而不是 getElement**s**ByTagName
    2. 它返回的是一个集合,不是一个元素!
  7. 实时的集合
    1. 所有的 "getElementsBy*" 方法都会返回一个 实时的(live) 集合。这样的集合始终反映的是文档的当前状态,并且在文档发生更改时会“自动更新”。
    2. querySelectorAll 返回的是一个 静态的 集合。就像元素的固定数组。

节点属性:type,tag 和 content

  1. DOM 节点类
    1. 不同的 DOM 节点可能有不同的属性。例如,标签 <a> 相对应的元素节点具有链接相关的(link-related)属性,标签 <input> 相对应的元素节点具有与输入相关的属性,等。
    2. 每个 DOM 节点都属于相应的内建类。
    3. 层次结构(hierarchy)的根节点是 EventTargetNode 继承自它,其他 DOM 节点继承自 Node。
    4. 类:
      • EventTarget —— 是一切的根“抽象(abstract)”类。 该类的对象从未被创建。它作为一个基础,以便让所有 DOM 节点都支持所谓的“事件(event)”,我们会在之后学习它。
      • Node —— 也是一个“抽象”类,充当 DOM 节点的基础。 它提供了树的核心功能:parentNodenextSiblingchildNodes 等(它们都是 getter)。Node 类的对象从未被创建。但是还有一些继承自它的其他类(因此继承了 Node 的功能)。
      • Document 由于历史原因通常被 HTMLDocument 继承(尽管最新的规范没有规定)—— 是一个整体的文档。 全局变量 document 就是属于这个类。它作为 DOM 的入口。
      • CharacterData —— 一个“抽象”类,被下述类继承:
        • Text —— 对应于元素内部文本的类,例如 <p>Hello</p> 中的 Hello
        • Comment —— 注释类。它们不会被展示出来,但每个注释都会成为 DOM 中的一员。
      • Element —— 是 DOM 元素的基础类。 它提供了元素级导航(navigation),如 nextElementSiblingchildren,以及搜索方法,如 getElementsByTagName 和 querySelector。 浏览器不仅支持 HTML,还支持 XML 和 SVG。因此,Element 类充当的是更具体的类的基础:SVGElementXMLElement(我们在这里不需要它)和 HTMLElement
      • 最后,HTMLElement —— 是所有 HTML 元素的基础类。我们大部分时候都会用到它。 它会被更具体的 HTML 元素继承:
    5. console.dir(elem) 与 console.log(elem)
      1. 它们是不同的:
        • console.log(elem) 显示元素的 DOM 树。
        • console.dir(elem) 将元素显示为 DOM 对象,非常适合探索其属性。
    6. 规范中的 IDL
      1. 在规范中,DOM 类不是使用 JavaScript 来描述的,而是一种特殊的 接口描述语言(Interface description language),简写为 IDL,它通常很容易理解。
  2. “nodeType” 属性
    1. nodeType 属性提供了另一种“过时的”用来获取 DOM 节点类型的方法。
    2. 它有一个数值型值(numeric value):
      • 对于元素节点 elem.nodeType == 1
      • 对于文本节点 elem.nodeType == 3
      • 对于 document 对象 elem.nodeType == 9
      • 在 规范 中还有一些其他值。
  3. 标签:nodeName 和 tagName
    1. 给定一个 DOM 节点,我们可以从 nodeName 或者 tagName 属性中读取它的标签名
    2. tagName 和 nodeName 之间有什么不同吗?
      • tagName 属性仅适用于 Element 节点。
      • nodeName 是为任意 Node 定义的:
      • 对于元素,它的意义与 tagName 相同。
      • 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串。
      • tagName 仅受元素节点支持(因为它起源于 Element 类),而 nodeName 则可以说明其他节点类型。
    3. 标签名称始终是大写的,除非是在 XML 模式下
  4. innerHTML:内容
    1. innerHTML 属性允许将元素中的 HTML 获取为字符串形式。
    2. 如果 innerHTML 将一个 <script> 标签插入到 document 中 —— 它会成为 HTML 的一部分,但是不会执行。
    3. 小心:“innerHTML+=” 会进行完全重写
      1. 我们可以使用 elem.innerHTML+="more html" 将 HTML 附加到元素上。
      2. innerHTML+= 的作用
        1. 移除旧的内容。
        2. 然后写入新的 innerHTML(新旧结合)。
        3. 因为内容已“归零”并从头开始重写,因此所有的图片和其他资源都将重写加载。
  5. outerHTML:元素的完整 HTML
    1. outerHTML 属性包含了元素的完整 HTML。就像 innerHTML 加上元素本身一样。
    2. 与 innerHTML 不同,写入 outerHTML 不会改变元素。而是在 DOM 中替换它。
  6. nodeValue/data:文本节点内容
    1. innerHTML 属性仅对元素节点有效。
    2. 其他节点类型,例如文本节点,具有它们的对应项:nodeValue 和 data 属性。这两者在实际使用中几乎相同,只有细微规范上的差异。因此,我们将使用 data,因为它更短。
  7. textContent:纯文本
    1. textContent 提供了对元素内的 文本 的访问权限:仅文本,去掉所有 <tags>
    2. 写入 textContent 要有用得多,因为它允许以“安全方式”写入文本。
      • 使用 textContent,我们将其“作为文本”插入,所有符号(symbol)均按字面意义处理。
  8. “hidden” 属性
    1. “hidden” 特性(attribute)和 DOM 属性(property)指定元素是否可见。
    2. 从技术上来说,hidden 与 style="display:none" 做的是相同的事。但 hidden 写法更简洁。
  9. 更多属性
    1. DOM 元素还有其他属性,特别是那些依赖于 class 的属性:
      • value —— <input><select> 和 <textarea>HTMLInputElementHTMLSelectElement……)的 value。
      • href —— <a href="...">HTMLAnchorElement)的 href。
      • id —— 所有元素(HTMLElement)的 “id” 特性(attribute)的值。
      • ……以及更多其他内容……

特性和属性(Attributes and properties)

当浏览器加载页面时,它会“读取”(或者称之为:“解析”)HTML 并从中生成 DOM 对象。对于元素节点,大多数标准的 HTML 特性(attributes)会自动变成 DOM 对象的属性(properties)。

  1. DOM 属性
    1. DOM 节点是常规的 JavaScript 对象。我们可以更改它们。
    2. DOM 属性和方法的行为就像常规的 Javascript 对象一样
        • 它们可以有很多值。
        • 它们是大小写敏感的(要写成 elem.nodeType,而不是 elem.NoDeTyPe)。
  2. HTML 特性
    1. 在 HTML 中,标签可能拥有特性(attributes)。当浏览器解析 HTML 文本,并根据标签创建 DOM 对象时,浏览器会辨别 标准的 特性并以此创建 DOM 属性。
    2. 所以,当一个元素有 id 或其他 标准的 特性,那么就会生成对应的 DOM 属性。但是非 标准的 特性则不会。
    3. 所有特性都可以通过使用以下方法进行访问
      • elem.hasAttribute(name) —— 检查特性是否存在。
      • elem.getAttribute(name) —— 获取这个特性值。
      • elem.setAttribute(name, value) —— 设置这个特性值。
      • elem.removeAttribute(name) —— 移除这个特性。
    4. HTML 特性有以下几个特征
      • 它们的名字是大小写不敏感的(id 与 ID 相同)。
      • 它们的值总是字符串类型的。
  3. 属性—特性同步
    1. 当一个标准的特性被改变,对应的属性也会自动更新,(除了几个特例)反之亦然。
  4. DOM 属性是多类型的
    1. DOM 属性不总是字符串类型的。
  5. 非标准的特性,dataset
    1. 有时,非标准的特性常常用于将自定义的数据从 HTML 传递到 JavaScript,或者用于为 JavaScript “标记” HTML 元素。
    2. 所有以 “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.";
  1. 插入方法
    1. 元素插入方法,指明了不同的插入位置
      • 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 替换为给定的节点或字符串。
  2. insertAdjacentHTML/Text/Element
    1. 一个非常通用的方法:elem.insertAdjacentHTML(where, html)
      • "beforebegin" —— 将 html 插入到 elem 之前,
      • "afterbegin" —— 将 html 插入到 elem 开头,
      • "beforeend" —— 将 html 插入到 elem 末尾,
      • "afterend" —— 将 html 插入到 elem 之后。
    2. 这个方法有两个兄弟
        • elem.insertAdjacentText(where, text) —— 语法一样,但是将 text 字符串“作为文本”插入而不是作为 HTML,
        • elem.insertAdjacentElement(where, elem) —— 语法一样,但是插入的是一个元素。
  3. 节点移除
    1. 想要移除一个节点,可以使用 node.remove()
    2. 如果我们要将一个元素 移动 到另一个地方,则无需将其从原来的位置中删除。
    3. 所有插入方法都会自动从旧位置删除该节点。
  4. 克隆节点:cloneNode
    1. 调用 elem.cloneNode(true) 来创建元素的一个“深”克隆 —— 具有所有特性(attribute)和子元素。如果我们调用 elem.cloneNode(false),那克隆就不包括子元素。
  5. DocumentFragment
    1. DocumentFragment 是一个特殊的 DOM 节点,用作来传递节点列表的包装器(wrapper)。
  6. 老式的 insert/remove 方法
  7. 聊一聊 “document.write”
    1. 还有一个非常古老的向网页添加内容的方法:document.write
    2. 调用 document.write(html) 意味着将 html “就地马上”写入页面。html 字符串可以是动态生成的,所以它很灵活。我们可以使用 JavaScript 创建一个完整的页面并对其进行写入。
    3. document.write 调用只在页面加载时工作。
    4. 运行起来出奇的快,因为它 不涉及 DOM 修改

样式和类

通常有两种设置元素样式的方式: 1. 在 CSS 中创建一个类,并添加它:<div class="..."> 2. 将属性直接写入 style<div style="...">

  1. className 和 classList
    1. 更改类是脚本中最常见的操作之一。
    2. "className"elem.className 对应于 "class" 特性(attribute)。
    3. elem.classList 是一个特殊的对象,它具有 add/remove/toggle 单个类的方法。
    4. classList 的方法
      • elem.classList.add/remove(class) —— 添加/移除类。
      • elem.classList.toggle(class) —— 如果类不存在就添加类,存在就移除它。
      • elem.classList.contains(class) —— 检查给定类,返回 true/false
  2. 元素样式
    1. elem.style 属性是一个对象,它对应于 "style" 特性(attribute)中所写的内容。elem.style.width="100px" 的效果等价于我们在 style 特性中有一个 width:100px 字符串。
  3. 重置样式属性
    1. 有时我们想要分配一个样式属性,稍后移除它。
    2. 这里不应该使用 delete elem.style.display,而应该使用 elem.style.display = "" 将其赋值为空。
    3. 用 style.cssText 进行完全的重写
      1. 我们使用 style.* 来对各个样式属性进行赋值。我们不能像这样的 div.style="color: red; width: 100px" 设置完整的属性,因为 div.style 是一个对象,并且它是只读的。
  4. 注意单位
    1. px,rem,vh等
  5. 计算样式:getComputedStyle
    1. style 属性仅对 "style" 特性(attribute)值起作用,而没有任何 CSS 级联(cascade)。
    2. element
      1. 需要被读取样式值的元素。
    3. pseudo
      1. 伪元素(如果需要),例如 ::before。空字符串或无参数则意味着元素本身。
    4. 计算值和解析值
      1. 计算 (computed) 样式值是所有 CSS 规则和 CSS 继承都应用后的值,这是 CSS 级联(cascade)的结果。它看起来像 height:1em 或 font-size:125%
      2. 解析 (resolved) 样式值是最终应用于元素的样式值。诸如 1em 或 125% 这样的值是相对的。浏览器将使用计算(computed)值,并使所有单位均为固定的,且为绝对单位,例如:height:20px 或 font-size:16px。对于几何属性,解析(resolved)值可能具有浮点,例如:width:50.5px
    5. getComputedStyle 需要完整的属性名
      1. 要的确切的属性,例如 paddingLeftmarginTop 或 borderTopWidth。否则,就不能保证正确的结果。
    6. 应用于 :visited 链接的样式被隐藏了!
      1. 可以使用 CSS 伪类 :visited 对被访问过的链接进行着色。
      2. 但 getComputedStyle 没有给出访问该颜色的方式,因为如果允许的话,任意页面都可以通过在页面上创建它,并通过检查样式来确定用户是否访问了某链接。
      3. JavaScript 看不到 :visited 所应用的样式。

元素大小和滚动

  1. offsetParent,offsetLeft/Top
    1. offsetParent 是最接近的祖先(ancestor),在浏览器渲染期间,它被用于计算坐标。
    2. 最近的祖先为下列之一:
        1. CSS 定位的(position 为 absoluterelativefixed 或 sticky),
      1. 或 <td><th><table>
      2. 或 <body>
      3. 属性 offsetLeft/offsetTop 提供相对于 offsetParent 左上角的 x/y 坐标。
      4. 有以下几种情况下,offsetParent 的值为 null
        1. 对于未显示的元素(display:none 或者不在文档中)。
        2. 对于 <body> 与 <html>
        3. 对于带有 position:fixed 的元素。
  2. offsetWidth/Height
    1. 提供了元素的“外部” width/height
        • offsetWidth = 390 —— 外部宽度(width),可以计算为内部 CSS-width(300px)加上 padding(2 * 20px)和 border(2 * 25px)。
        • offsetHeight = 290 —— 外部高度(height)。
    2. 对于未显示的元素,几何属性为 0/null
      1. 如果一个元素(或其任何祖先)具有 display:none 或不在文档中,则所有几何属性均为零(或 offsetParent 为 null)。
      2. 当我们创建了一个元素,但尚未将其插入文档中,或者它(或它的祖先)具有 display:none 时,offsetParent 为 null,并且 offsetWidth 和 offsetHeight 为 0
  3. clientTop/Left
    1. 为了测量它们,可以使用 clientTop 和 clientLeft
        • clientLeft = 25 —— 左边框宽度
        • clientTop = 25 —— 上边框宽度
  4. clientWidth/Height
    1. “content width” 和 “padding”,但不包括滚动条宽度(scrollbar)
  5. scrollWidth/Height
    1. 属性就像 clientWidth/clientHeight,但它们还包括滚动出(隐藏)的部分
    2. 是内容区域的完整内部高度,包括滚动出的部分。
    3. 是完整的内部宽度,这里我们没有水平滚动,因此它等于 clientWidth
  6. scrollLeft/scrollTop
    1. 属性 scrollLeft/scrollTop 是元素的隐藏、滚动部分的 width/height。
    2. scrollLeft/scrollTop 是可修改的
    3. 大多数几何属性是只读的,但是 scrollLeft/scrollTop 是可修改的,并且浏览器会滚动该元素。
  7. 不要从 CSS 中获取 width/height
      1. 首先,CSS width/height 取决于另一个属性:box-sizing,它定义了“什么是” CSS 宽度和高度。出于 CSS 的目的而对 box-sizing 进行的更改可能会破坏此类 JavaScript 操作。
    1. 其次,CSS 的 width/height 可能是 auto,例如内联(inline)元素

Window 大小和滚动

  1. 窗口的 width/height
    1. 为了获取窗口(window)的宽度和高度,我们可以使用 document.documentElement 的 clientWidth/clientHeight
    2. 不是 window.innerWidth/innerHeight
      1. 如果这里存在一个滚动条,并且滚动条占用了一些空间,那么 clientWidth/clientHeight 会提供没有滚动条(减去它)的 width/height。换句话说,它们返回的是可用于内容的文档的可见部分的 width/height。window.innerWidth/innerHeight 包括了滚动条。
    3. DOCTYPE 很重要
      1. 当 HTML 中没有 <!DOCTYPE HTML> 时,顶层级(top-level)几何属性的工作方式可能就会有所不同。可能会出现一些稀奇古怪的情况。
  2. 文档的 width/height
    1. 由于根文档元素是 document.documentElement,并且它包围了所有内容,因此我们可以通过使用 documentElement.scrollWidth/scrollHeight 来测量文档的完整大小。
  3. 获得当前滚动
    1. DOM 元素的当前滚动状态在其 scrollLeft/scrollTop 属性中。
    2. 对于文档滚动,在大多数浏览器中,我们可以使用 document.documentElement.scrollLeft/scrollTop,但在较旧的基于 WebKit 的浏览器中则不行,例如在 Safari(bug 5991)中,我们应该使用 document.body 而不是 document.documentElement
  4. 我们也可以从 window 的 scrollX 和 scrollY 属性中获取滚动信息
      • window.pageXOffset 是 window.scrollX 的别名。
      • window.pageYOffset 是 window.scrollY 的别名。
  5. 滚动:scrollTo,scrollBy,scrollIntoView
    1. 可以通过更改 scrollTop/scrollLeft 来滚动常规元素
      1. 方法 scrollBy(x,y) 将页面滚动至 相对于当前位置的 (x, y) 位置。例如,scrollBy(0,10) 会将页面向下滚动 10px
      2. 方法 scrollTo(pageX,pageY) 将页面滚动至 绝对坐标,使得可见部分的左上角具有相对于文档左上角的坐标 (pageX, pageY)。就像设置了 scrollLeft/scrollTop 一样。
  6. scrollIntoView
    1. elem.scrollIntoView(top) 的调用将滚动页面以使 elem 可见。它有一个参数
        • 如果 top=true(默认值),页面滚动,使 elem 出现在窗口顶部。元素的上边缘将与窗口顶部对齐。
        • 如果 top=false,页面滚动,使 elem 出现在窗口底部。元素的底部边缘将与窗口底部对齐。
  7. 禁止滚动
    1. 要使文档不可滚动,只需要设置 document.body.style.overflow = "hidden"

坐标

移动页面的元素,我们应该先熟悉坐标

  1. 大多数 JavaScript 方法处理的是以下两种坐标系中的一个
    1. 相对于窗口 —— 类似于 position:fixed,从窗口的顶部/左侧边缘计算得出。
      • 我们将这些坐标表示为 clientX/clientY,当我们研究事件属性时,就会明白为什么使用这种名称来表示坐标。
    2. 相对于文档 —— 与文档根(document root)中的 position:absolute 类似,从文档的顶部/左侧边缘计算得出。
      • 我们将它们表示为 pageX/pageY
  2. 元素坐标:getBoundingClientRect
    1. 方法 elem.getBoundingClientRect() 返回最小矩形的窗口坐标,该矩形将 elem 作为内建 DOMRect 类的对象。
    2. 主要的 DOMRect 属性
        • x/y —— 矩形原点相对于窗口的 X/Y 坐标,
        • width/height —— 矩形的 width/height(可以为负)。
    3. 派生(derived)属性
        • top/bottom —— 顶部/底部矩形边缘的 Y 坐标,
        • left/right —— 左/右矩形边缘的 X 坐标。
    4. 为什么需要派生(derived)属性?如果有了 x/y,为什么还要还会存在 top/left
      1. 从技术上讲,width/height 可能为负数,从而允许“定向(directed)”矩形,例如代表带有正确标记的开始和结束的鼠标选择。
      2. 负的 width/height 值表示矩形从其右下角开始,然后向左上方“增长”。
      3. elem.getBoundingClientRect() 总是返回正数的 width/height,这里我们提及负的 width/height 只是为了帮助你理解,为什么这些看起来重复的属性,实际上并不是重复的。
    5. IE 浏览器不支持 x/y
      1. 由于历史原因,IE 浏览器不支持 x/y 属性。
    6. 坐标的 right/bottom 与 CSS position 属性不同
      1. 相对于窗口(window)的坐标和 CSS position:fixed 之间有明显的相似之处。
      2. 在 CSS 定位中,right 属性表示距右边缘的距离,而 bottom 属性表示距下边缘的距离
    7. elementFromPoint(x, y)
      1. 对 document.elementFromPoint(x, y) 的调用会返回在窗口坐标 (x, y) 处嵌套最多(the most nested)的元素。
    8. 对于在窗口之外的坐标,elementFromPoint 返回 null
      1. 方法 document.elementFromPoint(x,y) 只对在可见区域内的坐标 (x,y) 起作用。
      2. 如果任何坐标为负或者超过了窗口的 width/height,那么该方法就会返回 null
    9. 用于 “fixed” 定位
      1. 想要在某元素附近展示内容,我们可以使用 getBoundingClientRect 来获取这个元素的坐标,然后使用 CSS position 以及 left/top(或 right/bottom)。
    10. 文档坐标
      1. 在 CSS 中,窗口坐标对应于 position:fixed,而文档坐标与顶部的 position:absolute 类似。
      2. 我们可以使用 position:absolute 和 top/left 来把某些内容放到文档中的某个位置,以便在页面滚动时,元素仍能保留在该位置。但是我们首先需要正确的坐标。
        • pageY = clientY + 文档的垂直滚动出的部分的高度。
      3. pageX = clientX + 文档的水平滚动出的部分的宽度。