六、Element节点
Element节点对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一个Element节点对象(以下简称元素节点)。
元素节点的nodeType属性都是1。
var p = document.querySelector('p'); p.nodeName // "P" p.nodeType // 1 节点类型 1代表元素节点
Element对象继承了Node接口,因此*Node的属性和方法在Element对象都存在*。此外,不同的 HTML 元素对应的元素节点是不一样的,浏览器使用不同的构造函数,生成不同的元素节点,比如<a>元素的节点对象由HTMLAnchorElement构造函数生成,<button>元素的节点对象由HTMLButtonElement构造函数生成。因此,元素节点不是一种对象,而是一组对象,这些对象除了继承Element的属性和方法,还有各自构造函数的属性和方法。
元素节点拥有 各自构造函数 的属性和方法,继承Element的属性和方法,同时继承Node的属性和方法。
1、实例属性
1.1 元素特性的相关属性
(1)Element.id 返回元素ID属性,可读写
Element.id属性返回指定元素的id属性,该属性可读写。
// HTML 代码为 <p id="foo"> var p = document.querySelector('p'); p.id // "foo"
注意,id属性的值是大小写敏感,即浏览器能正确识别<p id="foo">和<p id="FOO">这两个元素的id属性,但是最好不要这样命名。
(2)Element.tagName 返回元素的大写标签
Element.tagName属性返回指定元素的大写标签名,与nodeName属性的值相等。
// HTML代码为 // <span id="myspan">Hello</span> var span = document.getElementById('myspan'); span.id // "myspan" span.tagName // "SPAN"
(3)Element.dir 元素的文字方向,可读写(ltr、rtl)
Element.dir属性用于读写当前元素的文字方向,可能是从左到右("ltr"),也可能是从右到左("rtl")。
(4)Element.accessKey 分配给当前元素的快捷键,可读写
Element.accessKey属性用于读写分配给当前元素的快捷键。
// HTML 代码如下 // <button accesskey="h" id="btn">点击</button> var btn = document.getElementById('btn'); btn.accessKey // "h"
上面代码中,btn元素的快捷键是h,按下Alt + h就能将焦点转移到它上面。
(5)Element.draggable 当前元素是否可拖动,布尔值,可读写
Element.draggable属性返回一个布尔值,表示当前元素是否可拖动。该属性可读写。
<p draggable="true" ondragstart="drag(this)">这是一段可移动的段落。</p> <script> function drag(el) { console.log(el.draggable) // true } </script>
(6)Element.lang 返回当前元素的语言设置,可读写
Element.lang属性返回当前元素的语言设置。该属性可读写。
// HTML 代码如下 // <html lang="en"> document.documentElement.lang // "en"
(7)Element.tabIndex 当前元素在 Tab 键遍历时的顺序,整数,可读写
Element.tabIndex属性返回一个整数,表示当前元素在 Tab 键遍历时的顺序。该属性可读写。
tabIndex属性值如果是负值(通常是-1),则 Tab 键不会遍历到该元素。如果是正整数,则按照顺序,从小到大遍历。如果两个元素的tabIndex属性的正整数值相同,则按照出现的顺序遍历。遍历完所有tabIndex为正整数的元素以后,再遍历所有tabIndex等于0、或者属性值是非法值、或者没有tabIndex属性的元素,顺序为它们在网页中出现的顺序。
(8)Element.title 当前元素的 HTML 属性title,可读写
Element.title属性用来读写当前元素的 HTML 属性title。该属性通常用来指定,鼠标悬浮时弹出的文字提示框。
1.2 元素状态的相关属性
(1)Element.hidden 当前元素的hidden属性,控制是否可见,布尔值,可读写
Element.hidden属性返回一个布尔值,表示当前元素的hidden属性,用来控制当前元素是否可见。该属性可读写。
var btn = document.getElementById('btn'); var mydiv = document.getElementById('mydiv'); btn.addEventListener('click', function () { mydiv.hidden = !mydiv.hidden; }, false);
注意,该属性与 CSS 设置是互相独立的。CSS 对这个元素可见性的设置,Element.hidden并不能反映出来。也就是说,这个属性并不能用来判断当前元素的实际可见性。
**CSS 的设置高于Element.hidden。**如果 CSS 指定了该元素不可见(display: none)或可见(display: hidden),那么Element.hidden并不能改变该元素实际的可见性。换言之,这个属性只在 CSS 没有明确设定当前元素的可见性时才有效。
(2)Element.contentEditable元素内容的可编辑性,字符串,可读写,
Element.isContentEditable 元素内容是否可编辑,布尔值,只读
HTML 元素可以设置contentEditable属性,使得元素的内容可以编辑。
<div contenteditable>123</div>
上面代码中,<div>元素有contenteditable属性,因此用户可以在网页上编辑这个区块的内容。
Element.contentEditable属性返回一个字符串,表示是否设置了contenteditable属性,有三种可能的值。该属性可写。
"true":元素内容可编辑"false":元素内容不可编辑"inherit":元素是否可编辑,继承了父元素的设置
Element.isContentEditable属性返回一个布尔值,同样表示是否设置了contenteditable属性。该属性只读。
1.3 Element.attributes 类数组,成员是元素的所有HTML属性节点
Element.attributes属性返回一个类似数组的对象,成员是当前元素节点的所有属性节点,详见《属性的操作》一章。
var p = document.querySelector('p'); var attrs = p.attributes; for (var i = attrs.length - 1; i >= 0; i--) { console.log(attrs[i].name + '->' + attrs[i].value); }
上面代码遍历p元素的所有属性。
1.4 Element.className 元素的class,Element.classList 类数组,成员是各class,有自身方法
className属性**用来读写当前元素节点的class属性。**它的值是一个字符串,每个class之间用空格分割。
classList属性返回一个类似数组的对象,当前元素节点的每个class就是这个对象的一个成员。
// HTML 代码 <div class="one two three" id="myDiv"></div> var div = document.getElementById('myDiv'); div.className // "one two three" div.classList // { // 0: "one" // 1: "two" // 2: "three" // length: 3 // }
上面代码中,className属性返回一个空格分隔的字符串,而classList属性指向一个类似数组的对象,该对象的length属性(只读)返回当前元素的class数量。
classList对象有下列方法。
add():增加一个 class。remove():移除一个 class。contains():检查当前元素是否包含某个 class。toggle():将某个 class 移入或移出当前元素。item():返回指定索引位置的 class。toString():将 class 的列表转为字符串。
var div = document.getElementById('myDiv'); div.classList.add('myCssClass'); div.classList.add('foo', 'bar'); div.classList.remove('myCssClass'); div.classList.toggle('myCssClass'); // 如果 myCssClass 不存在就加入,否则移除 div.classList.contains('myCssClass'); // 返回 true 或者 false div.classList.item(0); // 返回第一个 Class div.classList.toString();
下面比较一下,className和classList在添加和删除某个 class 时的写法。
var foo = document.getElementById('foo'); // 添加class foo.className += 'bold'; foo.classList.add('bold'); // 删除class foo.classList.remove('bold'); foo.className = foo.className.replace(/^bold$/, '');
**toggle方法可以接受一个布尔值,作为第二个参数。**如果为true,则添加该属性;如果为false,则去除该属性。
el.classList.toggle('abc', boolValue); // 等同于 if (boolValue) { el.classList.add('abc'); } else { el.classList.remove('abc'); }
1.5 Element.dataset 返回一个对象,读写元素的data-属性
网页元素可以自定义data-属性,用来添加数据。
<div data-timestamp="1522907809292"></div>
上面代码中,<div>元素有一个自定义的data-timestamp属性,用来为该元素添加一个时间戳。
Element.dataset属性返回一个对象,可以从这个对象读写data-属性。
// <article // id="foo" // data-columns="3" // data-index-number="12314" // data-parent="cars"> // ... // </article> var article = document.getElementById('foo'); article.dataset.columns // "3" article.dataset.indexNumber // "12314" article.dataset.parent // "cars"
注意,dataset上面的各个属性返回都是字符串。
HTML 代码中,data-属性的属性名,只能包含英文字母、数字、连词线(-)、点(.)、冒号(:)和下划线(_)。它们转成 JavaScript 对应的dataset属性名,规则如下。
- 开头的
data-会省略。 - 如果连词线后面跟了一个英文字母,那么连词线会取消,该字母变成大写。
- 其他字符不变。
因此,data-abc-def对应dataset.abcDef,data-abc-1对应dataset["abc-1"]。
除了使用dataset读写data-属性,也可以使用Element.getAttribute()和Element.setAttribute(),通过完整的属性名读写这些属性。
var mydiv = document.getElementById('mydiv'); mydiv.dataset.foo = 'bar'; mydiv.getAttribute('data-foo') // "bar"
1.6 Element.innerHTML 返回一个字符串,是元素包含的所有 HTML 代码,可读写
Element.innerHTML属性返回一个字符串,等同于该元素包含的所有 HTML 代码。该属性可读写,常用来设置某个节点的内容。它能改写所有元素节点的内容,包括<HTML>和<body>元素。
如果将innerHTML属性设为空,等于删除所有它包含的所有节点。
el.innerHTML = '';
上面代码等于将el节点变成了一个空节点,el原来包含的节点被全部删除。
注意,读取属性值的时候,如果文本节点包含&、小于号(<)和大于号(>),innerHTML属性会将它们转为实体形式 & amp;、& lt;、& gt; 。如果想得到原文,建议使用element.textContent属性。
// HTML代码如下 <p id="para"> 5 > 3 < 6 &</p> document.getElementById('para').innerHTML // 5 > 3 < &
写入的时候,如果插入的文本包含 HTML 标签,会被解析成为节点对象插入 DOM。注意,如果文本之中含有<script>标签,虽然可以生成script节点,但是插入的代码不会执行。
var name = "<script>alert('haha')</script>"; // 插入包含js代码字符串,插入后js不会执行 el.innerHTML = name;
上面代码将脚本插入内容,脚本并不会执行。但是,innerHTML还是有安全风险的。
var name = "<img src=x onerror=alert(1)>"; el.innerHTML = name;
上面代码中,alert方法是会执行的。因此为了安全考虑,如果插入的是文本,最好用textContent属性代替innerHTML。
1.7 Element.outerHTML 返回一个字符串,包含元素本身和所有子元素的HTML代码,可读写
Element.outerHTML属性返回一个字符串,表示当前元素节点的所有 HTML 代码,包括该元素本身和所有子元素。
// HTML 代码如下 // <div id="d"><p>Hello</p></div> var d = document.getElementById('d'); d.outerHTML // '<div id="d"><p>Hello</p></div>'
outerHTML属性是可读写的,对它进行赋值,等于替换掉当前元素。
// HTML 代码如下 // <div id="container"><div id="d">Hello</div></div> var container = document.getElementById('container'); var d = document.getElementById('d'); container.firstChild.nodeName // "DIV" d.nodeName // "DIV" d.outerHTML = '<p>Hello</p>'; //div替换成p标签 container.firstChild.nodeName // "P" d.nodeName // "DIV" 但是变量b依旧指向原来的div元素,还存在于内存中
上面代码中,变量d代表子节点,它的outerHTML属性重新赋值以后,内层的div元素就不存在了,被p元素替换了。但是,变量d依然指向原来的div元素,这表示被替换的DIV元素还存在于内存中。
注意,如果一个节点没有父节点,设置outerHTML属性会报错。
var div = document.createElement('div'); div.outerHTML = '<p>test</p>'; // DOMException: This element has no parent node.
上面代码中,div元素没有父节点,设置outerHTML属性会报错。
1.8 Element.clientHeight 元素高度,Element.clientWidth元素宽度
Element.clientHeight属性返回一个整数值,表示元素节点的 CSS 高度(单位像素),只对块级元素生效,对于行内元素返回0。如果块级元素没有设置 CSS 高度,则返回实际高度。
除了元素本身的高度,它还包括padding部分,但是不包括border、margin。如果有水平滚动条,还要减去水平滚动条的高度。注意,这个值始终是整数,如果是小数会被四舍五入。
Element.clientWidth属性返回元素节点的 CSS 宽度,同样只对块级元素有效,也是只包括元素本身的宽度和padding,如果有垂直滚动条,还要减去垂直滚动条的宽度。
document.documentElement的clientHeight属性,返回当前视口的高度(即浏览器窗口的高度),等同于window.innerHeight属性减去水平滚动条的高度(如果有的话)。document.body的高度则是网页的实际高度。一般来说,document.body.clientHeight大于document.documentElement.clientHeight。
// 视口高度 不包括不可见的部分 document.documentElement.clientHeight // 网页总高度 document.body.clientHeight
1.9 Element.clientLeft 左边框宽度,Element.clientTop上边框宽
Element.clientLeft属性等于元素节点左边框(left border)的宽度(单位像素),不包括左侧的padding和margin。如果没有设置左边框,或者是行内元素(display: inline),该属性返回0。该属性总是返回整数值,如果是小数,会四舍五入。
Element.clientTop属性等于网页元素顶部边框的宽度(单位像素),其他特点都与clientLeft相同。
1.10 Element.scrollHeight 元素总高度,Element.scrollWidth 元素总宽度
Element.scrollHeight属性返回一个整数值(小数会四舍五入),表示当前元素的总高度(单位像素),包括溢出容器、当前不可见的部分。它包括padding,但是不包括border、margin以及水平滚动条的高度(如果有水平滚动条的话),还包括伪元素(::before或::after)的高度。
Element.scrollWidth属性表示当前元素的总宽度(单位像素),其他地方都与scrollHeight属性类似。这两个属性只读。
整张网页的总高度可以从document.documentElement或document.body上读取。
// 返回网页的总高度 document.documentElement.scrollHeight document.body.scrollHeight
注意,如果元素节点的内容出现溢出,即使溢出的内容是隐藏的,scrollHeight属性仍然返回元素的总高度。
// HTML 代码如下 // <div id="myDiv" style="height: 200px; overflow: hidden;">...<div> document.getElementById('myDiv').scrollHeight // 356
上面代码中,即使myDiv元素的 CSS 高度只有200像素,且溢出部分不可见,但是scrollHeight仍然会返回该元素的原始高度。
1.11 Element.scrollLeft 元素水平滚动条右侧滚动的像素量,Element.scrollTop 元素垂直滚动条向下滚动的像素量
Element.scrollLeft属性表示当前元素的水平滚动条向右侧滚动的像素数量,Element.scrollTop属性表示当前元素的垂直滚动条向下滚动的像素数量。对于那些没有滚动条的网页元素,这两个属性总是等于0。
如果要查看整张网页的水平的和垂直的滚动距离,要从document.documentElement元素上读取。
document.documentElement.scrollLeft document.documentElement.scrollTop
这两个属性都可读写,设置该属性的值,会导致浏览器将当前元素自动滚动到相应的位置。
1.12 Element.offsetParent 返回最靠近当前元素的、并且 CSS 的position属性不等于static的上层元素
Element.offsetParent属性返回最靠近当前元素的、并且 CSS 的position属性不等于static的上层元素。
position:static; 即默认没有定位的样式。
<div style="position: absolute;"> <p> <span>Hello</span> </p> </div>
上面代码中,span元素的offsetParent属性就是div元素。
该属性主要用于确定子元素位置偏移的计算基准,Element.offsetTop和Element.offsetLeft就是offsetParent元素计算的。
如果该元素是不可见的(display属性为none),或者位置是固定的(position属性为fixed),则offsetParent属性返回null。
<div style="position: absolute;"> <p> <span style="display: none;">Hello</span> </p> </div>
上面代码中,span元素的offsetParent属性是null。
如果某个元素的所有上层节点的position属性都是static,则Element.offsetParent属性指向<body>元素。
1.13 Element.offsetHeight 元素的CSS垂直高度,Element.offsetWidth元素的CSS水平宽度
Element.offsetHeight属性返回一个整数,表示元素的 CSS 垂直高度(单位像素),**包括元素本身的高度、padding 和 border,以及水平滚动条的高度(**如果存在滚动条)。
Element.offsetWidth属性表示元素的 CSS 水平宽度(单位像素),其他都与Element.offsetHeight一致。
这两个属性都是只读属性,只比Element.clientHeight和Element.clientWidth多了边框的高度或宽度。如果元素的 CSS 设为不可见(比如display: none;),则返回0。
1.14 Element.offsetLeft 水平位移,Element.offsetTop 垂直位移
Element.offsetLeft返回当前元素左上角相对于Element.offsetParent节点的水平位移,Element.offsetTop返回垂直位移,单位为像素。通常,这两个值是指相对于父节点的位移。
下面的代码可以算出元素左上角相对于整张网页的坐标。
function getElementPosition(e) { var x = 0; var y = 0; while (e !== null) { x += e.offsetLeft; y += e.offsetTop; e = e.offsetParent; } return {x: x, y: y}; }
1.15 Element.style 返回CSSStyleDeclaration实例,用于操作CSS
每个元素节点都有style用来读写该元素的行内样式信息,具体介绍参见《CSS 操作》一章。
1.16 Element.children 类数组,所有子元素,Element.childElementCount 子元素个数
Element.children属性返回一个类似数组的对象(HTMLCollection实例),包括当前元素节点的所有子元素。如果当前元素没有子元素,则返回的对象包含零个成员。
if (para.children.length) { var children = para.children; for (var i = 0; i < children.length; i++) { // ... } }
上面代码遍历了para元素的所有子元素。
这个属性与Node.childNodes属性的区别是,它只包括元素类型的子节点,不包括其他类型的子节点。
Element.childElementCount属性返回当前元素节点包含的子元素节点的个数,与Element.children.length的值相同。
1.17 Element.firstElementChild 首个子元素,Element.lastElementChild 最后一个子元素
Element.firstElementChild属性返回当前元素的第一个元素子节点,Element.lastElementChild返回最后一个元素子节点。
如果没有元素子节点,这两个属性返回null。
1.18 Element.nextElementSibling后一个兄弟元素节点,Element.previousElementSibling上一个兄弟元素节点
Element.nextElementSibling属性返回当前元素节点的后一个同级元素节点,如果没有则返回null。
// HTML 代码如下 // <div id="div-01">Here is div-01</div> // <div id="div-02">Here is div-02</div> var el = document.getElementById('div-01'); el.nextElementSibling // <div id="div-02">Here is div-02</div>
Element.previousElementSibling属性返回当前元素节点的前一个同级元素节点,如果没有则返回null。
2、实例方法
2.1 标签属性相关方法
元素节点提供六个方法,用来操作属性。
(1)getAttribute():读取某个属性的值
(2)getAttributeNames():返回当前元素的所有属性名
(3)setAttribute():写入属性值
(4)hasAttribute():某个属性是否存在
(5)hasAttributes():当前元素是否有属性
(6)removeAttribute():删除属性
这些方法的介绍请看《属性的操作》一章。
2.2 Element.querySelector() 返回第一个匹配的子元素
Element.querySelector方法接受 CSS 选择器作为参数,返回父元素的第一个匹配的子元素。如果没有找到匹配的子元素,就返回null。
var content = document.getElementById('content'); var el = content.querySelector('p');
上面代码返回content节点的第一个p元素。
Element.querySelector方法可以接受任何复杂的 CSS 选择器。
document.body.querySelector("style[type='text/css'], style:not([type])");
注意,这个方法无法选中伪元素。
它可以接受多个选择器,它们之间使用逗号分隔。
element.querySelector('div, p')
上面代码返回element的第一个div或p子元素。
需要注意的是,浏览器执行querySelector方法时,是先在全局范围内搜索给定的 CSS 选择器,然后过滤出哪些属于当前元素的子元素。因此,会有一些违反直觉的结果,下面是一段 HTML 代码。
<div> <p>111</p> <blockquote id="outer"> <p>Hello</p> <div id="inner"> <p>World</p> </div> </blockquote> </div>
那么,像下面这样查询的话,实际上返回的是第一个p元素,而不是第二个。
var outer = document.getElementById('outer'); outer.querySelector('div p') // <p>Hello</p> // 违反自觉的结果,是因为会全局范围搜索到outer外面的div,然后再选出当前元素outer的子元素第一个p outer.querySelector('div').querySelector('p') // <p>World</p> 这样就能拿到里面的p元素
2.3 Element.querySelectorAll() 返回NodeList实例,包含所有子元素
Element.querySelectorAll方法接受 CSS 选择器作为参数,返回一个NodeList实例,包含所有匹配的子元素。
返回的NodeList实例是类数组对象
var el = document.querySelector('#test'); var matches = el.querySelectorAll('div.highlighted > p');
该方法的执行机制与querySelector方法相同,也是先在全局范围内查找,再过滤出当前元素的子元素。因此,选择器实际上针对整个文档的。
它也可以接受多个 CSS 选择器,它们之间使用逗号分隔。如果选择器里面有伪元素的选择器,则总是返回一个空的NodeList实例。
2.4 Element.getElementsByClassName() 通过class获取所有子元素
Element.getElementsByClassName方法返回一个HTMLCollection实例,成员是当前元素节点的所有具有指定 class 的子元素节点。该方法与document.getElementsByClassName方法的用法类似,只是搜索范围不是整个文档,而是当前元素节点。
element.getElementsByClassName('red test');
注意,该方法的参数大小写敏感。
由于HTMLCollection实例是一个活的集合,document对象的任何变化会立刻反应到实例,下面的代码不会生效。
// HTML 代码如下 // <div id="example"> // <p class="foo"></p> // <p class="foo"></p> // </div> var element = document.getElementById('example'); var matches = element.getElementsByClassName('foo'); for (var i = 0; i< matches.length; i++) { matches[i].classList.remove('foo'); // 当删除了foo,matches的集合就会立刻改变 matches.item(i).classList.add('bar'); } // 执行后,HTML 代码如下 // <div id="example"> // <p></p> // <p class="foo bar"></p> // </div>
上面代码中,matches集合的第一个成员,一旦被拿掉 class 里面的foo,就会立刻从matches里面消失,导致出现上面的结果。
2.5 Element.getElementsByTagName() 通过标签获取索引子元素
Element.getElementsByTagName方法返回一个HTMLCollection实例,成员是当前节点的所有匹配指定标签名的子元素节点。该方法与document.getElementsByClassName方法的用法类似,只是搜索范围不是整个文档,而是当前元素节点。
var table = document.getElementById('forecast-table'); var cells = table.getElementsByTagName('td');
注意,该方法的参数是大小写不敏感的。
2.6 Element.closest() 接受CSS选择器参数,返回最靠近的祖先节点
Element.closest方法接受一个 CSS 选择器作为参数,返回匹配该选择器的、最接近当前节点的一个祖先节点(包括当前节点本身)。如果没有任何节点匹配 CSS 选择器,则返回null。
// HTML 代码如下 // <article> // <div id="div-01">Here is div-01 // <div id="div-02">Here is div-02 // <div id="div-03">Here is div-03</div> // </div> // </div> // </article> var div03 = document.getElementById('div-03'); // div-03 最近的祖先节点 div03.closest("#div-02") // div-02 div03.closest("div") // div-03 closest包含当前节点本身 div03.closest("div div") // div-03 div03.closest("div>div") // div-03 div03.closest("article > div") //div-01 div03.closest(":not(div)") // article
上面代码中,由于closest方法将当前节点也考虑在内,所以第二个closest方法返回div-03。
2.7 Element.matches() 当前元素是否匹配给定的css选择器
Element.matches方法返回一个布尔值,表示当前元素是否匹配给定的 CSS 选择器。
if (el.matches('.someClass')) { console.log('Match!'); }
2.8 事件相关方法
以下三个方法与Element节点的事件相关。这些方法都继承自EventTarget接口,详见相关章节。
(1)Element.addEventListener():添加事件的回调函数
(2)Element.removeEventListener():移除事件监听函数
(3)Element.dispatchEvent():触发事件
element.addEventListener('click', listener, false); element.removeEventListener('click', listener, false); var event = new Event('click'); element.dispatchEvent(event);
2.9 Element.scrollIntoView() 让元素滚动到浏览器可视区
Element.scrollIntoView方法让当前的元素滚动到浏览器窗口的可视区域内 ,类似于设置window.location.hash的效果。
el.scrollIntoView(); // 等同于el.scrollIntoView(true) el.scrollIntoView(false);
该方法可以接受一个布尔值作为参数。如果为true,表示元素的顶部与当前区域的可见部分的顶部对齐(前提是当前区域可滚动);如果为false,表示元素的底部与当前区域的可见部分的尾部对齐(前提是当前区域可滚动)。如果没有提供该参数,默认为true。
2.10 Element.getBoundingClientRect() 返回元素大小、位置信息的对象
Element.getBoundingClientRect方法返回一个对象,提供当前元素节点的大小、位置等信息,基本上就是 CSS 盒状模型的所有信息。
var rect = obj.getBoundingClientRect();
上面代码中,getBoundingClientRect方法返回的rect对象,具有以下属性(全部为只读)。
x:元素左上角相对于视口的横坐标y:元素左上角相对于视口的纵坐标height:元素高度width:元素宽度left:元素左上角相对于视口的横坐标,与x属性相等right:元素右边界相对于视口的横坐标(等于x + width)top:元素顶部相对于视口的纵坐标,与y属性相等bottom:元素底部相对于视口的纵坐标(等于y + height)
由于元素相对于视口(viewport)的位置,会随着页面滚动变化,因此表示位置的四个属性值,都不是固定不变的。如果想得到绝对位置,可以将left属性加上window.scrollX,top属性加上window.scrollY。
注意,getBoundingClientRect方法的所有属性,都把边框(border属性)算作元素的一部分。也就是说,都是从边框外缘的各个点来计算。因此,width和height包括了元素本身 + padding + border。
另外,上面的这些属性,都是继承自原型的属性,Object.keys会返回一个空数组,这一点也需要注意。
var rect = document.body.getBoundingClientRect(); Object.keys(rect) // []
上面代码中,rect对象没有自身属性,而Object.keys方法只返回对象自身的属性,所以返回了一个空数组。
2.11 Element.getClientRects() 元素在页面上形成的所有矩形,类数组
Element.getClientRects方法返回一个类似数组的对象,里面是当前元素在页面上形成的所有矩形(所以方法名中的Rect用的是复数)。每个矩形都有bottom、height、left、right、top和width六个属性,表示它们相对于视口的四个坐标,以及本身的高度和宽度。
对于盒状元素(比如<div>和<p>),该方法返回的对象中只有该元素一个成员。对于行内元素(比如<span>、<a>、<em>),该方法返回的对象有多少个成员,取决于该元素在页面上占据多少行。这是它和Element.getBoundingClientRect()方法的主要区别,后者对于行内元素总是返回一个矩形。
<span id="inline">Hello World Hello World Hello World</span>
上面代码是一个行内元素<span>,如果它在页面上占据三行,getClientRects方法返回的对象就有三个成员,如果它在页面上占据一行,getClientRects方法返回的对象就只有一个成员。
var el = document.getElementById('inline'); el.getClientRects().length // 3 el.getClientRects()[0].left // 8 el.getClientRects()[0].right // 113.908203125 el.getClientRects()[0].bottom // 31.200000762939453 el.getClientRects()[0].height // 23.200000762939453 el.getClientRects()[0].width // 105.908203125
这个方法主要用于判断行内元素是否换行,以及行内元素的每一行的位置偏移。
注意,如果行内元素包括换行符,那么该方法会把换行符考虑在内。
<span id="inline"> Hello World Hello World Hello World </span>
上面代码中,<span>节点内部有三个换行符,即使 HTML 语言忽略换行符,将它们显示为一行,getClientRects()方法依然会返回三个成员。如果行宽设置得特别窄,上面的<span>元素显示为6行,那么就会返回六个成员。
2.12 Element.insertAdjacentElement() 插入新节点到元素的指定位置
Element.insertAdjacentElement方法在相对于当前元素的指定位置,插入一个新的节点。该方法返回被插入的节点,如果插入失败,返回null。
element.insertAdjacentElement(position, element);
Element.insertAdjacentElement方法一共可以接受两个参数,第一个参数是一个字符串,表示插入的位置,第二个参数是将要插入的节点。第一个参数只可以取如下的值。
beforebegin:当前元素之前afterbegin:当前元素内部的第一个子节点前面beforeend:当前元素内部的最后一个子节点后面afterend:当前元素之后
注意,beforebegin和afterend这两个值,只在当前节点有父节点时才会生效。如果当前节点是由脚本创建的,没有父节点,那么插入会失败。
var p1 = document.createElement('p') var p2 = document.createElement('p') p1.insertAdjacentElement('afterend', p2) // null
上面代码中,p1没有父节点,所以插入p2到它后面就失败了。
如果插入的节点是一个文档里现有的节点,它会从原有位置删除,放置到新的位置。
2.13 Element.insertAdjacentHTML() 插入html字符串到指定位置,Element.insertAdjacentText() 插入text到指定位置
Element.insertAdjacentHTML方法用于将一个 HTML 字符串,解析生成 DOM 结构,插入相对于当前节点的指定位置。
element.insertAdjacentHTML(position, text);
该方法接受两个参数,第一个是一个表示指定位置的字符串,第二个是待解析的 HTML 字符串。第一个参数只能设置下面四个值之一。
beforebegin:当前元素之前afterbegin:当前元素内部的第一个子节点前面beforeend:当前元素内部的最后一个子节点后面afterend:当前元素之后
// HTML 代码:<div id="one">one</div> var d1 = document.getElementById('one'); d1.insertAdjacentHTML('afterend', '<div id="two">two</div>'); // 执行后的 HTML 代码: // <div id="one">one</div><div id="two">two</div>
该方法只是在现有的 DOM 结构里面插入节点,这使得它的执行速度比innerHTML方法快得多。
注意,该方法不会转义 HTML 字符串,这导致它不能用来插入用户输入的内容,否则会有安全风险。
Element.insertAdjacentText方法在相对于当前节点的指定位置,插入一个文本节点,用法与Element.insertAdjacentHTML方法完全一致。
// HTML 代码:<div id="one">one</div> var d1 = document.getElementById('one'); d1.insertAdjacentText('afterend', 'two'); // 执行后的 HTML 代码: // <div id="one">one</div>two
2.14 Element.remove() 移除元素
Element.remove方法继承自 ChildNode 接口,用于将当前元素节点从它的父节点移除。
var el = document.getElementById('mydiv'); el.remove();
上面代码将el节点从 DOM 树里面移除。
2.15 Element.focus() 将页面焦点转移到元素,Element.blur() 将焦点移除
Element.focus方法用于将当前页面的焦点,转移到指定元素上。
document.getElementById('my-span').focus();
该方法可以接受一个对象作为参数。参数对象的preventScroll属性是一个布尔值,指定是否将当前元素停留在原始位置,而不是滚动到可见区域。
function getFocus() { document.getElementById('btn').focus({preventScroll:false}); }
上面代码会让btn元素获得焦点,并滚动到可见区域。
最后,从document.activeElement属性可以得到当前获得焦点的元素。
Element.blur方法用于将焦点从当前元素移除。
2.16 Element.click() 模拟点击事件
Element.click方法用于在当前元素上模拟一次鼠标点击,相当于触发了click事件。
七、元素属性的操作
HTML 元素包括标签名和若干个键值对,这个键值对就称为“属性”(attribute)。
<a id="test" href="http://www.example.com"> 链接 </a>
上面代码中,a元素包括两个属性:id属性和href属性。
属性本身是一个对象(Attr对象),但是实际上,这个对象极少使用。一般都是通过元素节点对象(HTMlElement对象)来操作属性。本章介绍如何操作这些属性。
1、Element.attributes 属性 (返回包含标签所有属性的类数组动态对象)
元素对象有一个attributes属性,返回一个类似数组的动态对象,成员是该元素标签的所有属性节点对象,属性的实时变化都会反映在这个节点对象上。其他类型的节点对象,虽然也有attributes属性,但返回的都是null,因此可以把这个属性视为元素对象独有的。
单个属性可以通过序号引用,也可以通过属性名引用。
// <a id="test" href="http://www.example.com">链接</a> // document.getElementsByTagName('a')[0].attributes 返回一个 NamedNodeMap 类数组对象 NamedNodeMap {0: id, 1: href, id: id, href: href, length: 2} // 这两个访问方法都是返回一个id的属性节点对象 // NamedNodeMap[0] // NamedNodeMap.id { baseURI: "file:///C:/Users/dell/Desktop/test.html" childNodes: NodeList [] firstChild: null isConnected: false lastChild: null localName: "id" name: "id" // 属性是‘id’ namespaceURI: null nextSibling: null nodeName: "id" nodeType: 2 nodeValue: "test" ownerDocument: document ownerElement: a#test parentElement: null parentNode: null prefix: null previousSibling: null specified: true textContent: "test" value: "test" // id的值 }
// HTML 代码如下 // <body bgcolor="yellow" onload=""> document.body.attributes[0] document.body.attributes.bgcolor document.body.attributes['ONLOAD']
注意,上面代码的三种方法,返回的都是属性节点对象,而不是属性值。
属性节点对象有name和value属性,对应该属性的属性名和属性值,等同于nodeName属性和nodeValue属性。
// HTML代码为 // <div id="mydiv"> var n = document.getElementById('mydiv'); n.attributes[0].name // "id" n.attributes[0].nodeName // "id" n.attributes[0].value // "mydiv" n.attributes[0].nodeValue // "mydiv"
下面代码可以遍历一个元素节点的所有属性。
var para = document.getElementsByTagName('p')[0]; var result = document.getElementById('result'); if (para.hasAttributes()) { var attrs = para.attributes; var output = ''; for(var i = attrs.length - 1; i >= 0; i--) { output += attrs[i].name + '->' + attrs[i].value; } result.textContent = output; } else { result.textContent = 'No attributes to show'; }
2、元素的标准属性
HTML 元素的标准属性(即在标准中定义的属性),会自动成为元素节点对象的属性。
// <a id="aId" href="http://www.example.com" class="aClass">链接</a> var a = document.getElementById('test'); // a是节点对象,id和href自动成为a节点的属性 a.id // "aId" a.href // "http://www.example.com/" a.className // "aClass"
上面代码中,a元素标签的属性id和href,自动成为节点对象的属性。
这些属性都是可写的。
var img = document.getElementById('myImage'); img.src = 'http://www.example.com/image.jpg';
上面的写法,会立刻替换掉img对象的src属性,即会显示另外一张图片。
这种修改属性的方法,常常用于添加表单的属性。
var f = document.forms[0]; f.action = 'submit.php'; f.method = 'POST';
上面代码为表单添加提交网址和提交方法。
注意,这种用法虽然可以读写属性,但是无法删除属性,delete运算符在这里不会生效。
HTML 元素的属性名是大小写不敏感的,但是 JavaScript 对象的属性名是大小写敏感的。转换规则是,转为 JavaScript 属性名时,一律采用小写。如果属性名包括多个单词,则采用骆驼拼写法,即从第二个单词开始,每个单词的首字母采用大写,比如onClick。
有些 HTML 属性名是 JavaScript 的保留字,转为 JavaScript 属性时,必须改名。主要是以下两个。
for属性改为htmlForclass属性改为className
另外,HTML 属性值一般都是字符串,但是 JavaScript 属性会自动转换类型。比如,将字符串true转为布尔值,将onClick的值转为一个函数,将style属性的值转为一个CSSStyleDeclaration对象。因此,可以对这些属性赋予各种类型的值。
3、属性操作的标准方法
3.1 概述
元素节点提供六个方法,用来操作属性。
getAttribute()getAttributeNames()setAttribute()hasAttribute()hasAttributes()removeAttribute()
这有几点注意。
(1)适用性
这六个方法对所有属性(包括用户自定义的属性)都适用。
(2)返回值
getAttribute()只返回字符串,不会返回其他类型的值。
(3)属性名
这些方法只接受属性的标准名称,不用改写保留字,比如for和class都可以直接使用。另外,这些方法对于属性名是大小写不敏感的。
var image = document.images[0]; image.setAttribute('class', 'myImage');
上面代码中,setAttribute方法直接使用class作为属性名,不用写成className。
3.2 Element.getAttribute() 返回元素的指定属性值,字符串
Element.getAttribute方法返回当前元素节点的指定属性。如果指定属性不存在,则返回null。
// HTML 代码为 // <div id="div1" align="left"> var div = document.getElementById('div1'); div.getAttribute('align') // "left"
3.3 Element.getAttributeNames() 返回一个数组,成员是元素的所有属性的名字
Element.getAttributeNames()返回一个数组,成员是当前元素的所有属性的名字。如果当前元素没有任何属性,则返回一个空数组。使用Element.attributes属性,也可以拿到同样的结果,唯一的区别是它返回的是类似数组的对象。
var mydiv = document.getElementById('mydiv'); mydiv.getAttributeNames().forEach(function (key) { var value = mydiv.getAttribute(key); console.log(key, value); })
上面代码用于遍历某个节点的所有属性。
3.4 Element.setAttribute() 为元素设置(新增)属性
Element.setAttribute方法用于为当前元素节点新增属性。如果同名属性已存在,则相当于编辑已存在的属性。该方法没有返回值。
// HTML 代码为 // <button>Hello World</button> var b = document.querySelector('button'); b.setAttribute('name', 'myButton'); b.setAttribute('disabled', true); // 属性值总是字符串,其他类型会转成字符串,这里会转成字符串'true'
上面代码中,button元素的name属性被设成myButton,disabled属性被设成true。
这里有两个地方需要注意,首先,属性值总是字符串,其他类型的值会自动转成字符串,比如布尔值true就会变成字符串true;其次,上例的disable属性是一个布尔属性,对于<button>元素来说,这个属性不需要属性值,只要设置了就总是会生效,因此setAttribute方法里面可以将disabled属性设成任意值。
3.5 Element.hasAttribute() 是否包含指定属性
Element.hasAttribute方法返回一个布尔值,表示当前元素节点是否包含指定属性。
var d = document.getElementById('div1'); if (d.hasAttribute('align')) { d.setAttribute('align', 'center'); }
上面代码检查div节点是否含有align属性。如果有,则设置为居中对齐。
3.6 Element.hasAttributes() 是否有任何的属性
Element.hasAttributes方法返回一个布尔值,表示当前元素是否有属性,如果没有任何属性,就返回false,否则返回true。
var foo = document.getElementById('foo'); foo.hasAttributes() // true
3.7 Element.removeAttribute() 移除指定属性
Element.removeAttribute方法移除指定属性。该方法没有返回值。
// HTML 代码为 // <div id="div1" align="left" width="200px"> document.getElementById('div1').removeAttribute('align'); // 现在的HTML代码为 // <div id="div1" width="200px">
4、dataset 属性 获取data-*自定义属性
有时,需要在HTML元素上附加数据,供 JavaScript 脚本使用。一种解决方法是自定义属性。
<div id="mydiv" foo="bar">
上面代码为div元素自定义了foo属性,然后可以用getAttribute()和setAttribute()读写这个属性。
var n = document.getElementById('mydiv'); n.getAttribute('foo') // bar n.setAttribute('foo', 'baz')
这种方法虽然可以达到目的,但是会使得 HTML 元素的属性不符合标准,导致网页代码通不过校验。
更好的解决方法是,使用标准提供的data-*属性。
<div id="mydiv" data-foo="bar">
然后,使用元素节点对象的dataset属性,它指向一个对象,可以用来操作 HTML 元素标签的data-*属性。
var n = document.getElementById('mydiv'); n.dataset.foo // bar 省略data-,直接访问foo即可访问到 data-foo的值 n.dataset.foo = 'baz'
上面代码中,通过dataset.foo读写data-foo属性。
删除一个data-*属性,可以直接使用delete命令。
delete document.getElementById('myDiv').dataset.foo;
除了dataset属性,也可以用getAttribute('data-foo')、removeAttribute('data-foo')、setAttribute('data-foo')、hasAttribute('data-foo')等方法操作data-*属性。
注意,data-后面的属性名有限制,只能包含字母、数字、连词线(-)、点(.)、冒号(:)和下划线(_)。而且,属性名不应该使用A到Z的大写字母,比如不能有data-helloWorld这样的属性名,而要写成data-hello-world。
data-后面不能使用驼峰写法,可以使用烤串写法。
转成dataset的键名时,连词线后面如果跟着一个小写字母,那么连词线会被移除,该小写字母转为大写字母,其他字符不变。反过来,dataset的键名转成属性名时,所有大写字母都会被转成连词线+该字母的小写形式,其他字符不变。比如,dataset.helloWorld会转成data-hello-world。
通过dataset访问的时候,如有烤串写法属性需通过驼峰写法访问
八、Text 节点和 DocumentFragment 节点
1、Text 节点的概念
文本节点(Text)代表元素节点(Element)和属性节点(Attribute)的文本内容。如果一个节点只包含一段文本,那么它就有一个文本子节点,代表该节点的文本内容。
通常我们使用父节点的firstChild、nextSibling等属性获取文本节点,或者使用Document节点的createTextNode方法创造一个文本节点。
// 获取文本节点 var textNode = document.querySelector('p').firstChild; // 创造文本节点 var textNode = document.createTextNode('Hi'); document.querySelector('div').appendChild(textNode);
浏览器原生提供一个*Text构造函数*。它返回一个文本节点实例。它的参数就是该文本节点的文本内容。
// 空字符串 var text1 = new Text(); // 返回一个文本实例 // 非空字符串 var text2 = new Text('This is a text node'); // 参数为该文本节点的文本内容
注意,由于空格也是一个字符,所以哪怕只有一个空格,也会形成文本节点。比如,包含一个空格,它的子节点就是一个文本节点。
文本节点除了继承Node接口,还继承了CharacterData接口。Node接口的属性和方法请参考《Node 接口》一章,这里不再重复介绍了,以下的属性和方法大部分来自CharacterData接口。
2、Text 节点的属性
2.1 data 设置或读取文本节点内容
data属性等同于nodeValue属性,用来设置或读取文本节点的内容。
// 读取文本内容 document.querySelector('p').firstChild.data // 等同于 document.querySelector('p').firstChild.nodeValue // 设置文本内容 document.querySelector('p').firstChild.data = 'Hello World';
2.2 wholeText 当前文本和毗邻文本节,作为整体返回
wholeText属性将当前文本节点与毗邻的文本节点,作为一个整体返回。大多数情况下,wholeText属性的返回值,与data属性和textContent属性相同。但是,某些特殊情况会有差异。
举例来说,HTML 代码如下。
<p id="para">A <em>B</em> C</p>
这时,文本节点的wholeText属性和data属性,返回值相同。
var el = document.getElementById('para'); el.firstChild.wholeText // "A " el.firstChild.data // "A "
但是,一旦移除<em>节点,wholeText属性与data属性就会有差异,因为这时其实<p>节点下面包含了两个毗邻的文本节点。
el.removeChild(para.childNodes[1]); el.firstChild.wholeText // "A C" el.firstChild.data // "A "
2.3 length 文本长度
length属性返回当前文本节点的文本长度。
(new Text('Hello')).length // 5
2.4 nextElementSibling 后一个兄弟元素节点,previousElementSibling 前一个兄弟元素节点
nextElementSibling属性返回紧跟在当前文本节点后面的那个同级元素节点。如果取不到元素节点,则返回null。
// HTML 为 // <div>Hello <em>World</em></div> var tn = document.querySelector('div').firstChild; tn.nextElementSibling // <em>World</em>
previousElementSibling属性返回当前文本节点前面最近的同级元素节点。如果取不到元素节点,则返回null:。
3、Text 节点的方法
3.1 appendData(),deleteData(),insertData(),replaceData(),substringData()
以下5个方法都是编辑Text节点文本内容的方法。
appendData():在Text节点尾部追加字符串。deleteData():删除Text节点内部的子字符串,第一个参数为子字符串开始位置,第二个参数为子字符串长度。insertData():在Text节点插入字符串,第一个参数为插入位置,第二个参数为插入的子字符串。replaceData():用于替换文本,第一个参数为替换开始位置,第二个参数为需要被替换掉的长度,第三个参数为新加入的字符串。substringData():用于获取子字符串,第一个参数为子字符串在Text节点中的开始位置,第二个参数为子字符串长度。
// HTML 代码为 // <p>Hello World</p> var pElementText = document.querySelector('p').firstChild; pElementText.appendData('!'); // 页面显示 Hello World! pElementText.deleteData(7, 5); // 页面显示 Hello W pElementText.insertData(7, 'Hello '); // 页面显示 Hello WHello pElementText.replaceData(7, 5, 'World'); // 页面显示 Hello WWorld pElementText.substringData(7, 10); // 页面显示不变,返回"World "
3.2 remove() 移除Text节点
remove方法用于移除当前Text节点。
// HTML 代码为 // <p>Hello World</p> document.querySelector('p').firstChild.remove() // 现在 HTML 代码为 // <p></p>
3.3 splitText() 分割Text节点变为两个
splitText方法将Text节点一分为二,变成两个毗邻的Text节点。它的参数就是分割位置(从零开始),分割到该位置的字符前结束。如果分割位置不存在,将报错。
分割后,该方法返回分割位置后方的字符串,而原Text节点变成只包含分割位置前方的字符串。
// html 代码为 <p id="p">foobar</p> var p = document.getElementById('p'); var textnode = p.firstChild; var newText = textnode.splitText(3); newText // "bar" textnode // "foo"
父元素节点的normalize方法可以将毗邻的两个Text节点合并。
接上面的例子,文本节点的splitText方法将一个Text节点分割成两个,父元素的normalize方法可以实现逆操作,将它们合并。
p.childNodes.length // 2 // 将毗邻的两个 Text 节点合并 p.normalize(); p.childNodes.length // 1
4、DocumentFragment 文档片段节点
DocumentFragment节点代表一个文档的片段,本身就是一个完整的 DOM 树形结构。它没有父节点,parentNode返回null,但是可以插入任意数量的子节点。它不属于当前文档,操作DocumentFragment节点,要比直接操作 DOM 树快得多。
它一般用于构建一个 DOM 结构,然后插入当前文档。document.createDocumentFragment方法,以及浏览器原生的DocumentFragment构造函数,可以创建一个空的DocumentFragment节点。然后再使用其他 DOM 方法,向其添加子节点。
var docFrag = document.createDocumentFragment(); // 等同于 var docFrag = new DocumentFragment(); var li = document.createElement('li'); li.textContent = 'Hello World'; docFrag.appendChild(li); document.querySelector('ul').appendChild(docFrag);
上面代码创建了一个DocumentFragment节点,然后将一个li节点添加在它里面,最后将DocumentFragment节点移动到原文档。
注意,DocumentFragment节点本身不能被插入当前文档。当它作为appendChild()、insertBefore()、replaceChild()等方法的参数时,是它的所有子节点插入当前文档,而不是它自身。一旦DocumentFragment节点被添加进当前文档,它自身就变成了空节点(textContent属性为空字符串),可以被再次使用。如果想要保存DocumentFragment节点的内容,可以使用cloneNode方法。
document .querySelector('ul') .appendChild(docFrag.cloneNode(true));
上面这样添加DocumentFragment节点进入当前文档,不会清空DocumentFragment节点。
下面是一个例子,使用DocumentFragment反转一个指定节点的所有子节点的顺序。
function reverse(n) { var f = document.createDocumentFragment(); while(n.lastChild) f.appendChild(n.lastChild); n.appendChild(f); }
DocumentFragment节点对象没有自己的属性和方法,全部继承自Node节点和ParentNode接口。也就是说,DocumentFragment节点比Node节点多出以下四个属性。
children:返回一个动态的HTMLCollection集合对象,包括当前DocumentFragment对象的所有子元素节点。firstElementChild:返回当前DocumentFragment对象的第一个子元素节点,如果没有则返回null。lastElementChild:返回当前DocumentFragment对象的最后一个子元素节点,如果没有则返回null。childElementCount:返回当前DocumentFragment对象的所有子元素数量。
八、CSS 操作
CSS 与 JavaScript 是两个有着明确分工的领域,前者负责页面的视觉效果,后者负责与用户的行为互动。但是,它们毕竟同属网页开发的前端,因此不可避免有着交叉和互相配合。本章介绍如何通过 JavaScript 操作 CSS。
1、HTML 元素的 style 属性
操作 CSS 样式最简单的方法,就是使用网页元素节点的getAttribute()方法、setAttribute()方法和removeAttribute()方法,直接读写或删除网页元素的style属性。
div.setAttribute( 'style', 'background-color:red;' + 'border:1px solid black;' );
上面的代码相当于下面的 HTML 代码。
<div style="background-color:red; border:1px solid black;" />
style不仅可以使用字符串读写,它本身还是一个对象,部署了 CSSStyleDeclaration 接口(详见下面的介绍),可以直接读写个别属性。
e.style.fontSize = '18px'; e.style.color = 'black';
2、CSSStyleDeclaration 接口
2.1 简介 - 用来操作元素的样式,可读写
CSSStyleDeclaration 接口用来操作元素的样式。三个地方部署了这个接口。
- 元素节点的
style属性(Element.style) CSSStyle实例的style属性window.getComputedStyle()的返回值
CSSStyleDeclaration 接口可以直接读写 CSS 的样式属性,不过,连词号需要变成骆驼拼写法。
var divStyle = document.querySelector('div').style; // 可读写 divStyle.backgroundColor = 'red'; divStyle.border = '1px solid black'; divStyle.width = '100px'; divStyle.height = '100px'; divStyle.fontSize = '10em'; // 注意,读的样式只是行内样式,不能读取样式表的样式 divStyle.backgroundColor // red divStyle.border // 1px solid black divStyle.height // 100px divStyle.width // 100px
上面代码中,style属性的值是一个 CSSStyleDeclaration 实例。这个对象所包含的属性与 CSS 规则一一对应,但是名字需要改写,比如background-color写成backgroundColor。改写的规则是将横杠从 CSS 属性名中去除,然后将横杠后的第一个字母大写。如果 CSS 属性名是 JavaScript 保留字,则规则名之前需要加上字符串css,比如*float写成cssFloat*。
改写规则:烤串变驼峰,保留字前加css
注意,该对象的属性值都是字符串,设置时必须包括单位,但是不含规则结尾的分号。比如,divStyle.width不能写为100,而要写为100px。
另外,Element.style返回的只是行内样式,并不是该元素的全部样式。通过样式表设置的样式,或者从父元素继承的样式,无法通过这个属性得到。元素的全部样式要通过window.getComputedStyle()得到。
2.2 CSSStyleDeclaration 实例属性
(1)CSSStyleDeclaration.cssText 用来读写所有行内样式
CSSStyleDeclaration.cssText属性用来读写当前规则的所有样式声明文本。
var divStyle = document.querySelector('div').style; divStyle.cssText = 'background-color: red;' // 会覆盖原有的整个行内样式 + 'border: 1px solid black;' + 'height: 100px;' + 'width: 100px;';
注意,cssText的属性值不用改写 CSS 属性名。
删除一个元素的所有行内样式,最简便的方法就是设置cssText为空字符串。
divStyle.cssText = '';
(2)CSSStyleDeclaration.length 行内样式的长度
CSSStyleDeclaration.length属性返回一个整数值,表示当前规则包含多少条样式声明。
// HTML 代码如下 // <div id="myDiv" // style="height: 1px;width: 100%;background-color: #CA1;" // ></div> var myDiv = document.getElementById('myDiv'); var divStyle = myDiv.style; divStyle.length // 3
上面代码中,myDiv元素的行内样式共包含3条样式规则。
(3)CSSStyleDeclaration.parentRule 返回当前规则所属的那个样式块
CSSStyleDeclaration.parentRule属性返回当前规则所属的那个样式块(CSSRule 实例)。如果不存在所属的样式块,该属性返回null。
该属性只读,且只在使用 CSSRule 接口时有意义。
var declaration = document.styleSheets[0].rules[0].style; declaration.parentRule === document.styleSheets[0].rules[0] // true
2.3 CSSStyleDeclaration 实例方法
(1)CSSStyleDeclaration.getPropertyPriority() 指定样式有没有设置important优先级
CSSStyleDeclaration.getPropertyPriority方法接受 CSS 样式的属性名作为参数,返回一个字符串,表示有没有设置important优先级。如果有就返回important,否则返回空字符串。
// HTML 代码为 // <div id="myDiv" style="margin: 10px!important; color: red;"/> var style = document.getElementById('myDiv').style; style.margin // "10px" style.getPropertyPriority('margin') // "important" style.getPropertyPriority('color') // ""
上面代码中,margin属性有important优先级,color属性没有。
(2)CSSStyleDeclaration.getPropertyValue() 返回指定样式的值
CSSStyleDeclaration.getPropertyValue方法接受 CSS 样式属性名作为参数,返回一个字符串,表示该属性的属性值。
// HTML 代码为 // <div id="myDiv" style="margin: 10px!important; color: red;"/> var style = document.getElementById('myDiv').style; style.margin // "10px" style.getPropertyValue("margin") // "10px"
(3)CSSStyleDeclaration.item() 返回指定位置的CSS属性名
CSSStyleDeclaration.item方法接受一个整数值作为参数,返回该位置的 CSS 属性名。
// HTML 代码为 // <div id="myDiv" style="color: red; background-color: white;"/> var style = document.getElementById('myDiv').style; style.item(0) // "color" style.item(1) // "background-color"
上面代码中,0号位置的 CSS 属性名是color,1号位置的 CSS 属性名是background-color。
如果没有提供参数,这个方法会报错。如果参数值超过实际的属性数目,这个方法返回一个空字符值。
(4)CSSStyleDeclaration.removeProperty() 移除指定CSS属性,并返回原来的值
CSSStyleDeclaration.removeProperty方法接受一个属性名作为参数,在 CSS 规则里面移除这个属性,返回这个属性原来的值。
// HTML 代码为 // <div id="myDiv" style="color: red; background-color: white;"> // 111 // </div> var style = document.getElementById('myDiv').style; style.removeProperty('color') // 'red' // HTML 代码变为 // <div id="myDiv" style="background-color: white;">
上面代码中,删除color属性以后,字体颜色从红色变成默认颜色。
(5)CSSStyleDeclaration.setProperty() 设置新的 CSS 属性
CSSStyleDeclaration.setProperty方法用来设置新的 CSS 属性。该方法没有返回值。
该方法可以接受三个参数。
- 第一个参数:属性名,该参数是必需的。
- 第二个参数:属性值,该参数可选。如果省略,则参数值默认为空字符串。
- 第三个参数:优先级,该参数可选。如果设置,唯一的合法值是
important,表示 CSS 规则里面的!important。
// HTML 代码为 // <div id="myDiv" style="color: red; background-color: white;"> // 111 // </div> var style = document.getElementById('myDiv').style; style.setProperty('border', '1px solid blue');
上面代码执行后,myDiv元素就会出现蓝色的边框。
3、CSS 模块的侦测(判断浏览器是否支持某个样式)
CSS 的规格发展太快,新的模块层出不穷。不同浏览器的不同版本,对 CSS 模块的支持情况都不一样。有时候,需要知道当前浏览器是否支持某个模块,这就叫做“CSS模块的侦测”。
判断浏览器是否支持某个样式
一个比较普遍适用的方法是,判断元素的style对象的某个属性值是否为字符串。
typeof element.style.animationName === 'string'; typeof element.style.transform === 'string';
如果该 CSS 属性确实存在,会返回一个字符串。即使该属性实际上并未设置,也会返回一个空字符串。如果该属性不存在,则会返回undefined。
document.body.style['maxWidth'] // "" document.body.style['maximumWidth'] // undefined
上面代码说明,这个浏览器支持max-width属性,但是不支持maximum-width属性。
注意,不管 CSS 属性名的写法带不带连词线,style属性上都能反映出该属性是否存在。
document.body.style['backgroundColor'] // "" document.body.style['background-color'] // ""
另外,使用的时候,需要把不同浏览器的 CSS 前缀也考虑进去。
var content = document.getElementById('content'); typeof content.style['webkitAnimation'] === 'string'
这种侦测方法可以写成一个函数。
function isPropertySupported(property) { if (property in document.body.style) return true; var prefixes = ['Moz', 'Webkit', 'O', 'ms', 'Khtml']; var prefProperty = property.charAt(0).toUpperCase() + property.substr(1); for(var i = 0; i < prefixes.length; i++){ if((prefixes[i] + prefProperty) in document.body.style) return true; } return false; } isPropertySupported('background-clip') // true
4、原生CSS 对象
浏览器原生提供 CSS 对象,为 JavaScript 操作 CSS 提供一些工具方法。
这个对象目前有两个静态方法。
4.1 CSS.escape() 转义CSS选择器里的特殊字符
CSS.escape方法用于转义 CSS 选择器里面的特殊字符。
<div id="foo#bar">
上面代码中,该元素的id属性包含一个#号,该字符在 CSS 选择器里面有特殊含义。不能直接写成document.querySelector('#foo#bar'),只能写成document.querySelector('#foo\\#bar')。这里必须使用双斜杠的原因是,单引号字符串本身会转义一次斜杠。
CSS.escape方法就用来转义那些特殊字符。
document.querySelector('#' + CSS.escape('foo#bar')) //CSS.escape('foo#bar')转义成foo\#bar
4.2 CSS.supports() 当前环境是否支持某一句 CSS 规则
CSS.supports方法返回一个布尔值,表示当前环境是否支持某一句 CSS 规则。
它的参数有两种写法,一种是第一个参数是属性名,第二个参数是属性值;另一种是整个参数就是一行完整的 CSS 语句。
// 第一种写法 CSS.supports('transform-origin', '5px') // true // 第二种写法 CSS.supports('display: table-cell') // true
注意,第二种写法的参数结尾不能带有分号,否则结果不准确。
CSS.supports('display: table-cell;') // false
5、window.getComputedStyle() 返回元素最终样式
行内样式(inline style)具有最高的优先级,改变行内样式,通常会立即反映出来。但是,网页元素最终的样式是综合各种规则计算出来的。因此,如果想得到元素实际的样式,只读取行内样式是不够的,需要得到浏览器最终计算出来的样式规则。
window.getComputedStyle方法,就用来返回浏览器计算后得到的最终样式规则。它接受一个元素节点对象作为参数,返回一个 CSSStyleDeclaration 实例,包含了指定节点的最终样式信息。所谓“最终样式信息”,指的是各种 CSS 规则叠加后的结果。
var div = document.querySelector('div'); var styleObj = window.getComputedStyle(div); styleObj.backgroundColor
上面代码中,得到的背景色就是div元素真正的背景色。
注意,CSSStyleDeclaration 实例是一个活的对象,任何对于样式的修改,会实时反映到这个实例上面。另外,这个实例是只读的。
getComputedStyle方法还可以接受第二个参数,表示当前元素的伪元素(比如:before、:after、:first-line、:first-letter等)。
var result = window.getComputedStyle(div, ':before');
下面的例子是如何获取元素的高度。
var elem = document.getElementById('elem-container'); var styleObj = window.getComputedStyle(elem, null) var height = styleObj.height; // 得到元素最终渲染出来的高度,最可靠 // 等同于 var height = styleObj['height']; var height = styleObj.getPropertyValue('height');
上面代码得到的height属性,是浏览器最终渲染出来的高度,比其他方法得到的高度更可靠。由于styleObj是 CSSStyleDeclaration 实例,所以可以使用各种 CSSStyleDeclaration 的实例属性和方法。
有几点需要注意。
- CSSStyleDeclaration 实例返回的 CSS 值都是绝对单位。比如,长度都是像素单位(返回值包括
px后缀),颜色是rgb(#, #, #)或rgba(#, #, #, #)格式。 - CSS 规则的简写形式无效。比如,想读取
margin属性的值,不能直接读,只能读marginLeft、marginTop等属性;再比如,font属性也是不能直接读的,只能读font-size等单个属性。 - 如果读取 CSS 原始的属性名,要用方括号运算符,比如
styleObj['z-index'];如果读取骆驼拼写法的 CSS 属性名,可以直接读取styleObj.zIndex。 - 该方法返回的 CSSStyleDeclaration 实例的
cssText属性无效,返回undefined。
6、CSS 伪元素样式的获取
CSS 伪元素是通过 CSS 向 DOM 添加的元素,主要是通过:before和:after选择器生成,然后用content属性指定伪元素的内容。
下面是一段 HTML 代码。
<div id="test">Test content</div>
CSS 添加伪元素:before的写法如下。
#test:before { content: 'Before '; color: #FF0; }
节点元素的style对象无法读写伪元素的样式,这时就要用到window.getComputedStyle()。JavaScript 获取伪元素,可以使用下面的方法。
var test = document.querySelector('#test'); var result = window.getComputedStyle(test, ':before').content; var color = window.getComputedStyle(test, ':before').color;
此外,也可以使用 CSSStyleDeclaration 实例的getPropertyValue方法,获取伪元素的属性。
var result = window.getComputedStyle(test, ':before') .getPropertyValue('content'); var color = window.getComputedStyle(test, ':before') .getPropertyValue('color');
7、StyleSheet 接口
7.1 概述
StyleSheet接口代表网页的一张样式表,包括<link>元素加载的样式表和<style>元素内嵌的样式表。
document对象的styleSheets属性,可以返回当前页面的所有StyleSheet实例(即所有样式表)。它是一个类似数组的对象。
var sheets = document.styleSheets; var sheet = document.styleSheets[0]; sheet instanceof StyleSheet // true
如果是<style>元素嵌入的样式表,还有另一种获取StyleSheet实例的方法,就是这个节点元素的sheet属性。
// HTML 代码为 <style id="myStyle"></style> var myStyleSheet = document.getElementById('myStyle').sheet;// 另一种获取styleSheet方式 myStyleSheet instanceof StyleSheet // true
严格地说,StyleSheet接口不仅包括网页样式表,还包括 XML 文档的样式表。所以,**它有一个子类CSSStyleSheet表示网页的 CSS 样式表。我们在网页里面拿到的样式表实例,实际上是CSSStyleSheet的实例。这个子接口继承了StyleSheet的所有属性和方法**,并且定义了几个自己的属性,下面把这两个接口放在一起介绍。
7.2 实例属性
StyleSheet实例有以下属性。
(1)StyleSheet.disabled 样式表是否处于禁用状态,可读写
StyleSheet.disabled返回一个布尔值,表示该样式表是否处于禁用状态。手动设置disabled属性为true,等同于在<link>元素里面,将这张样式表设为alternate stylesheet,即该样式表将不会生效。
注意,disabled属性只能在 JavaScript 脚本中设置,不能在 HTML 语句中设置。
<link rel="alternate stylesheet" href="style.css"> <!--设置了alternate stylesheet表示禁用此样式表--> <style> body{font-size: 16px;} </style> <script> console.log(document.styleSheets) /* StyleSheetList {} 0: CSSStyleSheet {ownerRule: null, type: "text/css", href: "file:///C:/Users/dell/Desktop/css.css", ownerNode: link, parentStyleSheet: null, …} 1: CSSStyleSheet {ownerRule: null, cssRules: CSSRuleList, rules: CSSRuleList, type: "text/css", href: null, …} length: 2 */ console.log(document.styleSheets[0].disabled) // false 未禁用?不能判断样式表设置了alternate? document.styleSheets[0].disabled = true console.log(document.styleSheets[0].disabled) // true 已禁用 </script>
(2)Stylesheet.href 返回样式表的网址
Stylesheet.href返回样式表的网址。对于内嵌样式表,该属性返回null。该属性只读。
document.styleSheets[0].href
(3)StyleSheet.media 返回一个媒介类数组
StyleSheet.media属性**返回一个类似数组的对象(MediaList实例),成员是表示适用媒介的字符串。**表示当前样式表是用于屏幕(screen),还是用于打印(print)或手持设备(handheld),或各种媒介都适用(all)。该属性只读,默认值是screen。
document.styleSheets[0].media.mediaText // "all"
MediaList实例的appendMedium方法,用于增加媒介;deleteMedium方法用于删除媒介。
document.styleSheets[0].media.appendMedium('handheld'); document.styleSheets[0].media.deleteMedium('print');
(4)StyleSheet.title 返回样式表的title属性
StyleSheet.title属性返回样式表的title属性。
(5)StyleSheet.type 返回样式表的type属性
StyleSheet.type属性返回样式表的type属性,通常是text/css。
document.styleSheets[0].type // "text/css"
(6)StyleSheet.parentStyleSheet 返回父样式表,当前样式表是通过@import加载才有值,否则null
CSS 的@import命令允许在样式表中加载其他样式表。StyleSheet.parentStyleSheet属性返回包含了当前样式表的那张样式表。如果当前样式表是顶层样式表,则该属性返回null。
if (stylesheet.parentStyleSheet) { sheet = stylesheet.parentStyleSheet; } else { sheet = stylesheet; }
(7)StyleSheet.ownerNode 返回样式表所在的DOM节点
StyleSheet.ownerNode属性返回StyleSheet对象所在的 DOM 节点,通常是<link>或<style>。对于那些由其他样式表引用的样式表,该属性为null。
// HTML代码为 // <link rel="StyleSheet" href="example.css" type="text/css" /> document.styleSheets[0].ownerNode // [object HTMLLinkElement]
(8)CSSStyleSheet.cssRules 返回一个包含所有css规则的类数组
CSSStyleSheet.cssRules属性**指向一个类似数组的对象(CSSRuleList实例),里面每一个成员就是当前样式表的一条 CSS 规则。**使用该规则的cssText属性,可以得到 CSS 规则对应的字符串。
var sheet = document.querySelector('#styleElement').sheet; sheet.cssRules[0].cssText // "body { background-color: red; margin: 20px; }" sheet.cssRules[1].cssText // "p { line-height: 1.4em; color: blue; }"
每条 CSS 规则还有一个style属性,指向一个对象,用来读写具体的 CSS 命令。
cssStyleSheet.cssRules[0].style.color = 'red'; cssStyleSheet.cssRules[1].style.color = 'purple';
(9)CSSStyleSheet.ownerRule 规则所有者
有些样式表是通过@import规则输入的,它的ownerRule属性会返回一个CSSRule实例,代表那行@import规则。如果当前样式表不是通过@import引入的,ownerRule属性返回null。
7.3 实例方法
(1)CSSStyleSheet.insertRule() 插入css规则
CSSStyleSheet.insertRule方法用于在当前样式表的插入一个新的 CSS 规则。
var sheet = document.querySelector('#styleElement').sheet; sheet.insertRule('#block { color: white }', 0); sheet.insertRule('p { color: red }', 1);
该方法可以接受两个参数,第一个参数是表示 CSS 规则的字符串,这里只能有一条规则,否则会报错。第二个参数是该规则在样式表的插入位置(从0开始),该参数可选,默认为0(即默认插在样式表的头部)。注意,如果插入位置大于现有规则的数目,会报错。
该方法的返回值是新插入规则的位置序号。
注意,浏览器对脚本在样式表里面插入规则有很多限制。所以,这个方法最好放在try...catch里使用。
(2)CSSStyleSheet.deleteRule() 移除css规则
CSSStyleSheet.deleteRule方法用来在样式表里面移除一条规则,它的参数是该条规则在cssRules对象中的位置。该方法没有返回值。
document.styleSheets[0].deleteRule(1);
8、实例:添加样式表
网页添加样式表有两种方式。一种是添加一张内置样式表,即在文档中添加一个<style>节点。
// 写法一 var style = document.createElement('style'); style.setAttribute('media', 'screen'); style.innerHTML = 'body{color:red}'; document.head.appendChild(style); // 写法二 var style = (function () { var style = document.createElement('style'); document.head.appendChild(style); return style; })(); style.sheet.insertRule('.foo{color:red;}', 0); // 样式表插入规则
另一种是添加外部样式表,即在文档中添加一个<link>节点,然后将href属性指向外部样式表的 URL。
var linkElm = document.createElement('link'); linkElm.setAttribute('rel', 'stylesheet'); linkElm.setAttribute('type', 'text/css'); linkElm.setAttribute('href', 'reset-min.css'); document.head.appendChild(linkElm);
9、CSSRuleList 接口
CSSRuleList 接口是一个类似数组的对象,表示一组 CSS 规则,成员都是 CSSRule 实例。
获取 CSSRuleList 实例,一般是通过StyleSheet.cssRules属性。
// HTML 代码如下 // <style id="myStyle"> // h1 { color: red; } // p { color: blue; } // </style> var myStyleSheet = document.getElementById('myStyle').sheet; var crl = myStyleSheet.cssRules; crl instanceof CSSRuleList // true
CSSRuleList 实例里面,每一条规则(CSSRule 实例)可以通过rules.item(index)或者rules[index]拿到。CSS 规则的条数通过rules.length拿到。还是用上面的例子。
crl[0] instanceof CSSRule // true crl.length // 2
注意,添加规则和删除规则不能在 CSSRuleList 实例操作,而要在它的父元素 StyleSheet 实例上,通过StyleSheet.insertRule()和StyleSheet.deleteRule()操作。
10、CSSRule 接口
10.1 概述
一条 CSS 规则包括两个部分:CSS 选择器和样式声明。下面就是一条典型的 CSS 规则。
.myClass { color: red; background-color: yellow; }
JavaScript 通过 CSSRule 接口操作 CSS 规则。一般通过 CSSRuleList 接口(StyleSheet.cssRules)获取 CSSRule 实例。
// HTML 代码如下 // <style id="myStyle"> // .myClass { // color: red; // background-color: yellow; // } // </style> var myStyleSheet = document.getElementById('myStyle').sheet; var ruleList = myStyleSheet.cssRules; var rule = ruleList[0]; rule instanceof CSSRule // true
10.2 CSSRule 实例的属性
(1)CSSRule.cssText
CSSRule.cssText属性返回当前规则的文本,还是使用上面的例子。
rule.cssText // ".myClass { color: red; background-color: yellow; }"
如果规则是加载(@import)其他样式表,cssText属性返回@import 'url'。
(2)CSSRule.parentStyleSheet
CSSRule.parentStyleSheet属性返回当前规则所在的样式表对象(StyleSheet 实例),还是使用上面的例子。
rule.parentStyleSheet === myStyleSheet // true
(3)CSSRule.parentRule
CSSRule.parentRule属性返回包含当前规则的父规则,如果不存在父规则(即当前规则是顶层规则),则返回null。
父规则最常见的情况是,当前规则包含在@media规则代码块之中。
// HTML 代码如下 // <style id="myStyle"> // @supports (display: flex) { // @media screen and (min-width: 900px) { // article { // display: flex; // } // } // } // </style> var myStyleSheet = document.getElementById('myStyle').sheet; var ruleList = myStyleSheet.cssRules; var rule0 = ruleList[0]; rule0.cssText // "@supports (display: flex) { // @media screen and (min-width: 900px) { // article { display: flex; } // } // }" // 由于这条规则内嵌其他规则, // 所以它有 cssRules 属性,且该属性是 CSSRuleList 实例 rule0.cssRules instanceof CSSRuleList // true var rule1 = rule0.cssRules[0]; rule1.cssText // "@media screen and (min-width: 900px) { // article { display: flex; } // }" var rule2 = rule1.cssRules[0]; rule2.cssText // "article { display: flex; }" rule1.parentRule === rule0 // true rule2.parentRule === rule1 // true
(4)CSSRule.type
CSSRule.type属性返回一个整数值,表示当前规则的类型。
最常见的类型有以下几种。
- 1:普通样式规则(CSSStyleRule 实例)
- 3:
@import规则 - 4:
@media规则(CSSMediaRule 实例) - 5:
@font-face规则
10.3 CSSStyleRule 接口
如果一条 CSS 规则是普通的样式规则(不含特殊的 CSS 命令),那么除了 CSSRule 接口,它还部署了 CSSStyleRule 接口。
CSSStyleRule 接口有以下两个属性。
(1)CSSStyleRule.selectorText
CSSStyleRule.selectorText属性返回当前规则的选择器。
var stylesheet = document.styleSheets[0]; stylesheet.cssRules[0].selectorText // ".myClass"
注意,这个属性是可写的。
(2)CSSStyleRule.style
CSSStyleRule.style属性返回一个对象(CSSStyleDeclaration 实例),代表当前规则的样式声明,也就是选择器后面的大括号里面的部分。
// HTML 代码为 // <style id="myStyle"> // p { color: red; } // </style> var styleSheet = document.getElementById('myStyle').sheet; styleSheet.cssRules[0].style instanceof CSSStyleDeclaration // true
CSSStyleDeclaration 实例的cssText属性,可以返回所有样式声明,格式为字符串。
styleSheet.cssRules[0].style.cssText // "color: red;" styleSheet.cssRules[0].selectorText // "p"
10.4 CSSMediaRule 接口
如果一条 CSS 规则是@media代码块,那么它除了 CSSRule 接口,还部署了 CSSMediaRule 接口。
该接口主要提供media属性和conditionText属性。前者返回代表@media规则的一个对象(MediaList 实例),后者返回@media规则的生效条件。
// HTML 代码如下 // <style id="myStyle"> // @media screen and (min-width: 900px) { // article { display: flex; } // } // </style> var styleSheet = document.getElementById('myStyle').sheet; styleSheet.cssRules[0] instanceof CSSMediaRule // true styleSheet.cssRules[0].media // { // 0: "screen and (min-width: 900px)", // appendMedium: function, // deleteMedium: function, // item: function, // length: 1, // mediaText: "screen and (min-width: 900px)" // } styleSheet.cssRules[0].conditionText // "screen and (min-width: 900px)"
11、window.matchMedia()
11.1 基本用法
window.matchMedia方法用来将 CSS 的MediaQuery条件语句,转换成一个 MediaQueryList 实例。
var mdl = window.matchMedia('(min-width: 400px)'); mdl instanceof MediaQueryList // true
上面代码中,变量mdl就是 mediaQueryList 的实例。
注意,如果参数不是有效的MediaQuery条件语句,window.matchMedia不会报错,依然返回一个 MediaQueryList 实例。
window.matchMedia('bad string') instanceof MediaQueryList // true
11.2 MediaQueryList 接口的实例属性
MediaQueryList 实例有三个属性。
(1)MediaQueryList.media
MediaQueryList.media属性返回一个字符串,表示对应的 MediaQuery 条件语句。
var mql = window.matchMedia('(min-width: 400px)'); mql.media // "(min-width: 400px)"
(2)MediaQueryList.matches
MediaQueryList.matches属性返回一个布尔值,表示当前页面是否符合指定的 MediaQuery 条件语句。
if (window.matchMedia('(min-width: 400px)').matches) { /* 当前视口不小于 400 像素 */ } else { /* 当前视口小于 400 像素 */ }
下面的例子根据mediaQuery是否匹配当前环境,加载相应的 CSS 样式表。
var result = window.matchMedia("(max-width: 700px)"); if (result.matches){ var linkElm = document.createElement('link'); linkElm.setAttribute('rel', 'stylesheet'); linkElm.setAttribute('type', 'text/css'); linkElm.setAttribute('href', 'small.css'); document.head.appendChild(linkElm); }
(3)MediaQueryList.onchange
如果 MediaQuery 条件语句的适配环境发生变化,会触发change事件。MediaQueryList.onchange属性用来指定change事件的监听函数。该函数的参数是change事件对象(MediaQueryListEvent 实例),该对象与 MediaQueryList 实例类似,也有media和matches属性。
var mql = window.matchMedia('(max-width: 600px)'); mql.onchange = function(e) { if (e.matches) { /* 视口不超过 600 像素 */ } else { /* 视口超过 600 像素 */ } }
上面代码中,change事件发生后,存在两种可能。一种是显示宽度从700像素以上变为以下,另一种是从700像素以下变为以上,所以在监听函数内部要判断一下当前是哪一种情况。
11.3 MediaQueryList 接口的实例方法
MediaQueryList 实例有两个方法MediaQueryList.addListener()和MediaQueryList.removeListener(),用来为change事件添加或撤销监听函数。
var mql = window.matchMedia('(max-width: 600px)'); // 指定监听函数 mql.addListener(mqCallback); // 撤销监听函数 mql.removeListener(mqCallback); function mqCallback(e) { if (e.matches) { /* 视口不超过 600 像素 */ } else { /* 视口超过 600 像素 */ } }
注意,MediaQueryList.removeListener()方法不能撤销MediaQueryList.onchange属性指定的监听函数。
12、本章小结
本章当中的实例(接口)有:
CSSStyleDeclaration接口(css样式声明): 用来操作元素的样式
StyleSheet接口(样式表): 代表网页的一张样式表,包括<link>元素加载的样式表和<style>元素内嵌的样式表。
CSSRuleList接口(css规则列表): 一个类似数组的对象,表示一组 CSS 规则,成员都是 CSSRule 实例。
CSSRule接口(css规则): JavaScript 通过 CSSRule 接口操作 CSS 规则。一般通过 CSSRuleList 接口(StyleSheet.cssRules)获取 CSSRule 实例。
CSSStyleRule接口: 如果一条 CSS 规则是普通的样式规则(不含特殊的 CSS 命令),那么除了 CSSRule 接口,它还部署了 CSSStyleRule 接口。
CSSMediaRule 接口: 如果一条 CSS 规则是@media代码块,那么它除了 CSSRule 接口,还部署了 CSSMediaRule 接口。
九、Mutation Observer API 监视DOM变动
1、概述
Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。
概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。
这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果文档中连续插入1000个<p>元素,就会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。
Mutation Observer 有以下特点。
- 它等待所有脚本任务完成后,才会运行(即异步触发方式)。
- 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。
- 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。
2、MutationObserver 构造函数 (使用方式)
使用时,首先使用MutationObserver构造函数,新建一个观察器实例,同时指定这个实例的回调函数。
var observer = new MutationObserver(callback);
上面代码中的回调函数,会在每次 DOM 变动后调用。该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例,下面是一个例子。
var observer = new MutationObserver(function (mutations, observer) { mutations.forEach(function(mutation) { console.log(mutation); }); });
3、MutationObserver 的实例方法
3.1 observe()
observe方法用来启动监听,它接受两个参数。
- 第一个参数:所要观察的 DOM 节点
- 第二个参数:一个配置对象,指定所要观察的特定变动
var article = document.querySelector('article'); var options = { // 配置对象,用于observer参数二,配置指定观察的特定变动 'childList': true, // 子节点变动 'attributes':true, // 属性变动 'characterData': true // 节点内容或节点文本的变动 } ; observer.observe(article, options);
上面代码中,observe方法接受两个参数,第一个是所要观察的DOM元素是article,第二个是所要观察的变动类型(子节点变动和属性变动)。
观察器所能观察的 DOM 变动类型(即上面代码的options对象),有以下几种。
- childList:子节点的变动(指新增,删除或者更改)。
- attributes:属性的变动。
- characterData:节点内容或节点文本的变动。
想要观察哪一种变动类型,就在option对象中指定它的值为true。需要注意的是,必须同时指定childList、attributes和characterData中的一种或多种,若未均指定将报错。
除了变动类型,options对象还可以设定以下属性:
subtree:布尔值,表示是否将该观察器应用于该节点的所有后代节点。attributeOldValue:布尔值,表示观察attributes变动时,是否需要记录变动前的属性值。characterDataOldValue:布尔值,表示观察characterData变动时,是否需要记录变动前的值。attributeFilter:数组,表示需要观察的特定属性(比如['class','src'])。
// 开始监听文档根节点(即<html>标签)的变动 mutationObserver.observe(document.documentElement, { attributes: true, // 属性变动 characterData: true, // 节点内容或节点文本的变动 childList: true, // 子节点的变动 subtree: true, // 是否将该观察器应用于该节点的所有后代节点 attributeOldValue: true, // 观察attributes变动时,是否需要记录变动前的属性值 characterDataOldValue: true // 观察characterData变动时,是否需要记录变动前的值。 });
对一个节点添加观察器,就像使用addEventListener方法一样,多次添加同一个观察器是无效的,回调函数依然只会触发一次。如果指定不同的options对象,以后面添加的那个为准,类似覆盖。
下面的例子是观察新增的子节点。
var insertedNodes = []; var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { for (var i = 0; i < mutation.addedNodes.length; i++) { insertedNodes.push(mutation.addedNodes[i]); } }); console.log(insertedNodes); }); observer.observe(document, { childList: true, subtree: true });
3.2 disconnect(),takeRecords()
disconnect方法用来停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器。
observer.disconnect();
takeRecords方法用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。
observer.takeRecords();
下面是一个例子。
// 保存所有没有被观察器处理的变动 var changes = mutationObserver.takeRecords(); // 停止观察 mutationObserver.disconnect();
4、MutationRecord 对象 (变动记录)
DOM 每次发生变化,就会生成一条变动记录(MutationRecord 实例)。该实例包含了与变动相关的所有信息。Mutation Observer 处理的就是一个个MutationRecord实例所组成的数组。
MutationRecord对象包含了DOM的相关信息,有如下属性:
type:观察的变动类型(attributes、characterData或者childList)。target:发生变动的DOM节点。addedNodes:新增的DOM节点。removedNodes:删除的DOM节点。previousSibling:前一个同级节点,如果没有则返回null。nextSibling:下一个同级节点,如果没有则返回null。attributeName:发生变动的属性。如果设置了attributeFilter,则只返回预先指定的属性。oldValue:变动前的值。这个属性只对attribute和characterData变动有效,如果发生childList变动,则返回null。
5、应用示例
5.1 子元素的变动
下面的例子说明如何读取变动记录。
var callback = function (records){ console.log(records) // records参数是 MutationRecord实例 records.map(function(record){ console.log('Mutation type: ' + record.type); console.log('Mutation target: ' + record.target); }); }; /* [MutationRecord] 0: MutationRecord addedNodes: NodeList [text] attributeName: null attributeNamespace: null nextSibling: null oldValue: null previousSibling: script removedNodes: NodeList [] target: body type: "childList" __proto__: MutationRecord length: 1 __proto__: Array(0) Mutation type: childList test.html:23 Mutation target: [object HTMLBodyElement] */ var mo = new MutationObserver(callback); var option = { 'childList': true, 'subtree': true }; mo.observe(document.body, option); document.body.append(document.createElement('div').textContent = '123')
上面代码的观察器,观察<body>的所有下级节点(childList表示观察子节点,subtree表示观察后代节点)的变动。回调函数会在控制台显示所有变动的类型和目标节点。
5.2 属性的变动
下面的例子说明如何追踪属性的变动。
var callback = function (records) { records.map(function (record) { console.log('Previous attribute value: ' + record.oldValue); }); }; var mo = new MutationObserver(callback); var element = document.getElementById('#my_element'); var options = { 'attributes': true, 'attributeOldValue': true } mo.observe(element, options);
上面代码先设定追踪属性变动('attributes': true),然后设定记录变动前的值。实际发生变动时,会将变动前的值显示在控制台。
5.3 取代 DOMContentLoaded 事件
网页加载的时候,DOM 节点的生成会产生变动记录,因此只要观察 DOM 的变动,就能在第一时间触发相关事件,也就没有必要使用DOMContentLoaded事件。
var observer = new MutationObserver(callback); observer.observe(document.documentElement, { childList: true, subtree: true });
上面代码中,监听document.documentElement(即网页的<html>HTML 节点)的子节点的变动,subtree属性指定监听还包括后代节点。因此,任意一个网页元素一旦生成,就能立刻被监听到。
封装一个监听 DOM 生成的函数
下面的代码,使用MutationObserver对象封装一个监听 DOM 生成的函数。
(function(win){ 'use strict'; var listeners = []; var doc = win.document; var MutationObserver = win.MutationObserver || win.WebKitMutationObserver; var observer; function ready(selector, fn){ // 储存选择器和回调函数 listeners.push({ selector: selector, fn: fn }); if(!observer){ // 监听document变化 observer = new MutationObserver(check); observer.observe(doc.documentElement, { childList: true, subtree: true }); } // 检查该节点是否已经在DOM中 check(); } function check(){ // 检查是否匹配已储存的节点 for(var i = 0; i < listeners.length; i++){ var listener = listeners[i]; // 检查指定节点是否有匹配 var elements = doc.querySelectorAll(listener.selector); for(var j = 0; j < elements.length; j++){ var element = elements[j]; // 确保回调函数只会对该元素调用一次 if(!element.ready){ element.ready = true; // 对该节点调用回调函数 listener.fn.call(element, element); } } } } // 对外暴露ready win.ready = ready; })(this); // 使用方法 ready('.foo', function(element){ // ... });