renovations
This commit is contained in:
parent
61c212cbb5
commit
221e595b11
11 changed files with 299 additions and 309 deletions
|
@ -6,64 +6,6 @@
|
|||
|
||||
[cut]
|
||||
|
||||
## Не стоит брать width/height из CSS
|
||||
|
||||
Какой способ первый приходит на ум, когда есть задача определить ширину/высоту элемента.
|
||||
|
||||
Если вы внимательно читали до этого момента, то уж точно знаете, что CSS-высоту и ширину можно установить с помощью `elem.style` и извлечь, используя `getComputedStyle`, которые в подробностях обсуждаются в главе [](/styles-and-classes).
|
||||
|
||||
Получение ширины может быть таким:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var elem = document.body;
|
||||
alert( getComputedStyle(elem).width ); // вывести CSS-ширину для body
|
||||
```
|
||||
|
||||
Всегда ли такой подход сработает? Увы, нет!
|
||||
|
||||
<ol>
|
||||
<li>Во-первых, CSS-свойства `width/height` зависят от другого свойства -- `box-sizing`, которое определяет, что такое, собственно, эти ширина и высота. Получается, что изменение этого свойства, к примеру, для более удобной вёрстки, может сломать JavaScript.</li>
|
||||
<li>Во-вторых, свойства `width/height` могут быть равны `auto`.
|
||||
|
||||
Например, для инлайн-элемента:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<span id="elem">Привет!</span>
|
||||
|
||||
<script>
|
||||
*!*
|
||||
alert( getComputedStyle(elem).width ); // auto
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
Конечно, с точки зрения CSS размер `auto` -- совершенно нормально, но нам-то в JavaScript нужен конкретный размер в пикселях, который мы могли бы использовать для вычислений. Получается, что в данном случае ширина `width` из CSS вообще бесполезна.
|
||||
|
||||
## Полоса прокрутки и содержимое
|
||||
|
||||
Полоса прокрутки -- причина многих проблем и недопониманий. Как говорится, "дьявол кроется в деталях". Недопустимо, чтобы наш код работал на элементах без прокрутки и начинал "глючить" с ней. Поэтому мы с самого начала будем её учитывать.
|
||||
|
||||
При наличии вертикальной полосы прокрутки -- во многих операционных системах и браузерах она забирает себе место у "внутренней части" элемента.
|
||||
|
||||
...Но при этом некоторые браузеры отражают реальное уменьшение ширины в результате `getComputedStyle(elem).width`, а некоторые -- нет.
|
||||
|
||||
В примере ниже у элемента с текстом в стилях указано `width:300px`. А вот `getComputedStyle` возвращает значение от `280px` до `300px`, в зависимости от ОС и браузера.
|
||||
|
||||
[online]
|
||||
Если ваш браузер в принципе показывает полосу прокрутки (например, под Windows почти все браузеры так делают), то вы можете протестировать это сами, нажав на кнопку в ифрейме ниже:
|
||||
[/online]
|
||||
|
||||
[iframe src="cssWidthScroll" link border=1]
|
||||
|
||||
Описанные разночтения касаются только чтения свойства `getComputedStyle(...).width` из JavaScript, визуальное отображение корректно в обоих случаях -- ширина текста при наличии прокрутки в обоих случаях уменьшается.
|
||||
|
||||
Этот пример важен, чтобы ещё лучше понять: свойство `width` из CSS не следует использовать в JavaScript. И, конечно, далее мы будем вспоминать о полосе прокрутке там, где она важна.
|
||||
|
||||
## Метрики: образец документа
|
||||
|
||||
Мы будем использовать для примера вот такой элемент, у которого есть рамка (border), поля (padding), отступы (margin) и прокрутка:
|
||||
|
@ -209,7 +151,7 @@ function isHidden(elem)
|
|||
|
||||
<img src="clientWidthNoPadding.png">
|
||||
|
||||
Поэтому в тех случаях, когда мы точно знаем, что `padding` нет, их используют для определения внутренних размеров элемента. И, в отличие от `getComputedStyle(elem).width`, эти свойства всегда работают верно.
|
||||
Поэтому в тех случаях, когда мы точно знаем, что `padding` нет, их используют для определения внутренних размеров элемента.
|
||||
|
||||
## scrollWidth/Height
|
||||
|
||||
|
@ -261,6 +203,65 @@ element.style.height = element.scrollHeight + 'px';
|
|||
[/smart]
|
||||
|
||||
|
||||
## Не стоит брать width/height из CSS
|
||||
|
||||
Теперь несколько слов о том, как не надо делать.
|
||||
|
||||
Как мы знаем, CSS-высоту и ширину можно установить с помощью `elem.style` и извлечь, используя `getComputedStyle`, которые в подробностях обсуждаются в главе [](/styles-and-classes).
|
||||
|
||||
Получение ширины элемента может быть таким:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var elem = document.body;
|
||||
|
||||
alert( getComputedStyle(elem).width ); // вывести CSS-ширину для elem
|
||||
```
|
||||
|
||||
Всегда ли такой подход сработает? Увы, нет!
|
||||
|
||||
<ol>
|
||||
<li>Во-первых, CSS-свойства `width/height` зависят от другого свойства -- `box-sizing`, которое определяет, что такое, собственно, эти ширина и высота. Получается, что изменение этого свойства, к примеру, для более удобной вёрстки, может сломать JavaScript.</li>
|
||||
<li>Во-вторых, свойства `width/height` могут быть равны `auto`, например, для инлайн-элемента:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<span id="elem">Привет!</span>
|
||||
|
||||
<script>
|
||||
*!*
|
||||
alert( getComputedStyle(elem).width ); // auto
|
||||
*/!*
|
||||
</script>
|
||||
```
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
Конечно, с точки зрения CSS размер `auto` -- совершенно нормально, но нам-то в JavaScript нужен конкретный размер в пикселях, который мы могли бы использовать для вычислений. Получается, что в данном случае ширина `width` из CSS вообще бесполезна.
|
||||
|
||||
## Полоса прокрутки и содержимое
|
||||
|
||||
Полоса прокрутки -- причина многих проблем и недопониманий. Как говорится, "дьявол кроется в деталях". Недопустимо, чтобы наш код работал на элементах без прокрутки и начинал "глючить" с ней.
|
||||
|
||||
При наличии вертикальной полосы прокрутки, она как правило забирает себе место из "области содержимого" элемента.
|
||||
|
||||
И это учитывают свойства `clientWidth/clientHeight`.
|
||||
|
||||
...Но при этом некоторые браузеры отражают реальное уменьшение ширины в результате `getComputedStyle(elem).width`, а некоторые -- нет.
|
||||
|
||||
В примере ниже у элемента с текстом в стилях указано `width:300px`. А вот `getComputedStyle` возвращает значение от `280px` до `300px`, в зависимости от ОС и браузера.
|
||||
|
||||
[online]
|
||||
Если ваш браузер в принципе показывает полосу прокрутки (например, под Windows почти все браузеры так делают), то вы можете протестировать это сами, нажав на кнопку в ифрейме ниже:
|
||||
[/online]
|
||||
|
||||
[iframe src="cssWidthScroll" link border=1]
|
||||
|
||||
Описанные разночтения касаются только чтения свойства `getComputedStyle(...).width` из JavaScript, визуальное отображение корректно в обоих случаях -- ширина текста при наличии прокрутки в обоих случаях уменьшается.
|
||||
|
||||
Этот пример важен, чтобы ещё лучше понять: CSS-свойство `width` не следует использовать в JavaScript. А стоит использовать метрики, которые мы разобрали в этом разделе.
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
`top` -- можно кроссбраузерно получить, как указано в главе [](/metrics-window):
|
||||
|
||||
```js
|
||||
function getDocumentScrollTop() {
|
||||
var html = document.documentElement;
|
||||
var body = document.body;
|
||||
|
||||
var scrollTop = html.scrollTop || body && body.scrollTop || 0;
|
||||
scrollTop -= html.clientTop; // IE<8
|
||||
|
||||
return scrollTop;
|
||||
}
|
||||
```
|
||||
|
||||
`bottom` -- это `top` плюс высота видимой части:
|
||||
|
||||
```js
|
||||
function getDocumentScrollBottom() {
|
||||
return getDocumentScrollTop() + document.documentElement.clientHeight;
|
||||
}
|
||||
```
|
||||
|
||||
Полная высота -- максимум двух значений, детали см. в [](/metrics-window):
|
||||
|
||||
```js
|
||||
function getDocumentScrollHeight() {
|
||||
var scrollHeight = document.documentElement.scrollHeight;
|
||||
var clientHeight = document.documentElement.clientHeight;
|
||||
|
||||
scrollHeight = Math.max(scrollHeight, clientHeight);
|
||||
|
||||
return scrollHeight;
|
||||
}
|
||||
```
|
||||
|
||||
Итого, ответ, использующий описанные выше функции:
|
||||
|
||||
```js
|
||||
function getDocumentScroll() {
|
||||
return {
|
||||
top: getDocumentScrollTop(),
|
||||
bottom: getDocumentScrollBottom(),
|
||||
height: getDocumentScrollHeight()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# TODO: перенеси меня в координаты? Тут координат еще не было
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите функцию `getDocumentScroll()`, которая возвращает объект с координатами области видимости относительно документа.
|
||||
|
||||
Свойства объекта результата:
|
||||
|
||||
<ul>
|
||||
<li>`top` -- координата верхней границы видимой части (относительно документа).</li>
|
||||
<li>`bottom` -- координата нижней границы видимой части (относительно документа).</li>
|
||||
<li>`height` -- полная высота документа, включая прокрутку.</li>
|
||||
</ul>
|
||||
|
||||
В задаче можно учитывать только вертикальную прокрутку (горизонтальную отдельно нет смысла разбирать, она делается аналогично, а нужна сильно реже).
|
|
@ -1,27 +1,31 @@
|
|||
# Размеры и прокрутка страницы
|
||||
|
||||
Многие метрики для страницы работают совсем не так, как для элементов.
|
||||
Как найти ширину окна браузера? Как узнать всю высоту страницы, с учётом прокрутки?
|
||||
Как прокрутить её из JavaScript?
|
||||
|
||||
Поэтому мы рассмотрим решения типичных задач для страницы отдельно.
|
||||
|
||||
С точки зрения HTML, документ -- это `document.documentElement`. У этого элемента, соответствующего тегу `<html>`, есть все стандартные свойства и метрики и, в теории, они и должны нам помочь. Однако, на практике есть ряд нюансов, именно их мы рассмотрим в этой главе.
|
||||
|
||||
[cut]
|
||||
|
||||
## Ширина/высота видимой части окна
|
||||
|
||||
Свойства `clientWidth/Height` для элемента `document.documentElement` позволяют получить ширину/высоту видимой области окна.
|
||||
Свойства `clientWidth/Height` для элемента `document.documentElement` -- это как раз ширина/высота видимой области окна.
|
||||
|
||||
<img src="document-client-width-height.svg">
|
||||
|
||||
[online]
|
||||
Например, кнопка ниже выведет размер такой области для этой страницы:
|
||||
|
||||
<button onclick="alert(document.documentElement.clientHeight)">alert(document.documentElement.clientHeight)</button>
|
||||
|
||||
Этот способ -- кросс-браузерный.
|
||||
[/online]
|
||||
|
||||
[warn header="Не `window.innerWidth/Height`"]
|
||||
Все браузеры, кроме IE8-, также поддерживают свойства `window.innerWidth/innerHeight`. Они хранят текущий размер окна.
|
||||
Все браузеры, кроме IE8-, также поддерживают свойства `window.innerWidth/innerHeight`. Они хранят текущий размер *окна браузера*.
|
||||
|
||||
Выглядят они короче, чем `document.documentElement.clientWidth`, однако есть один нюанс.
|
||||
В чём отличие? Оно небольшое, но чрезвычайно важное.
|
||||
|
||||
Свойства `clientWidth/Height` берут в расчёт полосу прокрутку, а эти свойства -- нет.
|
||||
Свойства `clientWidth/Height`, если есть полоса прокрутки, возвращают именно ширину/высоту документа, за вычетом прокрутки, а эти свойства -- игнорируют её наличие.
|
||||
|
||||
Если справа часть страницы занимает полоса прокрутки, то эти строки выведут разное:
|
||||
```js
|
||||
|
@ -30,18 +34,18 @@ alert( window.innerWidth ); // вся ширина окна
|
|||
alert( document.documentElement.clientWidth ); // ширина минус прокрутка
|
||||
```
|
||||
|
||||
Обычно нам нужна именно *доступная* ширина окна, например, чтобы нарисовать что-либо, то есть за вычетом полосы прокрутки. Поэтому используем `document.documentElement.clientWidth`.
|
||||
Обычно нам нужна именно *доступная* ширина окна, например, чтобы нарисовать что-либо, то есть за вычетом полосы прокрутки. Поэтому используем `documentElement.clientWidth`.
|
||||
[/warn]
|
||||
|
||||
## Ширина/высота страницы с учётом прокрутки
|
||||
|
||||
Если прокрутка на странице заведомо присутствует, то полные размеры страницы можно взять в `document.documentElement.scrollWidth/scrollHeight`.
|
||||
Теоретически, видимая часть страницы -- это `documentElement.clientWidth/Height`, а полный размер с учётом прокрутки -- по аналогии, `documentElement.scrollWidth/scrollHeight`.
|
||||
|
||||
Проблемы с этими свойствами возникают, когда *прокрутка то есть, то нет*. В этом случае они работают некорректно.
|
||||
Это верно для обычных элементов.
|
||||
|
||||
В браузерах Chrome/Safari и Opera при отсутствии прокрутки значение `document.documentElement.scrollHeight` в этом случае может быть даже меньше, чем `document.documentElement.clientHeight` (нонсенс!).
|
||||
А вот для страницы с этими свойствами возникает проблема, когда *прокрутка то есть, то нет*. В этом случае они работают некорректно. В браузерах Chrome/Safari и Opera при отсутствии прокрутки значение `documentElement.scrollHeight` в этом случае может быть даже меньше, чем `documentElement.clientHeight`, что, конечно же, выглядит как совершеннейшая чепуха и нонсенс.
|
||||
|
||||
Эта проблема возникает именно для `document.documentElement`, то есть для всей страницы. С обычными элементами здесь всё в порядке.
|
||||
Эта проблема возникает именно для `documentElement`, то есть для всей страницы.
|
||||
|
||||
Надёжно определить размер страницы с учетом прокрутки можно, взяв максимум из нескольких свойств:
|
||||
|
||||
|
@ -56,11 +60,17 @@ var scrollHeight = Math.max(
|
|||
alert('Высота с учетом прокрутки: ' + scrollHeight);
|
||||
```
|
||||
|
||||
Почему так? Лучше и не спрашивайте, это одно из редких мест, где просто обходятся ошибки в браузерах. Глубокой логики здесь нет.
|
||||
Почему так? Лучше и не спрашивайте, это одно из редких мест, где просто ошибки в браузерах. Глубокой логики здесь нет.
|
||||
|
||||
## Получение текущей прокрутки [#page-scroll]
|
||||
|
||||
Значение текущей прокрутки страницы хранится в свойствах `window.pageXOffset/pageYOffset`.
|
||||
У обычного элемента текущую прокрутку можно получить в `scrollLeft/scrollTop`.
|
||||
|
||||
Что же со страницей?
|
||||
|
||||
Большинство браузеров корректно обработает запрос к `documentElement.scrollLeft/Top`, однако Safari/Chrome/Opera есть ошибки (к примеру [157855](https://code.google.com/p/chromium/issues/detail?id=157855), [106133](https://bugs.webkit.org/show_bug.cgi?id=106133)), из-за которых следует использовать `document.body`.
|
||||
|
||||
Чтобы вообще обойти проблему, можно использовать специальные свойства `window.pageXOffset/pageYOffset`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -74,11 +84,9 @@ alert('Текущая прокрутка слева: ' + window.pageXOffset);
|
|||
<li>Их можно только читать, а менять нельзя.</li>
|
||||
</ul>
|
||||
|
||||
Если IE8- не волнует, то для чтения прокрутки лучше способа нет, рецепт готов.
|
||||
Если IE8- не волнует, то просто используем эти свойства.
|
||||
|
||||
Альтернативный вариант -- это свойства `document.documentElement.scrollLeft/Top`, но они не работают в Safari/Chrome/Opera, которые используют `document.body` (это неправильно).
|
||||
|
||||
Так что кросс-браузерный вариант с учётом IE8:
|
||||
Кросс-браузерный вариант с учётом IE8 предусматривает откат на `documentElement`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -95,9 +103,9 @@ alert("Текущая прокрутка: " + scrollTop);
|
|||
|
||||
На обычных элементах свойства `scrollTop/scrollLeft` можно изменять, и при этом элемент будет прокручиваться.
|
||||
|
||||
Никто не мешает точно так же поступать и со страницей. Во всех браузерах, кроме Chrome/Safari/Opera можно осуществить прокрутку установкой `document.documentElement.scrollTop`, а в указанных -- использовать для этого `document.body.scrollTop`. И будет работать.
|
||||
Никто не мешает точно так же поступать и со страницей. Во всех браузерах, кроме Chrome/Safari/Opera можно осуществить прокрутку установкой `document.documentElement.scrollTop`, а в указанных -- использовать для этого `document.body.scrollTop`. И будет работать. Можно попробовать прокручивать и так и эдак и проверять, подействовала ли прокрутка, будет кросс-браузерно.
|
||||
|
||||
Но есть и другое, полностью кросс-браузерное и универсальное решение -- специальные методы прокрутки страницы [window.scrollBy(x,y)](https://developer.mozilla.org/en/Window.scrollBy) и [window.scrollTo(pageX,pageY)](https://developer.mozilla.org/en/Window.scrollTo).
|
||||
Но есть и другое, простое и универсальное решение -- специальные методы прокрутки страницы [window.scrollBy(x,y)](https://developer.mozilla.org/en/Window.scrollBy) и [window.scrollTo(pageX,pageY)](https://developer.mozilla.org/en/Window.scrollTo).
|
||||
|
||||
<ul>
|
||||
<li>Метод `scrollBy(x,y)` прокручивает страницу относительно текущих координат.
|
||||
|
@ -138,17 +146,23 @@ alert("Текущая прокрутка: " + scrollTop);
|
|||
|
||||
**Чтобы запретить прокрутку страницы, достаточно поставить `document.body.style.overflow = "hidden"`.**
|
||||
|
||||
При этом страница замрёт в текущем положении. Попробуйте сами:
|
||||
При этом страница замрёт в текущем положении.
|
||||
|
||||
[online]
|
||||
Попробуйте сами:
|
||||
|
||||
<button onclick="document.body.style.overflow = 'hidden'">`document.body.style.overflow = 'hidden'`</button>
|
||||
|
||||
<button onclick="document.body.style.overflow = ''">`document.body.style.overflow = ''`</button>
|
||||
|
||||
При нажатии на верхнюю кнопку страница замрёт на текущем положении прокрутки. После нажатия на нижнюю -- прокрутка возобновится.
|
||||
[/online]
|
||||
|
||||
**Вместо `document.body` может быть любой элемент, прокрутку которого необходимо запретить.**
|
||||
Вместо `document.body` может быть любой элемент, прокрутку которого необходимо запретить.
|
||||
|
||||
Недостатком этого способа является то, что сама полоса прокрутки исчезает. Если она занимала некоторую ширину, то теперь эта ширина освободится, и содержимое страницы расширится, заняв её место. Такая перерисовка иногда выглядит как "прыжок" страницы. Это может быть не очень красиво, но обходится, если вычислить размер прокрутки и добавить `padding-right`.
|
||||
Недостатком этого способа является то, что сама полоса прокрутки исчезает. Если она занимала некоторую ширину, то теперь эта ширина освободится, и содержимое страницы расширится, текст "прыгнет", заняв освободившееся место.
|
||||
|
||||
Это может быть не очень красиво, но легко обходится, если вычислить размер прокрутки и добавить такой же по размеру `padding`.
|
||||
|
||||
## Итого
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="550px" height="203px" viewBox="0 0 550 203" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>document-client-width-height</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="document-client-width-height" sketch:type="MSArtboardGroup">
|
||||
<g id="noun_69008_cc" sketch:type="MSLayerGroup" transform="translate(236.000000, 8.000000)" fill="#8A704D">
|
||||
<path d="M179.1875,145 L3.8125,145 C1.708,145 0,143.36875 0,141.375 L0,3.625 C0,1.63125 1.708,0 3.8125,0 L179.1875,0 C181.284375,0 183,1.63125 183,3.625 L183,141.375 C183,143.36875 181.284375,145 179.1875,145 L179.1875,145 Z M7.625,137.75 L175.375,137.75 L175.375,7.25 L7.625,7.25 L7.625,137.75 L7.625,137.75 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M175.375,36.25 L7.625,36.25 C5.5205,36.25 3.8125,34.61875 3.8125,32.625 C3.8125,30.63125 5.5205,29 7.625,29 L175.375,29 C177.471875,29 179.1875,30.63125 179.1875,32.625 C179.1875,34.61875 177.471875,36.25 175.375,36.25 L175.375,36.25 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M61.32025,126.57775 C60.0545,126.57775 58.811625,125.98325 58.08725,124.89575 L34.892,89.84925 C34.060875,88.57325 34.121875,86.97825 35.052125,85.7675 L58.247375,55.86125 C59.497875,54.23 61.876875,53.8965 63.57725,55.1145 C65.27,56.3035 65.636,58.57275 64.377875,60.18225 L42.7305,88.102 L64.538,121.05325 C65.658875,122.7425 65.125125,124.99 63.340875,126.04125 C62.72325,126.411 62.014125,126.57775 61.32025,126.57775 L61.32025,126.57775 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M122,126.57775 C121.306125,126.57775 120.597,126.411 119.97175,126.0195 C118.1875,124.961 117.646125,122.72075 118.774625,121.0315 L140.582125,88.08025 L118.927125,60.15325 C117.676625,58.5365 118.035,56.2745 119.72775,55.0855 C121.4205,53.88925 123.807125,54.23725 125.057625,55.84675 L148.24525,85.753 C149.183125,86.9565 149.244125,88.57325 148.405375,89.83475 L125.210125,124.88125 C124.501,125.98325 123.26575,126.57775 122,126.57775 L122,126.57775 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M76.25,119.93675 C75.617125,119.93675 74.98425,119.7845 74.3895,119.48 C72.551875,118.50125 71.896125,116.29725 72.9255,114.521 L103.10525,63.162 C104.134625,61.4075 106.445,60.77675 108.3055,61.79175 C110.1355,62.7705 110.79125,64.96725 109.761875,66.729 L79.582125,118.088 C78.880625,119.26975 77.584375,119.93675 76.25,119.93675 L76.25,119.93675 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M22.875,18.125 C22.875,20.1278125 21.1689062,21.75 19.0625,21.75 C16.9560938,21.75 15.25,20.1278125 15.25,18.125 C15.25,16.1221875 16.9560938,14.5 19.0625,14.5 C21.1689062,14.5 22.875,16.1221875 22.875,18.125 L22.875,18.125 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M38.125,18.125 C38.125,20.1278125 36.4189062,21.75 34.3125,21.75 C32.2060938,21.75 30.5,20.1278125 30.5,18.125 C30.5,16.1221875 32.2060938,14.5 34.3125,14.5 C36.4189062,14.5 38.125,16.1221875 38.125,18.125 L38.125,18.125 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M53.375,18.125 C53.375,20.1278125 51.6689062,21.75 49.5625,21.75 C47.4560938,21.75 45.75,20.1278125 45.75,18.125 C45.75,16.1221875 47.4560938,14.5 49.5625,14.5 C51.6689062,14.5 53.375,16.1221875 53.375,18.125 L53.375,18.125 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
<path d="M274.078672,96.0852289 L254.783869,88.4952289 C254.041565,88.2042289 253.073012,88.0582289 252.099374,88.0582289 C251.128279,88.0582289 250.154641,88.2042289 249.41488,88.4952289 C247.932815,89.0792289 247.932815,90.0242289 249.41488,90.6072289 L263.343236,96.0852289 L218.459304,96.0852289 C218.451678,96.0852289 218.444051,96.0862289 218.433883,96.0862289 L185.561537,96.0862289 L199.489894,90.6082289 C200.971959,90.0242289 200.971959,89.0792289 199.489894,88.4962289 C198.005287,87.9132289 195.60297,87.9132289 194.120905,88.4962289 L171.030695,97.5792289 L174.826102,99.0732289 L194.120905,106.663229 C194.863209,106.954229 195.831762,107.101229 196.8054,107.101229 C197.776495,107.101229 198.750133,106.954229 199.489894,106.663229 C200.971959,106.079229 200.971959,105.134229 199.489894,104.551229 L185.561537,99.0732289 L230.44547,99.0732289 C230.453096,99.0732289 230.460723,99.0722289 230.470891,99.0722289 L263.343236,99.0722289 L249.41488,104.550229 C247.932815,105.134229 247.932815,106.079229 249.41488,106.662229 C250.899486,107.245229 253.301804,107.245229 254.783869,106.662229 L277.874079,97.5792289 L274.078672,96.0852289" id="Fill-25" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(224.452387, 97.579729) rotate(-91.000000) translate(-224.452387, -97.579729) "></path>
|
||||
<path d="M408.414472,164.793761 L375.121164,157.203761 C373.840315,156.912761 372.16907,156.766761 370.489052,156.766761 C368.81342,156.766761 367.133402,156.912761 365.856939,157.203761 C363.299627,157.787761 363.299627,158.732761 365.856939,159.315761 L389.890409,164.793761 L312.442893,164.793761 C312.429734,164.793761 312.416574,164.794761 312.399028,164.794761 L255.677584,164.794761 L279.711053,159.316761 C282.268365,158.732761 282.268365,157.787761 279.711053,157.204761 C277.149355,156.621761 273.00414,156.621761 270.446828,157.204761 L230.604521,166.287761 L237.15352,167.781761 L270.446828,175.371761 C271.727678,175.662761 273.398923,175.809761 275.078941,175.809761 C276.754572,175.809761 278.43459,175.662761 279.711053,175.371761 C282.268365,174.787761 282.268365,173.842761 279.711053,173.259761 L255.677584,167.781761 L333.125099,167.781761 C333.138259,167.781761 333.151418,167.780761 333.168964,167.780761 L389.890409,167.780761 L365.856939,173.258761 C363.299627,173.842761 363.299627,174.787761 365.856939,175.370761 C368.418638,175.953761 372.563852,175.953761 375.121164,175.370761 L414.963472,166.287761 L408.414472,164.793761" id="Fill-26" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(322.783996, 166.288261) rotate(-179.000000) translate(-322.783996, -166.288261) "></path>
|
||||
<text id="documentElement.clie" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="4" y="99">documentElement.clientHeight</tspan>
|
||||
</text>
|
||||
<text id="document.documentEle" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="253" y="198">document.documentElement.clientWidth</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.8 KiB |
|
@ -2,20 +2,16 @@
|
|||
|
||||
Для того, чтобы поместить один элемент рядом с другим на странице, а также двигать его произвольным образом, к примеру, рядом с указателем мыши -- используются координаты.
|
||||
|
||||
Первая координатная система, которую мы посмотрим, начинается в левом-верхнем углу текущей видимой области окна.
|
||||
*Координатная система относительно окна браузера* начинается в левом-верхнем углу текущей видимой области окна.
|
||||
|
||||
Мы будем называть координаты в ней `clientX/clientY`.
|
||||
|
||||
|
||||
## getBoundingClientRect()
|
||||
|
||||
Синтаксис:
|
||||
Метод `elem.getBoundingClientRect()` возвращает координаты элемента, под которыми понимаются размеры "воображаемого прямоугольника", который охватывает весь элемент.
|
||||
|
||||
```js
|
||||
var coords = elem.getBoundingClientRect();
|
||||
```
|
||||
|
||||
Возвращает координаты элемента, а точнее -- размеры прямоугольника, который охватывает элемент, в виде объекта со свойствами: `top`, `left`, `right` и `bottom`:
|
||||
Координаты возвращаются в виде объекта со свойствами:
|
||||
<ul>
|
||||
<li>`top` -- Y-координата верхней границы элемента,</li>
|
||||
<li>`left` -- X-координата левой границы,</li>
|
||||
|
@ -25,12 +21,13 @@ var coords = elem.getBoundingClientRect();
|
|||
|
||||
Например:
|
||||
|
||||
<img src="transitions-bare.png">
|
||||
|
||||
Обратите внимание: страница в этом примере прокручена, её часть осталась сверху.
|
||||
<img src="coords.svg">
|
||||
|
||||
**Координаты относительно окна не учитывают прокрутку, они высчитываются от границ текущей видимой области.**
|
||||
|
||||
Иначе говоря, если страницу прокрутить, то элемент поднимется выше или опустится ниже -- его координаты относительно окна изменятся.
|
||||
|
||||
[online]
|
||||
Например, кликните на кнопку, чтобы увидеть её координаты:
|
||||
|
||||
<input id="brTest" type="button" value="Показать button.getBoundingClientRect() для этой кнопки" onclick='showRect(this)'/>
|
||||
|
@ -43,13 +40,22 @@ function showRect(elem) {
|
|||
</script>
|
||||
|
||||
Если вы прокрутите эту страницу, то положение кнопки в окне изменится, и её координаты, соответственно, тоже.
|
||||
[/online]
|
||||
|
||||
<ul>
|
||||
<li>Координаты могут быть дробными -- это нормально, так как они возвращаются из внутренних структур браузера.</li>
|
||||
<li>Координаты могут быть и отрицательными, например если прокрутить страницу так, что часть кнопки будет выходить за верхнуюю границу окна, то её `top`-координата будет меньше нуля.</li>
|
||||
<li>Некоторые современные браузеры также добавляют к объекту свойства для ширины и высоты: `width/height`, но их можно получить и простым вычитанием: `height = bottom - top`, `width = right - left`.</li>
|
||||
<li>Координаты могут быть и отрицательными, например если прокрутить страницу так, что верх элемента будет выходить за верхнуюю границу окна, то его `top`-координата будет меньше нуля.</li>
|
||||
<li>Некоторые современные браузеры также добавляют к результату `getBoundingClientRect` свойства для ширины и высоты: `width/height`, но их можно получить и простым вычитанием: `height = bottom - top`, `width = right - left`.</li>
|
||||
</ul>
|
||||
|
||||
[warn header="Координаты right/bottom отличаются от CSS-свойств"]
|
||||
Если рассмотреть позиционирование элементов при помощи CSS-свойства `position`, то там тоже указываются `left`, `right`, `top`, `bottom`.
|
||||
|
||||
Однако, по CSS свойство `right` задаёт расстояние от правой границы, а `bottom` -- от нижней.
|
||||
|
||||
Если вы взглянете на иллюстрацию выше, то увидите, что в JavaScript это не так. Все координаты отсчитываются слева/сверху, в том числе и эти.
|
||||
[/warn]
|
||||
|
||||
|
||||
[smart header="Метод `elem.getBoundingClientRect()` изнутри"]
|
||||
|
||||
|
@ -89,54 +95,61 @@ alert( elem.tagName );
|
|||
elem.style.background = "";
|
||||
```
|
||||
|
||||
Аналогично предыдущему методу, используются координаты относительно окна. В зависимости от прокрутки страницы, от размеров окна браузера, в центре может быть разный элемент.
|
||||
Аналогично предыдущему методу, используются координаты относительно окна, так что, в зависимости от прокрутки страницы, в центре может быть разный элемент.
|
||||
|
||||
## position:fixed
|
||||
## Вместе с position:fixed
|
||||
|
||||
Координаты обычно требуются не просто так, а, например, чтобы переместить элемент на них.
|
||||
|
||||
В CSS для позиционирования элемента относительно окна используется свойство `position:fixed`. Как правило, вместе с ним идут и координаты, например `left/top`.
|
||||
|
||||
Например, этот код покажет сообщение под элементом с `id="coords-show-mark"`:
|
||||
Например, функция `createMessageUnder` из кода ниже код покажет сообщение под элементом `elem`:
|
||||
|
||||
```js
|
||||
var elem = document.getElementById("coords-show-mark");
|
||||
|
||||
// получить координаты
|
||||
var coords = elem.getBoundingClientRect();
|
||||
function createMessageUnder(elem, text) {
|
||||
// получить координаты
|
||||
var coords = elem.getBoundingClientRect();
|
||||
|
||||
// создать элемент для сообщения
|
||||
var message = document.createElement('div');
|
||||
|
||||
// эти свойства можно было бы задать классом
|
||||
message.style.position = "fixed";
|
||||
message.style.background = "red";
|
||||
message.style.color = "yellow";
|
||||
// создать элемент для сообщения
|
||||
var message = document.createElement('div');
|
||||
// стиль лучше задавать классом
|
||||
message.style.cssText = "position:fixed; color: red";
|
||||
|
||||
*!*
|
||||
// к координатам обязательно добавляем "px"!
|
||||
message.style.left = coords.left + "px";
|
||||
message.style.top = coords.bottom + "px";
|
||||
// к координатам обязательно добавляем "px"!
|
||||
message.style.left = coords.left + "px";
|
||||
message.style.top = coords.bottom + "px";
|
||||
*/!*
|
||||
|
||||
message.innerHTML = "Привет, мир!";
|
||||
message.innerHTML = text;
|
||||
|
||||
// добавить на 10 сек в документ
|
||||
return message;
|
||||
}
|
||||
|
||||
// Использование
|
||||
// добавить на 5 сек в документ
|
||||
var message = createMessageUnder(elem, 'Привет, мир!');
|
||||
document.body.appendChild(message);
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(message);
|
||||
}, 10000);
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
[online]
|
||||
Нажмите на кнопку, чтобы запустить его:
|
||||
|
||||
<button id="coords-show-mark">кнопка с id="coords-show-mark"</button>
|
||||
[/online]
|
||||
|
||||
Этот код можно модифицировать, чтобы показывать сообщение слева, справа, сверху, делать это вместе с CSS-анимацией и так далее. Для этого нужно всего лишь понимать, как получить координаты.
|
||||
|
||||
**Заметим, однако, важную деталь: при прокрутке страницы сообщение отделяется от кнопки.**
|
||||
**Заметим, однако, важную деталь: при прокрутке страницы сообщение будет визуально отдаляться от кнопки.**
|
||||
|
||||
Причина очевидна, ведь оно использует `position: fixed`. Как это обойти, мы посмотрим в следующей главе.
|
||||
Причина очевидна, ведь оно использует `position: fixed`, так что при прокрутке остаётся на месте, а страница скроллируется.
|
||||
|
||||
Как сделать, чтобы сообщение было именно на конкретном месте документа, а не окна, мы рассмотрим в следующей главе.
|
||||
|
||||
|
||||
[head]
|
||||
|
@ -146,30 +159,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
document.getElementById('coords-show-mark').onclick = function() {
|
||||
var elem = document.getElementById("coords-show-mark");
|
||||
|
||||
// получить координаты
|
||||
var coords = elem.getBoundingClientRect();
|
||||
function createMessageUnder(elem, text) {
|
||||
// получить координаты
|
||||
var coords = elem.getBoundingClientRect();
|
||||
|
||||
// создать элемент для сообщения
|
||||
var message = document.createElement('div');
|
||||
// создать элемент для сообщения
|
||||
var message = document.createElement('div');
|
||||
// стиль лучше задавать классом
|
||||
message.style.cssText = "position:fixed; color: red";
|
||||
|
||||
// эти свойства можно было бы задать классом
|
||||
message.style.position = "fixed";
|
||||
message.style.background = "red";
|
||||
message.style.color = "yellow";
|
||||
message.style.padding = "5px 3px";
|
||||
// к координатам обязательно добавляем "px"!
|
||||
message.style.left = coords.left + "px";
|
||||
message.style.top = coords.bottom + "px";
|
||||
|
||||
// к координатам обязательно добавляем "px"!
|
||||
message.style.left = coords.left + "px";
|
||||
message.style.top = coords.bottom + "px";
|
||||
message.innerHTML = text;
|
||||
|
||||
message.innerHTML = "Привет, мир!";
|
||||
return message;
|
||||
}
|
||||
|
||||
// добавить на 10 сек в документ
|
||||
// Использование
|
||||
// добавить на 5 сек в документ
|
||||
var message = createMessageUnder(elem, 'Привет, мир!');
|
||||
document.body.appendChild(message);
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(message);
|
||||
}, 10000);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
38
2-ui/1-document/17-coordinates/coords.svg
Normal file
38
2-ui/1-document/17-coordinates/coords.svg
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="434px" height="296px" viewBox="0 0 434 296" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>coords</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="coords" sketch:type="MSArtboardGroup">
|
||||
<g id="noun_69008_cc" sketch:type="MSLayerGroup" transform="translate(49.000000, 27.000000)" fill="#8A704D">
|
||||
<path d="M339.770833,256 L7.22916667,256 C3.23866667,256 0,253.12 0,249.6 L0,6.4 C0,2.88 3.23866667,0 7.22916667,0 L339.770833,0 C343.746875,0 347,2.88 347,6.4 L347,249.6 C347,253.12 343.746875,256 339.770833,256 L339.770833,256 Z M14.4583333,243.2 L332.541667,243.2 L332.541667,12.8 L14.4583333,12.8 L14.4583333,243.2 L14.4583333,243.2 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M332.541667,64 L14.4583333,64 C10.4678333,64 7.22916667,61.12 7.22916667,57.6 C7.22916667,54.08 10.4678333,51.2 14.4583333,51.2 L332.541667,51.2 C336.517708,51.2 339.770833,54.08 339.770833,57.6 C339.770833,61.12 336.517708,64 332.541667,64 L332.541667,64 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M43.375,32 C43.375,35.536 40.1399479,38.4 36.1458333,38.4 C32.1517188,38.4 28.9166667,35.536 28.9166667,32 C28.9166667,28.464 32.1517188,25.6 36.1458333,25.6 C40.1399479,25.6 43.375,28.464 43.375,32 L43.375,32 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M72.2916667,32 C72.2916667,35.536 69.0566146,38.4 65.0625,38.4 C61.0683854,38.4 57.8333333,35.536 57.8333333,32 C57.8333333,28.464 61.0683854,25.6 65.0625,25.6 C69.0566146,25.6 72.2916667,28.464 72.2916667,32 L72.2916667,32 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M101.208333,32 C101.208333,35.536 97.9732812,38.4 93.9791667,38.4 C89.9850521,38.4 86.75,35.536 86.75,32 C86.75,28.464 89.9850521,25.6 93.9791667,25.6 C97.9732812,25.6 101.208333,28.464 101.208333,32 L101.208333,32 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
<path d="M219.778498,125.408418 L207.15957,117.818418 C206.674099,117.527418 206.040659,117.381418 205.403893,117.381418 C204.76879,117.381418 204.132025,117.527418 203.648216,117.818418 C202.678936,118.402418 202.678936,119.347418 203.648216,119.930418 L212.757453,125.408418 L183.403067,125.408418 C183.398079,125.408418 183.393092,125.409418 183.386441,125.409418 L161.887712,125.409418 L170.996949,119.931418 C171.966229,119.347418 171.966229,118.402418 170.996949,117.819418 C170.026006,117.236418 168.454875,117.236418 167.485595,117.819418 L152.384446,126.902418 L154.866667,128.396418 L167.485595,135.986418 C167.971066,136.277418 168.604506,136.424418 169.241272,136.424418 C169.876375,136.424418 170.51314,136.277418 170.996949,135.986418 C171.966229,135.402418 171.966229,134.457418 170.996949,133.874418 L161.887712,128.396418 L191.242098,128.396418 C191.247086,128.396418 191.252073,128.395418 191.258724,128.395418 L212.757453,128.395418 L203.648216,133.873418 C202.678936,134.457418 202.678936,135.402418 203.648216,135.985418 C204.619159,136.568418 206.19029,136.568418 207.15957,135.985418 L222.260719,126.902418 L219.778498,125.408418" id="Fill-25" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(187.322583, 126.902918) rotate(-91.000000) translate(-187.322583, -126.902918) "></path>
|
||||
<path d="M253.647832,139.004454 L236.11755,131.414454 C235.443131,131.123454 234.563153,130.977454 233.678555,130.977454 C232.796267,130.977454 231.911669,131.123454 231.239559,131.414454 C229.89303,131.998454 229.89303,132.943454 231.239559,133.526454 L243.894159,139.004454 L203.114891,139.004454 C203.107962,139.004454 203.101033,139.005454 203.091794,139.005454 L173.225646,139.005454 L185.880245,133.527454 C187.226774,132.943454 187.226774,131.998454 185.880245,131.415454 C184.531407,130.832454 182.348783,130.832454 181.002254,131.415454 L160.023658,140.498454 L163.471973,141.992454 L181.002254,149.582454 C181.676673,149.873454 182.556652,150.020454 183.44125,150.020454 C184.323538,150.020454 185.208136,149.873454 185.880245,149.582454 C187.226774,148.998454 187.226774,148.053454 185.880245,147.470454 L173.225646,141.992454 L214.004914,141.992454 C214.011843,141.992454 214.018772,141.991454 214.02801,141.991454 L243.894159,141.991454 L231.239559,147.469454 C229.89303,148.053454 229.89303,148.998454 231.239559,149.581454 C232.588398,150.164454 234.771022,150.164454 236.11755,149.581454 L257.096146,140.498454 L253.647832,139.004454" id="Fill-27" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(208.559902, 140.498954) rotate(-91.000000) translate(-208.559902, -140.498954) "></path>
|
||||
<path d="M183.699419,188.81504 L161.29081,181.22504 C160.428713,180.93404 159.303854,180.78804 158.173091,180.78804 C157.045279,180.78804 155.914515,180.93404 155.055371,181.22504 C153.33413,181.80904 153.33413,182.75404 155.055371,183.33704 L171.231494,188.81504 L119.104167,188.81504 C119.09531,188.81504 119.086453,188.81604 119.074644,188.81604 L80.8973409,188.81604 L97.0734635,183.33804 C98.7947045,182.75404 98.7947045,181.80904 97.0734635,181.22604 C95.3492701,180.64304 92.5592654,180.64304 90.8380244,181.22604 L64.0215029,190.30904 L68.4294151,191.80304 L90.8380244,199.39304 C91.7001211,199.68404 92.8249801,199.83104 93.9557439,199.83104 C95.0835554,199.83104 96.2143192,199.68404 97.0734635,199.39304 C98.7947045,198.80904 98.7947045,197.86404 97.0734635,197.28104 L80.8973409,191.80304 L133.024667,191.80304 C133.033524,191.80304 133.042381,191.80204 133.054191,191.80204 L171.231494,191.80204 L155.055371,197.28004 C153.33413,197.86404 153.33413,198.80904 155.055371,199.39204 C156.779564,199.97504 159.569569,199.97504 161.29081,199.39204 L188.107332,190.30904 L183.699419,188.81504" id="Fill-26" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(126.064417, 190.309540) rotate(-179.000000) translate(-126.064417, -190.309540) "></path>
|
||||
<path d="M258.034748,211.487649 L221.706421,203.897649 C220.308809,203.606649 218.485213,203.460649 216.652045,203.460649 C214.823663,203.460649 212.990494,203.606649 211.597669,203.897649 C208.807232,204.481649 208.807232,205.426649 211.597669,206.009649 L237.822031,211.487649 L153.314396,211.487649 C153.300037,211.487649 153.285678,211.488649 153.266532,211.488649 L91.3743578,211.488649 L117.59872,206.010649 C120.389156,205.426649 120.389156,204.481649 117.59872,203.898649 C114.803497,203.315649 110.280404,203.315649 107.489968,203.898649 L64.0156331,212.981649 L71.1616401,214.475649 L107.489968,222.065649 C108.887579,222.356649 110.711175,222.503649 112.544344,222.503649 C114.372726,222.503649 116.205894,222.356649 117.59872,222.065649 C120.389156,221.481649 120.389156,220.536649 117.59872,219.953649 L91.3743578,214.475649 L175.881993,214.475649 C175.896352,214.475649 175.910711,214.474649 175.929856,214.474649 L237.822031,214.474649 L211.597669,219.952649 C208.807232,220.536649 208.807232,221.481649 211.597669,222.064649 C214.392892,222.647649 218.915984,222.647649 221.706421,222.064649 L265.180755,212.981649 L258.034748,211.487649" id="Fill-28" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(164.598194, 212.982149) rotate(-179.000000) translate(-164.598194, -212.982149) "></path>
|
||||
<rect id="Rectangle-6" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="187" y="161.957" width="75" height="29.043"></rect>
|
||||
<text id="<DIV>" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
|
||||
<tspan x="205.051859" y="181.543917"><DIV></tspan>
|
||||
</text>
|
||||
<text id="top" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="155" y="128">top</tspan>
|
||||
</text>
|
||||
<text id="bottom" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="221" y="128">bottom</tspan>
|
||||
</text>
|
||||
<text id="left" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="109" y="178">left</tspan>
|
||||
</text>
|
||||
<text id="right" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="145" y="232">right</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 108 KiB |
Binary file not shown.
Before Width: | Height: | Size: 231 KiB |
|
@ -1,18 +1,16 @@
|
|||
# Координаты в документе
|
||||
|
||||
Система координат относительно всей страницы или, иначе говоря, относительно *документа*, тоже начинается в левом-верхнем углу.
|
||||
*Система координат относительно страницы* или, иначе говоря, *относительно документа*, начинается в левом-верхнем углу, но не окна, а именно страницы.
|
||||
|
||||
И координаты в ней означают позицию по отношению не к окну браузера, а к документу в целом.
|
||||
|
||||
Если провести аналогию с CSS, то координаты относительно окна -- это `position:fixed`, а относительно документа -- `position:absolute` (при позиционировании вне других элементов, естественно).
|
||||
|
||||
Мы будем называть координаты в ней `pageX/pageY`.
|
||||
|
||||
[cut]
|
||||
|
||||
Зачем нужны ещё какие-то координаты, кроме рассмотренных ранее?
|
||||
|
||||
Как мы видели в конце предыдущей главы, позиционирование через `position: fixed` привязывает элемент не к месту на странице, а к окну. Поэтому при прокрутке страница под таким элементом двигается, а сам элемент -- нет.
|
||||
|
||||
**Как правило, мы хотим показать элемент в определённом месте страницы, а не окна.**
|
||||
|
||||
Для этого используют `position: absolute` и координаты `left/top`, которые заданы относительно документа.
|
||||
Они нужны в первую очередь для того, чтобы показывать элемент в определённом месте страницы, а не окна.
|
||||
|
||||
## Сравнение систем координат
|
||||
|
||||
|
@ -29,46 +27,60 @@
|
|||
Посмотрите на рисунок ниже, на нём -- та же страница, только прокрученная, и тот же элемент "STANDARDS".
|
||||
|
||||
<ul>
|
||||
<li>Координата `clientY` изменилась. Она теперь равна `0`, так как элемент находится вверху окна.</li>
|
||||
<li>Координата `clientY` изменилась. Она была `175`, а стала `0`, так как элемент находится вверху окна.</li>
|
||||
<li>Координата `pageY` осталась такой же, так как отсчитывается от левого-верхнего угла *документа*.</li>
|
||||
</ul>
|
||||
|
||||
<img src="standards-scroll.png">
|
||||
|
||||
**Итак, координаты `pageX/pageY` не меняются при прокрутке, в отличие от `clientX/clientY`.**
|
||||
|
||||
Технически, координаты относительно страницы включают в себя текущую прокрутку. Эти две системы координат жёстко связаны, их разность `pageY-clientY` -- в точности размер текущей прокрученной области.
|
||||
Итак, координаты `pageX/pageY` не меняются при прокрутке, в отличие от `clientX/clientY`.
|
||||
|
||||
## Получение координат [#getCoords]
|
||||
|
||||
К сожалению, готовой функции для получения координат элемента относительно страницы нет. Но её можно легко написать самим.
|
||||
|
||||
Эти две системы координат жёстко связаны: `pageY = clientY + текущая вертикальная прокрутка`.
|
||||
|
||||
Наша функция `getCoords(elem)` будет брать результат `elem.getBoundingClientRect()` и прибавлять текущую прокрутку документа.
|
||||
|
||||
Результат: объект с координатами `{left: .., top: ..}`
|
||||
Результат `getCoords`: объект с координатами `{left: .., top: ..}`
|
||||
|
||||
```js
|
||||
function getCoords(elem) { // кроме IE8-
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
top: box.top + pageYOffset,
|
||||
left: box.left + pageXOffset
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Если нужно поддерживать более старые IE, то вот альтернативный, самый кросс-браузерный вариант:
|
||||
|
||||
```js
|
||||
//+ autorun
|
||||
function getCoords(elem) {
|
||||
// (1)
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docEl = document.documentElement;
|
||||
|
||||
// (2)
|
||||
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
// (3)
|
||||
var clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
// (4)
|
||||
var top = box.top + scrollTop - clientTop;
|
||||
var left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: top, left: left };
|
||||
// (1)
|
||||
var box = elem.getBoundingClientRect();
|
||||
|
||||
var body = document.body;
|
||||
var docEl = document.documentElement;
|
||||
|
||||
// (2)
|
||||
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
// (3)
|
||||
var clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
// (4)
|
||||
var top = box.top + scrollTop - clientTop;
|
||||
var left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: top, left: left };
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -95,9 +107,9 @@ function getOffsetSum(elem) {
|
|||
var top = 0, left = 0;
|
||||
|
||||
while(elem) {
|
||||
top = top + parseInt(elem.offsetTop);
|
||||
left = left + parseInt(elem.offsetLeft);
|
||||
elem = elem.offsetParent;
|
||||
top = top + parseInt(elem.offsetTop);
|
||||
left = left + parseInt(elem.offsetLeft);
|
||||
elem = elem.offsetParent;
|
||||
}
|
||||
|
||||
return {top: top, left: left};
|
||||
|
@ -106,9 +118,8 @@ function getOffsetSum(elem) {
|
|||
|
||||
Казалось бы, код нормальный. И он как-то работает, но разные браузеры преподносят "сюрпризы", включая или выключая размер рамок и прокруток из `offsetTop/Left`, некорректно учитывая позиционирование. В итоге результат не всегда верен. Можно, конечно, разобрать эти проблемы и посчитать действительно аккуратно и правильно этим способом, но зачем? Ведь есть `getBoundingClientRect`.
|
||||
|
||||
### Сравнение offset* с getBoundingClientRect
|
||||
|
||||
Посмотрим разницу между описанными способами вычисления координат на примере.
|
||||
[online]
|
||||
Вы можете увидеть разницу между вычислением координат через `offset*` и `getBoundingClientRect` на примере.
|
||||
|
||||
В прямоугольнике ниже есть 3 вложенных `DIV`. Все они имеют `border`, кое-кто из них имеет `position/margin/padding`.
|
||||
|
||||
|
@ -117,7 +128,7 @@ function getOffsetSum(elem) {
|
|||
[pre]
|
||||
<div style="position:relative;padding:10px;height:80px;width:380px;border:7px red solid">
|
||||
<div style="border:10px blue solid;padding:2px;position:absolute;left:20%;top:20%">
|
||||
<div id="getBoundingClientRectEx" style="background-color:yellow;border:4px solid black;margin:2px;cursor:pointer">Кликните, чтобы получить координаты getOffsetSum и getCoords</div>
|
||||
<div id="getBoundingClientRectEx" style="background-color:yellow;border:4px solid black;margin:2px;cursor:pointer">Кликните, чтобы получить координаты getOffsetSum и getCoords</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="getBoundingClientRectExRes">
|
||||
|
@ -128,20 +139,20 @@ function getOffsetSum(elem) {
|
|||
|
||||
<script>
|
||||
document.getElementById('getBoundingClientRectEx').onclick = function(event) {
|
||||
var o = getOffsetSum(this);
|
||||
var orect = getCoords(this);
|
||||
|
||||
event = event || window.event;
|
||||
if ( event.pageX == null && event.clientX != null ) {
|
||||
var html = document.documentElement, body = document.body;
|
||||
event.pageX = event.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0)
|
||||
event.pageY = event.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0)
|
||||
}
|
||||
var o = getOffsetSum(this);
|
||||
var orect = getCoords(this);
|
||||
|
||||
event = event || window.event;
|
||||
if ( event.pageX == null && event.clientX != null ) {
|
||||
var html = document.documentElement, body = document.body;
|
||||
event.pageX = event.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0)
|
||||
event.pageY = event.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0)
|
||||
}
|
||||
|
||||
var list = document.getElementById('getBoundingClientRectExRes').getElementsByTagName('SPAN')
|
||||
list[0].innerHTML = '{left:'+o.left+', top:'+o.top+'}'
|
||||
list[1].innerHTML = '{left:'+orect.left+', top:'+orect.top+'}'
|
||||
list[2].innerHTML = 'pageX='+event.pageX+' pageY='+event.pageY
|
||||
var list = document.getElementById('getBoundingClientRectExRes').getElementsByTagName('SPAN')
|
||||
list[0].innerHTML = '{left:'+o.left+', top:'+o.top+'}'
|
||||
list[1].innerHTML = '{left:'+orect.left+', top:'+orect.top+'}'
|
||||
list[2].innerHTML = 'pageX='+event.pageX+' pageY='+event.pageY
|
||||
}
|
||||
</script>
|
||||
[/pre]
|
||||
|
@ -154,79 +165,21 @@ document.getElementById('getBoundingClientRectEx').onclick = function(event) {
|
|||
|
||||
<img src="getcoords-compare.png">
|
||||
|
||||
**Именно `getCoords` всегда возвращает верное значение :).**
|
||||
|
||||
|
||||
|
||||
|
||||
### Комбинированный подход
|
||||
|
||||
Фреймворки, которые хотят быть совместимыми со старыми браузерами, используют комбинированный подход:
|
||||
|
||||
```js
|
||||
function getOffset(elem) {
|
||||
if (elem.getBoundingClientRect) {
|
||||
return getCoords(elem);
|
||||
} else { // старый браузер
|
||||
return getOffsetSum(elem);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[js hide="Открыть полный код getCoords/getOffsetSum"]
|
||||
function getOffsetSum(elem) {
|
||||
var top=0, left=0
|
||||
while(elem) {
|
||||
top = top + parseInt(elem.offsetTop)
|
||||
left = left + parseInt(elem.offsetLeft)
|
||||
elem = elem.offsetParent
|
||||
}
|
||||
|
||||
return {top: top, left: left}
|
||||
}
|
||||
|
||||
|
||||
function getCoords(elem) {
|
||||
var box = elem.getBoundingClientRect()
|
||||
|
||||
var body = document.body;
|
||||
var docEl = document.documentElement;
|
||||
|
||||
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
var clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
var top = box.top + scrollTop - clientTop;
|
||||
var left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return { top: Math.round(top), left: Math.round(left) };
|
||||
}
|
||||
|
||||
|
||||
function getOffset(elem) {
|
||||
if (elem.getBoundingClientRect) {
|
||||
return getCoords(elem)
|
||||
} else {
|
||||
return getOffsetSum(elem)
|
||||
}
|
||||
}
|
||||
[/js]
|
||||
|
||||
Именно `getCoords` всегда возвращает верное значение.
|
||||
[/online]
|
||||
|
||||
## Координаты на экране screenX/screenY
|
||||
|
||||
Есть ещё одна система координат, которая используется очень редко, но для полноты картины необходимо её упомянуть.
|
||||
|
||||
Координаты относительно *экрана* `screenX/screenY` отсчитываются от его левого-верхнего угла. Имеется в виду именно *весь экран*, а не окно браузера.
|
||||
*Координаты относительно экрана* `screenX/screenY` отсчитываются от его левого-верхнего угла. Имеется в виду именно *весь экран*, а не окно браузера.
|
||||
|
||||
<img src="screen.png">
|
||||
|
||||
Такие координаты могут быть полезны, например, при работе с мобильными устройствами или для открытия нового окна посередине экрана вызовом [window.open](https://developer.mozilla.org/en-US/docs/DOM/window.open).
|
||||
|
||||
<ul>
|
||||
<li>**Общая информация об экране хранится в глобальной переменной [screen](https://developer.mozilla.org/en/DOM/window.screen):**
|
||||
<li>Размеры экрана хранятся в глобальной переменной [screen](https://developer.mozilla.org/en/DOM/window.screen):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -240,7 +193,7 @@ alert( screen.availWidth + ' x ' + screen.availHeight);
|
|||
```
|
||||
|
||||
</li>
|
||||
<li>**Координаты левого-верхнего угла браузера на экране хранятся в `window.screenX,` `window.screenY`** (не поддерживаются IE8-):
|
||||
<li>Координаты левого-верхнего угла браузера на экране хранятся в `window.screenX,` `window.screenY` (не поддерживаются IE8-):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
@ -248,9 +201,10 @@ alert("Браузер находится на " + window.screenX + "," + window.
|
|||
```
|
||||
|
||||
Они могут быть и меньше нуля, если окно частично вне экрана. </li>
|
||||
<li>**Координаты *DOM-элемента* на экране получить нельзя, браузер не предоставляет свойств и методов для этого.**</li>
|
||||
</ul>
|
||||
|
||||
Заметим, что общую информацию об экране и браузере получить можно, а вот координаты конкретного элемента на экране -- нельзя, нет аналога `getBoundingClientRect` или иного метода для этого.
|
||||
|
||||
## Итого
|
||||
|
||||
У любой точки в браузере есть координаты:
|
||||
|
@ -260,7 +214,7 @@ alert("Браузер находится на " + window.screenX + "," + window.
|
|||
<li>Относительно экрана `screen` -- можно узнать координаты браузера, но не элемента.</li>
|
||||
</ol>
|
||||
|
||||
Метод `elem.getBoundingClientRect()` поддерживается IE очень давно, с версии 6, а вот версии других браузеров старше чем 2010 года (примерно), могут не иметь его. Для них (и только для них) используется подсчёт координат суммированием `offsetTop/Left`.
|
||||
Иногда в старом коде можно встретить использование `offsetTop/Left` для подсчёта координат. Это очень старый и неправильный способ, не стоит его использовать.
|
||||
|
||||
Координаты будут нужны нам далее, при работе с событиями мыши (координаты клика) и элементами (перемещение).
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue