This commit is contained in:
Ilya Kantor 2017-02-27 20:24:37 +03:00
parent caede8fe18
commit 4272b7bb13
32 changed files with 830 additions and 205 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -6,7 +6,7 @@ The coordinates start from the inner left-upper corner of the field:
The inner field width/height is `clientWidth/clientHeight`. So the field center has coordinates `(clientWidth/2, clientHeight/2)`.
...But if we set such values to `ball.style.left/top`, then not the ball as a whole, but the left-upper edge of the ball is in the center:
...But if we set `ball.style.left/top` to such values, then not the ball as a whole, but the left-upper edge of the ball would be in the center:
```js
ball.style.left = Math.round(field.clientWidth / 2) + 'px';
@ -26,24 +26,29 @@ ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'p
**Attention: the pitfall!**
The code won't work reliably while `<img>` width/height is not given to the browser:
The code won't work reliably while `<img>` has no width/height:
```html
<img src="ball.png" id="ball">
```
When the browser meets such a tag, it tries to figure out its width/height. Either from the `<img width=... height=...>` attributes or (if not given) from CSS.
When the browser does not know the width/height of an image (from tag attributes or CSS), then it assumes them to equal `0` until the image finishes loading.
If none is given, then the sizes are assumed to be `0` until the image loads.
In real life after the first load browser usually caches the image, and on next loads it will have the size immediately.
TODO
But on the first load the value of `ball.offsetWidth` is `0`. That leads to wrong coordinates.
После первой загрузки изображение уже будет в кеше браузера, и его размеры будут известны. Но когда браузер впервые видит документ -- он ничего не знает о картинке, поэтому значение `ball.offsetWidth` равно `0`. Вычислить координаты невозможно.
Чтобы это исправить, добавим `width/height` к картинке:
We should fix that by adding `width/height` to `<img>`:
```html
<img src="ball.png" *!*width="40" height="40"*/!* id="ball">
```
Теперь браузер всегда знает ширину и высоту, так что все работает. Тот же эффект дало бы указание размеров в CSS.
...Or provide the size in CSS:
```css
#ball {
width: 40px;
height: 40px;
}
```

View file

@ -9,7 +9,7 @@
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
@ -20,15 +20,12 @@
<div id="field">
<img src="https://js.cx/clipart/ball.svg" width="40" height="40" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
<img src="https://en.js.cx/clipart/ball.svg" width="40" height="40" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
var ball = document.getElementById('ball')
var field = document.getElementById('field')
// ball.offsetWidth=0 before image loaded!
// to fix: set width
ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px'
@ -38,4 +35,4 @@
</body>
</html>
</html>

View file

@ -9,7 +9,7 @@
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
@ -20,11 +20,11 @@
<div id="field">
<img src="https://js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
</body>
</html>
</html>

View file

@ -1,43 +0,0 @@
# Решение через width: auto
Вначале рассмотрим решение через "умную" установку CSS-свойства.
Они могут быть разными. Самое простое выглядит так:
```js
elem.style.width = 'auto';
```
Такой способ работает, так как `<div>` по умолчанию распахивается на всю ширину.
Конечно, такое решение не будет работать для элементов, которые сами по себе не растягиваются, например в случае со `<span>` или при наличии `position: absolute`.
Обратим внимание, такой вариант был бы неверен:
```js
elem.style.width = '100%';
```
По умолчанию в CSS ширина `width` -- это то, что *внутри `padding`*, а проценты отсчитываются от ширины родителя. То есть, ставя ширину в `100%`, мы говорим: "внутренняя область должна занимать `100%` ширины родителя". А в элементе есть ещё `padding`, которые в итоге вылезут наружу.
Можно бы поменять блочную модель, указав `box-sizing` через свойство `elem.style.boxSizing`, но такое изменение потенциально может затронуть много других свойств, поэтому нежелательно.
# Точное вычисление
Альтернатива -- вычислить ширину родителя через `clientWidth`.
Доступную внутреннюю ширину родителя можно получить, вычитая из `clientWidth` размеры `paddingLeft/paddingRight`, и затем присвоить её элементу:
```js
var bodyClientWidth = document.body.clientWidth;
var style = getComputedStyle(elem);
*!*
var bodyInnerWidth = bodyClientWidth - parseInt(style.paddingLeft) - parseInt(style.paddingRight);
*/!*
elem.style.width = bodyInnerWidth + 'px';
```
Такое решение будет работать всегда, вне зависимости от типа элемента. Конечно, при изменении размеров окна браузера ширина не адаптируется к новому размеру автоматически, как с `width:auto`. Это недостаток. Его, конечно, тоже можно обойти при помощи событий (изучим далее), но как общий рецепт -- если CSS может решить задачу -- лучше использовать CSS.

View file

@ -1,53 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#elem {
width: 200px;
height: 150px;
background-color: red;
padding: 20px;
overflow: auto;
/* position: absolute */
}
body {
border: 1px solid black;
}
</style>
</head>
<body>
<div id="elem">
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст
</div>
<script>
var elem = document.getElementById("elem");
// неверно!
//elem.style.width = '100%';
// верно (так как обычный div по умолчанию растягивается во всю ширину)
//elem.style.width = 'auto';
// верно (решение с JS)
var bodyWidth = document.body.clientWidth;
var style = getComputedStyle(elem);
var bodyInnerWidth = bodyWidth - parseInt(style.paddingLeft) - parseInt(style.paddingRight);
elem.style.width = bodyInnerWidth + 'px';
</script>
</body>
</html>

View file

@ -1,39 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#elem {
width: 200px;
height: 150px;
background-color: red;
padding: 20px;
overflow: auto;
}
body {
border: 1px solid black;
}
</style>
</head>
<body>
<div id="elem">
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст
</div>
<script>
var elem = document.getElementById("elem");
// ... ваш код
</script>
</body>
</html>

View file

@ -1,18 +0,0 @@
importance: 4
---
# Расширить элемент
В `<body>` есть элемент `<div>` с заданной шириной `width`.
Задача -- написать код, который "распахнет" `<div>` по ширине на всю страницу.
Исходный документ (`<div>` содержит текст и прокрутку):
[iframe height=220 src="source"]
P.S. Пользоваться следует исключительно средствами JS, CSS в этой задаче менять нельзя. Также ваш код должен быть универсален и не ломаться, если цифры в CSS станут другими.
P.P.S. При расширении элемент `<div>` не должен вылезти за границу `<body>`.

View file

@ -1,10 +1,6 @@
Отличия:
1. `getComputedStyle` не работает в IE8-.
2. `clientWidth` возвращает число, а `getComputedStyle(...).width` -- строку, на конце `px`.
3. `getComputedStyle` не всегда даст ширину, он может вернуть, к примеру, `"auto"` для инлайнового элемента.
4. `clientWidth` соответствует внутренней видимой области элемента, *включая `padding`, а CSS-ширина `width` при стандартном значении `box-sizing` соответствует зоне *внутри `padding`*.
5. Если есть полоса прокрутки, то некоторые браузеры включают её ширину в `width`, а некоторые -- нет.
Свойство `clientWidth`, с другой стороны, полностью кросс-браузерно. Оно всегда обозначает размер *за вычетом прокрутки*, т.е. реально доступный для содержимого.
Differences:
1. `clientWidth` is numeric, while `getComputedStyle(elem).width` returns a string with `px` at the end.
2. `getComputedStyle` may return non-numeric width like `"auto"` for an inline element.
3. `clientWidth` is the inner content area of the element plus paddings, while CSS width (with standard `box-sizing`) is the inner conand sometent area *without paddings*.
4. If there's a scrollbar and the browser reserves the space for it, some browser substract that space from CSS width (cause it's not available for content any more), and some do not. The `clientWidth` property is always the same: scrollbar size is substracted if reserved.

View file

@ -2,8 +2,8 @@ importance: 5
---
# В чём отличие "width" и "clientWidth" ?
# The difference: CSS width versus clientWidth
В чём отличия между `getComputedStyle(elem).width` и `elem.clientWidth`?
What's the difference between `getComputedStyle(elem).width` and `elem.clientWidth`?
Укажите хотя бы три отличия, лучше -- больше.
Give at least 3 differences. The more the better.

View file

@ -8,7 +8,7 @@
margin: 0;
padding: 0;
}
#example {
width: 300px;
height: 200px;
@ -16,7 +16,7 @@
border: 25px solid #E8C48F;
padding: 20px;
}
.key {
cursor: pointer;
text-decoration: underline;
@ -43,26 +43,22 @@
</div>
<div id="mouse-wrap">Координаты мыши: <span id="mouse">...</span></div>
<div id="mouse-wrap">Mouse coordinates: <span id="mouse">...</span></div>
<div id="info"></div>
<script>
var example = document.getElementById('example')
let props = {
geometry: ['clientLeft', 'clientTop', 'clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight', 'scrollWidth', 'scrollHeight'],
scroll: ['scrollLeft', 'scrollTop'],
offsetParent: ['offsetParent', 'offsetLeft', 'offsetTop']
};
var info = document.getElementById('info')
var props = {
'размеры': ['clientLeft', 'clientTop', 'clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight', 'scrollWidth', 'scrollHeight'],
'прокрутка': ['scrollLeft', 'scrollTop'],
'позиционирование по рендерингу': ['offsetParent', 'offsetLeft', 'offsetTop']
}
info.innerHTML = '<h3>Нажмите для просмотра значения:</h3>';
for (var k in props) {
info.innerHTML += '<h4>' + k + '</h4>';
var prop = props[k];
for (var i = 0; i < prop.length; i++) {
info.innerHTML = '<h3>Click to see the value:</h3>';
for (let k in props) {
info.innerHTML += `<h4>${k}</h4>`;
let prop = props[k];
for (let i = 0; i < prop.length; i++) {
info.innerHTML += "<span class='key'>" + prop[i] + '</span>: <span id="' + prop[i] + '">&nbsp;</span>' + " "
i++;
if (i < prop.length) {
@ -74,11 +70,11 @@
}
document.onclick = function(event) {
var target = event.target;
let target = event.target;
if (!target.classList.contains('key')) return;
var prop = target.innerHTML;
var value = example[prop];
let prop = target.innerHTML;
let value = example[prop];
value = value.tagName || value;
document.getElementById(prop).innerHTML = value;
};
@ -90,5 +86,4 @@
</script>
</body>
</html>
</html>

View file

@ -0,0 +1,175 @@
# Window sizes and scroll
How to find out the browser window width? How to get the full height of the document, including the scrolled out part? How to scroll the page from JavaScript?
From the DOM point of view, the root document element is `document.documentElement`. That element corresponds to `<html>` and has geometry properties described in the [previous chapter](info:size-and-scroll). For some cases we can use it, but there are additional methods and pecularities important enough to consider.
[cut]
## Width/height of the window
Properties `clientWidth/clientHeight` of `document.documentElement` is exactly what we want here:
![](document-client-width-height.png)
```online
For instance, the button below shows the height of your window:
<button onclick="alert(document.documentElement.clientHeight)">alert(document.documentElement.clientHeight)</button>
```
````warn header="Not `window.innerWidth/Height`"
Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So what's the difference?
Properties `clientWidth/clientHeight`, if there's a scrollbar occupying some space, return the width/height inside it. In other words, they return width/height of the visible part of the document, available for the content.
And `window.innerWidth/innerHeight` ignore the scrollbar.
If there's a scrollbar and it occupies some space, then these two lines show different values:
```js run
alert( window.innerWidth ); // full window width
alert( document.documentElement.clientWidth ); // window width minus the scrollbar
```
In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientWidth`.
````
```warn header="`DOCTYPE` is important"
Please note: top-level geometry properties may work a little bit differently when there's no `<!DOCTYPE HTML>` in HTML. Odd things are possible.
In modern HTML we should always write it. Generally that's not a JavaScript question, but here it affects JavaScript as well.
```
## Width/height of the document
Theoretically, as the visibble part of the document is `documentElement.clientWidth/Height`, the full size should be `documentElement.scrollWidth/scrollHeight`.
That's correct for regular elements.
But for the whole page these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! For regular elements that's a nonsense.
To have a reliable full window size, we should take the maximum of these properties:
```js run
let scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
alert('Full document width, with scrolled out part: ' + scrollHeight);
```
Why so? Better don't ask. These inconsistencies come from ancient times, not a "smart" logic.
## Get the current scroll [#page-scroll]
Regular elements have their current scroll state in `scrollLeft/scrollTop`.
What's with the page? Most browsers provide that for the whole page in `documentElement.scrollLeft/Top`, but Chrome/Safari/Opera have bugs (like [157855](https://code.google.com/p/chromium/issues/detail?id=157855), [106133](https://bugs.webkit.org/show_bug.cgi?id=106133)) and we should use `document.body` instead of `document.documentElement` there.
Luckily, we don't have to remember that at all, because of the special properties `window.pageXOffset/pageYOffset`:
```js run
alert('Current scroll from the top: ' + window.pageYOffset);
alert('Current scroll from the left: ' + window.pageXOffset);
```
These properties are read-only.
## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll]
```warn
To scroll the page from JavaScript, its DOM must be fully loaded.
```
Regular elements can be scrolled by changing `scrollTop/scrollLeft`.
We can do the same for the page:
- For all browsers except Chrome/Safari/Opera: modify `document.documentElement.scrollTop/Left`.
- In Chrome/Safari/Opera: use `document.body.scrollTop/Left` instead.
It should work.
But there's a simpler, more universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) и [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
- The method `scrollBy(x,y)` scrolls the page relative to its current position. For instance, `scrollBy(0,10)` scrolls the page `10px` down.
```online
The button below demonstrates this:
<button onclick="window.scrollBy(0,10)">window.scrollBy(0,10)</button>
```
- The method `scrollTo(pageX,pageY)` scrolls the page relative to the document top-left corner. It's like setting `scrollLeft/scrollTop`.
To scroll to the very beginning, we can use `scrollTo(0,0)`.
```online
<button onclick="window.scrollTo(0,0)">window.scrollTo(0,0)</button>
```
These properties are cross-browser.
## scrollIntoView
For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView).
The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument `top`:
- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window, upper side of the element aligned with the window top.
- if `top=false`, then the element bottom is aligned with the window bottom.
```online
The button below scrolls the page to make itself show at the window top:
<button onclick="this.scrollIntoView()">this.scrollIntoView()</button>
And this button scrolls the page to show it at the bottom:
<button onclick="this.scrollIntoView(false)">this.scrollIntoView(false)</button>
```
## Forbid the scrolling
Sometimes we need to make the document "unscrollable". For instance, when we need to show a large message over it, and we want the visitor to interact with that message, not with the document.
To make the document unscrollable, its enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll.
```online
Try it:
<button onclick="document.body.style.overflow = 'hidden'">`document.body.style.overflow = 'hidden'`</button>
<button onclick="document.body.style.overflow = ''">`document.body.style.overflow = ''`</button>
The first button freezes the scroll, the second one resumes it.
```
We can use the same technique to "freeze" the scroll for other elements, not just for `document.body`.
The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it.
That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the content area became wider) then add `padding` to `document.body`.
## Summary
Geometry:
- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/Height`
- Width/height of the whole document, with the scrolled out part:
```js
let scrollHeight = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
);
```
Scrolling:
- Read the current scroll: `window.pageYOffset/pageXOffset`.
- Scroll the page:
- `window.scrollTo(pageX,pageY)` -- absolute coordinates,
- `window.scrollBy(x,y)` -- scroll relative the current place?
- `elem.scrollIntoView(top)` -- scroll to make `elem` visible.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -0,0 +1,42 @@
# Outer corners
Outer corners are basically what we get from [elem.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect).
Coordinates of the upper-left corner `answer1` and the bottom-right corner `answer2`:
```js
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`:
```js
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:
```js
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:
```js
let answer4 = [
coords.left + elem.clientLeft + elem.clientWidth,
coords.top + elem.clientTop + elem.clientHeight
];
```

View file

@ -0,0 +1,27 @@
body {
padding: 20px 0 0 20px;
cursor: pointer;
}
#field {
overflow: hidden;
width: 200px;
height: 150px;
border-top: 10px solid black;
border-right: 10px solid gray;
border-bottom: 10px solid gray;
border-left: 10px solid black;
background-color: #00FF00;
font: 10px/1.2 monospace;
}
.triangle-right {
position: relative;
width: 0;
height: 0;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 20px solid red;
text-indent: -20px;
font: 12px/1 monospace;
}

View file

@ -0,0 +1,59 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
<script>
document.onclick = function(e) { // shows click coordinates
coords.innerHTML = e.clientX + ':' + e.clientY;
};
</script>
</head>
<body>
Click anywhere to get window coordinates.
<br> That's for testing, to check the result you get by JavaScript.
<br>
<div id="coords">(click coordinates show up here)</div>
<div id="field">
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<div class="triangle-right" style="left:-20px;top:-176px">1</div>
<div class="triangle-right" style="left:-10px;top:-178px">3</div>
<div class="triangle-right" style="left:190px;top:-40px">4</div>
<div class="triangle-right" style="left:200px;top:-42px">2</div>
<script>
let fieldCoords = field.getBoundingClientRect();
let answer = [
[ // 1
fieldCoords.left,
fieldCoords.top
],
[ // 2
fieldCoords.right,
fieldCoords.bottom
],
[ // 3
fieldCoords.left + field.clientLeft,
fieldCoords.top + field.clientTop
],
[ // 4
fieldCoords.left + field.clientLeft + field.clientWidth,
fieldCoords.top + field.clientTop + field.clientHeight
]
];
alert(answer.join(' '));
</script>
</body>
</html>

View file

@ -0,0 +1,27 @@
body {
padding: 20px 0 0 20px;
cursor: pointer;
}
#field {
overflow: hidden;
width: 200px;
height: 150px;
border-top: 10px solid black;
border-right: 10px solid gray;
border-bottom: 10px solid gray;
border-left: 10px solid black;
background-color: #00FF00;
font: 10px/1.2 monospace;
}
.triangle-right {
position: relative;
width: 0;
height: 0;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 20px solid red;
text-indent: -20px;
font: 12px/1 monospace;
}

View file

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
<script>
document.onclick = function(e) { // shows click coordinates
coords.innerHTML = e.clientX + ':' + e.clientY;
};
</script>
</head>
<body>
Click anywhere to get window coordinates.
<br> That's for testing, to check the result you get by JavaScript.
<br>
<div id="coords">(click coordinates show up here)</div>
<div id="field">
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<div class="triangle-right" style="left:-20px;top:-176px">1</div>
<div class="triangle-right" style="left:-10px;top:-178px">3</div>
<div class="triangle-right" style="left:190px;top:-40px">4</div>
<div class="triangle-right" style="left:200px;top:-42px">2</div>
<script>
// ...your code...
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
importance: 5
---
# Find the coordinates in the document
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.
[iframe border=1 height=360 src="source" link edit]
Your code should use DOM to get window coordinates of:
1. Left-upper outer corner (that's simple).
2. Right-bottom outer corner (simple too).
3. Left-upper inner corner (a bit harder).
4. Right-bottom 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 no border.

View file

@ -0,0 +1,3 @@
Please note: the elements must be in the document to read geometry properties.
For an hidden (`display:none`) or out of the document element they are zero, so it's impossible to get say a note element size until we place it in the document.

View file

@ -0,0 +1,28 @@
.note {
position: fixed;
z-index: 1000;
padding: 5px;
border: 1px solid black;
background: white;
text-align: center;
font: italic 14px Georgia;
}
blockquote {
background: #f9f9f9;
border-left: 10px solid #ccc;
margin: 0 0 0 100px;
padding: .5em 10px;
quotes: "\201C""\201D""\2018""\2019";
display: inline-block;
white-space: pre;
}
blockquote:before {
color: #ccc;
content: open-quote;
font-size: 4em;
line-height: .1em;
margin-right: .25em;
vertical-align: -.4em;
}

View file

@ -0,0 +1,82 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
</head>
<body>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae
esse sequi officia sapiente.</p>
<blockquote>
Teacher: Why are you late?
Student: There was a man who lost a hundred dollar bill.
Teacher: That's nice. Were you helping him look for it?
Student: No. I was standing on it.
</blockquote>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae
esse sequi officia sapiente.</p>
<script>
/**
* Positions elem relative to anchor as said in position.
*
* @param {Node} anchor Anchor element for positioning
* @param {string} position One of: top/right/bottom
* @param {Node} elem Element to position
*
* Both elements: elem and anchor must be in the document
*/
function positionAt(anchor, position, elem) {
let anchorCoords = anchor.getBoundingClientRect();
switch (position) {
case "top":
elem.style.left = anchorCoords.left + "px";
elem.style.top = anchorCoords.top - elem.offsetHeight + "px";
break;
case "right":
elem.style.left = anchorCoords.left + anchor.offsetWidth + "px";
elem.style.top = anchorCoords.top + "px";
break;
case "bottom":
elem.style.left = anchorCoords.left + "px";
elem.style.top = anchorCoords.top + anchor.offsetHeight + "px";
break;
}
}
/**
* Shows a note with the given html at the given position
* relative to the anchor element.
*/
function showNote(anchor, position, html) {
let note = document.createElement('div');
note.className = "note";
note.innerHTML = html;
document.body.append(note);
positionAt(anchor, position, note);
}
// test it
let blockquote = document.querySelector('blockquote');
showNote(blockquote, "top", "note above");
showNote(blockquote, "right", "note at the right");
showNote(blockquote, "bottom", "note below");
</script>
</body>
</html>

View file

@ -0,0 +1,28 @@
.note {
position: fixed;
z-index: 1000;
padding: 5px;
border: 1px solid black;
background: white;
text-align: center;
font: italic 14px Georgia;
}
blockquote {
background: #f9f9f9;
border-left: 10px solid #ccc;
margin: 0 0 0 100px;
padding: .5em 10px;
quotes: "\201C""\201D""\2018""\2019";
display: inline-block;
white-space: pre;
}
blockquote:before {
color: #ccc;
content: open-quote;
font-size: 4em;
line-height: .1em;
margin-right: .25em;
vertical-align: -.4em;
}

View file

@ -0,0 +1,57 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
</head>
<body>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae
esse sequi officia sapiente.</p>
<blockquote>
Teacher: Why are you late?
Student: There was a man who lost a hundred dollar bill.
Teacher: That's nice. Were you helping him look for it?
Student: No. I was standing on it.
</blockquote>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae
esse sequi officia sapiente.</p>
<script>
/**
* Positions elem relative to anchor as said in position.
*
* @param {Node} anchor Anchor element for positioning
* @param {string} position One of: top/right/bottom
* @param {Node} elem Element to position
*
* Both elements: elem and anchor must be in the document
*/
function positionAt(anchor, position, elem) {
// ... your code ...
}
/**
* Shows a note with the given html at the given position
* relative to the anchor element.
*/
function showNote(anchor, position, html) {
// ... your code ...
}
// test it
let blockquote = document.querySelector('blockquote');
showNote(blockquote, "top", "note above");
showNote(blockquote, "right", "note at the right");
showNote(blockquote, "bottom", "note below");
</script>
</body>
</html>

View file

@ -0,0 +1,13 @@
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`.
Use it to make a 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:
[iframe src="solution" height="450" border="1" link]

View file

@ -0,0 +1,152 @@
# 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 top/left.
2. Relative to the document top/left.
It's important to understand the difference and which type is where.
[cut]
## Window coordinates: getBoundingClientRect
Window coordinates start at the left-upper 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:
![](coords.png)
Window coordinates do not take the scrolled out part of the document into account, they are calculated from the "window itself".
In other words, when we scroll the page, the element goes up or down and *its window coordinates change*. That's kind of important.
```online
Click the button to see its window coordinates:
<input id="brTest" type="button" value="Show button.getBoundingClientRect() for this button" onclick='showRect(this)'/>
<script>
function showRect(elem) {
let r = elem.getBoundingClientRect();
alert("{top:"+r.top+", left:"+r.left+", right:"+r.right+", bottom:"+ r.bottom + "}");
}
</script>
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` etc.
- Coordinates may be negative. For instance, if the page is scrolled down and its top edge is above the window then `elem.getBoundingClientRect().top` is negative.
- Some browsers also add to the result `getBoundingClientRect` properties `width` and `height`. We could also get the same by the substraction: `height=bottom-top`, `width=right-left`.
```warn header="Coordinates right/bottom are different from CSS properties"
If we compare window coordinates vs CSS positioning, then they are closest to `position:fixed` -- the position relative to the viewport.
But in CSS the `right` property means the distance from the right edge, and the `bottom` -- from the bottom edge.
If we just look at the picture below, 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) [#elementFromPoint]
The call to `document.elementFromPoint(x, y)` returns the most nested element at window coordinates `(x, y)`.
The syntax is:
```js
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:
```js run
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.
````warn header="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:
```js
let elem = document.elementFromPoint(x, y);
// if the coordinates happen to be out of the window, then elem = null
*!*
elem.style.background = ''; // Error!
*/!*
```
````
## Usage 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 the coorinates, usually `left/top`.
For instance, the function `createMessageUnder(elem, html)` below shows the message under `elem`:
```js
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);
```
```online
Click the button to run it:
<button id="coords-show-mark">Button with id="coords-show-mark", the message will appear under it</button>
```
The code can be modified to show the message at the left, right, below, apply CSS animations to "fade it in" and so on.
**But note the important detail: when the page is scrolled, the message flows away from the button.**
The reason is obvious: the message element uses `position: fixed`, so it remains at the same place while the page scrolls away.
To change that, we need to use document-based coordinates. We'll cover them in the next chapter.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View file

@ -0,0 +1,29 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('coords-show-mark').onclick = function() {
let elem = document.getElementById("coords-show-mark");
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.innerHTML = text;
return message;
}
let message = createMessageUnder(elem, 'Hello, world!');
document.body.appendChild(message);
setTimeout(function() {
document.body.removeChild(message);
}, 5000);
}
});
</script>

Binary file not shown.