[TOC]
# DOM(文档对象模型)
根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:
- 整个文档是一个文档节点
- 每个 HTML 元素是元素节点
- HTML 元素内的文本是文本节点
- 每个 HTML 属性是属性节点
- 注释是注释节点
# 常用节点类型及其介绍
Document 类型表示整个文档,是一组分层节点的根节点。在 JavaScript 中,document 对象是 Document 的一个实例。使用 document 对象,有很多种方式可以查询和取得节点。
Element 节点表示文档中的所有 HTML 或 XML 元素,可以用来操作这些元素的内容和特性。
另外还有一些节点类型,分别表示文本内容、注释、文档类型、CDATA 区域和文档片段。
## Element 类型
Element 类型可以说是 Web 编程中最常用的类型
Element 节点具有以下特征:
- nodeType 的值为 1;
- nodeName 的值为元素的标签名;
- nodeValue 的值为 null;
- parentNode 可能是 Document 或 Element;
- 其子节点可能是 Element、Text、Comment、ProcessingInstruction、CDATASection 或 EntityReference。
要访问元素的标签名,可以使用 nodeName 属性,也可以使用 tagName 属性;这两个属性会返回相同的值(使用后者主要是为了清晰起见)。需要注意的是 `div.tagName` 实际上输出的是 'DIV' 而非 'div',所以最好是这么比较
```js
// 这样最好(适用于任何文档)
if (element.tagName.toLowerCase() == "div"){
// do something...
}
```
### 1.HTML 元素
所有 HTML 元素都由 HTMLElement 类型表示,不是直接通过这个类型,也是通过它的子类型来表 示。HTMLElement 类型直接继承自 Element 并添加了一些属性。每个 HTML 元素中都存在的下列标准特性:
- id,元素在文档中的唯一标识符。
- title,有关元素的附加说明信息,一般通过工具提示条显示出来。
- lang,元素内容的语言代码,很少使用。
- dir,语言的方向,值为"ltr"(left-to-right,从左至右)或"rtl"(right-to-left,从右至左),
也很少使用。
- className,与元素的 class 特性对应,即为元素指定的 CSS 类。没有将这个属性命名为 class,是因为 class 是 ECMAScript 的保留字
```js
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
var div = document.getElementById("myDiv")
alert(div.id) // "myDiv""
alert(div.className) // "bd"
alert(div.title) // "Body text"
alert(div.lang) // "en"
alert(div.dir) // "ltr"
```
所有 HTML 元素都是由 HTMLElement 或者其更具体的子类型来表示的,每一种类型都有与之相关的特性和方法,比如 A 元素和 IMG 元素它们的特性和对应的方法是不完全相同的。
### 2.取得特性
每个元素都有一或多个特性,这些特性的用途是给出相应元素或其内容的附加信息。操作特性的 DOM 方法主要有三个,分别是 getAttribute()、setAttribute() 和 removeAttribute(),这三个方法可以针对任何特性使用。
`getAttribute()`:由于 `div.getAttribute('id')` 和 `div.id` 的效果是一样的,所以一般不使用,直接使用后者来访问元素节点的特性(属性)即可。
关于自定义特性(一般用 data- 表示)可以通过 dataset 属性来访问(当然用 getAttribute 方法也行),比如
```html
<img id="test" src="" class="image-item"
lazyload="true"
data-original="https://xxx.webp"
alt="" />
// document.getElementById('test').dataset.original 可以访问到 data-original 的值
```
参考:[https://blog.csdn.net/qq\_39579242/article/details/81779170](https://blog.csdn.net/qq_39579242/article/details/81779170)
### 3.设置和移除特性
`setAttribute()`:这个方法接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute() 会以指定的值替换现有的值;如果特性不存在,setAttribute() 则创建该属性并设置相应的值。
用法如下:
```js
div.setAttribute("id", "someOtherId")
div.setAttribute("class", "ft")
div.setAttribute("title", "Some other text")
```
> 通过这个方法设置的特性名会被统一转换为小写形式,即"ID"最终会变成"id"。
`removeAttribute()`:这个方法用于彻底删除元素的特性。调用这个方法不仅会清除特性的值,而且也会从元素中完全删除特性
```
div.removeAttribute("class");
```
### 4.attributes 属性
Element 类型是使用 attributes 属性的唯一一个 DOM 节点类型。attributes 属性中包含一个 NamedNodeMap,与 NodeList 类似。虽然我们可以用它做以上三个方法的操作,但是不是很方便(额外的操作 API),一般在需要遍历元素的特性时可以使用这个属性
以下代码展示了如何迭代元素的每一个特性,然后将它们构造成 name="value" 这样的字符串格式
```js
// 迭代元素的每一个特性,将它们构造成 name = value 的字符串形式
function outputAttributes (element) {
const pairs = []
let attrName
let attrValue
for (let i = 0, len = element.attributes.length; i < len; i++) {
attrName = element.attributes[i].nodeName
attrValue = element.attributes[i].nodeValue
pairs.push(`${attrName}=${attrValue}`)
}
return pairs.join(" ")
}
```
### 5.classList 属性(DOM 扩展)
```html
<div class="bd user disabled">...</div>
```
这个`<div>`元素一共有三个类名。要从中删除一个类名,需要把这三个类名拆开,删除不想要的那个,然后再把其他类名拼成一个新字符串。请看下面的例子。
```js
// div.className = 'bd user disabled'
let classNames = div.className.split(/\s+/)
let pos = -1
for (let i = 0, len = classNames.length; i < len; i++) {
if (classNames[i] === 'user') {
pos = i
break
}
}
classNames.splice(pos, 1) // 删除类名
div.className = classNames.join(' ') // 把剩下的类名拼接成字符串并重新设置
```
HTML5 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加 classList 属性。这个 classList 属性是新集合类型 DOMTokenList 的实例。与其他 DOM 集合类似 DOMTokenList 有一个表示自己包含多少元素的 length 属性,而要取得每个元素可以使用 item() 方法,也可以使用方括号语法。此外,这个新类型还定义如下方法。
- add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
- contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false。
- remove(value):从列表中删除给定的字符串。
- toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
这样,前面那么多行代码用下面这一行代码就可以代替了
```js
div.classList.remove("user")
```
其他操作:
```js
// 删除 "disabled" 类
div.classList.remove("disabled")
// 添加 "current" 类
div.classList.add("current")
// 切换 "user" 类
div.classList.toggle("user")
// 确定元素中是否包含既定的类名
if (div.classList.contains("bd") && !div.classList.contains("disabled")) {
// do something...
)
// 迭代类名
for (var i = 0, len = div.classList.length; i < len; i++) {
doSomething(div.classList[i])
}
```
有了 classList 属性,除非你需要全部删除所有类名,或者完全重写元素的 class 属性,否则也就用不到 className 属性了
# 节点关系
![](https://box.kancloud.cn/e3e6da51171ba916d958366256b7185b_661x335.png)
注意关系指针都是只读的,见下表
| 关系指针 | 描述 |
| :---- | :---- |
| childNodes | 每个节点都有一个 childNodes 属性,其中保存着一个 NodeList 对象,NodeList 是一种类数组对象,用于保存一组有序的节点,可以通过下标来访问这些节点。 |
| parentNode | 指向该节点在文档树中的父节点 |
| previousSibling | 上一个兄弟,没有则为 null |
| nextSibling | 下一个兄弟,没有则为 null |
| firstChild | 第一个子节点 |
| lastChild | 最后一个子节点 |
| ownerDocument| 该属性指向表示整个文档的文档节点 |
contains() 方法用于判断某个节点是否为另一个节点的后代,调用 contains 方法的应该是祖先节点,也就是搜索开始的节点,这个方法接收一个参数,即需要检测的节点。
`console.log(document.documentElement.contains(document.body)) // true`
这个例子检测了\<body\>元素是不是\<html\>元素的后代
<br>
需要注意的是,用上面的方式访问节点返回的不一定是元素节点(Element 类型),而我们往往需要操作的只是元素节点,所以就有了以下的 DOM 扩展:
Element Traversal API 为 DOM 元素添加了以下 5 个属性:
- childElementCount:返回子元素(不包括文本节点和注释)的个数。
- firstElementChild:指向第一个子元素;firstChild 的元素版。
- lastElementChild:指向最后一个子元素;lastChild 的元素版。
- previousElementSibling:指向前一个同辈元素;previousSibling 的元素版。
- nextElementSibling:指向后一个同辈元素;nextSibling 的元素版。
支持的浏览器为 DOM 元素添加了这些属性,利用这些元素不必担心空白文本节点,从而可以更方便地查找 DOM 元素了。
下面来看一个例子。过去,要跨浏览器遍历某元素的所有子元素,需要像下面这样写代码。
```js
let child = element.firstChild
while (child !== element.lastChild) {
if (child.nodeType === 1) { // 检查是否为元素节点
processChild(child)
}
child = child.nextSibling
}
```
而使用 Element Traversal 新增的元素,代码会更简洁:
```js
let child = element.firstElementChild
while (child !== element.lastElementChild) {
processChild(child) // 肯定是元素节点
child = child.nextElementSibling // 遍历下一个元素节点
}
```
# 节点操作
## 创建节点
⒈`document.createElement(tagName)` 创建一个元素节点,tagName 为 HTML 标签的名称(不区分大小写,如'div'),并将返回一个节点对象。
⒉`document.createTextNode(text)`创建一个文本节点,text 为文本节点的内容,并将返回一个节点对象。
⒊`document.createDocumentFragment()`创建一个文档片段
文档片段用于保存将来可能添加到文档中的节点,可以通过 appendChild() 或 insertBefore() 方法将文档片段中的内容添加到文档中。在将文档片段作为参数传给这两个方法时,实际上只有文档片段的所有子节点会被添加到文档树中,**文档片段本身永远不会成为文档树中的一部分**。
```js
var fragment = document.createDocumentFragment()
var ul = document.getElementById('myList')
var li = null
for (var i = 0; i < 3; i++) {
li = document.createElement('li')
li.appendChild(document.createTextNode(`Item ${i + 1}`))
fragment.appendChild(li)
}
ul.appendChild(fragment)
```
这样做的好处是:如果逐个地添加列表项,将会导致浏览器的反复渲染(呈现)新信息,使用文档片段可以一次性地将多个节点添加到文档树。
## 获取元素节点
带 s 的就是返回一个 NodeList......
⒈`document.getElementById()`根据 id 获取节点
⒉`document.getElementsByTagName()`返回带有指定标签名的对象的集合
⒊`document.getElementsByClassName()`返回文档中所有指定类名的元素的集合,作为 NodeList 对象
⒋`document.getElementsByName()`查询的是元素的 name 属性
因为一个文档中的 name 属性可能不唯一(如 HTML 表单中的单选按钮通常具有相同的 name 属性),所以 getElementsByName() 方法返回的是元素的 NodeList,而不是一个元素。
⒌`document.querySelector()`
- 该方法接受一个 CSS 选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素则返回 null
- 选择符可以为 id、类、 类型、 属性、 属性值等。
- 对于多个选择器,使用逗号隔开,返回一个匹配的元素。
``` js
// 取得 body 元素
var body = document.querySelector("body")
// 取得 ID 为 "myDiv" 的元素
var myDiv = document.querySelector("#myDiv")
// 取得类为 "selected" 的第一个元素
var selected = document.querySelector(".selected")
// 取得类为 "button" 的第一个图像元素
var img = document.body.querySelector("img.button")
// 获取文档中 class="example" 的第一个 <p> 元素:
document.querySelector("p.example")
// 获取文档中有 "target" 属性的第一个 <a> 元素
document.querySelector("a[target]")
```
注意:通过 Document 类型调用 querySelector() 方法时,会在文档元素的范围内查找匹配的元素。而通过 Element 类型调用 querySelector() 方法时,只会在该元素后代元素的范围内查找匹配的元素。 CSS 选择符可以简单也可以复杂,视情况而定。如果传入了不被支持的选择符,querySelector() 会抛出错误。
⒍`document.querySelectorAll()`
querySelectorAll() 方法接收的参数与 querySelector() 方法一样,都是一个 CSS 选择符,但返回的是所有匹配的元素而不仅仅是一个元素。**这个方法返回的是一个 NodeList 的实例**,如果没有找到匹配的元素则 NodeList 为空。
``` js
// 取得某 <div> 中的所有 <em> 元素(类似于 getElementsByTagName("em"))
var ems = document.getElementById("myDiv").querySelectorAll("em")
// 取得类为 "selected" 的所有元素
var selecteds = document.querySelectorAll(".selected")
// 取得所有<p>元素中的所有<strong>元素
var strongs = document.querySelectorAll("p strong")
```
## 改变节点关系
| 方法 | 描述 |
| :---- | :---- |
| appendChild(newNode) | 向 childNodes 列表末尾添加一个节点,添加节点后,childNodes 的新增节点、父节点以及最后一个节点的关系指针都会相应地得到更新。更新完成后,该方法返回新增的节点 |
| insertBefore(newNode, 参照 Node) | 该方法接收两个参数:要插入的节点和作为参照的节点。插入节点后,插入的节点会成为参照节点的 previousSibling,同时被方法返回。 |
| replaceChild(要插入的节点,要替换的节点) | 要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置 |
| removeChild(要移除的节点) | 被移除的节点将成为方法的返回值 |
这四个方法操作的都是某个节点的子节点,也就是说,要使用这几个方法必须先取得父节点(使用 parentNode 属性)。另外,并不是所有类型的节点都有子节点,如果在不支持子节点的节点上调用了这些方法,将会导致错误发生。
# DOM 操作技术
## 动态脚本
```js
function loadScript (url) {
var script = document.createElement("script")
script.type = "text/javascript"
script.src = url
document.body.appendChild(script)
}
```
## 动态样式
```js
function loadStyles (url) {
var link = document.createElement("link")
link.rel = "stylesheet"
link.type = "text/css"
link.href = url
var head = document.getElementsByTagName("head")[0]
head.appendChild(link)
}
```
href 表示超文本引用(hypertext reference),在 link 和 a 等元素上使用。src 表示来源地址,在 img、script、iframe 等元素上。
src 的内容,是页面必不可少的一部分,是引入。href 的内容,是与该页面有关联,是引用。两者的区别就是对于当前页面来说是引入还是引用(只是从意义上讲)。
## 使用 NodeList
理解 NodeList 及其“近亲” NamedNodeMap 和 HTMLCollection,是从整体上透彻理解 DOM 的关键所在。这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会得到更新。因此,它们始终都会保存着最新、最准确的信息。从本质上说,所有 NodeList 对象都是在访问 DOM 文档时实时运行的查询。例如,下列代码会导致无限循环
```js
var divs = document.getElementsByTagName("div"),
i,
div
for (i=0; i < divs.length; i++) {
div = document.createElement("div")
document.body.appendChild(div)
}
```
第一行代码会取得文档中所有 \<div> 元素的 HTMLCollection。由于这个集合是“动态的”,因此只要有新 \<div> 元素被添加到页面中,这个元素也会被添加到该集合中。浏览器不会将创建的所有集合都保存在一个列表中,而是在下一次访问集合时再更新集合。结果,在遇到上例中所示的循环代码时,就会导致一个有趣的问题。每次循环都要对条件 i < divs.length 求值,意味着会运行取得所有 \<div> 元素的查询。考虑到循环体每次都会创建一个新 \<div> 元素并将其添加到文档中,因此 divs.length 的值在每次循环后都会递增。既然 i 和 divs.length 每次都会同时递增,结果它们的值永远也不会相等。
如果想要迭代一个 NodeList,最好是使用 length 属性初始化第二个变量,然后将迭代器与该变量进行比较,如下面的例子所示:
```js
var divs = document.getElementsByTagName("div"),
i,
len,
div
for (i = 0, len = divs.length; i < len; i++) {
div = document.createElement("div")
document.body.appendChild(div)
}
```
一般来说,应该尽量减少访问 NodeList 的次数。因为每次访问 NodeList,都会运行一次基于文档的查询。所以,可以考虑将从 NodeList 中取得的值缓存起来
## 焦点管理
HTML5 也添加了辅助管理 DOM 焦点的功能。首先就是 document.activeElement 属性,这个属性始终会引用 DOM 中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是通过按 Tab 键)和在代码中调用 focus() 方法。来看几个例子。
```js
var button = document.getElementById("myButton")
button.focus()
alert(document.activeElement === button) // true
```
默认情况下,文档刚刚加载完成时,document.activeElement 中保存的是 document.body 元素的引用。文档加载期间,document.activeElement 的值为 null。 另外就是新增了 document.hasFocus() 方法,这个方法用于确定文档是否获得了焦点
```js
var button = document.getElementById("myButton")
button.focus()
alert(document.hasFocus()) // true
```
通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。查询文档获知哪个元素获得了焦点,以及确定文档是否获得了焦点,这两个功能最重要的用途是提高 Web 应用的无障碍性。无障碍 Web 应用的一个主要标志就是恰当的焦点管理,而确切地知道哪个元素获得了焦点是一个极大的进步,至少我们不用再像过去那样靠猜测了。
## 自定义数据属性
HTML5 规定可以为元素添加非标准的属性,但要添加前缀 data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。这些属性可以任意添加、随便命名,只要以 data-开头即可。来看一个例子
```html
<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
```
添加了自定义属性之后,可以通过元素的 dataset 属性来访问自定义属性的值。dataset 属性的值是 DOMStringMap 的一个实例,也就是一个名值对儿的映射。在这个映射中,每个 data-name 形式的属性都会有一个对应的属性,只不过属性名没有 data-前缀(比如,自定义属性是 data-myname, 那映射中对应的属性就是 myname)。还是看一个例子吧
```js
// 本例中使用的方法仅用于演示
var div = document.getElementById("myDiv")
// 取得自定义属性的值
var appId = div.dataset.appId
var myName = div.dataset.myname
// 设置值
div.dataset.appId = 23456
div.dataset.myname = "Michael"
// 有没有"myname"值呢?
if (div.dataset.myname) {
alert("Hello, " + div.dataset.myname)
}
```
如果需要给元素添加一些不可见的数据以便进行其他处理,那就要用到自定义数据属性。在跟踪链接或混搭应用中,通过自定义数据属性能方便地知道点击来自页面中的哪个部分
## innerHTML 与 outerHTML
### innerHTML
在读模式下,innerHTML 属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的 HTML 标记。在写模式下,innerHTML 会根据指定的值创建新的 DOM 树,然后用这个 DOM 树完全替换调用元素原先的所有子节点。下面是一个例子
```html
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
对于上面的<div>元素来说,它的 innerHTML 属性会返回如下字符串。
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
```
但是,不同浏览器返回的文本格式会有所不同。IE 和 Opera 会将所有标签转换为大写形式,而 Safari、 Chrome 和 Firefox 则会原原本本地按照原先文档中(或指定这些标签时)的格式返回 HTML,包括空格 和缩进。不要指望所有浏览器返回的 innerHTML 值完全相同。
在写模式下,innerHTML 的值会被解析为 DOM 子树,替换调用元素原来的所有子节点。因为它的值被认为是 HTML,所以其中的所有标签都会按照浏览器处理 HTML 的标准方式转换为元素(同样, 这里的转换结果也因浏览器而异)。如果设置的值仅是文本而没有 HTML 标签,那么结果就是设置纯文本。
### outerHTML
在读模式下,outerHTML 返回调用它的元素及所有子节点的 HTML 标签。在写模式下,outerHTML 会根据指定的 HTML 字符串创建新的 DOM 子树,然后用这个 DOM 子树完全替换调用元素。
使用 outerHTML 属性以下面这种方式设置值:
```html
div.outerHTML = "<p>This is a paragraph.</p>";
```
这行代码完成的操作与下面这些 DOM 脚本代码一样:
```js
var p = document.createElement("p")
p.appendChild(document.createTextNode("This is a paragraph."))
div.parentNode.replaceChild(p, div)
```
结果,就是新创建的 \<p> 元素会取代 DOM 树中的 \<div> 元素。
一般来说,在插入大量新 HTML 标记时,使用 innerHTML 属性与通过多次 DOM 操作先创建节点再指定它们之间的关系相比,效率要高得多。这是因为在设置 innerHTML 或 outerHTML 时,就会创建一个 HTML 解析器。这个解析器是在浏览器级别的代码(通常是 C++ 编写的)基础上运行的,因此比执行 JavaScript 快得多。不可避免地,创建和销毁 HTML 解析器也会带来性能损失,所以最好能够将设置 innerHTML 或 outerHTML 的次数控制在合理的范围内。例如,下列代码使用 innerHTML 创建了很多列表项:
```
for (var i = 0, len = values.length; i < len; i++) {
ul.innerHTML += "<li>" + values[i] + "</li>" // 要避免这种频繁操作!!
}
```
这种每次循环都设置一次 innerHTML 的做法效率很低。而且,每次循环还要从 innerHTML 中读取一次信息,就意味着每次循环要访问两次 innerHTML。最好的做法是单独构建字符串,然后再一次性地将结果字符串赋值给 innerHTML,像下面这样:
```js
var itemsHtml = ""
for (var i = 0, len = values.length; i < len; i++) {
itemsHtml += "<li>" + values[i] + "</li>"
}
ul.innerHTML = itemsHtml
```
这个例子的效率要高得多,因为它只对 innerHTML 执行了一次赋值操作
## scrollIntoView() 方法
scrollIntoView() 可以在所有 HTML 元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果给这个方法传入 true 作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。如果传入 false 作为参数,调用元素会尽可能全部出现在视口中,(可能的话,调用元素的底部会与视口顶部平齐。)不过顶部不一定平齐,例如:
```
// 让元素可见
document.forms[0].scrollIntoView()
```
当页面发生变化时,一般会用这个方法来吸引用户的注意力。实际上,**为某个元素设置焦点也会导致浏览器滚动并显示出获得焦点的元素**
## 访问元素的样式
对于使用短划线(分隔不同的词汇,例如 background-image)的 CSS 属性名,必须将其转换成驼峰大小写形式,才能通过 JavaScript 来访问。下表列出了几个常见的 CSS 属性及 其在 style 对象中对应的属性名。
| CSS 属性 | JavaScript 属性 |
| --- | --- |
| background-image | style.backgroundImage |
| color | style.color|
| display | style.display |
| font-family | style.fontFamily|
# 元素大小
## 偏移量
首先要介绍的属性涉及偏移量(offset dimension),包括元素在屏幕上占用的所有可见的空间。元素的可见大小由其高度、宽度决定,包括所有内边距、滚动条和边框大小(注意,不包括外边距)。通过下列 4 个属性可以取得元素的偏移量。
- offsetHeight:元素在垂直方向上占用的空间大小,以像素计。包括元素的高度、(可见的)水平滚动条的高度、上边框高度和下边框高度。
- offsetWidth:元素在水平方向上占用的空间大小,以像素计。包括元素的宽度、(可见的)垂直滚动条的宽度、左边框宽度和右边框宽度。
- offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。
- offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。
![](https://box.kancloud.cn/25813e8ca7ee75147831b0e86d283632_605x346.png)
## 客户区大小
元素的客户区大小(client dimension),指的是元素**内容及其内边距**所占据的空间大小。有关客户区大小的属性有两个:clientWidth 和 clientHeight。其中,clientWidth 属性是元素内容区宽度加上左右内边距宽度;clientHeight 属性是元素内容区高度加上上下内边距高度。下图形象地说明 了这些属性表示的大小。
![](https://box.kancloud.cn/e48023b6abe1f39ad7b8147c1564c426_575x345.png)
可以用来确认浏览器视口大小(这不是用 window.innerWidth 和 window.innerHeight 就行了吗?)
```js
function getViewport() {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
}
```
## 滚动大小
以下是 4 个与滚动大小相关的属性:
- scrollHeight:在没有滚动条的情况下,元素内容的总高度。
- scrollWidth:在没有滚动条的情况下,元素内容的总宽度。
- scrollLeft:被隐藏在内容区域左侧的像素数。通过设置这个属性可以改变元素的滚动位置。
- scrollTop:被隐藏在内容区域上方的像素数。通过设置这个属性可以改变元素的滚动位置
![](https://box.kancloud.cn/39713029899f60e71bf608f8ac365562_647x393.png)
通过 scrollLeft 和 scrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置。在元素尚未被滚动时,这两个属性的值都等于 0。如果元素被垂直滚动了,那么 scrollTop 的值会大于 0,且表示元素上方不可见内容的像素高度。如果元素被水平滚动了,那么 scrollLeft 的值会大于 0,且表示元素左侧不可见内容的像素宽度。这两个属性都是可以设置的,因此将元素的 scrollLeft 和 scrollTop 设置为 0,就可以重置元素的滚动位置。下面这个函数会检测元素是否位于顶部,如果不是就将其回滚到顶部。
```js
function scrollToTop (element) {
if (element.scrollTop != 0) {
element.scrollTop = 0
}
}
```
这个函数既取得了 scrollTop 的值,也设置了它的值
### 滚动方法
`scrollTo(x, y)`:滚动当前 window 中显示的文档,让文档中由坐标 x 和 y 指定的点位于显示区域的左上角
`scrollBy(x, y)`:滚动当前 window 中显示的文档,x 和 y 指定滚动的相对量
【应用】:利用 scrollBy() + setInterval 实现快速滚动的功能
```html
<body style="height:1000px">
<button id='btn1' style="position:fixed">开始滚动</button>
<button id='btn2' style="position:fixed;top:40px">停止滚动</button>
<script>
var timer = 0;
btn1.onclick = function(){
timer = setInterval(function(){
scrollBy(0,10);
},100)}
btn2.onclick = function(){
clearInterval(timer);
timer = 0;
}
</script>
```
`scrollIntoView()`:方法滚动当前元素,使其进入浏览器的可见区域;该方法可以接受一个布尔值作为参数。如果为 true,表示元素的顶部与当前区域的可见部分的顶部对齐(前提是当前区域可滚动);如果为 false,表示元素的底部与当前区域的可见部分的尾部对齐(前提是当前区域可滚动)。如果没有提供该参数,默认为 true
### 滚动条
参考资料:[CSS 滚动条](https://www.cnblogs.com/xiaohuochai/p/5294409.html)
自定义滚动条样式:
【1】、IE
```css
scrollbar-face-color 滚动条凸出部分的颜色
scrollbar-shadow-color 立体滚动条阴影的颜色
scrollbar-highlight-color 滚动条空白部分的颜色
scrollbar-3dlight-color 滚动条亮边的颜色
scrollbar-darkshadow-color 滚动条强阴影的颜色
scrollbar-track-color 滚动条的背景颜色
scrollbar-arrow-color 上下按钮上三角箭头的颜色
scrollbar-base-color 滚动条的基本颜色
```
【2】、webkit
webkit 内核的浏览器支持滚动条自定义样式,但和 IE 不同,webkit 是通过伪类来实现的
```css
::-webkit-scrollbar 滚动条整体部分
::-webkit-scrollbar-thumb 滚动滑块
::-webkit-scrollbar-track 外层轨道
::-webkit-scrollbar-track-piece 内层轨道
::-webkit-scrollbar-corner 边角
::-webkit-scrollbar-button 两端按钮
```
常用的样式设置
```css
::-webkit-scrollbar { // 去掉滚动条
display: none;
}
```
我们经常会需要一个回到顶部的效果,可以使用`scroll-behavior`来快捷地实现
```css
scroll-behavior: smooth;
```
当用户手动导航或者 CSSOM scrolling API 触发滚动操作时,`scroll-behavior`为一个滚动框指定滚动行为,其他任何的滚动,例如那些由于用户行为而产生的滚动,不受这个属性的影响。在根元素中指定这个属性时,它反而适用于视窗。具体见 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-behavior)
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直线、矩形、多边形
- Part2-曲线图形
- Part3-线条操作
- Part4-文本操作
- Part5-图像操作
- Part6-变形操作
- Part7-像素操作
- Part8-渐变与阴影
- Part9-路径与状态
- Part10-物理动画
- Part11-边界检测
- Part12-碰撞检测
- Part13-用户交互
- Part14-高级动画
- CSS
- SCSS
- codePen
- 速查表
- 面试题
- 《CSS Secrets》
- SVG
- 移动端适配
- 滤镜(filter)的使用
- JS
- 基础概念
- 作用域、作用域链、闭包
- this
- 原型与继承
- 数组、字符串、Map、Set方法整理
- 垃圾回收机制
- DOM
- BOM
- 事件循环
- 严格模式
- 正则表达式
- ES6部分
- 设计模式
- AJAX
- 模块化
- 读冴羽博客笔记
- 第一部分总结-深入JS系列
- 第二部分总结-专题系列
- 第三部分总结-ES6系列
- 网络请求中的数据类型
- 事件
- 表单
- 函数式编程
- Tips
- JS-Coding
- Framework
- Vue
- 书写规范
- 基础
- vue-router & vuex
- 深入浅出 Vue
- 响应式原理及其他
- new Vue 发生了什么
- 组件化
- 编译流程
- Vue Router
- Vuex
- 前端路由的简单实现
- React
- 基础
- 书写规范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 与 Hook
- 《深入浅出React和Redux》笔记
- 前半部分
- 后半部分
- react-transition-group
- Vue 与 React 的对比
- 工程化与架构
- Hybird
- React Native
- 新手上路
- 内置组件
- 常用插件
- 问题记录
- Echarts
- 基础
- Electron
- 序言
- 配置 Electron 开发环境 & 基础概念
- React + TypeScript 仿 Antd
- TypeScript 基础
- 样式设计
- 组件测试
- 图标解决方案
- Algorithm
- 排序算法及常见问题
- 剑指 offer
- 动态规划
- DataStruct
- 概述
- 树
- 链表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 课程实战记录
- 服务器
- 操作系统基础知识
- Linux
- Nginx
- redis
- node.js
- 基础及原生模块
- express框架
- node.js操作数据库
- 《深入浅出 node.js》笔记
- 前半部分
- 后半部分
- 数据库
- SQL
- 面试题收集
- 智力题
- 面试题精选1
- 面试题精选2
- 问答篇
- Other
- markdown 书写
- Git
- LaTex 常用命令