ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[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)