Position/Dimension properties in Javascript
Fundamental concepts
- In general,
x
/left
suffices refer to the horizontal distance, whiley
/top
suffices refer to the vertical one. - Viewport is the area/portion of the web page (precisely speaking, the nearest iframe) that we can see on the screen. Imagine a web page is an infinite landscape, your web browser is a camera and the area which is seen through the camera viewfinder is the viewport. When you move the camera/viewfinder, the scene changes respectively.
- Floating-point values can be returned by or assigned to some properties, especially when the browser is zoomed or when elements are transformed. We will see in this post that
scrollTop
,scrollLeft
,DOMRect
are able to handle floating-point, while the others are rounded to integer numbers. getBoundingClientRect()
andgetClientRects()
return the rendered dimension which takes care of transforms while the others APIs return the layout dimension and ignore all transforms.- The order of an element's displaying components from inner to outer is as follows: content → padding → scroll → border → margin. Verbally speaking, the area from content to the border is considered to belong to the element (is involved in the results returned from
getBoundingClientRect
or inoffset*
properties). Of which, only content and padding belong to the client area of an element (involved inclient*
properties).
- The vertical scroll bar does not always stay on the right side of an element. For example, in a right-to-left mode, it is rendered on the left side.
- While this is not related to any javascript API (except the
window.getComputedStyle()
), it is worth noting.box-sizing
CSS property defines whether the CSS width/height includes padding, border (and thus, scroll bar).
In the CSS standard, by default,box-sizing
iscontent-box
, implying that the CSS width/height properties define only the dimension of the content.
While in practice, many frameworks, modern CSS reset library assignborder-box
tobox-sizing
, which means padding, border, scrollbar are included in the CSS width/height.
In bootstrap's _reboot.scss:
// Document
//
// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.
*,
*::before,
*::after {
box-sizing: border-box;
}
- When an element becomes a scroll view (usually due to the larger size of its children), its content is the viewable area, not the larger being scrolled area which is the content of its children.
DOM node hierarchy inheritance structure
The most generic class is the node class, its unique superclass is EventTarget
. There are many types of nodes. The nodeType
property is an integer used to define the type of a node, hence, the sub-class to which it belongs. The list of node types sorted by their popularity are:
Node.ELEMENT_NODE
: infers the sub-class Element.
Element
is the most common node type with its 2 common descendant classes are HTMLElement ( <a>
, <p>
, <iframe>
... elements), and SVGElement ( <svg>
, <path>
, <g>
... elements).
Node.TEXT_NODE
: infers the sub-class Text. For example:<span>hello world</span>
is aElement
node with a singleText
node whosewholeText
property is'hello world'
.Node.COMMENT_NODE
: infers the sub-class Comment, represents comment nodes<!-- ... -->
.Node.DOCUMENT_NODE
: infers the sub-class Document where thedocument
variable instantiated from.Node.DOCUMENT_TYPE_NODE
: infers the sub-class DocumentType, represents<!DOCTYPE html>
node.Node.CDATA_SECTION_NODE
: infers the sub-class CDATASection, represents<!CDATA[[ ... ]]>
nodes.- Other node types are less common:
Node.ATTRIBUTE_NODE
(Attribute sub-class),Node.PROCESSING_INSTRUCTION_NODE
(ProcessingInstruction sub-class),Node.DOCUMENT_FRAGMENT_NODE
(DocumentFragment sub-class). - And some types are deprecated:
Node.ENTITY_REFERENCE_NODE
,Node.ENTITY_NODE
,Node.NOTATION_NODE
.
window instanceof Window // true
window instanceof Node // false
document instanceof Node // true
document instanceof Document // true
document instanceof Element // false
document.documentElement instanceof Node // true
document.documentElement instanceof Element // true
document.documentElement instanceof HTMLElement // true
document.getElementsByTagName('path')[0] instanceof HTMLElement // false
document.getElementsByTagName('path')[0] instanceof SVGElement // true
Suffix naming convention
Common (not always true, for example, mouse event's client*
are exceptions), suffices used in properties are:
screen*
: refer to the position in the whole Operating System. If there are multiple monitors, the monitor in left and top are also involved.page*
: related to the web page root element,document.documentElement
.scroll*
: when an element turns into a scroll view, this refers to the whole area which requires the scroll.offset*
: element's rendered area (from inner up to border).client*
: element's area used to display its children (from inner up to padding).
Root element is a special case
The root element (the <html>
element in quirks mode, or, the <body>
element when not in quirks mode) is an instance of HTMLElement
(thus, Element
). It has the same properties as all other HTML elements, but some of the properties have particularly different definitions by the specs. By convention, through this post, I will use the term root-element
, and it should be considered as an <html>
or <body>
element depending on whether the document is in quirks mode.
The following codepen explains why the root-element
should be treated differently. border
, padding
styling are normally applicable but overflow
has no effect. The children always overflow the root element regardless of the overflow
styling. The height
styling determines the position of the border but the background
is applied on the whole web page instead of the area within the border.
To rephrase it, the scroll bar will never appear on the root element. The specs take this advantage to control the browser's scroll bar (window.scroll*
) when the APIs are called on the root document (document.documentElement.{scroll*,client*}
).
!!Important note!!: the browser's scroll bar does NOT belong to the root element, check the codepen, when the scroll bar appears, it stays outside of the root element's border.
Mouse event
Mouse Event's properties, in a mouse-related events, such as mousedown
, mouseenter
, mouseleave
, mousemove
, mouseout
, mouseover
.
clientX
,clientY
.
Mouse position relative to the viewport (of the nearest iframe). Try running the code pen. Note that the code pen itself is an embedded iframe.
Compatibility is unknown in Firefox, IE, Safari.movementX
,movementY
.
Mouse position relative to the previous mouse event.
Not supported in IE.pageX
,pageY
.
Mouse position relative to the whole web page content (of the nearest iframe).
Not supported in Firefox. Compatibility is unknown in IE, Safari.screenX
,screenY
.
Mouse position relative to your operating system's displaying screen. Note that if you have multiple monitors, left and top monitors' sizes are added to these values.
Compatibility is unknown in Firefox, IE, Safari.
These values are all floating-point numbers.
Codepen
Compatibility
While all properties' compatibility is unknown in Firefox, Safari, the unknown compatibility is likely considered as supported rather than unsupported. For pageX
, pageY
which are not supported in Firefox, you can use this polyfill from jquery.
// Calculate pageX/Y if missing and clientX/Y available
if ( event.pageX == null && original.clientX != null ) {
eventDoc = event.target.ownerDocument || document;
doc = eventDoc.documentElement;
body = eventDoc.body;
event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
}
None-standard properties
Element
The codepen for this section.
getClientRects(), getBoundingClientRect()
.
The former returns a list of DOMRect objects (for IE, Edge: DOMRectReadOnly objects), while the latter returns one single boundingDOMRect
object.
getClientRects()
typically returns an array of a single DOMRect
object. Except, for <textarea>
, <svg>
, display: none
elements or elements that are not directly rendered, the returned list is empty.
For <span>
elements rendered in multiple lines, multiple DOMRect
objects are included in the returned array.
In general, getBoundingClientRect
is adequate for general uses.
These methods take care of transforms, while other properties ( client*
, scroll*
, offset*
, screen*
, inner*
, outer*
, ...) return the layout dimension which does not involve transforms. From MDN Web Docs:
In case of transforms, theoffsetWidth
andoffsetHeight
returns the element's layout width and height, whilegetBoundingClientRect()
returns the rendering width and height. As an example, if the element haswidth: 100px;
andtransform: scale(0.5);
thegetBoundingClientRect()
will return 50 as the width, whileoffsetWidth
will return 100.
Both these 2 APIs have high compatibility and are very safe to be used. However, the returned DOMRect
objects require more careful treatment. The specs and caveats for the DOMRect
object are worth a separate sub-section later in this section.
When the element is the root-element
, and the browser's scroll bar appears, getBoundingClientRect()
excludes the scroll bar area because the scroll bar does not belong to the root element. There is not a special definition here.
Similar to clientWidth
/ clientHeight
, in the special case, window
becomes the scrolling element itself. Note that although having the same names, these methods are different from window.scroll()
, window.scrollBy()
, window.scrollTo()
. In IE, all these methods are not available (this is not true for window
's methods).
Safari supports all these APIs but does not support the scroll ScrollToOptions option, which specifies the behavior
option for the smooth scroll. However, a polyfill is available. If you are using ES6 dynamic import:
if (!('scrollBehavior' in document.documentElement.style)) {
//safari does not support smooth scroll
(async () => {
const {default: smoothScroll} = await import(
/* webpackChunkName: 'polyfill-modern' */
'smoothscroll-polyfill'
)
smoothScroll.polyfill()
})()
}
This API is supported by all major browsers. However, IE and Safari do not support the scrollIntoViewOptions
, which specifies behavior
, block
, inline
, and the being proposed scroll
(or scrollMode
?, it is going to fixed) options.
There is a popular ponyfill (in short, a polyfill that does not hook into the native implementation and ignore the native implementation if exists, which eliminates the inconsistency across browsers due to bugs, etc.). To support the if-needed
scrollMode
use the scroll-into-view-if-needed package (and optionally use the smooth behavior if the browser natively supports it), to support both the smooth
behavior and if-needed
mode, use the smooth-scroll-into-view-if-needed. Note that the latter does not follow the specs and adds additional features on its own. Both these 2 packages use the popular package compute-scroll-into-view internally to positioning the element.
clientHeight
,clientWidth
.
The values are rounded to integer numbers. They are read-only. If the element isinline
or has no associated CSS layout box, these values become0
.
They are equal to content size + padding size (excluded if hidden in a scroll view), the scroll bar size and outer components are not included.
There is a special case of this definition if the element is theroot-element
, these values are equal to the view port's size excluding scroll bar size (if present). The special treatment is logically acceptable because the root element when being larger than the viewport, requires a parent element (with a scroll bar) to scroll around, but it is already the root element, so it becomes the scroll view of itself.
clientLeft
,clientTop
.
The values are rounded to integer numbers. They are read-only. If the element isinline
or has no associated CSS layout box, these values become0
.
These values have corresponding meanings withclientHeight
/clientWidth
, which are the width of the top/left border. In the case ofclientLeft
, if the scroll bar appears on the left side, the scroll bar width is included.
Because theroot-element
can have a border, there is not any special definition for the root element. Because the root element can not obtain a scroll bar, these values are always equal to the border dimension.scrollHeight
,scrollWidth
.
The values are rounded to integer numbers. They are read-only.
In conjunction withclientHeight
/clientWidth
, these values represent the size of the area scrolled by the element's scroll. That is to say, the dimension required to include all element's children, including the pseudo-elements such as::before
/::after
. When the children fit into the element size, these values are equal toclientHeight
/clientWidth
, respectively.
scrollLeft
,scrollTop
.
These values can be modified to move the scroll position. They can receive/keep decimal values.scrollLeft
is non-negative in the left-to-right setting, non-positive in the right-to-left setting, whilescrollTop
is always non-negative.
In value assignment, the values are clipped in the acceptable range (defined by the scrollable value) before the assignment.
When there is no scroll bar, these values are0
.
There is a special definition if the element is theroot-document
, these values refer towindow.scrollX
/window.scrollY
, respectively, in both value retrieval and assignment.
When the scroll reaches the end, these expressions will be evaluated true
(depend on the scrolling axis). They can be used to determine whether the user scrolls to the end of an element.
// horizontal scroll
elm.scrollWidth === Math.abs(elm.scrollLeft) + elm.clientWidth
// vertical scroll
elm.scrollHeight === elm.scrollTop + elm.clientHeight
The DOMRect object
DOMRect
class inherits DOMRectReadOnly
(IE, Edge). They have the same properties except that DOMRectReadOnly
does not allow modification to the object.
The available properties in this object are left
, top
, right
, bottom
, x
, y
, width
, height
. They describe the rendered dimension/position of the element relative to the top-left corner of the viewport (not the document). For large elements that are covered by a scroll view, the whole element (including the hidden area) is considered. Content, padding, border (but not margin) are involved in the value of these properties.
To produce a high compatibility code, you should rely only on four properties left
, top
, right
, bottom
and calculate other properties manually if needed.
Because properties in DOMRect
are not own properties. In other words, they do not appear in Object.keys()
, and only appear in for ... in
operator. So, the ES6 rest/spread operator or Object.assign()
are not able to copy the properties.
None-standard properties
HTMLElement
HTMLElement
inherits the Element
class. Hence, it has all properties of Element
, plus the following properties:
offsetHeight
,offsetWidth
.
The values are rounded to integer numbers. They are read-only.
These values have the same meaning withheight
/width
returned inElement.getBoundingClientRect()
except thatoffset*
ignores transforms.offsetParent
.
Returns the reference to the parent element in the layout hierarchy.
This value can benull
in cases:
+ The element does not have an associated CSS layout box (it or its parents have anone
display
).
+ The element is the root element (<html>
) or<body>
element.
+ The element has afixed
position
. Note: Firefox returns the<body>
element.offsetLeft
,offsetTop
.
Distances from the outer border of the element to the inner border of itsoffsetParent
element.
ElementCSSInlineStyle
style
.
The returned object can be modified and the change is reflected to the inline style of the element.
Window
scrollX
,scrollY
.
Special case ofElement.scrollLeft
/Element.scrollTop
.
In IE, this value is rounded to an integer number.pageXOffset
,pageYOffset
.
Alias ofWindow.scroll*
but are read-only.innerHeight
,innerWidth
.
read-only values, which hold the layout dimension of the viewport, including the scroll bar area (if exist). They can be changed usingresizeBy()
/resizeTo()
.
Note that in the mobile devices, when users use pinching gestures to zoom in/out, these values do not change, because the rendered document does not change.outerHeight
,outerWidth
.
read-only values which store the dimension of the browser window. They can be changed usingresizeBy()
/resizeTo()
.
screenLeft
=screenX
,screenTop
=screenY
.
read-only values which present the position of the viewport related to the screen (multiple monitors are included). They can be changed usingmoveBy()
/moveTo()
.
screenX
/ screenY
are defined in W3 specs, while screenLeft
/ screenTop
are implemented due to their popular usage. They are all well-supported by all major browsers.
Try this example from MDN, open it, and move your browser window. The rendered circle position keeps the same when you move the browser window around.
resizeBy()
,resizeTo()
.
Used to resize the window. However, due to security reasons, this API is going to be restricted. Ones should wrap the call in atry ... catch
block and prepare a fallback operation in the case of failure.
At present, Firefox requires the window created bywindow.open()
with the'resizable'
feature, containing exactly one tab, and having the same origin as the opener window. Sample code from MDN web docs:
// Create resizable window
myExternalWindow = window.open("http://myurl.domain", "myWindowName", "resizable");
// Resize window to 500x500
myExternalWindow.resizeTo(500, 500);
// Make window relatively smaller to 400x400
myExternalWindow.resizeBy(-100, -100);
scroll()
,scrollBy()
,scrollTo()
.
The associated methods ofElement
class in special cases.
These methods have much higher compatibility with IE than the ones in theElement
class. They are all available in IE, except that thescrollBy()
method only supportsscrollBy(xCord, yCord)
interface.
The compatibility does not change for other major browsers.moveBy()
,moveTo()
.
Used to move the window position. At the moment, Firefox requiresgetComputedStyle()
.
The returned object is read-only.
None-standard properties
scrollMaxX
,scrollMaxY
.scrollByLines()
,scrollByPages()
.mozInnerScreenX
,mozInnerScreenY
(Firefox only).
Screen
availTop
/availLeft
.
These are not standard properties but implemented in all major browsers except IE.
Used to determine the total dimension of available monitors in top/left of the current monitor.availHeight
/availWidth
.
Screen dimension excluded the area reserved by the device/user agent, such as the dock in Unity/Mac.
pixelDepth
/colorDepth
.height
/width
.
Screen dimension including reversed area (dock, taskbar, ...). Note that this dimension is not always available for the browser to expand. UseavailWidth
/availHeight
, instead.orientation
.
Get screen orientation. Not available in Safari, Android WebView (but available in Chrome Android), Opera Android.
None-standard properties
Document
UIEvent
Because MouseEvent
inherits UIEvent
, MouseEvent
instances also have all these properties.
These properties are all none-standard.
HTMLImage element
Forced layout thrashing and performance issue
To be continued soon...