Mezr
Mezr is a lightweight JavaScript utility library for measuring and comparing the dimensions and positions of HTML DOM elements in modern browsers (IE9+).
Features
- Calculate any element's dimensions and offsets consistently.
- Get element's containing block.
- Position elements relative to other elements (similar to jQuery UI's position method).
- Calculate the intersection area between multiple elements.
- Calculate distance between two elements.
- Calculate how much an element overflows another element per each side.
Getting started
- Include mezr.js within the body element on your site. Mezr needs to access the body element, because it does some browser behaviour checking on initialization and will throw an error if
document.body
is not available.
<body>
<script src="mezr.js"></script>
</body>
```
- Then just start measuring the DOM. Here are some simple examples to get you started.
element: elemA,
target: elemB,
position: 'left top center center'
});
```
API v0.6.2
.width()
Returns the width of an element in pixels. Accepts also the window object (for getting the viewport width) and the document object (for getting the width of the whole document)..width( el, [ edge ] )
- el — element / window / document
- edge — string
'border'
Allowed values: 'content'
, 'padding'
, 'scroll'
, 'border'
, 'margin'
.
For window
and document
objects this argument behaves a bit differently since they cannot have any paddings, borders or margins. Only 'content' (without vertical scrollbar's width) and 'scroll' (with vertical scrollbar's width) are effective values. 'padding' is normalized to 'content' while 'border' and 'margin' are normalized to 'scroll'.
Returns —> number
The return value may be fractional when calculating the width of an element. For window
and document
objects the value is always an integer though.
Examples
```javascript
// Document width (with viewport scrollbar).
mezr.width(document, 'scroll'); // or just -> mezr.width(document);
// Document width (without viewport scrollbar).
mezr.width(document, 'content');
// Window width (with scrollbar).
mezr.width(window, 'scroll'); // or just -> mezr.width(window);
// Window width (without scrollbar).
mezr.width(window, 'content');
// Element content width.
mezr.width(elem, 'content');
// Element content width + left/right padding.
mezr.width(elem, 'padding');
// Element content width + left/right padding + vertical scrollbar width.
mezr.width(elem, 'scroll');
// Element content width + left/right padding + vertical scrollbar width +
// left/right border.
mezr.width(elem, 'border'); // or just -> mezr.width(elem);
// Element content width + left/right padding + vertical scrollbar width +
// left/right border + left/right (positive) margin.
mezr.width(elem, 'margin');
```
.height()
Returns the height of an element in pixels. Accepts also the window object (for getting the viewport height) and the document object (for getting the height of the whole document)..height( el, [ edge ] )
- el — element / window / document
- edge — string
'border'
Allowed values: 'content'
, 'padding'
, 'scroll'
, 'border'
, 'margin'
.
For window
and document
objects this argument behaves a bit differently since they cannot have any paddings, borders or margins. Only 'content' (without vertical scrollbar's width) and 'scroll' (with vertical scrollbar's width) are effective values. 'padding' is normalized to 'content' while 'border' and 'margin' are normalized to 'scroll'.
Returns —> number
The return value may be fractional when calculating the height of an element. For window
and document
objects the value is always an integer though.
Examples
```javascript
// Document height (with viewport scrollbar).
mezr.height(document, 'scroll'); // or just -> mezr.height(document);
// Document height (without viewport scrollbar).
mezr.height(document, 'content');
// Window height (with scrollbar).
mezr.height(window, 'scroll'); // or just -> mezr.height(window);
// Window height (without scrollbar).
mezr.height(window, 'content');
// Element content height.
mezr.height(elem, 'content');
// Element content height + top/bottom padding.
mezr.height(elem, 'padding');
// Element content height + top/bottom padding + horizontal scrollbar height.
mezr.height(elem, 'scroll');
// Element content height + top/bottom padding + horizontal scrollbar height +
// top/bottom border.
mezr.height(elem, 'border'); // or just -> mezr.height(elem);
// Element content height + top/bottom padding + horizontal scrollbar height +
// top/bottom border + top/bottom (positive) margin.
mezr.height(elem, 'margin');
```
.offset()
Returns the element's offset from another element, window or document..offset( el, [ edge ] )
Returns the element's offset from the document. The second argument defines which edge layer of the element should be used for the calculations.
- el — element / window / document
- edge — string
'border'
Allowed values: 'content'
, 'padding'
, 'scroll'
, 'border'
, 'margin'
.
This argument has no effect for window
or document
.
Note that 'padding' and 'scroll' values produce identical results. The 'scroll' value is only allowed here in order to make this method work in sync with .width()
and .height()
methods.
.offset( el, [ from ] )
Returns the element's offset from another element, window or document. The second (optional) argument defines from which element the offset is calculated.
- el — element / window / document / object / array
[someElem, 'content']
.
Object: must have left and top properties with numeric values (e.g. {left: 10, top: -10}
).
- from — element / window / document / object / array
document
Document/Element/Window: the edge is considered to be 'border'.
Array: allows one to control which layer (content, padding, scroll, border, margin) is considered as the element's edge, e.g. [someElem, 'content']
.
Object: must have left and top properties with numeric values (e.g. {left: 10, top: -10}
).
Returns —> object
- obj.left — number
- obj.top — number
.rect()
Returns an object containing the provided element's dimensions and offsets. This is basically a helper method for calculating an element's dimensions and offsets simultaneously. Mimics the native getBoundingClientRect method with the added bonus of allowing to define the edge layer of the element, and also the element from which the offset is calculated..rect( el, [ edge ] )
Get rect data of the element with the offset calculated relative to the document.
- el — element / window / document
- edge — boolean
'border'
Allowed values: 'content'
, 'padding'
, 'scroll'
, 'border'
, 'margin'
.
.rect( el, [ from ] )
Get rect data of the element with the offset calculated relative to another element, window or document.
- el — element / window / document / object / array
[someElem, 'content']
.
Object: must have left and top properties with numeric values (e.g. {left: 10, top: -10}
).
- from — element / window / document / object / array
document
Document/Element/Window: the edge is considered to be 'border'.
Array: allows one to control which layer (content, padding, scroll, border, margin) is considered as the element's edge, e.g. [someElem, 'content']
.
Object: must have left and top properties with numeric values (e.g. {left: 10, top: -10}
).
Returns —> object
- obj.width — number
- obj.height — number
- obj.left — number
- obj.right — number
- obj.top — number
- obj.bottom — number
.containingBlock()
Returns the element's containing block, which is considered to be the closest ancestor element (or window, or document, or the target element itself) that the target element's positioning is relative to. In other words, containing block is the element that the target element's CSS properties left, right, top and bottom are relative to. You should not confuse this with the native elem.offsetParent read-only property, which works in a similar fashion (and even identically in certain situations), but is really not the same thing (although the name might imply it). The logic- Document is considered to be the root containing block of all elements and the window.
- Getting the document's containing block will return
null
.
- Static element does not have a containing block since setting values to the left, right, top and bottom CSS properties does not have any effect on the element's position. Thus, getting the containing block of a static element will return
null
.
- Relative element's containing block is always the element itself.
- Fixed element's containing block is always the closest transformed ancestor or
window
if the element does not have any transformed ancestors. An exception is made for browsers which allow fixed elements to bypass the W3C specification of transform rendering. In those browsers fixed element's containing block is always thewindow
.
- Absolute element's containing block is the closest ancestor element that is transformed or positioned (any element which is not static). If no positioned or transformed ancestor is not found the containing block is the
document
.
- Sticky element is a special case since "left", "right", "top" and "bottom" CSS properties do not always affect the element's position. However, for consistency, the closest scrolling ancestor element is always considered as sticky element's containing block, and if no scrolling ancestor is found window is returned.
- Root element and body element are treated equally with all other elements.
.containingBlock( el, [ fakePosition ] )
- el — element / window / document
- fakePosition — element / window / document
"static"
, "relative"
, "absolute"
, "fixed"
and "sticky"
.
Returns —> element / window / document / null
Examples
```javascript
// Get element's containing block.
mezr.containingBlock(elem);
// Get element's containing block with faked position value.
mezr.containingBlock(elem, 'fixed');
// Static behaviour.
mezr.containingBlock(document); // always null
mezr.containingBlock(window); // always document
mezr.containingBlock(elem, 'static'); // always null
mezr.containingBlock(elem, 'relative'); // always elem
```
.distance()
Returns the distance between two elements (in pixels) or-1
if the elements overlap.
.distance( from, to )
- from — element / array / object
[someElem, 'content']
.
Object: must have width, height, left and top properties with numeric values (e.g. {width: 10, height: 10, left: 10, top: -10}
).
- to — element / array / object
[someElem, 'content']
.
Object: must have width, height, left and top properties with numeric values (e.g. {width: 10, height: 10, left: 10, top: -10}
).
Returns —> number
If elements/objects overlap the method returns -1
. Otherwise the method returns the distance between the elements/objects.
Examples
```javascript
var elemA = document.getElementById('a');
var elemB = document.getElementById('b');
var rectA = {left: 0, top: 0, width: 5, height: 5};
var rectB = {left: 20, top: 20, width: 5, height: 5};
// Calculate the distance between two elements.
mezr.distance(elemA, elemB);
// Calculate the distance between two objects.
mezr.distance(rectA, rectB);
// Calculate the distance between an object and element.
mezr.distance(elemA, rectB);
// Define which edge to use for element calculations.
mezr.distance(elemA, 'content', elemB, 'scroll');
```
.intersection()
Detect if two or more elements overlap and calculate their possible intersection area (dimensions and offsets). If the intersection area exists the function returns an object containing the intersection area's dimensions and offsets. Otherwisenull
is returned.
.intersection( ...el )
- el — array / element / object
[someElem, 'content']
.
Object: must have width, height, left and top properties with numeric values (e.g. {width: 10, height: 20, left: 15, top: -10}
).
Returns —> null / object
In case the provided elements/objects do not overlap the method returns null
. Otherwise the intersection area's data (object) is returned with the following properties:
- obj.width — number
- obj.height — number
- obj.left — number
- obj.right — number
- obj.top — number
- obj.bottom — number
.overflow()
Calculate how much an element overflows another element per each side..overflow( container, el )
- container — array / element / object
[someElem, 'content']
.
Object: must have width, height, left and top properties with numeric values (e.g. {width: 10, height: 20, left: 15, top: -10}
).
- el — array / element / object
[someElem, 'content']
.
Object: must have width, height, left and top properties with numeric values (e.g. {width: 10, height: 20, left: 15, top: -10}
).
Returns —> object
- obj.left — number
- obj.right — number
- obj.top — number
- obj.bottom — number
.place()
Calculate an element's position (left/top CSS properties) when positioned relative to another element, window or the document. Note that this method does not actually position the element, it just returns the new position which can be applied to the element if needed..place( options )
The options argument should be an object. You may configure it with the following properties.
- element — element / window / document / array
null
Element: the element’s edge is considered to be “border”.
Array: allows one to control which layer (content, padding, scroll, border, margin) is considered as the element's edge, e.g. [someElem, 'content']
.
- target — element / window / document / array / object
null
Element: the element's edge is considered to be 'border'.
Array: allows one to control which layer (content, padding, scroll, border, margin) is considered as the element's edge, e.g. [someElem, 'content']
.
Object: must have width, height, left and top properties with numeric values (e.g. {width: 10, height: 20, left: 15, top: -10}
).
- position — string
'left top left top'
The syntax is 'elementX elementY targetX targetY' .
* Describe horizontal position with `'left'`, `'center'` and `'right'`.
* Describe vertical position with `'top'`, `'center'` and `'bottom'`.
- offsetX — number / string
'50%'
. The percentage values are relative to the target element's width. For example if the target element's width is 50 pixels a value of '100%'
would push the element 50 pixels to the right.
Default: 0
- offsetY — number / string
'50%'
. The percentage values are relative to the target element's height. For example if the target element's height is 50 pixels a value of '100%'
would push down the element 50 pixels.
Default: 0
- contain — null / object
null
- contain.within — element / window / document / array / object
null
Element: the element's edge is considered to be 'border'.
Array: allows one to control which layer (content, padding, scroll, border, margin) is considered as the element's edge, e.g. [someElem, 'content']
.
Object: must have width, height, left and top properties with numeric values (e.g. {width: 10, height: 20, left: 15, top: -10}
).
- contain.onOverflow — string / object / null
'none'
For maximum control you can provide an object with four properties: 'left', 'right', 'top' and 'bottom'. Each property represents an edge of the container and each property's value should be one of the predefined actions (see below) which will be called when/if the element overflows the container. Here's an example: {left: 'push', right: 'forcepush', top: 'none', bottom: 'push'}
.
Alternatively you can also just define collision actions per axis: {x: 'push', y: 'none'}
.
Or you can mix and match edges and axis: {x: 'push', top: 'none', bottom: 'push'}
.
For minimum configuration you can just provide a single value as a string, e.g. 'push'
which will be used for all edges.
Collision actions:
* `'none'`: Ignore containment.
* `'push'`: Push the element back within the container, so that it does not overlap the container. If the element is larger than the container and opposite edges both have 'push' action enabled, the element will be positioned so that it overlaps the container an equal amount from both edges.
* `'forcepush'`: Identical to 'push', but with one exception: it makes sure that the element's edge is always pushed fully back within the container. This action is only useful when the opposite edge has 'push' action enabled.
- adjust — function / null
null
This callback receives two arguments:
* **position** — *object*
* This object is the same object that the method will return, so modifying it's properties will affect the return value of the method.
* **position.left** — *number*
* The positioned element's left (CSS) property value (fractional).
* **position.top** — *number*
* The positioned element's top (CSS) property value (fractional).
* **data** — *object*
* This object contains all the positioning data.
* **data.elementRect** — *object*
* Element's *new* [rect](#rect) data where the element is assumed to be in the newly calculated position.
* **data.targetRect** — *object*
* Target's *current* [rect](#rect) data.
* **data.containerRect** — *object / null*
* Container's *current* [rect](#rect) data if `collision.within` is defined. Otherwise `null`.
* **data.shift** — *object*
* Horiozontal and vertical diff between the element's current and new offset. In other words, answers the question how much the element moved in the x-axis and y-axis and in which direction.
* **data.shift.left** — *number*
* **data.shift.top** — *number*
* **data.overflow** — *object / null*
* How much the element (in it's *new* position) [overflows](#overflow) the container per each side. Identical to `mezr.overflow(data.containerRect, data.elementRect)`. If `collision.within` is not defined this will be `null`.
* **data.overflow.left** — *number*
* **data.overflow.right** — *number*
* **data.overflow.top** — *number*
* **data.overflow.bottom** — *number*
* **data.overflowCorrection** — *object*
* This object contains data on how much the `contain.onOverflow` action moved the element in x-axis and y-axis, and in which direction. If no `contain.onOverflow` action was defined or the action had no effect on the element's position the values of the `left` and `top` attributes are `0`.
* **data.overflowCorrection.left** — *number*
* **data.overflowCorrection.top** — *number*
Returns —> object
- obj.left — number
- obj.top — number
// Contain elemA within elemC (padding layer).
within: [elemC, 'padding'],
// Define (for each side) what to do when/if elemA overflows elemC.
onOverflow: {
left: 'forcepush',
right: 'push',
top: 'none',
bottom: 'push'
}
},
adjust: function (position, data) {
// Nudge the element's position 1px to the left and 1px to the bottom
// after all positioning stuff above has happened.
position.left -= 1;
position.top += 1;
}
});
```