This commit is contained in:
Ilya Kantor 2017-02-25 19:53:02 +03:00
parent 3e33cd6a81
commit caede8fe18
12 changed files with 70 additions and 244 deletions

View file

@ -1 +1,5 @@
Решение: `elem.scrollHeight - elem.scrollTop - elem.clientHeight`. The solution is:
```js
let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;
```

View file

@ -2,10 +2,10 @@ importance: 5
--- ---
# Найти размер прокрутки снизу # What's the scroll from the bottom?
Свойство `elem.scrollTop` содержит размер прокрученной области при отсчете сверху. А как подсчитать размер прокрутки снизу? The `elem.scrollTop` property is the size of the scrolled out part from the top. How to get "`scrollBottom`" -- the size from the bottom?
Напишите соответствующее выражение для произвольного элемента `elem`. Write the code that works for an arbitrary `elem`.
Проверьте: если прокрутки нет вообще или элемент полностью прокручен -- оно должно давать ноль. P.S. Please check your code: if there's no scroll or the element is fully scrolled down, then it should return `0`.

View file

@ -1,22 +1,20 @@
Создадим элемент с прокруткой, но без `border` и `padding`. Тогда разница между его полной шириной `offsetWidth` и внутренней `clientWidth` будет равна как раз прокрутке: To get the scrollbar width, we can create an element with the scroll, but without borders and paddings.
Then the difference between its full width `offsetWidth` and the inner content area width `clientWidth` will be exactly the scrollbar:
```js run ```js run
// создадим элемент с прокруткой // create a div with the scroll
var div = document.createElement('div'); let div = document.createElement('div');
div.style.overflowY = 'scroll'; div.style.overflowY = 'scroll';
div.style.width = '50px'; div.style.width = '50px';
div.style.height = '50px'; div.style.height = '50px';
// при display:none размеры нельзя узнать // must put it in the document, otherwise sizes will be 0
// нужно, чтобы элемент был видим, document.body.append(div);
// visibility:hidden - можно, т.к. сохраняет геометрию let scrollWidth = div.offsetWidth - div.clientWidth;
div.style.visibility = 'hidden';
document.body.appendChild(div); div.remove();
var scrollWidth = div.offsetWidth - div.clientWidth;
document.body.removeChild(div);
alert( scrollWidth ); alert(scrollWidth);
``` ```

View file

@ -2,8 +2,10 @@ importance: 3
--- ---
# Узнать ширину полосы прокрутки # What is the scrollbar width?
Напишите код, который возвращает ширину стандартной полосы прокрутки. Именно самой полосы, где ползунок. Обычно она равна `16px`, в редких и мобильных браузерах может колебаться от `14px` до `18px`, а кое-где даже равна `0px`. Write the code that returns the width of a standard scrollbar.
P.S. Ваш код должен работать на любом HTML-документе, независимо от его содержимого. For Windows it usually varies between `12px` and `20px`, but if the browser reserves no space for it, then it may be `0px`.
P.S. The code should work in any HTML document, do not depend on its content.

View file

@ -1,29 +0,0 @@
Нам нужно создать `div` с такими же размерами и вставить его на место "переезжающего".
Один из вариантов -- это просто клонировать элемент.
Если делать это при помощи `div.cloneNode(true)`, то склонируется все содержимое, которого может быть много. Обычно нам это не нужно, поэтому можно использовать `div.cloneNode(false)` для клонирования элемента со стилями, и потом поправить его `width/height`.
Можно и просто создать новый `div` и поставить ему нужные размеры.
**Всё, кроме `margin`, можно получить из свойств DOM-элемента, а `margin` -- только через `getComputedStyle`.**
Причём `margin` мы обязаны поставить, так как иначе наш элемент при вставке будет вести себя иначе, чем исходный.
Код:
```js
var div = document.getElementById('moving-div');
var placeHolder = document.createElement('div');
placeHolder.style.height = div.offsetHeight + 'px';
// можно и width, но в этом примере это не обязательно
// IE || другой браузер
var computedStyle = div.currentStyle || getComputedStyle(div, '');
placeHolder.style.marginTop = computedStyle.marginTop; // (1)
placeHolder.style.marginBottom = computedStyle.marginBottom;
```
В строке `(1)` использование полного название свойства `"marginTop"` гарантирует, что полученное значение будет корректным.

View file

@ -1,50 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#moving-div {
border: 5px groove green;
padding: 5px;
margin: 10px;
background-color: yellow;
}
</style>
</head>
<body>
Before Before Before
<div id="moving-div">
Text Text Text
<br> Text Text Text
<br>
</div>
After After After
<script>
var div = document.getElementById('moving-div')
var placeHolder = document.createElement('div')
placeHolder.style.height = div.offsetHeight + 'px'
var computedStyle = div.currentStyle || getComputedStyle(div, null)
placeHolder.style.marginTop = computedStyle.marginTop // full prop name
placeHolder.style.marginBottom = computedStyle.marginBottom
// highlight it for demo purposes
placeHolder.style.backgroundColor = '#C0C0C0'
document.body.insertBefore(placeHolder, div)
div.style.position = 'absolute'
div.style.right = div.style.top = 0
</script>
</body>
</html>

View file

@ -1,37 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#moving-div {
border: 5px groove green;
background-color: yellow;
padding: 5px;
margin: 10px;
}
</style>
</head>
<body>
Before Before Before
<div id="moving-div">
Text Text Text
<br> Text Text Text
<br>
</div>
After After After
<script>
// .. Add your code here
var div = document.getElementById('moving-div')
div.style.position = 'absolute'
div.style.right = div.style.top = 0
</script>
</body>
</html>

View file

@ -1,50 +0,0 @@
importance: 3
---
# Подменить div на другой с таким же размером
Посмотрим следующий случай из жизни. Был текст, который, в частности, содержал `div` с зелеными границами:
```html run no-beautify
<style>
#moving-div {
border: 5px groove green;
padding: 5px;
margin: 10px;
background-color: yellow;
}
</style>
Before Before Before
<div id="moving-div">
Text Text Text<br>
Text Text Text<br>
</div>
After After After
```
Программист Валера из вашей команды написал код, который позиционирует его абсолютно и смещает в правый верхний угол. Вот этот код:
```js
var div = document.getElementById('moving-div');
div.style.position = 'absolute';
div.style.right = div.style.top = 0;
```
Побочным результатом явилось смещение текста, который раньше шел после `DIV`. Теперь он поднялся вверх:
[iframe height=90 src="source"]
**Допишите код Валеры, сделав так, чтобы текст оставался на своем месте после того, как `DIV` будет смещен.**
Сделайте это путем создания вспомогательного `DIV` с теми же `width`, `height`, `border`, `margin`, `padding`, что и у желтого `DIV`.
Используйте только JavaScript, без CSS.
Должно быть так (новому блоку задан фоновый цвет для демонстрации):
[iframe height=140 src="solution"]

View file

@ -1,42 +1,42 @@
При абсолютном позиционировании мяча внутри поля его координаты `left/top` отсчитываются от **внутреннего** угла поля, например верхнего-левого: The ball has `position:absolute`. It means that its `left/top` coordinates are measured from the nearest positioned element, that is `#field` (because it has `position:relative`).
The coordinates start from the inner left-upper corner of the field:
![](field.png) ![](field.png)
Метрики для внутренней зоны поля -- это `clientWidth/Height`. The inner field width/height is `clientWidth/clientHeight`. So the field center has coordinates `(clientWidth/2, clientHeight/2)`.
Центр - это `(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:
Но если мы установим мячу такие значения `ball.style.left/top`, то в центре будет не сам мяч, а его левый верхний угол:
```js ```js
var ball = document.getElementById('ball');
var field = document.getElementById('field');
ball.style.left = Math.round(field.clientWidth / 2) + 'px'; ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px'; ball.style.top = Math.round(field.clientHeight / 2) + 'px';
``` ```
[iframe hide="Нажмите, чтобы посмотреть текущий результат" height=180 src="ball-half"] Here's how it looks:
Для того, чтобы центр мяча находился в центре поля, нам нужно сместить мяч на половину его ширины влево и на половину его высоты вверх. [iframe height=180 src="ball-half"]
To align the ball center with the center of the field, we should move the ball to the half of its width to the left and to the half of its height to the top:
```js ```js
var ball = document.getElementById('ball');
var field = document.getElementById('field');
ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px'; ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'; ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';
``` ```
**Внимание, подводный камень!** **Attention: the pitfall!**
Код выше стабильно работать не будет, потому что `IMG` идет без ширины/высоты: The code won't work reliably while `<img>` width/height is not given to the browser:
```html ```html
<img src="ball.png" id="ball"> <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.
If none is given, then the sizes are assumed to be `0` until the image loads.
TODO
После первой загрузки изображение уже будет в кеше браузера, и его размеры будут известны. Но когда браузер впервые видит документ -- он ничего не знает о картинке, поэтому значение `ball.offsetWidth` равно `0`. Вычислить координаты невозможно. После первой загрузки изображение уже будет в кеше браузера, и его размеры будут известны. Но когда браузер впервые видит документ -- он ничего не знает о картинке, поэтому значение `ball.offsetWidth` равно `0`. Вычислить координаты невозможно.
@ -47,4 +47,3 @@ ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'p
``` ```
Теперь браузер всегда знает ширину и высоту, так что все работает. Тот же эффект дало бы указание размеров в CSS. Теперь браузер всегда знает ширину и высоту, так что все работает. Тот же эффект дало бы указание размеров в CSS.

View file

@ -2,20 +2,19 @@ importance: 5
--- ---
# Поместите мяч в центр поля # Place the ball in the field center
Поместите мяч в центр поля. Here's how the source document looks:
Исходный документ выглядит так:
[iframe src="source" edit link height=180] [iframe src="source" edit link height=180]
**Используйте JavaScript, чтобы поместить мяч в центр:** What are coordinates of the field center?
Calculate them and use to place the ball into the center of the field:
[iframe src="solution" height=180] [iframe src="solution" height=180]
- Менять CSS нельзя, мяч должен переносить в центр ваш скрипт, через установку нужных стилей элемента. - The element should be moved by JavaScript, not CSS.
- JavaScript-код должен работать при разных размерах мяча (`10`, `20`, `30` пикселей) без изменений. - The code should work with any ball size (`10`, `20`, `30` pixels) and any field size, not be bound to the given values.
- JavaScript-код должен работать при различных размерах и местоположениях поля на странице без изменений. Также он не должен зависеть от ширины рамки поля `border`.
P.S. Да, центрирование можно сделать при помощи чистого CSS, но задача именно на JavaScript. Далее будут другие темы и более сложные ситуации, когда JavaScript будет уже точно необходим, это -- своего рода "разминка". P.S. Sure, centering could be done with CSS, but here we want exactly JavaScript. Further we'll meet other topics and more complex situations when JavaScript must be used. Here we do a "warm-up".

View file

@ -3,7 +3,7 @@
To show elements at arbitrary places in the page we should: To show elements at arbitrary places in the page we should:
1. First, know CSS positioning. 1. First, know CSS positioning.
2. Second, know how to handle "geometrical" properties in Javascript. 2. Second, know how to handle "geometry" properties in Javascript.
[cut] [cut]
@ -204,6 +204,8 @@ If you click the element below, the code `elem.scrollTop += 10` executes. That m
<div onclick="this.scrollTop+=10" style="cursor:pointer;border:1px solid black;width:100px;height:80px;overflow:auto">Click<br>Me<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9</div> <div onclick="this.scrollTop+=10" style="cursor:pointer;border:1px solid black;width:100px;height:80px;overflow:auto">Click<br>Me<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9</div>
``` ```
Setting `scrollTop` to `0` or `Infinity` will make the element scroll to the top/bottom respectively.
```` ````
## Don't take width/height from CSS ## Don't take width/height from CSS
@ -246,29 +248,27 @@ As we've seen a scrollbar takes the space from the content in some browsers. So
...But some browsers also take that into account in `getComputedStyle(elem).width`. That is: some of them return real inner width and some of them -- CSS width. Such cross-browser differences is a reason not to use `getComputedStyle`, but rather rely on geometry propeties. ...But some browsers also take that into account in `getComputedStyle(elem).width`. That is: some of them return real inner width and some of them -- CSS width. Such cross-browser differences is a reason not to use `getComputedStyle`, but rather rely on geometry propeties.
```online ```online
Если ваш браузер показывает полосу прокрутки (например, под Windows почти все браузеры так делают), то вы можете протестировать это сами, нажав на кнопку в ифрейме ниже. If your browser reserves the space for a scrollbar (most browsers for Windows do), then you can test it below.
[iframe src="cssWidthScroll" link border=1] [iframe src="cssWidthScroll" link border=1]
У элемента с текстом в стилях указано `width:300px`. The element with text has CSS `width:300px`.
На момент написания этой главы при тестировании в Chrome под Windows `alert` выводил `283px`, а в Firefox -- `300px`. При этом оба браузера показывали прокрутку. Это из-за того, что Firefox возвращал именно CSS-ширину, а Chrome -- реальную ширину, за вычетом прокрутки. Desktop Windows Firefox, Chrome, Edge all reserve the space for the scrollbar. But Firefox shows `300px`, while Chrome and Edge show less. That's because Firefox returns the CSS width and other browsers return the "real" width.
``` ```
Описанные разночтения касаются только чтения свойства `getComputedStyle(...).width` из JavaScript, визуальное отображение корректно в обоих случаях. Please note that the described difference are only about reading `getComputedStyle(...).width` from JavaScript, visually everything is correct.
## Итого ## Summary
У элементов есть следующие метрики: Elements have the following geometry properties:
- `offsetParent` -- "родитель по дереву рендеринга" -- ближайшая ячейка таблицы, body для статического позиционирования или ближайший позиционированный элемент для других типов позиционирования. - `offsetParent` -- is the nearest positioned ancestor or `td`, `th`, `table`, `body`.
- `offsetLeft/offsetTop` -- позиция в пикселях левого верхнего угла блока, относительно его `offsetParent`. - `offsetLeft/offsetTop` -- coordinates relative to the left-upper edge of `offsetParent`.
- `offsetWidth/offsetHeight` -- "внешняя" ширина/высота блока, включая рамки. - `offsetWidth/offsetHeight` -- "outer" width/height of an element including borders.
- `clientLeft/clientTop` -- отступ области содержимого от левого-верхнего угла элемента. Если операционная система располагает вертикальную прокрутку справа, то равны ширинам левой/верхней рамки, если же слева (ОС на иврите, арабском), то `clientLeft` включает в себя прокрутку. - `clientLeft/clientTop` -- the distance from the left-upper outer corner to its left-upper inner corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too.
- `clientWidth/clientHeight` -- ширина/высота содержимого вместе с полями `padding`, но без полосы прокрутки. - `clientWidth/clientHeight` -- the width/height of the content including paddings, but without the scrollbar.
- `scrollWidth/scrollHeight` -- ширина/высота содержимого, включая прокручиваемую область. Включает в себя `padding` и не включает полосы прокрутки. - `scrollWidth/scrollHeight` -- the width/height of the content including the scrolled out part. Also includes paddings, but not the scrollbar.
- `scrollLeft/scrollTop` -- ширина/высота прокрученной части документа, считается от верхнего левого угла. - `scrollLeft/scrollTop` -- width/height of the scrolled out part of the element, starting from its left-upper corner.
Все свойства, доступны только для чтения, кроме `scrollLeft/scrollTop`. Изменение этих свойств заставляет браузер прокручивать элемент. All properties are read-only except `scrollLeft/scrollTop`. They make the browser scroll the element if changed.
В этой главе мы считали, что страница находится в режиме соответствия стандартам. В режиме совместимости -- некоторые старые браузеры требуют `document.body` вместо `documentElement`, в остальном всё так же. Конечно, по возможности, стоит использовать только режим соответствия стандарту.

View file

@ -1,26 +1,16 @@
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html> <html>
<head>
<meta charset="utf-8">
</head>
<body> <body>
<div id="elem" style="overflow-y:scroll;width:300px;height:200px;border:1px solid black"> <div id="elem" style="overflow-y:scroll;width:300px;height:200px;border:1px solid black">
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
текст текст текст текст текст текст текст текст текст текст текст текст текст текст text text text text text text text text text text text text text text text text text text text text text text text text text text
</div> </div>
<script> The element has <code>style="width:300px"</code>
// FF: 200, Ch/Sa: 184, Op: 200, IE9: 184, IE8:200
</script>
У элемента стоит <code>style="width:300px"</code>
<br> <br>
<button onclick="alert( getComputedStyle(elem).width )">alert( getComputedStyle(elem).width )</button> <button onclick="alert( getComputedStyle(elem).width )">alert( getComputedStyle(elem).width )</button>
</body> </body>
</html>
</html>