布局
你可能已经注意到不同类型的元素有不同的布局。某些元素,比如段落(<p>
)和标题(<h1>
)会占据整个文档的宽度,并且在独立的一行中渲染。这些元素被称为块(Block)元素。其他的元素,比如链接(<a>
或<strong>
元素则与周围文本在同一行中渲染。这类元素我们称之为内联(Inline)元素。
对于任意特定文档,浏览器可以根据每个元素的类型和内容计算其尺寸与位置等布局信息。接着使用布局来绘制文档。
JavaScript 中可以访问元素的尺寸与位置。
属性offsetWidth
和offsetHeight
给出元素的起始位置(单位是像素)。像素是浏览器中的基本测量单元。它通常对应于屏幕可以绘制的最小的点,但是在现代显示器上,可以绘制非常小的点,这可能不再适用了,并且浏览器像素可能跨越多个显示点。
同样,clientWidth
和clientHeight
向你提供元素内的空间大小,忽略边框宽度。
<p style="border: 3px solid red">
I'm boxed in
</p>
<script>
let para = document.body.getElementsByTagName("p")[0];
console.log("clientHeight:", para.clientHeight);
console.log("offsetHeight:", para.offsetHeight);
</script>
getBoundingClientRect
方法是获取屏幕中某个元素精确位置的最有效方法。该方法返回一个对象,包含top
、bottom
、left
和right
四个属性,表示元素相对于屏幕左上角的位置(单位是像素)。若你想要知道其相对于整个文档的位置,必须加上其滚动位置,你可以在pageXOffset
和pageYOffset
绑定中找到。
我们还需要花些力气才能完成文档的排版工作。为了加快速度,每次你改变它时,浏览器引擎不会立即重新绘制整个文档,而是尽可能等待并推迟重绘操作。当一个修改文档的 JavaScript 程序结束时,浏览器会计算新的布局,并在屏幕上显示修改过的文档。若程序通过读取offsetHeight
和getBoundingClientRect
这类属性获取某些元素的位置或尺寸时,为了提供正确的信息,浏览器也需要计算布局。
如果程序反复读取 DOM 布局信息或修改 DOM,会强制引发大量布局计算,导致运行非常缓慢。下面的代码展示了一个示例。该示例包含两个不同的程序,使用X
字符构建一条线,其长度是 2000 像素,并计算每个任务的时间。
<p><span id="one"></span></p>
<p><span id="two"></span></p>
<script>
function time(name, action) {
let start = Date.now(); // Current time in milliseconds
action();
console.log(name, "took", Date.now() - start, "ms");
}
time("naive", () => {
let target = document.getElementById("one");
while (target.offsetWidth < 2000) {
target.appendChild(document.createTextNode("X"));
}
});
// → naive took 32 ms
time("clever", function() {
let target = document.getElementById("two");
target.appendChild(document.createTextNode("XXXXX"));
let total = Math.ceil(2000 / (target.offsetWidth / 5));
target.firstChild.nodeValue = "X".repeat(total);
});
// → clever took 1 ms
</script>