Introducing the getBoxQuads API

Web developers often need to determine where an element has been placed in the page, or more generally, where it is relative to another element. Existing APIs for doing this have significant limitations. The new GeometryUtils interface and its supporting interfaces DOMPoint, DOMRect and DOMQuad provide Web-standard APIs to address these problems. Firefox is the first browser to implement these APIs; they are available in Firefox 31 Nightly builds.

Current best standardized APIs for retrieving element geometry

Currently the best standardized DOM APIs for retrieving element geometry are element.getBoundingClientRect() and element.getClientRects(). These return the border-box rectangle(s) for an element relative to the viewport of the containing document. These APIs are supported cross-browser but have several limitations:

  • When complex CSS transforms are present, they return the smallest axis-aligned rectangle enclosing the transformed border-box. This loses information.
  • There is no way to obtain the coordinates of the content-box, padding-box or border-box. In simple cases you can add or subtract computed style values from the results of getBoundingClientRect()/getClientRects() but this is clumsy and difficult to get right. For example, when a <span> breaks into several fragments, its left border is only added to one of the fragments — either the first or the last, depending on the directionality of the text.
  • There is no way to obtain box geometry relative to another element.

Introducing getBoxQuads()

The GeometryUtils.getBoxQuads() method, implemented on Document, Element and TextNode, solves these problems. It returns a list of DOMQuads, one for each CSS fragment of the object (normally this list would just have a single
DOMQuad).

Example:

var quads = document.getElementById("d").getBoxQuads();
// quads.length == 1
// quads[0].p1.x == 100
// quads[0].p1.y == 100
// quads[0].p3.x == 200
// quads[0].p3.y == 200
p1
p2
p3
p4

Using bounds

A DOMQuad is a collection of four DOMPoints defining the corners of an arbitrary quadrilateral. Returning DOMQuads lets getBoxQuads() return accurate information even when arbitrary 2D or 3D transforms are present. It has a handy bounds attribute returning a DOMRectReadOnly for those cases where you just want an axis-aligned bounding rectangle.

For example:

var quads = document.getElementById("d").getBoxQuads();
// quads[0].p1.x == 150
// quads[0].p1.y == 150 - 50*sqrt(2) (approx)
// quads[0].p3.x == 150
// quads[0].p3.y == 150 + 50*sqrt(2) (approx)
// quads[0].bounds.width == 100*sqrt(2) (approx)
p1
p2
p3
p4
bounds

Passing in options

By default getBoxQuads() returns border-boxes relative to the node’s document viewport, but this can be customized by passing in an optional
options dictionary with the following (optional) members:

  • box: one of "content", "padding", "border" or "margin", selecting which CSS box type to return.
  • relativeTo: a Document, Element or TextNode; getBoxQuads() returns coordinates relative to the top-left of the border-box of that node (the border-box of the first fragment, if there’s more than one fragment). For documents, the origin of the document’s viewport is used.

Example:

var quads = document.getElementById("e").getBoxQuads({
  relativeTo:document.getElementById("d")
});
// quads[0].p1.x == 0
// quads[0].p1.y == 0

quads = document.getElementById("e").getBoxQuads({
  relativeTo:document.getElementById("d"),
  box:"content"
});
// quads[0].p1.x == 20
// quads[0].p1.y == 20
d
e content-box
e border-box

The relativeTo node need not be an ancestor of the node receiving getBoxQuads(). The nodes can even be in different documents, although they must be in the same toplevel browsing context (i.e. browser tab).

Scratching the surface

If you’ve read this far, you’re probably observant enough to have noticed additional methods in GeometryUtils — methods for coordinate conversion. These will be covered in a future blog post.

About roc

Robert O'Callahan is a distinguished engineer at Mozilla Corporation. Prior to joining MoCo he was a volunteer Mozilla contributor for several years (since 2000).

More articles by roc…

About Robert Nyman [Editor emeritus]

Technical Evangelist & Editor of Mozilla Hacks. Gives talks & blogs about HTML5, JavaScript & the Open Web. Robert is a strong believer in HTML5 and the Open Web and has been working since 1999 with Front End development for the web - in Sweden and in New York City. He regularly also blogs at http://robertnyman.com and loves to travel and meet people.

More articles by Robert Nyman [Editor emeritus]…


10 comments

  1. Aditya Bhatt

    This looks amazing and should prove incredibly useful in WebODF when ready. :-)
    Is there a good polyfill maintained somewhere?

    March 27th, 2014 at 03:04

    1. Robert Nyman [Editor]

      Not aware of any polyfill at this time, and how that would be useful/working in this context.

      March 28th, 2014 at 07:52

  2. PhistucK

    Please, please, please, do not encourage developers to reference elements according to their ID as a global variable. Either use document.querySelector(“#d”) or whatever, never encourage developers to just use “d”.
    Can you fix the examples?

    March 27th, 2014 at 12:48

    1. Robert Nyman [Editor]

      Good point, I’ve updated the article.

      March 28th, 2014 at 09:57

      1. PhistucK

        Great, thank you!

        March 28th, 2014 at 09:59

  3. Aras

    Cool stuff! This will be very useful. One thing I often wish was available is a way to listen to changes on the bounds of a dom element. Has there been any progress on that as well?

    March 27th, 2014 at 15:52

    1. Robert O’Callahan

      No, that is a separate problem (but an important one!).

      March 28th, 2014 at 08:31

  4. Mikael Gramont

    “[getBoxQuads] returns a list of DOMQuads, one for each CSS fragment of the object (normally this list would just have a single DOMQuad).”

    I’m not familiar with the notion of CSS fragment. When would this return more than one DOMQuad?

    April 2nd, 2014 at 21:47

    1. thinsoldier

      When you have a span or text node that wraps across multiple lines I think. Each line would be a separate rectangle.

      April 3rd, 2014 at 14:27

      1. Robert O’Callahan

        Yes. Also if you have a CSS block broken across multiple columns. There are other cases too.

        April 10th, 2014 at 21:00

Comments are closed for this article.