Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@
* relative to the anchor element.
*/
function showNote(anchor, position, html) {
// ... your code ...

let note = document.createElement('div');
note.className = "note";
note.innerHTML = html;
document.body.append(note);

positionAt(anchor, position, note);
}

// test it
Expand Down
13 changes: 8 additions & 5 deletions 2-ui/1-document/11-coordinates/2-position-at/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ importance: 5

# Show a note near the element

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`.
Create a function `positionAt(anchor, position, elem)` that positions `elem`, depending on `position` near `anchor` element.

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.
The `position` must be a string with any one of 3 values:
- `"top"` - position `elem` right above `anchor`
- `"right"` - position `elem` immediately at the right of `anchor`
- `"bottom"` - position `elem` right below `anchor`

Show the notes like here:
It's used inside function `showNote(anchor, position, html)`, provided in the task source code, that creates a "note" element with given `html` and shows it at the given `position` near the `anchor`.

[iframe src="solution" height="350" border="1" link]
Here's the demo of notes:

P.S. The note should have `position:fixed` for this task.
[iframe src="solution" height="350" border="1" link]
110 changes: 61 additions & 49 deletions 2-ui/1-document/11-coordinates/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,68 @@ 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.
1. **Relative to the window** - similar to `position:fixed`, calculated from the window top/left edge.
- we'll denote these coordinates as `clientX/clientY`, the reasoning for such name will become clear later, when we study event properties.
2. **Relative to the document** - similar to `position:absolute` in the document root, calculated from the document top/left edge.
- we'll denote them `pageX/pageY`.

It's important to understand the difference and which type is where.
When the page is scrolled to the very beginning, so that the top/left corner of the window is exactly the document top/left corner, these coordinates equal each other. But after the document shifts, window-relative coordinates of elements change, as elements move across the window, while document-relative coordinates remain the same.

## Window coordinates: getBoundingClientRect
On this picture we take a point in the document and demonstrate its coordinates before the scroll (left) and after it (right):

Window coordinates start at the upper-left corner of the window.
![](document-and-window-coordinates-scrolled.svg)

The method `elem.getBoundingClientRect()` returns window coordinates for `elem` as an object with properties:
When the document scrolled:
- `pageY` - document-relative coordinate stayed the same, it's counted from the document top (now scrolled out).
- `clientY` - window-relative coordinate did change (the arrow became shorter), as the same point became closer to window top.

- `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.
## Element coordinates: getBoundingClientRect

Like this:
The method `elem.getBoundingClientRect()` returns window coordinates for a minimal rectangle that encloses `elem` as an object of built-in [DOMRect](https://www.w3.org/TR/geometry-1/#domrect) class.

![](coords.png)
Main `DOMRect` properties:

- `x/y` -- X/Y-coordinates of the rectangle origin relative to window,
- `width/height` -- width/height of the rectangle (can be negative).

Window coordinates do not take the scrolled out part of the document into account, they are calculated from the window's upper-left corner.
Additionally, there are derived properties:

In other words, when we scroll the page, the element goes up or down, *its window coordinates change*. That's very important.
- `top/bottom` -- Y-coordinate for the top/bottom rectangle edge,
- `left/right` -- X-coordinate for the left/right rectangle edge.

```online
Click the button to see its window coordinates:
For instance click this button to see its window coordinates:

<input id="brTest" type="button" value="Show button.getBoundingClientRect() for this button" onclick='showRect(this)'/>
<p><input id="brTest" type="button" value="Get coordinates using button.getBoundingClientRect() for this button" onclick='showRect(this)'/></p>

<script>
function showRect(elem) {
let r = elem.getBoundingClientRect();
alert("{top:"+r.top+", left:"+r.left+", right:"+r.right+", bottom:"+ r.bottom + "}");
alert(`x:${r.x}
y:${r.y}
width:${r.width}
height:${r.height}
top:${r.top}
bottom:${r.bottom}
left:${r.left}
right:${r.right}
`);
}
</script>

If you scroll the page, the button position changes, and window coordinates as well.
If you scroll the page and repeat, you'll notice that as window-relative button position changes, its window coordinates (`y/top/bottom` if you scroll vertically) change as well.
```

Also:
Here's the picture of `elem.getBoundingClientRect()` output:

- 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.svg)

```warn header="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.
As you can see, `x/y` and `width/height` fully describe the rectangle. Derived properties can be easily calculated from them:

But in CSS, the `right` property means the distance from the right edge, and the `bottom` property means the distance from the bottom edge.
- `left = x`
- `top = y`
- `right = x + width`
- `bottom = y + height`

Please note:

Expand Down Expand Up @@ -119,8 +131,6 @@ The method `document.elementFromPoint(x,y)` only works if `(x,y)` are inside the

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:

```js
Expand All @@ -132,11 +142,11 @@ elem.style.background = ''; // Error!
```
````

## Using for position:fixed
## Using for "fixed" positioning

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`).
Most of time we need coordinates in order to position something.

We can use `getBoundingClientRect` to get coordinates of an element, and then to show something near it.
To show something near an element, we can use `getBoundingClientRect` to get its coordinates, and then CSS `position` together with `left/top` (or `right/bottom`).

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

Expand Down Expand Up @@ -183,32 +193,14 @@ The reason is obvious: the message element relies on `position:fixed`, so it rem

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

## Document coordinates
## Document coordinates [#getCoords]

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:

![](document-window-coordinates-zero.png)

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:

![](document-window-coordinates-scroll.png)

- `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 [#getCoords]

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:
Expand All @@ -231,6 +223,26 @@ function getCoords(elem) {
}
```

If in the example above we used it with `position:absolute`, then the message would stay near the element on scroll.

The modified `createMessageUnder` function:

```js
function createMessageUnder(elem, html) {
let message = document.createElement('div');
message.style.cssText = "*!*position:absolute*/!*; color: red";

let coords = *!*getCoords(elem);*/!*

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

message.innerHTML = html;

return message;
}
```

## Summary

Any point on the page has coordinates:
Expand Down
Binary file removed 2-ui/1-document/11-coordinates/coords.png
Binary file not shown.
Binary file removed 2-ui/1-document/11-coordinates/coords@2x.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
34 changes: 17 additions & 17 deletions 2-ui/1-document/11-coordinates/head.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
<script>
document.addEventListener('DOMContentLoaded', function() {

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

// no elem in ebook mode
if (elem) {
elem.onclick = function() {
// no elem in ebook (pdf/epub) mode
if (elem) {
elem.onclick = function() {

function createMessageUnder(elem, text) {
let coords = elem.getBoundingClientRect();
let message = document.createElement('div');
message.style.cssText = "position:fixed; color: red";
function createMessageUnder(elem, text) {
let coords = elem.getBoundingClientRect();
let message = document.createElement('div');
message.style.cssText = "position:fixed; color: red";

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

message.innerHTML = text;
message.innerHTML = text;

return message;
}
return message;
}

let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);
}
}
}

});

Expand Down