在前端开发中,获取元素的尺寸(如宽度、高度)是常见的操作,特别是在动态布局、响应式设计、界面交互中。
下面按照浏览器渲染的步骤依次说明:
1. 构建 DOM 树(DOM Construction)
浏览器首先解析 HTML 并构建 DOM 树,DOM 树中包含页面的所有元素。此时未进行任何样式计算和布局计算。
1.1 dom.style.xxx
dom.style.width 和 dom.style.height:如果页面中有设置内联样式,能直接读取或设置这些值。
举个 🌰
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="box" style="background-color: pink; width: 200px; height: 200px"></div>
</body>
</html>
📢注意
如果没有在 HTML 元素中设置内联样式,使用 dom.style.xxx访问尺寸,得到的值是空字符串。其仅获取内联样式,它不会考虑任何外部或内部 CSS 文件中的规则。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.box {
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
该方法获取的是 DOM 树中的内容,不会导致回流,性能很高。
适用场景:
- 动态交互:如果在交互过程中需要调整元素的宽度,并且这种调整是瞬时的、局部的(例如点击按钮时改变元素宽度),使用 dom.style.width 非常合适。
- 动画效果:通过 JS 动态修改元素的宽度,配合 requestAnimationFrame 可以创建平滑的动画效果。
- 小范围控制:如果页面中的元素需要进行局部样式调整而不需要修改全局样式,dom.style.xxx提供了简洁的方式。
🌰:动态改变元素宽度
// 当点击按钮时改变元素宽度
const button = document.querySelector('button');
const box = document.querySelector('.box');
button.addEventListener('click', () => {
box.style.width = '300px'; // 点击时,box 宽度变为 300px
});
Tip
1、返回的是字符串值:如果获取 dom.style.width 的值,返回的是字符串(例如 '100px'),需要进行类型转换(如 parseInt() 或 parseFloat())来进行数值运算。
2、设置 dom.style.width 只会影响内联样式,不会更改外部样式表或 <style> 标签中的定义。如果元素的样式是由类名控制的,那么使用 dom.style.width 不会起作用。
3、dom.style.width 不会影响被计算后的宽度。即使通过 dom.style.width 设置了元素宽度,offsetWidth 或 clientWidth 等方法返回的尺寸可能仍然是计算后的值,而不单纯是内联样式的值。例如,如果元素有 padding 或 border,计算后的宽度会包括这些额外的空间。
2. 构建渲染树(Render Tree Construction)
在构建渲染树时,浏览器会根据样式表(CSS)计算出每个元素的样式,形成渲染树。此时,浏览器会考虑外部和内部样式、内联样式、继承的样式等。
2.1 window.getComputedStyle(xx)
浏览器会开始计算元素的宽度、高度等相关样式,这时 window.getComputedStyle(xx) 方法才会返回完整的计算样式(一个对象)。这是获取 最终样式(包括由 CSS 计算出来的所有属性,如宽度、高度、边距、内边距等)的最佳方法。
注意📢:
getComputedStyle 这个函数获取的值,不可能是 %、em、rem 这种相对单位数据,一定是计算后的数据拼接 px。
举个 🌰:仅定义一个 div,那么它的宽度是什么?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="box"></div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.box {
width: 2em;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div class="box" style="width: 3em"></div>
</body>
</html>
Tip:getComputedStyle 是一个计算过程,性能相对较差,尤其是对动态改变的样式进行频繁访问时。返回的是字符串值,需要解析为数值。
3. 布局计算(Layout / Reflow)
当渲染树和样式计算完成,浏览器进行布局计算(也叫 Reflow),根据元素的尺寸、位置、内外边距等信息计算每个元素在页面中的确切位置和尺寸。
获取布局树的信息:元素在页面上的布局信息,它在页面上占据多少空间。数值是没有单位的。
3.1 clientWidth 和 clientHeight
clientWidth 和 clientHeight 返回元素的可视区域的宽度和高度(包括内边距),不包括边框、外边距和滚动条。对于元素来说,这些属性值是只读的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 100px;
height: 100px;
border: 1px solid palevioletred;
padding: 10px;
margin: 10px;
background-color: pink;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
Tip:访问非常快速。不包括滚动条和边框,适用于需要知道内容区尺寸的场景。
布局树的信息不等于计算后的样式信息。
3.2 offsetWidth 和 offsetHeight
offsetWidth 和 offsetHeight 返回元素的宽度和高度(包括内边距、边框和滚动条,但不包括外边距)。它们是一个整数值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 120px;
height: 100px;
border: 1px solid palevioletred;
padding: 10px;
margin: 10px;
background-color: pink;
overflow: scroll;
}
.item {
margin: auto;
padding: 5px;
border: 1px solid palegoldenrod;
width: 100px;
height: 1000px;
background-color: peachpuff;
}
</style>
</head>
<body>
<div class="box">
<div class="item"></div>
</div>
</body>
</html>
Tip:获取该值时,浏览器会考虑到当前渲染的实际效果。
3.3 scrollWidth 和 scrollHeight
scrollWidth 和 scrollHeight 返回元素的完整内容尺寸(包括超出可视区域的部分),也就是说,它们考虑了内容的溢出部分。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.container {
width: 200px;
height: 100px;
overflow: auto;
background-color: antiquewhite;
border: 1px solid yellow;
}
.content {
width: 300px;
height: 400px;
background-color: lightblue;
}
</style>
</head>
<body>
<div class="container" id="container">
<div class="content">
<p>这是一个超出可视区域的内容。</p>
<p>这是第二行内容。</p>
<p>这是第三行内容。</p>
<p>这是第四行内容。</p>
<p>这是第五行内容。</p>
<p>这是第六行内容。</p>
</div>
</div>
</body>
</html>
适用于需要获取元素内容的总尺寸(包括滚动区域)。 对于动态内容或大于可视区域的内容非常有用。
3.4 window.innerWidth 和 window.innerHeight
获取浏览器窗口的内宽度和高度,不包括工具栏、滚动条等外部区域。适用于响应式布局和窗口缩放时,确定视口的大小(具有实时性)。
但是不能直接获取元素的尺寸,只能获取视口的尺寸。
总结:在布局计算阶段,方法如 offsetWidth 和 offsetHeight 会返回元素的实际尺寸。因为这些属性基于浏览器的布局计算结果,考虑元素的内外边距、边框、滚动条等。返回页面实际渲染后的尺寸,反映浏览器在布局计算后的结果。
4. 绘制(Painting)
在布局计算完成后,浏览器会根据渲染树的结构将每个元素的视觉表现绘制到屏幕上。此时,所有元素的位置和尺寸已确定。
4.1 getBoundingClientRect()
视觉尺寸
getBoundingClientRect() 方法返回的是一个包含元素的 大小 和 相对于视口(viewport)的位置 的 DOMRect 对象(包含 top、left、right、bottom、x、y、width 和 height 属性)。简而言之,返回的是元素在当前视口中的位置和尺寸,而不是整个文档或页面中的位置。
常用于获取元素相对于视口的矩形区域,如用于计算滚动位置。可以处理 偏移(如 CSS 的 transform 属性)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.box {
width: 100px;
height: 100px;
padding: 10px;
margin: auto;
border: 4px solid palevioletred;
background-color: pink;
margin-bottom: 100px;
}
.scale {
width: 100px;
height: 100px;
padding: 10px;
margin: auto;
border: 4px solid lavender;
background-color: lightcyan;
/* 放大两倍 */
transform: scale(2);
}
</style>
</head>
<body>
<div class="box"></div>
<div class="scale"></div>
</body>
</html>
注意:视觉信息不等于布局信息。
Tip:getBoundingClientRect 返回的值是相当于视口的,通过加上滚动偏移量,将元素的坐标转换为相对于整个页面的位置。
视口与页面坐标系的区别
1、视口坐标系
- 视口坐标系是浏览器窗口的坐标系,即用户当前可以看到的区域。
- 视口坐标系的原点通常是窗口的左上角 (0, 0),并且它随着滚动条的滚动而变化。
- getBoundingClientRect 返回值是相当于视口坐标系的,即元素的位置和尺寸相对于当前视口的位置,忽略了滚动条的影响。
2、页面坐标系(文档坐标系)
- 页面坐标系是整个页面的坐标系,原点 (0, 0) 在页面的左上角,不受视口滚动的影响。
- 如果页面有滚动条,元素的位置会随着页面的滚动发生变化,因此页面坐标系的位置是相对文档的。
举个 🌰
页面上有一个元素,可以使用 getBoundingClientRect() 获取它相对于视口的位置,并在页面滚动的情况下,通过调整来获取它相对于整个页面的坐标。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
body {
height: 2000px; /* 设置页面足够高,便于滚动 */
}
#myElement {
width: 100px;
height: 100px;
background-color: violet;
margin-top: 1000px; /* 假设元素距离页面顶部有 1000px */
color: white;
}
</style>
</head>
<body>
<div id="myElement">我是一个元素</div>
<script>
const element = document.getElementById('myElement');
// 获取元素相对于视口的边界信息
const rect = element.getBoundingClientRect();
console.log('元素相对于视口的位置:');
console.log('top:', rect.top); // 视口顶部到元素顶部的距离
console.log('left:', rect.left); // 视口左边到元素左边的距离
// 获取页面的滚动偏移量
const scrollTop = window.scrollY || document.documentElement.scrollTop;
const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
// 计算元素相对于页面的位置
const pageTop = rect.top + scrollTop;
const pageLeft = rect.left + scrollLeft;
console.log('元素相对于页面的位置:');
console.log('top:', pageTop); // 页面顶部到元素顶部的距离
console.log('left:', pageLeft); // 页面左边到元素左边的距离
</script>
</body>
</html>
当页面没有滚动时,元素相当于视口的位置和相当于页面的位置一样。
5. 补充
5.1 为什么布局树的信息不等于计算后的样式信息呢?
在浏览器渲染过程中,布局树(layout tree)和 计算后的样式(computed styles) 是两个不同的概念,它们的生成过程和用途也有所不同。具体来说,布局树的信息不等于计算后的样式信息,主要原因是它们的作用和形成的时机不同,且它们关注的内容也有所区别。
1、布局树(layout tree)
布局树是在 渲染树 中,经过计算元素的几何信息后(比如位置、尺寸等),最终形成的树形结构。这个树🌲包含了 可见元素 的位置和尺寸信息,是浏览器用来确定元素位置和尺寸的关键数据。
- 布局树中不包含被隐藏的元素(如 display: none 的元素)。
- 布局树的生成是根据 计算后的样式 来完成的,但它只关心元素的 位置和大小,而不关心元素的具体样式(比如颜色、字体、边框等)。
- 它在浏览器的 布局阶段 生成,通常是 计算样式之后 的结果。布局树是为了确定元素的最终位置和尺寸,为绘制阶段做准备。
2、计算后的样式(Computed Styles)
计算后的样式是浏览器计算出的一套最终的样式规则,它是基于 所有CSS规则(包括外部CSS、内联样式、浏览器默认样式等)综合计算得出的。这个过程涉及到:
- 解析样式表并应用到 DOM 元素。
- 将所有的样式合并成一个 最终的样式,包括继承的样式、用户定义的样式、计算的值等。
计算后的样式包括了每个元素的 所有样式信息,如 颜色、边框、字体大小、margin、padding、position 等。
为什么它们不相等?
1、布局树只关心位置和尺寸
布局树的构建过程关注的是元素的几何信息,即元素的 宽度、高度、位置 等,它并不关心样式属性(如颜色、字体、边框等),这些属性不会影响布局树的形成。
例如,元素的 color 或 background 等视觉样式信息,并不会影响布局树。即使元素的颜色改变,布局树的结构和位置不会受到影响。
2、计算后的样式包含所有样式信息
计算后的样式是 元素最终应用的所有样式,其中包含了所有的视觉效果、布局信息、字体设置、边框、间距等。
计算后的样式信息比布局树的信息更加详细和全面,它包括了所有对元素外观有影响的属性,而不仅仅是影响元素几何位置和尺寸的属性。
举个 🌰
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
div {
width: 200px;
height: 100px;
background-color: pink;
padding: 10px;
border: 5px solid palevioletred;
}
</style>
</head>
<body>
<div id="box">Hello</div>
</body>
</html>
1、计算后样式:
- width: 200px
- height: 100px
- background-color: pink
- padding: 10px
- border: 5px solid palevioletred
这些信息都会被 计算后样式 考虑,它包含了元素的所有视觉和布局样式。
2、布局树
布局树只会关心:元素的 实际宽度(包括 width + padding + border)、实际高度(包括 height + padding),以及元素的 位置(如左上角的位置),而不关心 background-color 或 color 等样式属性。
总结:
布局树主要关心的是元素的 几何属性(位置、尺寸等)。用于确定页面元素的 位置、排版 等。计算后样式包含了所有应用到元素的样式信息,包括字体、颜色、边框、背景等。提供了一个 全面的样式视图,用于确定元素的 外观 和 行为。
两者的区别就在于,计算后的样式描述的是元素如何“看起来”,而布局树描述的是元素如何“占据空间”并参与到页面的渲染和布局中。
5.2 为什么视觉信息不等于布局信息呢?
布局信息在上面已经讲述,下面介绍一下视觉信息。
视觉信息涉及的是元素的外观样式,包括颜色、边框、背景、字体、阴影等样式属性,它们直接影响元素的渲染效果。视觉信息主要在绘制阶段应用,以确保元素的外观符合样式定义。
通常包含:
- 元素的背景颜色、文字颜色、边框样式等。
- 元素的字体、文本对齐方式、阴影效果等。
- 透明度(eg:opacity)、过渡效果等影响外观的属性。
- 渲染效果,如元素的渐变、透明度、图像背景等。
为什么视觉信息不等于布局信息?
1、不同的渲染阶段
浏览器的渲染过程是一个多阶段的过程:
- 计算样式阶段:根据 CSS 样式表、内联样式和默认样式等,计算元素的最终样式属性,包括视觉信息(如颜色、字体、边框等)和布局信息(如宽高、位置、边距等)。
- 布局阶段:计算元素的位置、尺寸以及它们如何排列在页面中。这里生成的就是布局信息,它决定了元素占据的空间。
- 绘制阶段:将元素的视觉样式应用到页面中,包括背景颜色、边框、文本内容等。此时,浏览器将元素渲染到屏幕上。
在计算样式阶段,浏览器同时计算布局信息和视觉信息。但在布局阶段,浏览器仅关注布局信息,而 视觉信息 并不影响布局过程,虽然它会影响元素最终的显示效果,但不会改变元素的位置、尺寸等布局属性。
2、视觉效果和布局信息的独立性
一些视觉样式不会改变元素的实际布局,反而可能只影响其外观。比如:
- 透明度(opacity):它只影响元素的可见度,不会改变元素的占用空间或位置。
- 背景颜色(background-color):它仅改变元素的背景色,不会影响元素的布局。
- 文本颜色(color):它影响文本的颜色,但不改变文本的位置或元素的尺寸。
- 阴影(box-shadow, text-shadow):它仅影响视觉效果,但不改变元素的布局(即元素仍然占据其原本的空间)。
举个 🌰
设置一个元素的 border: 10px solid black,它会增加元素的外部空间,从而改变布局信息,因为元素的总尺寸(包括边框)会增加。但它仍然是一个“视觉信息”的例子,因为它并不会影响元素的核心内容或功能。
3、CSS 属性的区别
-
布局相关属性(影响元素的位置和尺寸):例如 width, height, margin, padding, position, top, left, right, bottom 等。
-
视觉相关属性(影响元素的外观):例如 color, background-color, border, font-size, box-shadow, opacity 等。
布局属性会直接影响浏览器如何计算元素的位置和尺寸,从而影响元素在页面中的排列。而视觉属性则是根据布局结果来展示元素的外观,不会影响页面中的布局。
综上: 每种方法有其优缺点,开发者需要根据场景需求权衡选择。