mezr

Measure and compare the dimensions and distances of DOM elements.

Usage no npm install needed!

<script type="module">
  import mezr from 'https://cdn.skypack.dev/mezr';
</script>

README

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

  1. 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.
<html>
  <body>
    <script src="mezr.js"></script>
  </body>
</html>
  1. Then just start measuring the DOM. Here are some simple examples to get you started.
// Get element's content dimensions.
mezr.width(elem, 'content');
mezr.height(elem, 'content');

// Get element's content + padding dimensions.
mezr.width(elem, 'padding');
mezr.height(elem, 'padding');

// Get element's content + padding + scrollbar dimensions.
mezr.width(elem, 'scroll');
mezr.height(elem, 'scroll');

// Get element's content + padding + scrollbar + border dimensions (default).
mezr.width(elem, 'border');
mezr.height(elem, 'border');

// Get element's content + padding + scrollbar + border + margin dimensions.
mezr.width(elem, 'margin');
mezr.height(elem, 'margin');

// Calculate element's offsets from the document.
// The second argument defines the element's edge for the calculations.
mezr.offset(elem); // {left: ..., top: ...}
mezr.offset(elem, 'content');
mezr.offset(elem, 'padding');
mezr.offset(elem, 'scroll');
mezr.offset(elem, 'border');
mezr.offset(elem, 'margin');

// Calculate element's offsets from another element or the window.
mezr.offset([elemTo, 'content'], [elemFrom, 'padding']);
mezr.offset([elemTo, 'content'], window);

// Calculate direct distance between two elements.
mezr.distance(elemA, elemB);
mezr.distance([elemA, 'content'], [elemB, 'margin']);

// Calculate the intersection area between two or more elements.
mezr.intersection(elemA, elemB, elemC);
mezr.intersection([elemA, 'content'], [elemB, 'margin']);

// Calculate how much elemB overflows elemA.
mezr.overflow(elemA, elemB);

// Calculate what elemA's position (left and top CSS properties) should
// be when it's left-top (northwest) corner is placed in the center of
// elemB. Works only for for positioned elements (CSS position attribute
// must be something else than static).
mezr.place({
  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
    • Accepts any DOM element, the document object or the window object.
  • edge  —  string
    • Defines which edge (content, padding, scroll, border, margin) of the element is considered as it's outer edge. Optional.
    • Default: '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

// 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
    • Accepts any DOM element, the document object or the window object.
  • edge  —  string
    • Defines which edge (content, padding, scroll, border, margin) of the element is considered as it's outer edge. Optional.
    • Default: '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

// 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
    • Accepts any DOM element, the document object or the window object.
  • edge  —  string
    • Defines which edge (content, padding, scroll, border, margin) of the element is considered as its outer edge. Optional.
    • Default: '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
    • 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}).
  • from  —  element / window / document / object / array
    • Defines the element from which the offset is calculated. Optional.
    • Default: 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
    • The element's left offset in pixels (fractional).
  • obj.top  —  number
    • The element's top offset in pixels (fractional).

Examples

// Document's offset.
mezr.offset(document);
// Returns always {left: 0, top: 0}

// Window's offset from document.
mezr.offset(window);

// Element's offset from document.
mezr.offset(elem, 'content');
mezr.offset(elem, 'padding');
mezr.offset(elem, 'scroll');
mezr.offset(elem, 'border'); // or just -> mezr.offset(elem);
mezr.offset(elem, 'margin');

// Element's offset from window.
mezr.offset(elem, window);

// Element's offset from another element.
mezr.offset(elem, otherElem);
mezr.offset([elem, 'content'], [otherElem, 'margin']);

// Mimic jQuery's .position() method. Do note that this is not
// a drop in replacement for jQuery's .position() method, but
// works in a similar fashion.
mezr.offset(elem, mezr.containingBlock(elem));

 

.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
    • Accepts any DOM element, the document object or the window object.
  • edge  —  boolean
    • Defines which edge (content, padding, scroll, border, margin) of the element is considered as its outer edge.
    • Default: '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
    • 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}).
  • from  —  element / window / document / object / array
    • Defines the element from which the offset is calculated. Optional.
    • Default: 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
    • The width of the element in pixels (fractional).
  • obj.height  —  number
    • The height of the element in pixels (fractional).
  • obj.left  —  number
    • The element's left offset (from document's northwest corner).
  • obj.right  —  number
    • The element's left offset + width.
  • obj.top  —  number
    • The element's top offset (from document's northwest corner).
  • obj.bottom  —  number
    • The element's top offset + height.

Examples

// Element's rect data with offset relative to the document.
mezr.rect(elem, 'content');
mezr.rect(elem, 'padding');
mezr.rect(elem, 'scroll');
mezr.rect(elem, 'border'); // or just -> mezr.rect(elem);
mezr.rect(elem, 'margin');

// Element rect data with offset relative to the window.
mezr.rect(elem, window);

// Element rect data with offset relative to another element.
mezr.rect(elem, anotherElem);
mezr.rect([elem, 'padding'], [anotherElem, 'margin']);

 

.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 the window.
  • 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
    • Accepts any DOM element, the document object or the window object.
  • fakePosition  —  element / window / document
    • An optional argument which allows you to get the element's containing block as if the element had this CSS position value applied. Using this argument does not modify the element's true CSS position in any way, it's only used for the calculations.
    • Default: the element's current CSS position.
    • Allowed values: "static", "relative", "absolute", "fixed" and "sticky".

Returns  —>  element / window / document / null

Examples

// 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
    • 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: 10, left: 10, top: -10}).
  • to  —  element / array / object
    • 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: 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

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. Otherwise null is returned.

.intersection( ...el )

  • el  —  array / element / object
    • 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}).

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
    • The width of the intersection area in pixels (fractional).
  • obj.height  —  number
    • The height of the intersection area in pixels (fractional).
  • obj.left  —  number
    • The intersection area's left offset (from document's northwest corner).
  • obj.right  —  number
    • The intersection area's left offset + width.
  • obj.top  —  number
    • The intersection area's top offset (from document's northwest corner).
  • obj.bottom  —  number
    • The intersection area's top offset + height.

Examples

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 intersection area between two elements.
mezr.intersection(elemA, elemB);

// Calculate the intersection area between two objects.
mezr.intersection(rectA, rectB);

// Calculate the intersection area between an object and an
// element.
mezr.intersection(elemA, rectB);

// Define which edge to use for element calculations.
mezr.intersection([elemA, 'content'], [elemB, 'scroll']);

// Calculate the intersection area between two elements and
// two objects.
mezr.intersection(elemA, [elemB, 'margin'], rectA, rectB);

 

.overflow()

Calculate how much an element overflows another element per each side.

.overflow( container, el )

  • container  —  array / element / object
    • Element: the container's edge is considered to be 'border'.
    • Array: allows one to control which layer (content, padding, scroll, border, margin) is considered as the container'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}).
  • el  —  array / element / object
    • 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}).

Returns  —>  object

  • obj.left  —  number
    • How much element's left edge overflows the container's left edge.
  • obj.right  —  number
    • How much element's right edge overflows the container's right edge.
  • obj.top  —  number
    • How much element's top edge overflows the container's top edge.
  • obj.bottom  —  number
    • How much element's bottom edge overflows the container's bottom edge.

Note: A positive value means that the element overflows the container.

Examples

var container = document.getElementById('container');
var elem = document.getElementById('elem');

// Calculate the intersection area between two elements.
mezr.overflow(container, elem);

// Define which edge to use for element calculations.
mezr.overflow([container, 'content'], [elem, 'scroll']);

 

.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
    • The element which is to be positioned. This element's position needs to be either "relative", "absolute" or "fixed" for the function to work correctly. Although "sticky" elements are considered to be positioned this method does not yet support calculating their placement correctly.
    • Default: 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
    • Defines which element the element is positioned relative to.
    • Default: 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
    • Define the attachment joint.
    • Default: '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
    • An optional horizontal offset in pixels or in percentages. A number is always considered as a pixel value. A string is considered as a percentage value when it contains '%', e.g. '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
    • An optional vertical offset in pixels or in percentages. A number is always considered as a pixel value. A string is considered as a percentage value when it contains '%', e.g. '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
    • Defines an optional container element/area that is used for restricting the element's positioning.
    • Default: null
  • contain.within  —  element / window / document / array / object
    • The container element/area.
    • Default: 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
    • Defines what to do when the element overflows the container.
    • Default: '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
    • An optional callback function which is called just before returning the element's new calculated position. Can be used to adjust the return value just before returning it and accessing all the positioning data that was used to calculate the element's new position.
    • Default: 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 data where the element is assumed to be in the newly calculated position.
        • data.targetRect  —  object
          • Target's current rect data.
        • data.containerRect  —  object / null
          • Container's current 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 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
    • The positioned element's left (CSS) property value (fractional).
  • obj.top  —  number
    • The positioned element's top (CSS) property value (fractional).

Examples

// Calculate elemA's new position (left and top CSS properties).
var newElementPosition = mezr.place({
  // Let's use elemA's content layer for calculations.
  element: [elemA, 'content'],
  // Let's use elemB's margin layer for calculations.
  target: [elemB, 'margin'],
  // Let's position elemA's "left top" corner to the center of elemB.
  position: 'left top center center',
  // Nudge elemA 5px to the left.
  offsetX: -5,
  // Nudge elemA 50% (relative to it's own current height) to the bottom.
  offsetY: '50%',
  // Define containment for elemA.
  contain: {
    // 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;
  }
});

 

License

Copyright © 2015 Niklas Rämö. Licensed under the MIT license.