[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 屬性了
# 節點關系

注意關系指針都是只讀的,見下表
| 關系指針 | 描述 |
| :---- | :---- |
| 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:元素的上外邊框至包含元素的上內邊框之間的像素距離。

## 客戶區大小
元素的客戶區大小(client dimension),指的是元素**內容及其內邊距**所占據的空間大小。有關客戶區大小的屬性有兩個:clientWidth 和 clientHeight。其中,clientWidth 屬性是元素內容區寬度加上左右內邊距寬度;clientHeight 屬性是元素內容區高度加上上下內邊距高度。下圖形象地說明 了這些屬性表示的大小。

可以用來確認瀏覽器視口大小(這不是用 window.innerWidth 和 window.innerHeight 就行了嗎?)
```js
function getViewport() {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
}
```
## 滾動大小
以下是 4 個與滾動大小相關的屬性:
- scrollHeight:在沒有滾動條的情況下,元素內容的總高度。
- scrollWidth:在沒有滾動條的情況下,元素內容的總寬度。
- scrollLeft:被隱藏在內容區域左側的像素數。通過設置這個屬性可以改變元素的滾動位置。
- scrollTop:被隱藏在內容區域上方的像素數。通過設置這個屬性可以改變元素的滾動位置

通過 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 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs