Chapter 5 – Element Node Geometry & Scrolling Geometry
最后更新于:2022-04-01 04:39:18
## 5.1 Element node size, offsets, and scrolling overview
DOM nodes are parsed and [painted](http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#Painting) into visual shapes when viewing html documents in a web browser. Nodes, mostly element nodes, have a corresponding visual representation made viewable/visual by browsers. To inspect and in some cases manipulate the visual representation and gemometry of nodes programatically a set of API's exists and are specified in the [CSSOM View Module](http://www.w3.org/TR/cssom-view/). A subset of methods and properties found in this specification provide an API to determine the geometry (i.e. size & position using offset) of element nodes as well as hooks for manipulating scrollable nodes and getting values of scrolled nodes. This chapter breaks down these methods and properties.
> ### Notes
> Most of the properties (excluding *scrollLeft* & *scrollTop*) from the CSSOM View Module specification are read only and calculated each time they are accessed. In other words, the values are live
## 5.2 Getting an elements *offsetTop* and *offsetLeft* values relative to the*offsetParent*
Using the properties *offsetTop* and *offsetLeft* we can get the offset pixel value of an element node from the*offsetParent*. These element node properties give us the distance in pixels from an elements outside top and left border to the inside top and left border of the *offsetParent*. The value of the *offsetParent* is determined by searching the nearest ancestor elements for an element that has a CSS position value not equal to static. If none are found then the *`<body>`* element or what some refer to as the "document" (as opposed to the browser viewport) is the *offsetParent* value. If during the ancestral search a *`<td>`*, *`<th>`*, or *`<table> `* element with a CSS position value of static is found then this becomes the value of *offsetParent*.
Lets verify that *offsetTop* and *offsetLeft* provide the values one might expect. The properties *offsetLeft*and *offsetTop* in the code below tell us that the *`<div>`* with an *id* of *red* is 60px's from the top and left of the*offsetParent* (i.e. the *`<body>`* element in this example).
live code: [http://jsfiddle.net/domenlightenment/dj5h9](http://jsfiddle.net/domenlightenment/dj5h9)
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body{margin:0;}
#blue{height:100px;width:100px;background-color:blue;border:10px solid gray; padding:25px;margin:25px;}#red{height:50px;width:50px;background-color:red;border:10px solid gray;}
</style>
</head>
<body>
<div id="blue"><div id="red"></div></div>
<script>
var div = document.querySelector('#red'); console.log(div.offsetLeft); //logs 60console.log(div.offsetTop); //logs 60
console.log(div.offsetParent); //logs <body>
</script>
</body>
</html>
~~~
Examine the following image showing what the code visually show in browser to help aid your understanding of how the *offsetLeft* and *offsetTop* values are deteremined. The red *`<div>`* shown in the image is exactly 60 pixels from the *offsetParent*.
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-11-03_563848c640b2d.png)
Notice I am measuring from the outside border of the red *`<div>`* element to the inside border of the*offsetParent* (i.e. *`<body>`*).
As previously mentioned If I was to change the blue *`<div>`* in the above code to have a position of absolute this would alter the value of the *offsetParent*. In the code below, absolutely positioning the blue *`<div>`* will cause the values returned from *offsetLeft* and *offsetTop* to report an offset (i.e. 25px's). This is because the offset parent is now the blue *`<div>`* and not the *`<body>`* .
live code: [http://jsfiddle.net/domenlightenment/ft2ZQ](http://jsfiddle.net/domenlightenment/ft2ZQ)
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<style>
#blue{height:100px;width:100px;background-color:blue;border:10px solid gray; padding:25px;margin:25px;position:absolute;}
#red{height:50px;width:50px;background-color:red;border:10px solid gray;}
</style>
</head>
<body>
<div id="blue"><div id="red"></div></div>
<script>
var div = document.querySelector('#red'); console.log(div.offsetLeft); //logs 25console.log(div.offsetTop); //logs 25
console.log(div.offsetParent); //logs <div id="blue">
</script>
</body>
</html>
~~~
The image of the browser view shown below clarifies the new measurements returned from *offsetLeft* and*offsetTop* when the *offsetParent* is the blue *`<div>`*.
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-11-03_563848c64d9e2.png)
> ### Notes
> Many of the browsers break the outside border to inside border measurement when the *offsetParent* is the * `<body>`* and the*`<body>`* or *`<html>`* element has a visible margin, padding, or border value.
> The *offsetParent*, *offsetTop*, and *offsetLeft* are extensions to the *HTMLelement* object.
## 5.3 Getting an elements top, right, bottom and left border edge offset relative to the viewport using *getBoundingClientRect()*
Using the *getBoundingClientRect()* method we can get the position of an elements outside border edges as its painted in the browser viewport relative to the top and left edge of the viewport. This means the left and right edge are measured from the outside border edge of an element to the left edge of the viewport. And the top and bottom edges are measured from the outside border edge of an element to the top edge of the viewport.
In the code below I create a 50px X 50px *`<div>`* with a 10px border and 100px margin. To get the distance in pixels from each border edge of the *`<div>`* I call the* getBoundingClientRect()* method on the *`<div>`* which returns an object containing a *top*, *right*, *bottom*, and *left* property.
live code: [http://jsfiddle.net/domenlightenment/A3RN9](http://jsfiddle.net/domenlightenment/A3RN9)
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body{margin:0;}div{height:50px;width:50px;background-color:red;border:10px solid gray;margin:100px;}</style>
</head>
<body>
<div></div>
<script>
var divEdges = document.querySelector('div').getBoundingClientRect(); console.log(divEdges.top, divEdges.right, divEdges.bottom, divEdges.left); //logs '100 170 170 100'
</script>
</body>
</html>
~~~
The image below shows the browser rendered view of the above code with some added measurement indicators to show exactly how *getBoudingClientRect()* is calculated.
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-11-03_563848c65c332.png)
The *top* outside border edge of the *`<div>`* element is 100px from the top edge of the viewport. The *right* outside border edge of the element *`<div>`* is 170px from the left edge of the viewport. The *bottom* outside border edge of the element *`<div>`* is 170px from the top edge of the viewport. And the *left* outside border edge of the element*`<div>`* is 100px from the left edge of the viewport.
## 5.4 Getting an elements size (border + padding + content) in the viewport
The *getBoundingClientRect()* returns an object with a top, right, bottom, and left property/value but also with a height and width property/value. The *height* and *width* properties indicate the size of the element where the total size is derived by adding the content of the div, its padding, and borders together.
In the code below I get the size of the *`<div>`* element in the DOM using *getBoundingClientRect()*.
live code: [http://jsfiddle.net/domenlightenment/PuXmL](http://jsfiddle.net/domenlightenment/PuXmL)
~~~
<!DOCTYPE html>
<html lang="en">
<head><style>div{height:25px;width:25px;background-color:red;border:25px solid gray;padding:25px;}</style></head><body><div></div><script>var div = document.querySelector('div').getBoundingClientRect();
console.log(div.height, div.width); //logs '125 125'
//because 25px border + 25px padding + 25 content + 25 padding + 25 border = 125</script></body>
</html>
~~~
The exact same size values can also be found using from the *offsetHeight* and *offsetWidth *properties. In the code below I leverage these properties to get the same exact height and width values provided by*getBoundingClientRect()*.
live code: [http://jsfiddle.net/domenlightenment/MSzL3](http://jsfiddle.net/domenlightenment/MSzL3)
~~~
<!DOCTYPE html>
<html lang="en">
<head><style>div{height:25px;width:25px;background-color:red;border:25px solid gray;padding:25px;}</style></head><body><div></div><script>var div = document.querySelector('div');
console.log(div.offsetHeight, div.offsetWidth); //logs '125 125'
//because 25px border + 25px padding + 25 content + 25 padding + 25 border = 125</script></body>
</html>
~~~
## 5.5 Getting an elements size (padding + content) in the viewport excluding borders
The *clientWidth* and *clientHeight* properties return a total size of an element by adding together the content of the element and its padding excluding the border sizes. In the code below I use these two properties to get the height and width of an element including padding but excluding borders.
live code: [http://jsfiddle.net/domenlightenment/bSrSb](http://jsfiddle.net/domenlightenment/bSrSb)
~~~
<!DOCTYPE html>
<html lang="en">
<head><style>div{height:25px;width:25px;background-color:red;border:25px solid gray;padding:25px;}</style></head><body><div></div><script>var div = document.querySelector('div');
console.log(div.clientHeight, div.clientWidth); //logs '75 75' because 25px padding + 25 content + 25 padding = 75</script></body>
</html>
~~~
## 5.6 Getting topmost element in viewport at a specific point using*elementFromPoint()*
Using *elementFromPoint()* it's possible to get a reference to the topmost element in an html document at a specific point in the document. In the code example below I simply ask what is the topmost element 50 pixels from the top and left of the viewport. Since we have two *'s at that location the topmost (or if there is no z-index set the last one in document order) div is selected and returned.
live code: [http://jsfiddle.net/domenlightenment/8ksS5](http://jsfiddle.net/domenlightenment/8ksS5)
~~~
<!DOCTYPE html>
<html lang="en">
<head><style>div{height:50px;width:50px;background-color:red;position:absolute;top:50px;left:50px;}</style></head><body><div id="bottom"></div><div id="top"></div><script>console.log(document.elementFromPoint(50,50)); //logs <div id="top"></script></body>
</html>
~~~
## 5.7 Getting the size of the element being scrolled using *scrollHeight*and *scrollWidth*
The *scrollHeight* and *scrollWidth* properties simply give you the height and width of the node being scrolled. For example, open any HTML document that scrolls in a web browser and access these properties on the *`<html>`*(e.g.* document.documentElement.scrollWidth*) or *`<body>`* (e.g.* document.body.scrollWidth*) and you will get the total size of the HTML document being scrolled. Since we can apply, using CSS (i.e overflow:scroll), to elements lets look at a simpler code example. In the code below I make a *`<div>`* scroll a *`<p>`* element that is 1000px's x 1000px's. Accessing the *scrollHeight* and *scrollWidth* properties on the *`<div>`* will tell us that the element being scroll is 1000px's x 1000px's.
live code: [http://jsfiddle.net/domenlightenment/9sZtZ](http://jsfiddle.net/domenlightenment/9sZtZ)
~~~
<!DOCTYPE html>
<html lang="en">
<head><style>
*{margin:0;padding:0;}div{height:100px;width:100px; overflow:auto;}p{height:1000px;width:1000px;background-color:red;}</style></head><body><div><p></p></div><script>var div = document.querySelector('div'); console.log(div.scrollHeight, div.scrollWidth); //logs '1000 1000'</script></body>
</html>
~~~
> ### Notes
> If you need to know the height and width of the node inside a scrollable area when the node is smaller than the viewport of the scrollable area don't use *scrollHeight* and *scrollWidth* as this will give you the size of the viewport. If the node being scrolled is smaller than the scroll area then use *clientHeight* and *clientWidth* to determine the size of the node contained in the scrollable area.
## 5.8 Getting & Setting pixels scrolled from the top and left using*scrollTop* and *scrollLeft*
The *scrollTop* and *scrollLeft* properties are read-write properties that return the pixels to the left or top that are not currently viewable in the scrollable viewport due to scrolling. In the code below I setup a *`<div>`* that scrolls a *`<p>`* element.
live code: [http://jsfiddle.net/domenlightenment/DqZYH](http://jsfiddle.net/domenlightenment/DqZYH)
~~~
<!DOCTYPE html>
<html lang="en">
<head><style>div{height:100px;width:100px;overflow:auto;}p{height:1000px;width:1000px;background-color:red;}</style></head><body><div><p></p></div><script>var div = document.querySelector('div'); div.scrollTop = 750;div.scrollLeft = 750;console.log(div.scrollTop,div.scrollLeft); //logs '750 750' </script></body>
</html>
~~~
I programatically scroll the *`<div>`* by setting the *scrollTop* and *scrollLeft* to 750\. Then I get the current value of *scrollTop* and *scrollLeft*, which of course since we just set the value to 750 will return a value of 750\. The 750 reports the number of pixels scroll and indicates 750 px's to the left and top are not viewable in the viewport. If it helps just think of these properties as the pixel measurements of the content that is not shown in the viewport to the left or top.
## 5.9 Scrolling an element into view using *scrollIntoView()*
By selecting a node contained inside a node that is scrollable we can tell the selected node to scroll into view using the *scrollIntoView()* method. In the code below I select the fifth *`<p>`* element contained in the scrolling*`<div>`* and call *scrollIntoView()* on it.
live code: [http://jsfiddle.net/domenlightenment/SyeFZ](http://jsfiddle.net/domenlightenment/SyeFZ)
~~~
<!DOCTYPE html>
<html lang="en">
<head><style>div{height:30px;width:30px; overflow:auto;}p{background-color:red;}</style></head><body><div><content><p>1</p><p>2</p><p>3</p><p>4</p><p>5</p><p>6</p><p>7</p><p>8</p><p>9</p><p>10</p> </content> </div><script>
//select <p>5</p> and scroll that element into view, I pass children '4' because its a zero index array-like structuredocument.querySelector('content').children[4].scrollIntoView(true); </script></body>
</html>
~~~
By passing the *scrollIntoView()* method a parameter of *true* I am telling the method to scroll to the top of the element being scrolled too. The *true* parameter is however not needed as this is the default action performed by the method. If you want to scroll align to the bottom of the element pass a parameter of *false *to the*scrollIntoView()* method.