5 Nisan 2019

Coordinates

To move elements around we should be familiar with coordinates.

Most JavaScript methods deal with one of two coordinate systems:

  1. Relative to the window(or another viewport) top/left.
  2. Relative to the document top/left.

It’s important to understand the difference and which type is where.

Window coordinates: getBoundingClientRect

Window coordinates start at the upper-left corner of the window.

The method elem.getBoundingClientRect() returns window coordinates for elem as an object with properties:

  • top – Y-coordinate for the top element edge,
  • left – X-coordinate for the left element edge,
  • right – X-coordinate for the right element edge,
  • bottom – Y-coordinate for the bottom element edge.

Like this:

Window coordinates do not take the scrolled out part of the document into account, they are calculated from the window’s upper-left corner.

In other words, when we scroll the page, the element goes up or down, its window coordinates change. That’s very important.

Click the button to see its window coordinates:

If you scroll the page, the button position changes, and window coordinates as well.

Also:

  • Coordinates may be decimal fractions. That’s normal, internally browser uses them for calculations. We don’t have to round them when setting to style.position.left/top, the browser is fine with fractions.
  • Coordinates may be negative. For instance, if the page is scrolled down and the top elem is now above the window. Then, elem.getBoundingClientRect().top is negative.
  • Some browsers (like Chrome) provide additional properties, width and height of the element that invoked the method to getBoundingClientRect as the result. We can also get them by subtraction: height=bottom-top, width=right-left.
Coordinates right/bottom are different from CSS properties

If we compare window coordinates versus CSS positioning, then there are obvious similarities to position:fixed. The positioning of an element is also relative to the viewport.

But in CSS, the right property means the distance from the right edge, and the bottom property means the distance from the bottom edge.

If we just look at the picture above, we can see that in JavaScript it is not so. All window coordinates are counted from the upper-left corner, including these ones.

elementFromPoint(x, y)

The call to document.elementFromPoint(x, y) returns the most nested element at window coordinates (x, y).

The syntax is:

let elem = document.elementFromPoint(x, y);

For instance, the code below highlights and outputs the tag of the element that is now in the middle of the window:

let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;

let elem = document.elementFromPoint(centerX, centerY);

elem.style.background = "red";
alert(elem.tagName);

As it uses window coordinates, the element may be different depending on the current scroll position.

For out-of-window coordinates the elementFromPoint returns null

The method document.elementFromPoint(x,y) only works if (x,y) are inside the visible area.

If any of the coordinates is negative or exceeds the window width/height, then it returns null.

In most cases such behavior is not a problem, but we should keep that in mind.

Here’s a typical error that may occur if we don’t check for it:

let elem = document.elementFromPoint(x, y);
// if the coordinates happen to be out of the window, then elem = null
elem.style.background = ''; // Error!

Using for position:fixed

Most of time we need coordinates to position something. In CSS, to position an element relative to the viewport we use position:fixed together with left/top (or right/bottom).

We can use getBoundingClientRect to get coordinates of an element, and then to show something near it.

For instance, the function createMessageUnder(elem, html) below shows the message under elem:

let elem = document.getElementById("coords-show-mark");

function createMessageUnder(elem, html) {
  // create message element
  let message = document.createElement('div');
  // better to use a css class for the style here
  message.style.cssText = "position:fixed; color: red";

  // assign coordinates, don't forget "px"!
  let coords = elem.getBoundingClientRect();

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

// Usage:
// add it for 5 seconds in the document
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);

Click the button to run it:

The code can be modified to show the message at the left, right, below, apply CSS animations to “fade it in” and so on. That’s easy, as we have all the coordinates and sizes of the element.

But note the important detail: when the page is scrolled, the message flows away from the button.

The reason is obvious: the message element relies on position:fixed, so it remains at the same place of the window while the page scrolls away.

To change that, we need to use document-based coordinates and position:absolute.

Document coordinates

Document-relative coordinates start from the upper-left corner of the document, not the window.

In CSS, window coordinates correspond to position:fixed, while document coordinates are similar to position:absolute on top.

We can use position:absolute and top/left to put something at a certain place of the document, so that it remains there during a page scroll. But we need the right coordinates first.

For clarity we’ll call window coordinates (clientX,clientY) and document coordinates (pageX,pageY).

When the page is not scrolled, then window coordinate and document coordinates are actually the same. Their zero points match too:

And if we scroll it, then (clientX,clientY) change, because they are relative to the window, but (pageX,pageY) remain the same.

Here’s the same page after the vertical scroll:

  • clientY of the header "From today's featured article" became 0, because the element is now on window top.
  • clientX didn’t change, as we didn’t scroll horizontally.
  • pageX and pageY coordinates of the element are still the same, because they are relative to the document.

Getting document coordinates

There’s no standard method to get the document coordinates of an element. But it’s easy to write it.

The two coordinate systems are connected by the formula:

  • pageY = clientY + height of the scrolled-out vertical part of the document.
  • pageX = clientX + width of the scrolled-out horizontal part of the document.

The function getCoords(elem) will take window coordinates from elem.getBoundingClientRect() and add the current scroll to them:

// get document coordinates of the element
function getCoords(elem) {
  let box = elem.getBoundingClientRect();

  return {
    top: box.top + pageYOffset,
    left: box.left + pageXOffset
  };
}

Summary

Any point on the page has coordinates:

  1. Relative to the window – elem.getBoundingClientRect().
  2. Relative to the document – elem.getBoundingClientRect() plus the current page scroll.

Window coordinates are great to use with position:fixed, and document coordinates do well with position:absolute.

Both coordinate systems have their “pro” and “contra”, there are times we need one or the other one, just like CSS position absolute and fixed.

Görevler

önem: 5

In the iframe below you can see a document with the green “field”.

Use JavaScript to find window coordinates of corners pointed by with arrows.

There’s a small feature implemented in the document for convenience. A click at any place shows coordinates there.

Your code should use DOM to get window coordinates of:

  1. Upper-left, outer corner (that’s simple).
  2. Bottom-right, outer corner (simple too).
  3. Upper-left, inner corner (a bit harder).
  4. Bottom-right, inner corner (there are several ways, choose one).

The coordinates that you calculate should be the same as those returned by the mouse click.

P.S. The code should also work if the element has another size or border, not bound to any fixed values.

Görevler için korunaklı alan aç.

Outer corners

Outer corners are basically what we get from elem.getBoundingClientRect().

Coordinates of the upper-left corner answer1 and the bottom-right corner answer2:

let coords = elem.getBoundingClientRect();

let answer1 = [coords.left, coords.top];
let answer2 = [coords.right, coords.bottom];

Left-upper inner corner

That differs from the outer corner by the border width. A reliable way to get the distance is clientLeft/clientTop:

let answer3 = [coords.left + field.clientLeft, coords.top + field.clientTop];

Right-bottom inner corner

In our case we need to substract the border size from the outer coordinates.

We could use CSS way:

let answer4 = [
  coords.right - parseInt(getComputedStyle(field).borderRightWidth),
  coords.bottom - parseInt(getComputedStyle(field).borderBottomWidth)
];

An alternative way would be to add clientWidth/clientHeight to coordinates of the left-upper corner. That’s probably even better:

let answer4 = [
  coords.left + elem.clientLeft + elem.clientWidth,
  coords.top + elem.clientTop + elem.clientHeight
];

Çözümü korunaklı alanda aç.

önem: 5

Create a function positionAt(anchor, position, elem) that positions elem, depending on position either at the top ("top"), right ("right") or bottom ("bottom") of the element anchor.

Call it inside the function showNote(anchor, position, html) that shows an element with the class "note" and the text html at the given position near the anchor.

Show the notes like here:

P.S. The note should have position:fixed for this task.

Görevler için korunaklı alan aç.

In this task we only need to accurately calculate the coordinates. See the code for details.

Please note: the elements must be in the document to read offsetHeight and other properties. A hidden (display:none) or out of the document element has no size.

Çözümü korunaklı alanda aç.

önem: 5

Modify the solution of the previous task so that the note uses position:absolute instead of position:fixed.

That will prevent its “runaway” from the element when the page scrolls.

Take the solution of that task as a starting point. To test the scroll, add the style <body style="height: 2000px">.

The solution is actually pretty simple:

  • Use position:absolute in CSS instead of position:fixed for .note.
  • Use the function getCoords() from the chapter Coordinates to get document-relative coordinates.

Çözümü korunaklı alanda aç.

önem: 5

Extend the previous task Show a note near the element (absolute): teach the function positionAt(anchor, position, elem) to insert elem inside the anchor.

New values for position:

  • top-out, right-out, bottom-out – work the same as before, they insert the elem over/right/under anchor.
  • top-in, right-in, bottom-in – insert elem inside the anchor: stick it to the upper/right/bottom edge.

For instance:

// shows the note above blockquote
positionAt(blockquote, "top-out", note);

// shows the note inside blockquote, at the top
positionAt(blockquote, "top-in", note);

The result:

As the source code, take the solution of the task Show a note near the element (absolute).

Eğitim haritası