GithubHelp home page GithubHelp logo

niklasramo / mezr Goto Github PK

View Code? Open in Web Editor NEW
147.0 4.0 9.0 964 KB

A lightweight JavaScript library for measuring and comparing the dimensions and distances of DOM elements.

License: MIT License

JavaScript 5.08% TypeScript 94.92%
dimensions offset intesection overlap overflow position element-position element-dimensions

mezr's Introduction

Mezr

npm install mezr

Mezr is a lightweight utility library, written in TypeScript, for measuring and comparing the dimensions, distances and other tricky stuff of DOM elements in modern browsers.

  • 🧩 Simple API which reduces boilerplate code a lot.
  • πŸ“¦ Modular, organized into clear independent modules.
  • πŸ€– Extensively tested across all modern browsers.
  • 🍭 No runtime dependencies, what you see is what you get.
  • πŸ’ Free and open source, MIT Licensed.

Why?

Mezr is a collection of methods that I've found myself writing over and over again in different projects. I've also found that these methods are not something that you can easily find from other maintained libraries. I've tried to keep the API as simple as possible, so that you can get started with it right away without having to read a lot of documentation. There are also hundreds of unit tests to ensure that the methods work as expected.

Some of the methods are also quite tricky to implement correctly, especially when you need to take browser differences into account. For example, the getContainingBlock() method is something that I've seen implemented incorrectly (or rather not fully correctly) in many libraries. It's very tricky to compute the containing block correctly while taking browser differences into account. Mezr does all the heavy lifting for you.

Here's a simple example of measuring an element's content width with both vanilla JS and Mezr.

Vanilla JS

// Let's start by getting the element's bounding client rect, this gives us
// always computed fractional pixel values, which is important. It contains
// the element's borders, paddings and scrollbars, which we need to subtract
// from the final width. Alternatively, we could start with elem.clientWidth to
// make this simpler, but it returns rounded integers, which is not ideal for
// precise calculations.
let width = elem.getBoundingClientRect().width;

// Get the computed style of the element, this gives us always computed pixel
// values.
const style = window.getComputedStyle(elem);

// Subtract borders.
width -= parseFloat(style.borderLeftWidth) || 0;
width -= parseFloat(style.borderRightWidth) || 0;

// Subtract scrollbar.
if (!(elem instanceof HTMLHtmlElement)) {
  width -= Math.max(0, Math.round(width) - elem.clientWidth);
}

// Subtract paddings.
width -= parseFloat(style.paddingLeft) || 0;
width -= parseFloat(style.paddingRight) || 0;

// Done!
console.log(width);

Mezr

import { getWidth } from 'mezr/getWidth';

// Done!
console.log(getWidth(elem, 'content'));

Getting started

Install

npm install mezr

Import

All the public API methods are provided as CommonJS modules (CJS) and ECMAScript modules (ESM) via subpath exports.

import { getWidth } from 'mezr/getWidth';
import { getHeight } from 'mezr/getHeight';

You can also import all the public methods from the library root if you wish.

import { getWidth, getHeight } from 'mezr';

Lastly, a UMD module is also provided if you want to include Mezr directly in your website (like in the good old days).

<script src="mezr.js"></script>
<script>
  const bodyWidth = mezr.getWidth(document.body);
  const bodyHeight = mezr.getHeight(document.body);
</script>

Measure

Now you're ready to start measuring the DOM, here's a quick demo on the dimensions/offsets methods. Please refer to the API section for more details and more methods.

import { getWidth } from 'mezr/getWidth';
import { getHeight } from 'mezr/getHeight';
import { getOffset } from 'mezr/getOffset';

// DIMENSIONS

// Measure element's dimensions (with paddings + scrollbar + borders).
// elem.getBoundingClientRect().width/height.
getWidth(elem);
getHeight(elem);

// The second argument defines element's box edge.

// Only the content.
getWidth(elem, 'content');
getHeight(elem, 'content');

// Content + paddings.
getWidth(elem, 'padding');
getHeight(elem, 'padding');

// Content + paddings + scrollbar.
getWidth(elem, 'scrollbar');
getHeight(elem, 'scrollbar');

// Content + paddings + scrollbar + borders (default).
getWidth(elem, 'border');
getHeight(elem, 'border');

// Content + paddings + scrollbar + borders + (positive) margins.
getWidth(elem, 'margin');
getHeight(elem, 'margin');

// OFFSETS

// Measure element's offset from it's owner document.
getOffset(elem);
// => { left: number, top: number }

// The second argument defines the offset root which is basically the
// element/document/window that the element's offset is measured from.
getOffset(elemA, elemB);

// By default the "border" box edge is used for the argument elements, but you
// can define another box edge also by using array syntax.
getOffset([elemA, 'padding'], [elemB, 'margin']);

API

getWidth()

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 document). Document width, in the context of this method, is either the document element's width (including it's scroll area) or window's width (without possible scrollbar), whichever is greater.

This method also measures custom subpixel scrollbar sizes accurately, which is something you don't bump into too often, but might be extremely important in those special cases where the site has custom subpixel scrollbars defined.

Syntax

type getWidth = (element: BoxElement, boxEdge: BoxElementEdge = 'border') => number;

Parameters

  1. element
    • The element which's width we want to measure.
    • Accepts: BoxElement.
  2. boxEdge
    • Defines which box edge of the element is considered as it's outer edge for the calculations.
    • For document this option is ignored since document cannot have any scrollbars, paddings, borders or margins.
    • For window only "content" (without vertical scrollbar's width) and "scrollbar" (with vertical scrollbar's width) are effective values. "padding" is normalized to "content" while "border" and "margin" are normalized to "scrollbar".
    • Accepts: BoxElementEdge.
    • Optional. Defaults to "border".

Returns

The element's width in pixels. Tries to always return fractional subpixel values, which is important for precise calculations. For window this is not possible, so it will return integer values.

Examples

import { getWidth } from 'mezr/getWidth';

// Document width.
getWidth(document);

// Window width (with scrollbar).
getWidth(window);

// Window width (without scrollbar).
getWidth(window, 'content');

// Element content-box width.
// Includes content only.
getWidth(elem, 'content');

// Element padding-box width.
// Includes content + paddings.
getWidth(elem, 'padding');

// Element padding-box + scrollbar width.
// Includes content + paddings + scrollbar.
getWidth(elem, 'scrollbar');

// Element border-box width.
// Includes content + paddings + scrollbar + borders.
getWidth(elem, 'border');
getWidth(elem);

// Element margin-box width.
// Includes content + paddings + scrollbar + borders + positive margins.
getWidth(elem, 'margin');

getHeight()

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). Document height, in the context of this method, is either the document element's height (including it's scroll area) or window's height (without possible scrollbar), whichever is greater.

This method also measures custom subpixel scrollbar sizes accurately, which is something you don't bump into too often, but might be extremely important in those special cases where the site has custom subpixel scrollbars defined.

Syntax

type getHeight = (element: BoxElement, boxEdge: BoxElementEdge = 'border') => number;

Parameters

  1. element
    • The element which's height we want to measure.
    • Accepts: BoxElement.
  2. boxEdge
    • Defines which box edge of the element is considered as it's outer edge for the calculations.
    • For document this option is ignored since document cannot have any scrollbars, paddings, borders or margins.
    • For window only "content" (without horizontal scrollbar's height) and "scrollbar" (with horizontal scrollbar's height) are effective values. "padding" is normalized to "content" while "border" and "margin" are normalized to "scrollbar".
    • Accepts: BoxElementEdge.
    • Optional. Defaults to "border".

Returns

The element's height in pixels. Tries to always return fractional subpixel values, which is important for precise calculations. For window this is not possible, so it will return integer values.

Examples

import { getHeight } from 'mezr/getHeight';

// Document height.
getHeight(document);

// Window height (with scrollbar).
getHeight(window);

// Window height (without scrollbar).
getHeight(window, 'content');

// Element content-box height.
// Includes content only.
getHeight(elem, 'content');

// Element padding-box height.
// Includes content + paddings.
getHeight(elem, 'padding');

// Element padding-box + scrollbar height.
// Includes content + paddings + scrollbar.
getHeight(elem, 'scrollbar');

// Element border-box height.
// Includes content + paddings + scrollbar + borders.
getHeight(elem, 'border');
getHeight(elem);

// Element margin-box height.
// Includes content + paddings + scrollbar + borders + positive margins.
getHeight(elem, 'margin');

getOffset()

Returns the element's offset from another element, window or document.

Syntax

type getOffset = (element: BoxObject, offsetRoot?: BoxObject) => { left: number; top: number };

Parameters

  1. element
    • The element which's offset we want to compute from the offset root.
    • Accepts: BoxObject.
  2. offsetRoot
    • The element from which the offset is computed to the target element.
    • Accepts: BoxObject.
    • Optional. Defaults to the first argument's closest document.

Returns

An object containing the element's offset (left and top) from the offset root in pixels.

Examples

import { getOffset } from 'mezr/getOffset';

// Document's offset from document.
getOffset(document);
// => { left: 0, top: 0 }

// Window's offset from document.
getOffset(window);
// => { left: window.scrollX, top: window.scrollY }

// Element's offset from it's owner document.
getOffset(elem);

// You can also define the element's box edge for the calculations.
getOffset([elem, 'content']);
getOffset([elem, 'padding']);
getOffset([elem, 'scrollbar']); // equals getOffset(elem, 'padding');
getOffset([elem, 'border']); // equals getOffset(elem);
getOffset([elem, 'margin']);

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

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

getRect()

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 box edge of the element, and also the element from which the offset is measured.

Syntax

type getRect = (element: BoxObject, offsetRoot?: BoxObject) => BoxRectFull;

Parameters

  1. element
    • The element which's dimensions and offset (from the offset root) we want to compute.
    • Accepts: BoxObject.
  2. offsetRoot
    • The element from which to compute the offset from.
    • Accepts: BoxObject.
    • Optional. Defaults to the first argument's closest document.

Returns

A BoxRectFull object containing the element's dimensions and offset (in pixels) from the offset root.

Examples

import { getRect } from 'mezr/getRect';

// Element's bounding rect data with offsets relative to the element's
// owner document.
getRect(elem);

// Element's bounding rect data with offsets relative to the window. Actually
// produces identical data to elem.getBoundingClienRect() with the exception
// that it doesn't have x and y properties in result data.
getRect(elem, window);

// You can also define the element's box edge for the calculations.
getRect([elem, 'content']);
getRect([elem, 'padding']);
getRect([elem, 'scrollbar']);
getRect([elem, 'border']); // equals getRect(elem);
getRect([elem, 'margin']);

// Element's bounding rect data with offsets from another element.
getRect(elem, anotherElem);
getRect([elem, 'padding'], [anotherElem, 'margin']);

getDistance()

Measure the shortest distance between two elements.

type getDistance = (elementA: BoxObject, elementB: BoxObject) => number | null;

Parameters

  1. elementA
  2. elementB

Returns

The shortest distance between two elements (in pixels), or null if the elements intersect. In case the elements are touching, but not intersecting, the returned distance is 0.

Examples

import { getDistance } from 'mezr/getDistance';

// Measure distance between two elements.
getDistance(elemA, elemB);

// Measure distance between element and window.
getDistance(elem, window);

// You can also define the elements' box edge for the calculations.
getDistance([elemA, 'content'], [elemB, 'scrollbar']);

getIntersection()

Measure the intersection area of two or more elements.

Syntax

type getIntersection = (...elements: BoxObject[]) => BoxRectFull | null;

Parameters

  1. ...elements
    • Provide at least two elements.
    • Accepts: BoxObject.

Returns

A BoxRectFull object containing the intersection area dimensions and offsets if all the provided elements intersect, otherwise returns null.

Examples

import { getIntersection } from 'mezr/getIntersection';

// Measure intersection area of two elements.
getIntersection(elemA, elemB);

// Measure intersection area of element and window.
getIntersection(elem, window);

// You can also define the elements' box edge for the calculations.
getIntersection([elemA, 'content'], [elemB, 'scrollbar']);

// You can provide as many elements as you want.
getIntersection(elemA, elemB, [elemC, 'scrollbar'], { left: 0, top: 0, width: 100, height: 100 });

getOverflow()

Measure how much target overflows container per each side.

Syntax

type getOverflow = (
  target: BoxObject,
  container: BoxObject,
) => {
  left: number;
  right: number;
  top: number;
  bottom: number;
};

Parameters

  1. target
  2. container

Returns

An object containing the overflow values for each side: left, right, top, bottom. Note that the overflow values are reported even if the elements don't intersect. If a side's value is positive it means that target overflows container by that much from that side. If the value is negative it means that container overflows target by that much from that side.

Examples

import { getOverflow } from 'mezr/getOverflow';

// Measure how much elemA overflows elemB per each side.
getOverflow(elemA, elemB);

// Measure how much elem overflows window per each side.
getOverflow(elem, window);

// You can also define the elements' box edges for the calculations.
getOverflow([elemA, 'content'], [elemB, 'scrollbar']);

getContainingBlock()

Returns the element's containing block, meaning the closest element/document/window which the target element's percentage-based width, height, inset, left, right, top, bottom, padding, and margin properties are relative to in terms of size. In case the containing block can not be computed null will be returned (e.g. in some cases we can't query all the information needed from elements with display:none).

Important

Containing block is often thought to be both:

  1. The element which the target element's percentage-based width, height, inset, left, right, top, bottom, padding, and margin properties are relative to in terms of size. For example, if the target element's left is set to 50% and the containing block's width is 100px, the target element's left will be 50px.
  2. The element which the target element's inset, left, right, top, bottom are relative to in terms of position. E.g., if the target element's left is set to 0, it will be positioned at the containing block's left edge.

However, in reality, these are not the same element always, and this method returns the answer to the first case. We have another method, getOffsetContainer, for the second case.

Syntax

type getContainingBlock = (
  element: HTMLElement | SVGSVGElement,
  options: { container?: HTMLElement; position?: string; skipDisplayNone?: boolean } = {},
) => HTMLElement | Window | null;

Parameters

  1. element
    • The element which's containing block we want to compute.
    • Accepts: HTMLElement |Β SVGSVGElement.
  2. options
    • Optional options object.
    • container
      • Forcefully provide the element's parent element if you want to compute the containing block as if the element was a child of some other element than it's current parent. If not provided the element's parent element will be queried from the element.
      • Accepts: HTMLElement.
      • Defaults to undefined.
    • position
      • Forcefully provide the element's position value for the calculations. If not provided the element's position will be queried from the element.
      • Accepts: string.
      • Defaults to "".
    • skipDisplayNone
      • Defines how to treat "display:none" ancestor elements when computing the containing block in the specific scenarios where we need to know if an ancestor is a block or inline element. By default this is false, which means that null will be returned by the method in these scenarios, indicating that containing block could not be resolved. If set to true all the "display:none" ancestors will be treated as inline elements, meaning that they will be skipped in these problematic scenarios.
      • Accepts: boolean.
      • Defaults to false.

Examples

import { getContainingBlock } from 'mezr/getContainingBlock';

// Get element's containing block.
getContainingBlock(elem);

// Get element's containing block as if it were a fixed element.
getContainingBlock(elem, { position: 'fixed' });

// Get element's containing block as if it were a child of document.body.
getContainingBlock(elem, { container: document.body });

// Get element's containing block while treating all the "display:none"
// ancestors as "display:inline" elements.
getContainingBlock(elem, { skipDisplayNone: true }});

getOffsetContainer()

Returns the element's offset container, meaning the closest element/document/window that the target element's inset, left, right, top and bottom CSS properties are relative to in terms of position. If the offset container can't be computed or the element is not affected by left/right/top/bottom CSS properties (e.g. static elements) null will be returned (in some cases we can't query all the information needed from elements with display:none). Additionally, due to the dynamic nature of sticky elements they are considered as static elements in this method's scope and will always return null.

Syntax

type getOffsetContainer = (
  element: HTMLElement | SVGSVGElement,
  options: { position?: string; skipDisplayNone?: boolean } = {},
) => HTMLElement | SVGSVGElement | Document | Window | null;

Parameters

  1. element
    • The element which's offset container we want to compute.
    • Accepts: HTMLElement | SVGSVGElement.
  2. options
    • Optional options object.
    • Accepts the following optional properties:
      • container
        • Forcefully provide the element's parent element if you want to compute the containing block as if the element was a child of some other element than it's current parent. If not provided the element's parent element will be queried from the element.
        • Accepts: HTMLElement.
        • Defaults to undefined.
      • position
        • Forcefully provide the element's position value for the calculations. If not provided the element's position will be queried from the element.
        • Accepts: string.
        • Defaults to "".
      • skipDisplayNone
        • Defines how to treat "display:none" ancestor elements when computing the offset container in the specific scenarios where we need to know if an ancestor is a block or inline element. By default this is false, which means that null will be returned by the method in these scenarios, indicating that offset container could not be resolved. If set to true all the "display:none" ancestors will be treated as inline elements, meaning that they will be skipped in these problematic scenarios.
        • Accepts: boolean.
        • Defaults to false.

Examples

import { getOffsetContainer } from 'mezr/getOffsetContainer';

// Get element's offset container.
getOffsetContainer(elem);

// Get element's offset container as if it were a fixed element.
getOffsetContainer(elem, { position: 'fixed' });

// Get element's offset container as if it were a child of document.body.
getOffsetContainer(elem, { container: document.body });

// Get element's offset container while treating all the "display:none"
// ancestors as "display:inline" elements.
getOffsetContainer(elem, { skipDisplayNone: true });

Types

BoxElementEdge

type BoxElementEdge = 'content' | 'padding' | 'scrollbar' | 'border' | 'margin';

In many methods you can explicitly define the box edge of the element for the calculcations. In practice the box edge indicates which parts of the the element are considered as part of the element. "border" box edge is always the default. The following table illustrates the different box edges.

Box edge Description
"content" The element's content box.
"padding" The element's content box + paddings.
"scrollbar" The element's content box + paddings + scrollbar.
"border" The element's content box + paddings + scrollbar + borders.
"margin" The element's content box + paddings + scrollbar + borders + positive margins.

BoxRect

type BoxRect = {
  width: number;
  height: number;
  left: number;
  top: number;
};

In many methods you can provide the raw rectangle data of the element instead of the element itself. The rectangle data is an object containing the element's dimensions and offsets.

Property Description
width The element's width in pixels.
height The element's height in pixels.
left The element's left edge offset from the left edge of the document in pixels.
top The element's top edge offset from the top edge of the document in pixels.

BoxRectFull

type BoxRectFull = {
  width: number;
  height: number;
  left: number;
  top: number;
  right: number;
  bottom: number;
};

This is a variation of the BoxRect type that also contains the element's right and bottom edge offsets.

Property Description
width The element's width in pixels.
height The element's height in pixels.
left The element's left edge offset from the left edge of the document in pixels.
top The element's top edge offset from the top edge of the document in pixels.
right The element's right edge offset from the left edge of the document in pixels.
bottom The element's bottom edge offset from the top edge of the document in pixels.

BoxElement

type BoxElement = Element | Document | Window;

Box element can be any HTML/SVG element, Document or Window.

BoxObject

type BoxObject = BoxElement | [BoxElement, BoxElementEdge] | BoxRect;

Many methods allow you to define either a box element, a box element with a box edge or a box rectangle data object.

Type Description
BoxElement Any HTML/SVG element, Document or Window. Uses "border" box edge.
[BoxElement, BoxElementEdge] Any HTML/SVG element, Document or Window with box edge explicitly defined.
BoxRect An object containing the element's dimensions and offsets.

Browser support

Mezr supports all modern browsers (Chrome, Firefox, Safari, Edge) and is tested on various devices and platforms. Thanks to BrowserStack for sponsoring the testing infrastructure!

Tested browsers

OS Version Device Browser Version
Windows 11 - Chrome Latest
Windows 11 - Firefox Latest
Windows 11 - Edge Latest
OS X Sonoma - Chrome Latest
OS X Sonoma - Firefox Latest
OS X Sonoma - Edge Latest
OS X Sonoma - Safari Latest
OS X Ventura - Chrome Latest
OS X Ventura - Firefox Latest
OS X Ventura - Edge Latest
OS X Ventura - Safari Latest
iOS 16 iPhone 14 Safari Latest
iOS 17 iPhone 15 Safari Latest
Android 13 Samsung Galaxy S23 Chrome Latest
Android 12 Samsung Galaxy S22 Chrome Latest
Android 11 Samsung Galaxy S21 Chrome Latest
Android 10 Samsung Galaxy S20 Chrome Latest
Android 9 Samsung Galaxy S10 Chrome Latest
Android 14 Google Pixel 6 Pro Chrome Latest
Android 13 Google Pixel 7 Chrome Latest
Android 11 OnePlus 9 Chrome Latest

License

Copyright Β© 2015 Niklas RΓ€mΓΆ. Licensed under the MIT license.

mezr's People

Contributors

niklasramo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

mezr's Issues

Project site to gh-pages

Build a project site to gh-pages which demonstrates how Mezr works with live examples. Make it look nice, if possible.

Handle "position: sticky"

When Mezr was built there was no sticky position, but now there is. So let's add some logic to the code to factor that in.

Basically this change should only affect getContainingBlock method.

Allow defining the element which the offset is calculated relative to in .offset() method

Currently the .offset() method mimics the behaviour of jQuery's .offset() method, meaning that the measured element's offset is always relative to the document. However it would be pretty nice if one could define which element (or window, or document) the offset is relative to. This would also compensate the fact that Mezr currently does not have a comparable API method to jQuery's .position() method. This could be done without breaking backwards compatibility.

If the second argument is not a string we assume it is an element (or window, or document) definition. As per Mezr's generic API convention the element can be described with an array ([elem, edgeLayer]) or as an element.

The default syntax:

mezr.offset(elem, edgeLayer);

The new additional syntax:

mezr.offset([elem, edgeLayer], [elem, edgeLayer]);
mezr.offset([elem, edgeLayer]);

With the new syntax we could also mimic jQuery's position method pretty easily.

mezr.offset(elem, mezr.containingBlock(elem));

Allow adding the script tag to head

Currently Mezr needs to be included in the body element, because it does some browser feature checking where it needs to inject two elements to DOM temporarily. It would be really nice if we could test this some other way reliably, but don't know if that's possible.

Anyhow, we could add this kind of check (which actually was implemented in a development version for some time):

if (document.body) {
  // Do browser behaviour checking...
}
else {
  document.addEventListener("DOMContentLoaded", function() {
    // Do browser behaviour checking...
  });
}

However, the problem in this approach is that we'd need to add a new Mezr method which can be used to check if Mezr is ready, just like with jQuery.

mezr.ready(function () {
  // do mezr calculations here.
});

It might not be a big deal and at least it would guarantee that stuff works as intended, also when including Mezr in the head. However, personally, I'd prefer Mezr to just work when it's included in the site.

So yep, now we need to think if we can live with the fact that Mezr needs to be included in the body or should we make it possible to add Mezr to the head and provide the new .ready() method for checking when Mezr is ready for usage.

And if you're wondering why on earth stuff needs to be injected to DOM to test something, it's because of this: https://bugs.chromium.org/p/chromium/issues/detail?id=20574. The test checks if transformed elements can contain fixed elements or not.

More accurate scrollbar width/height.

Currently the scrollbar widht/height calculation logic works correctly only if the width/height of the scrollbar is an integer, which it is in most cases (probably 99.9% of the time). However, you can customize the default scrollbar dimensions and styles in webkit browsers and in some of them (e.g. Chrome) and set the width/height to sub-pixel values, e.g 12.5px. In these special scenarios Mezr fails to compute the correct width/height of the element when box edge is "content" or "padding", which is not ideal.

We can compute sub-pixel precise scrollbar width/height by doing the following:

  1. Check if the element has an overflow value that allows scrollbar to appear.
  2. Place a temporary child element within the scrollable element and and give it the following CSS:
    .scroll-container-child-test {
      position: absolute;
      inset: 0;
    }
  3. Compute bounding client rects of the child and the scroll container.
  4. Subtract borders from the scroll container.
  5. Subtract the child's width and height from the scroll container's width and height.
  6. Remove the temporary child element.

However, this is a pretty heavy procedure to always do, and is mainly useful to be used in unit tests to make sure that the precise scrollbar width/height matches the one computed by Mezr. Also, this would cause a reflow as we are mutating the DOM between the read operations, which is definitely not ideal.

Let's see if we can come up with an alternative faster method to compute the precise scrollbar width/height.

Account for fixed elements being contained within transformed elements

W3C spec defines that fixed elements should be contained within the closest transformed element (if one exists) instead of window. However, IE and some older versions of Firefox leak fixed elements from within transformed elements. Currently .offsetParent() method returns wrong values due to this. So let's fix it by adding a test which tests the browsers behaviour and takes it into account when determining the offset parent.

rect method

Currently Mezr does not have a direct alternative to element.getBoundingClientRect(), but it should definitely have. So let's add a new method called simply rect, which accepts element as the first argument and also allows specifying the element's edge for the calculations. The method should return the element's left, top, right, bottom, width and height values. The syntax would be this: mezr.rect(el, edge).

place method review and updates

Review place method and compare it against Tether. Is needed at all in the end or is it just bloating the library? One point to argue for keeping it in the API is that it's actually really useful method and compared to Tether (while not as multi-faceted) it's considerably more light-weight. If it is to stay in the API, think about how it could be updated to be the even more useful and simpler to use.

New method: mezr.overflow(container, elem)

After adding ajdust callback option to .place() method I realized that getting overflow data between two elements might be worth a new method. So let's make one, internally we got the function for it ready and standing by, let's just expose it.

Account for element's transform values and border radius

At the moment all the methods assume that the element is a non-transformed rectangle. It would be awesome if Mezr automatically (or optionally) accounted for the element's possible transforms and border radius when calculating distances and offsets.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.