🥗 JavaScript DOM 样式和类
JavaScript 修改样式一般通过 修改 DOM 元素的 style
属性。
🌰 例子 / 动态计算元素的坐标:
let top = /* 复杂的计算 */;
let left = /* 复杂的计算 */;
elem.style.left = left; // 例如 '123px',在运行时计算出的
elem.style.top = top; // 例如 '456px'
2
3
4
5
# class...
elem.className
对应于 class
特性(attribute), 字符串值,可以很好地管理整个类的集合。。
🌰 例子:
<body class="main page">
<script>
console.log(document.body.className); // main page
</script>
</body>
2
3
4
5
特殊对象 classList
(类列表),提供 add/remove/toggle
单个类的方法:
elem.classList.add/remove(class)
:添加 / 移除类。elem.classList.toggle(class)
:如果类不存在就添加类,存在就移除它。elem.classList.contains(class)
:检查给定类,返回true/false
。
🌰 例子:
<body class="main page">
<script>
// 添加一个 class
document.body.classList.add('article');
alert(document.body.className); // main page article
</script>
</body>
2
3
4
5
6
7
8
# 元素样式 style
element.style
属性是一个对象,它对应于 style
特性中所写的内容。
遵循 驼峰式命名,意味着在特性中使用 -
的多词命名, -
意味着大写字母,在属性中使用驼峰式命名,例如 background-color
对应 element.style.backgroundColor
。
🌰 例子:
document.body.style.backgroundColor = 'green'
像
-moz-border-radius
和-webkit-border-radius
这样的浏览器前缀属性,也同样遵循这个规则。
# 重置样式
- 隐藏一个 元素,可以设置
element.style.display = 'none'
。 - 移除这个样式,将这个样式的属性值设置为空:
element.style.display = ''
。这时元素重新出现。
(空字符串的样式属性,浏览器会应用 CSS 类以及内建样式,就像这个属性没有出现过)
# 完整重写样式
通常使用 element.style.*
对各个样式赋值,但是不能设置完整的样式 style
,因为整个 div.style
是一个只读对象。
想要以字符串的形式设置完整的样式,可以使用特殊属性 style.cssText
:
🌰 例子:
<div id="div">Button</div>
div.style.cssText = `color: red !important
background-color: yellow;
width: 100px;
text-align: center;`;
console.log(div.css.cssText)
2
3
4
5
6
这样的方式对元素的样式赋值,会删除现有的元素样式;因为它不是添加而是替换。所以这个方法不安全。
要想在原来的基础上添加,设置一个新的特性实现同样的效果:
div.setAttribute('style', 'color: red; ... ')
# 注意样式单位
🌰 例子 / 设置 top
样式:
<body>
<script>
// 无效!
document.body.style.margin = 20;
alert(document.body.style.margin); // ''(空字符串,赋值被忽略了)
// 现在添加了 CSS 单位(px)— 生效了
document.body.style.margin = '20px';
console.log(document.body.style.margin); // 20px
console.log(document.body.style.marginTop); // 20px
console.log(document.body.style.marginLeft); // 20px
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 计算样式 getComputedStyle
修改样式可以直接对样式对应的属性赋值;例如直接 element.body.style.color
不能获取到样式属性的值。
要获取样式属性的值,可以使用:
let computedStyle = getComputedStyle(element, [presudo])
element
:要被读取样式值的元素;[presudo]
:伪元素;例如::before
。空字符串或者无参数意味着 元素本身。
获取到的结果是一个具有 样式属性的只读对象。类似 element.style
。
🌰 例子:
<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>
<script>
let computedStyle = getComputedStyle(document.body);
// 现在我们可以读取它的 margin 和 color 了
alert( computedStyle.marginTop ); // 5px
alert( computedStyle.color ); // rgb(255, 0, 0)
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对于
margin
、padding
类型的样式,最好使用精准的完整属性名,例如paddingLeft
、marginTop
或borderTopWidth
。否则,就不能保证正确的结果。
getComputedStyle
实际上返回的事属性的 解析值。
- 计算 (computed) 样式值是所有 CSS 规则和 CSS 继承都应用后的值,这是 CSS 级联(cascade)的结果。例如
height:1em
或font-size:125%
。- 解析 (resolved) 样式值是最终应用于元素的样式值值。诸如
1em
或125%
这样的值是相对的。浏览器将使用计算(computed)值,并使所有单位均为固定的,且为绝对单位,例如:height:20px
或font-size:16px
。对于几何属性,解析(resolved)值可能具有浮点,例如:width:50.5px
。
# 元素大小
在 JavaScript 中有许多属性可让我们读取有关元素宽度、高度和其他几何特征的信息。
🌰 例子:
.example
元素中有以下的样式:
<div id="example">
...Text...
</div>
<style>
#example {
width: 300px;
height: 200px;
border: 25px solid #E8C48F;
padding: 20px;
overflow: auto;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
注意 滚动条的占用宽度空间:一些浏览器(并非全部)通过从 内容(content width) 获取空间为 滚动条保留空间。如果没有滚动条,内容宽度将是300 px
,但是如果滚动条宽度是16px
(不同的设备和浏览器,滚动条的宽度可能有所不同),那么最后的内容宽度还剩下300 - 16 = 284px
。最近的浏览器版本已经将 滚动条的宽度 不再占用 内容的宽度了。
# 几何属性
# offsetParent
元素最外面的几何属性:
offsetParent
:是最接近的祖先元素,在浏览器渲染期间,它被用于计算坐标。- 最近的祖先为:CSS 中定位为
position: absolute / relative / fixed
;或者<td> / <th> / <table>
;或者<body>
- 最近的祖先为:CSS 中定位为
offsetLeft / offsetTop
提供相对于offsetParent
左上角的x / y
坐标。
🌰 例子:
<main style="position: relative" id="main">
<article>
<div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
</article>
</main>
<script>
alert(example.offsetParent.id); // main
alert(example.offsetLeft); // 180
alert(example.offsetTop); // 180
</script>
2
3
4
5
6
7
8
9
10
11
注意获取到的是不带单位的 数字(不是字符串
180px
)。
对于几种情况, offsetParent
可能为 null
:
- 未显示的元素(
display:none
或者不在文档中)。 - 对于
<body>
与<html>
。(无祖先元素) - 对于带有
position:fixed
的元素。
# offsetWidth/Height
在元素内部(本身中),这两个属性提供了 元素「外部」的宽度和高度(包括边框的完整大小)
🌰 例子:
这个例子中:
offsetWidth
为390
(外部宽度):
25px + 20px + 300px + 20px + 25px
(不包含滚动条宽度)
offsetHeight
为290
(外部高度):同理。
# client*
元素内部中有 边框 时,为了测量他们(边框的宽度)可以使用 client*
(相对来说,是内部和外部的相对坐标):
同上例子:
clientTop = 25
:上边框宽度clientLeft = 25
:左边框宽度
# clientWidth/Height
计算元素边框内区域的大小。包括 内容宽度(content width)和 padding
。(不包括滚动条宽度)
同上例子:
clientHeight = 200
:为内部的元素高度(200px
)加上padding
的宽度(2 * 20px
)clientWidth = 340
内部元素宽度(300px
) 加上padding
的宽度 (2 * 20px
) 。
# scrollWidth/Height
这个属性是 完整的 clientWidth/Height
:它们包括滚动出的隐藏部分。
scrollWidth = 340
scroll = *
利用这个属性可以将属性展开到整个 宽度或者高度。
🌰 例子:
# scrollLeft/scrollTop
这两个属性是元素的 隐藏 / 滚动部分的 width / Height
,换句话说, scrollTop
就是 “已经滚动了多少”。
大多数几何属性是只读的,但是 scrollLeft/scrollTop
是可修改的,并且浏览器会滚动该元素。
🌰 例子:点击下面的元素,则会执行代码 elem.scrollTop += 10
。这使得元素内容向下滚动 10px
。
🌰 例子 / 将元素滚动到顶部或者底部:
element.scrollTop = 0 // 顶部
element.scrollTop = 1e9 // 底部
2
# CSS 样式属性与集合属性比较
getComputedStyle
可以获取 CSS 样式中 元素的width
和height
。let element = document.body console.log(getComputedStyle(element).width)
1
2但是应该使用 几何属性 获得宽度、高度和计算距离:
CSS
width
/height
取决于另一个属性:box-sizing
,它定义了 CSS 宽度和高度。出于 CSS 的目的而对box-sizing
进行的更改可能会破坏此类 JavaScript 操作。CSS 的
width/height
可能是auto
,例如内联(inline)元素:<span id="elem">Hello!</span> <script> console.log( getComputedStyle(elem).width ); // auto </script>
1
2
3
4
5从 CSS 的观点来看,
width:auto
是完全正常的,但在 JavaScript 中,需要一个确切的px
大小,以便在计算中使用它。滚动条占用元素宽度的原因,导致内容的实际宽度小于 CSS 宽度。使用
getComputedStyle(element).width
获取的值是一个字符串。而几何元素是 数值。
# 窗口大小
# 获取窗口的 width
/ height
利用几何元素:使用 document.documentElement
的 clientWidth
/ clientHeight
:
关于 window.innerWidth
/ window.innerHeight
:
注意
对比使用 client*
,窗口的宽度有所不同。因为此时 滚动条占用了宽度的一些空间。而 client*
会减去滚动条的宽度。所以使用 client*
返回的是可见文档内容的 width
/ heiught
。
在大多数情况下,需要通过 可见窗口的宽度 绘制或者放置元素,所以应该使用更加合适的 client*
。
# 文档大小
# 获取文档的 width
/ height
根文档元素是 document.documentElement
,并且它包围了所有内容,因此可以通过使用 documentElement.scrollWidth
/ scrollHeight
来测量文档的完整大小。
由于未明历史原因, body
/ document.Element
( html
根元素)获取出来的 高度和宽度可能有所不同,所以要获取可靠的数值,要取最大值:
# 滚动状态
# 获得当前滚动位置
相对于当前页面:
::: demo[vanilla]
<html>
<button onclick="alert('当前已从顶部滚动:' + window.pageYOffset)">当前已从顶部滚动</button>
<button onclick="alert('当前已从左侧滚动:' + window.pageXOffset)">当前已从左侧滚动</button>
</html>
2
3
4
:::
也可以从
window.scrollX
/window.scrollY
中获取相同的信息(两种属性相同)。
# 控制滚动位置
注意
注意要在 页面 DOM 完全构建后 才能通过 JavaScript 滚动页面。所以脚本不能放在 <head>
标签当中。
通过更改 scrollTop
/ scrollLeft
滚动元素;
使用 document.documentElement.scrollTop/scrollLeft
对页面进行相同的操作(Safari 除外,而应该使用 document.body.scrollTop/Left
代替)。
或者使用更加简单的特殊方法:
window.scrollBy(x, y)
:将页面滚动至 相对于当前位置的(x, y)
位置。如,scrollBy(0,10)
会将页面向下滚动10px
。
🌰 例子:
window.scrollTo(pageX, pageY)
:将页面滚动至 绝对坐标,使得可见部分的左上角具有相对于文档左上角的坐标。(pageX, pageY)
。就像给scrollLeft
/scrollTop
设值一样。
🌰 例子:
(这些方法适用于所有浏览器)
element.scrollIntoView(top)
:将滚动页面以使element
可见。它有一个参数:- 如果
top = true
(默认值),页面滚动,使element
出现在窗口顶部。元素的上边缘将与窗口顶部对齐。 - 如果
top = false
,页面滚动,使element
出现在窗口底部。元素的底部边缘将与窗口底部对齐。
- 如果
# 禁止滚动
要使文档不可滚动,只需要设置 document.body.style.overflow = "hidden"
。该页面将 “冻结” 在其当前滚动位置上。
取消只要重置该属性的值即可。
# 坐标
JavaScript 两个坐标系:
相对于 窗口:类似于
position:fixed
,从窗口的顶部 / 左侧边缘计算得出。** 表示为clientX
/clientY
** ;相对于 文档:与文档根中的
position:absolute
类似,从文档的顶部 / 左侧边缘计算得出。表示为pageX/pageY
。
# 获取元素坐标
使用方法 element.getBoundingClientRect()
返回 最小矩形 的窗口坐标,该矩形将 element
作为内建的 DOMRect 类的对象。
DOMRect 类的对象包含主要的属性有:
x/y
:矩形原点相对于窗口的 X/Y 坐标;width/height
:矩形的 width/height(可以为负);top/bottom
:顶部 / 底部矩形边缘的 Y 坐标;left/right
: 左 / 右矩形边缘的 X 坐标。
🌰 例子 / 获取按钮元素的 窗口坐标:
<html>
<input id="brTest" type="button" value="Get coordinates using button.getBoundingClientRect() for this button" onclick="showRect(this)">
</html>
<script>
function showRect(elem) {
let r = elem.getBoundingClientRect();
alert(`x:${r.x}
y:${r.y}
width:${r.width}
height:${r.height}
top:${r.top}
bottom:${r.bottom}
left:${r.left}
right:${r.right}
`);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
随着窗口相对按钮位置的改变,其窗口坐标(如果你垂直滚动页面,则为
y/top/bottom
)也随之改变。
可以得出:
left = x
top = y
right = x + width
bottom = y + height
提示
注意:
- 坐标可能是小数;浏览器内部使用小数进行计算,所以设置元素的数值不一定必须舍入。
- 坐标可能是负数。例如滚动页面,元素在页面上方时。
从数学上讲,一个矩形是使用其起点
(x,y)
和方向向量(width,height)
唯一定义的。因此,其它派生属性是为了方便起见。
width/height
可能为负数,负数时从左上方构建 矩形。此时left
/right
不等于x
/y
(此时x/y
在右下角)但是实际上,
elem.getBoundingClientRect()
总是返回正数的width/height
。看起来重复了的属性,其实不重复的。
注意
此处的 right
/ bottom
要与 CSS 中的定位区分:
- 在 CSS 定位中,
right
属性表示距右边缘的距离,而bottom
属性表示距下边缘的距离。
# elementFromPoint(x,y)
对 document.elementFromPoint(x, y)
的调用会返回在窗口坐标 (x, y)
处嵌套最多的元素。
let element = document.elementFromPoint(x, y)
🌰 例子 / 高亮显示并输出现在位于窗口中间的元素的标签:
let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;
let elem = document.elementFromPoint(centerX, centerY);
elem.style.background = "red";
console.log(element.tagName)
2
3
4
5
6
注意
对于在窗口之外的坐标, elementFromPoint
返回 null
。所以只对在可见区域内的坐标 (x,y)
起作用。
# 结合 CSS 定位
使用 getBoundingClientRect
来获取其坐标,然后使用 CSS position
以及 left/top
(或 right/bottom
)。
🌰 例子 / 函数 createMessageUnder(elem, html)
在 elem
下显示了消息:
let elem = document.getElementById("coords-show-mark");
function createMessageUnder(elem, html) {
// 创建 message 元素
let message = document.createElement('div');
// 在这里最好使用 CSS class 来定义样式
message.style.cssText = "position:fixed; color: red";
// 分配坐标,不要忘记 "px"!
let coords = elem.getBoundingClientRect();
message.style.left = coords.left + "px";
message.style.top = coords.bottom + "px";
message.innerHTML = html;
return message;
}
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上面的例子 message 元素依赖于
position:fixed
,因此当页面滚动时,它仍位于窗口的同一位置。如果要使跟随元素移动,要使用
position: relative
以及使用基于文档的坐标。
# 文档坐标
文档相对坐标从文档的左上角开始计算。(与顶部的 position:absolute
类似)
获取正确的文档坐标,利用 client*
:
pageY
=clientY
+ 文档的垂直滚动出的部分的高度。pageX
=clientX
+ 文档的水平滚动出的部分的宽度。
🌰 例子 / 将从 elem.getBoundingClientRect()
获取元素相对于窗口坐标,并向其中添加当前滚动:
function getCoords(elem) {
let box = elem.getBoundingClientRect();
return {
top: box.top + window.pageYOffset,
right: box.right + window.pageXOffset,
bottom: box.bottom + window.pageYOffset,
left: box.left + window.pageXOffset
};
}
2
3
4
5
6
7
8
9
10
结合上面的消息例子:
function createMessageUnder(elem, html) {
let message = document.createElement('div');
message.style.cssText = "position:absolute; color: red";
let coords = getCoords(elem);
message.style.left = coords.left + "px";
message.style.top = coords.bottom + "px";
message.innerHTML = html;
return message;
}
2
3
4
5
6
7
8
9
10
11
12
13
此时页面滚动时,消息仍停留在元素附近。