Координаты элемента на странице
Для кросс-браузерного вычисления координат элемента давно используется суммирование offsetLeft/offsetTop. Список глюков этого подхода такой же длинный, как история его существования.
Эта статья - о том, как вычислять координаты не только кросс-браузерно, но и правильно. Да, и еще - быстро.
Логика этого подхода довольно проста.
Она заключается в том, что браузер позиционирует элементы относительно друг друга, и у каждого элемента есть свой "родитель по позиционированию": offsetParent.
В качестве offsetParent обычно выступает родитель parentNode. Но это не всегда так.
Например, для элемента с position='absolute' родителем по позиционированию является ближайший позиционированный родитель, то есть первый элемент в цепочке вложенности, у которого свойство position - одно из: absolute, relative или fixed(не поддерживается IE<7).
Наиболее подробно это описано в стандарте CSS: containing block details. Этот самый "containing block" - как раз и определяет offsetParent, от которого отсчитывается позиция элемента.
Как браузер находит elem.offsetParent
Двигаемся вверх по цепочке родителей elem, останавливаясь на следующих элементах, которые являются offsetParent:
<body>
- элемент, у которого
position - не static(значение по умолчанию)
- элементы table,th,td - если
elem.position='static'
У элемента <body> никогда нет offsetParent.
В IE 7+/Opera у элементов с elem.position='fixed' нет offsetParent.
Сдвиг относительно offsetParent'а задается свойствами offsetTop/offsetLeft:

Остается пройтись по всем offsetParent и просуммировать сдвиги. Последним offsetParent обычно является body:
function getOffsetSum(elem) {
var top=0, left=0
while(elem) {
top = top + parseFloat(elem.offsetTop)
left = left + parseFloat(elem.offsetLeft)
elem = elem.offsetParent
}
return {top: Math.round(top), left: Math.round(left)}
}
Основных проблем с этим кодом две.
- Он слегка глючит, в разных браузерах - по-разному. Есть проблемы с border'ами элементов, ошибки при прокрутке внутри элементов и некоторые другие.
- Он медленный. Каждый раз приходится пройти всю цепочку
offsetParent'ов.
Вместо того, чтобы писать длинный кроссбраузерный код с разбором багов, который уж точно везде работает корректно, рассмотрим альтернативное решение, которое мало того что соответствует стандарту - его отлично поддерживают Internet Explorer 6+, Firefox 3+ и Opera 9.62+.
Этот малоизвестный метод по стандарту должен быть у каждого элемента DOM.
На момент написания статьи он реализован в SVN у Webkit. Для нас это значит, что Chrome и Safari с его поддержкой будут готовы уже скоро.
Он возвращает прямоугольник, ограничивающий элемент.
Важно, что координаты прямоугольника заданы относительно окна, а не документа, то есть не учитывают прокрутку страницы. Кроме того, координаты прямоугольника могут быть дробными в Firefox 3.
Координаты элемента на странице - это левый-верхний угол прямоугольника + прокрутка страницы.
Код обработчика onclick:
var br=this.getBoundingClientRect()
alert("Top:"+br.top+", Left:"+br.left+", Right:"+br.right+", Bottom:"+br.bottom)
Как работает getBoundingClientRect() ?
По стандарту CSS любое содержимое находится внутри некоторого прямоугольника: css box.
В случае с блочными элементами, например DIV - этим прямоугольником яляется сам элемент. Такой прямоугольник называют block box.
Если элемент строчный, например, длинный текст - он уже может быть не такой простой формы, а требует для отображения нескольких прямоугольников anonymous box. Обо всем этом подробно об этом написано в стандарте: http://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level".
Так что содержание элемента DOM может находится как в одном, так и в нескольких прямоугольниках css box.
Можно получить список всех прямоугольников, соответствующих elem, вызовом elem.getClientRects(). Здесь IE<8 может сделать свои прямоугольники, не соответствующие стандарту, но они для нас не играют роли, так как метод getClientRects() нам не нужен.
Метод elem.getBoundingClientRect() возвращает один (минимальный) прямоугольник, который включает в себя все прямоугольники getClientRects() с содержимым элемента.
На основе метода elem.getBoundingClientRect() мы можем сделать новый вариант функции:
function getOffsetRect(elem) {
// (1)
var box = elem.getBoundingClientRect()
// (2)
var body = document.body
var docElem = document.documentElement
// (3)
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft
// (4)
var clientTop = docElem.clientTop || body.clientTop || 0
var clientLeft = docElem.clientLeft || body.clientLeft || 0
// (5)
var top = box.top + scrollTop - clientTop
var left = box.left + scrollLeft - clientLeft
return { top: Math.round(top), left: Math.round(left) }
}
- Получить ограничивающий прямоугольник для элемента.
- Задать две переменных для удобства
- Вычислить прокрутку документа. Все браузеры, кроме IE, поддерживают
pageXOffset/pageYOffset, а в IE, при наличии DOCTYPE прокрутка вычисляется либо на documentElement(<html>), иначе на body - что есть то и берем
- Документ(
html или body) бывает сдвинут относительно окна (IE). Получаем этот сдвиг.
- Прибавляем к координатам относительно окна прокрутку и вычитаем сдвиг
html/body, чтобы получить координаты относительно документа
Для Firefox дополнительно мы округляем координаты вызовом Math.round().
На демо находятся 3 вложенных DIV'а. Все они с border, некоторые с position/margin/padding.
Клик на внутреннем отображает значения getOffsetSum/getOffsetRect, а также показывает координаты курсора на момент клика: event.pageX/pageY.
Координаты выводятся сразу под DIV'ами.
На момент написания статьи демо работает в IE6+,Firefox 3+ и Opera 9.62+.
Сравнить getOffsetSum и getOffsetRect
getOffsetSum:значение getOffsetSum()
getOffsetRect:значение getOffsetRect()
event:координаты клика мышью относительно документа
Обратите внимание: результаты getOffsetSum(elem) и getOffsetRect(elem) не во всех браузерах совпадают.
Чтобы увидеть, какой вариант правильный - кликните на самой верхней-левой точке элемента (на самом верхнем-левом уголке черной рамки).
Тогда в pageX/pageY события появятся реальные значения угла элемента, и вы сможете их сравнить с getOffsetSum/getOffsetRect. Именно для такого сравнения в демо и добавлен вывод event.
Что делать, если метод getBoundingClientRect не поддерживается?
С одной стороны, мы могли бы рассмотреть корректную кросс-браузерную реализацию для FF2/Safari/Chrome/Konqueror. Она включает в себя много кода для обхода браузерных багов при подсчетах, которые нам совсем не интересны.
С другой - FF2 давно умер, а движок Safari/Chrome содержит поддержку getBoundingClientRect в SVN, и значит она скоро будет в релизе.
Думаю, и такие явные аутсайдеры как Konqueror подсуетятся, т.к движок Konqueror - по сути такой же, как и Safari.
Поэтому предлагаю использовать:
function getOffset(elem) {
if (elem.getBoundingClientRect) {
// "правильный" вариант
return getOffsetRect(elem)
} else {
// пусть работает хоть как-то
return getOffsetSum(elem)
}
}
Скачать полный финальный вариант.
Вы узнали, как вычислять координаты элемента на странице: быстро и точно в IE6+/FF3+/Opera 9.62+, и как-то чтоб работало - в Safari/Chrome/Konqueror.
Скоро и в Safari/Chrome/Konqueror будет поддержка правильного метода, поэтому аутсайдерами останутся лишь совсем редкие и багливые браузеры, и все будет работать ок .
|
Автор: Константин (не зарегистрирован), дата: 7 мая, 2009 - 18:27
#permalinkИнтересная статья. У меня ворос для разработчиков, которые смотрели в код популярных библиотек (jQuery, Mootools, Prototype, extJS) как там реализованно вычисление координат?
Автор: Гость (не зарегистрирован), дата: 20 мая, 2009 - 19:12
#permalinkДавно искал урок как найти абсолютную позицию, "Комбинированный вариант" подходит отлично.
Автор: vflash, дата: 22 мая, 2009 - 12:38
#permalinkХорошая статья. Добавлю только что стоит проверять body что он есть, те body&&body.clientTop
Автор: Гость (не зарегистрирован), дата: 23 мая, 2009 - 12:00
#permalinkСтатья супер!МОЛОДЕЦ!
Автор: multimetr, дата: 26 мая, 2009 - 22:14
#permalinkВ Opere 9.20 не работает.Видимо в версии 9.62 уже поддерживается.
Автор: ArmA (не зарегистрирован), дата: 10 июня, 2009 - 08:08
#permalinkБольшое спасибо за ёмкую статью по существу. А то копать по всем стандартам довольно затруднительно %^>
Автор: Гость (не зарегистрирован), дата: 25 июня, 2009 - 23:58
#permalinkв MooTools 2.2.3, как на меня сама удачная реализация определения координаторов с поддержкой getBoundingClientRect и бордеров элементов. Позволю себе привести часть исходного кода:
getOffsets: function() { if (this.getBoundingClientRect){ var bound = this.getBoundingClientRect(), html = document.id(this.getDocument().documentElement), scroll = html.getScroll(), isFixed = (styleString(this, 'position') == 'fixed'); return { x: parseInt(bound.left, 10) + ((isFixed) ? 0 : scroll.x) - html.clientLeft, y: parseInt(bound.top, 10) + ((isFixed) ? 0 : scroll.y) - html.clientTop }; } var element = this, position = {x: 0, y: 0}; if (isBody(this)) return position; while (element && !isBody(element)){ position.x += element.offsetLeft; position.y += element.offsetTop; if (Browser.Engine.gecko){ if (!borderBox(element)){ position.x += leftBorder(element); position.y += topBorder(element); } var parent = element.parentNode; if (parent && styleString(parent, 'overflow') != 'visible'){ position.x += leftBorder(parent); position.y += topBorder(parent); } } else if (element != this && Browser.Engine.webkit){ position.x += leftBorder(element); position.y += topBorder(element); } element = element.offsetParent; } if (Browser.Engine.gecko && !borderBox(this)){ position.x -= leftBorder(this); position.y -= topBorder(this); } return position; } function styleNumber(element, style){ return styleString(element, style).toInt() || 0; }; function borderBox(element){ return styleString(element, '-moz-box-sizing') == 'border-box'; }; function topBorder(element){ return styleNumber(element, 'border-top-width'); }; function leftBorder(element){ return styleNumber(element, 'border-left-width'); }; function isBody(element){ return (/^(?:body|html)$/i).test(element.tagName); };Автор: aavolkoff (не зарегистрирован), дата: 11 августа, 2009 - 15:38
#permalinkВ JQuery глюки с clientY функции offset() и подобных в Опере и Хроме вот один из тикетов:
http://dev.jquery.com/ticket/4583
Автор: Гость (не зарегистрирован), дата: 10 января, 2010 - 01:58
#permalinkОчен помогло, спасибо!
Автор: offset (не зарегистрирован), дата: 9 марта, 2010 - 11:14
#permalinkУ меня пока не работает как надо getBoundingClientRect(), так как у элементов (body и div) выставлено position:relative без смещений и есть margin у внутреннего div относительно body. Правильно отображает только Хром (Сафари 4 не проверял), остальные браузеры не учитывают margin (IE, Opera, FF), приходится его вручную дописывать к координатам.
Автор: Гость (не зарегистрирован), дата: 27 марта, 2010 - 16:16
#permalinkВ своё время много перечитал документации по этому вопросу. Но делалось всё под ужасно бажные IE5.5-6, старую Оперу < 9.1 и Фаерфокс. Наилучшим решение вроде казалось тогда применение getBoundingClientRect(). Но баг с тем, что эксплорер и опера под одни и те же числовые значения рисовали элементы не много, но различающиеся по размеру, иной раз сводил усилия по оформлению страницы к оценке "пойдёт". На тот момент было ужасно интересно заглянуть в код эксплорера и оперы. Кто-нибудь знает как там вычисляется размер элементов и как он соотносится с замерами окон?
Автор: HelpeR, дата: 17 мая, 2010 - 19:14
#permalinkСпасибо за отличную статью. Помогла мне очень, реализовал для эффекта PhotoLine, что бы определить место положение самой ленты, для прокрутки ее роликом мыши.
Автор: Гость (не зарегистрирован), дата: 11 декабря, 2010 - 10:30
#permalinkСпасибо. Очень помогла.
Автор: DimXenon (не зарегистрирован), дата: 31 марта, 2011 - 18:09
#permalinkПросто хочу сказать Большое Спасибо!
Много-много здоровья и долгих лет жизни автору!
Это то, что я искал!
Автор: yAnTar (не зарегистрирован), дата: 12 апреля, 2011 - 11:06
#permalinkТочно так же реализован offset в jQuery. Но недавно столкнулся с неожиданным багом - есть у меня
с заданной шириной, всередине его есть спаны и в случаи перехода спана на новую строку - в 1 случае getBoundingClientRect дает некорректный координаты - он видит слово на предыдущей строке, а оно уже в следующей находится.
Браузер safari, платформа ipod.
Автор: Гость (не зарегистрирован), дата: 16 апреля, 2011 - 17:21
#permalinkМолоток! Биг сенкс фор ю
очень полезная статья
Автор: Гость (не зарегистрирован), дата: 20 мая, 2011 - 21:22
#permalinkС использованием JQuery
$("#block")
.click(function(e){
X = e.pageX-$(this).offset().left;
Y = e.pageY-$(this).offset().top;
})
Автор: Wayne, дата: 13 августа, 2011 - 12:44
#permalinkВот рабочая функция. Может кому-то пригодиться. Статья понравилась. И это мой к ней комментарий. Чтобы уж совсем не был пустым коммент, выложил сию функцию.
function ElemCoords(obj){
var curleft = 0;
var curtop = 0;
if (obj.offsetParent)
{
while (1)
{
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
if (!obj.offsetParent)
break;
obj=obj.offsetParent;
}
}
else if (obj.x || obj.y)
{
curleft += obj.x;
curtop += obj.y;
}
return {"x":curleft, "y":curtop};
}
Получить координаты элемента можно вызовом функции, типа "ElemCoords(object).x".
Автор: Гость (не зарегистрирован), дата: 16 августа, 2011 - 01:06
#permalinkавтору искренняя благодарность
Автор: Гость (не зарегистрирован), дата: 31 августа, 2011 - 17:50
#permalinkНа лето 2011 getBoundingClientRect поддерживается всеми актуальными браузерами.
Автор: Гость (не зарегистрирован), дата: 29 ноября, 2011 - 11:56
#permalinkопределите пожалуйста координаты элемента/клика на такой странице:
body { background-color:#abcabc; margin:0 auto; max-width:900px; min-width:800px; position:relative; width:100%; }окошко браузера должно быть открыть шире чем на max-width

Автор: Гость (не зарегистрирован), дата: 3 февраля, 2012 - 04:42
#permalinkПревосходно! Браво! Прекрасная статья. Помогла однозначно разрулить проблемы с обработкой ситуаций сложного позиционирования (fixed->absolute->static->absolute) вложенных элементов. Достойный материал.
Автор: Анонимус (не зарегистрирован), дата: 9 мая, 2012 - 16:14
#permalink// координаты объекта function getAbsolutePosition(el) { var r = { x: el.offsetLeft, y: el.offsetTop }; if (el.offsetParent) { var tmp = getAbsolutePosition(el.offsetParent); r.x += tmp.x; r.y += tmp.y; } return r; }Автор: cyber, дата: 13 мая, 2012 - 15:46
#permalinkfunction left (elem) { var left=0; alert(elem.offsetLeft) while(elem) { left += parseFloat(elem.offsetLeft); elem= elem.offsetParent; } return Math.round(left); }как насчет такого варианта
Автор: Гость (не зарегистрирован), дата: 7 ноября, 2012 - 14:02
#permalinkкогда я писал этот (Автор: Гость (не зарегистрирован), дата: 29 ноября, 2011 - 12:56) пример, я имел ввиду что, левый верхний угол body необязательно начинается с верхнего левого угла вьюпорта.
Посмотрите на пример и протестите его на всех IE скроля и не скроля страницу.
Сделать метод, умеющий нормально определять координаты на экране для всех браузеров, включая IE6+, включая режим совместимости, можно.
Для этого нужно всего лишь учесть поправку смещения начала координат относительно вьюпорта:
var div = document.createElement('div'); div.style.position = 'absolute'; div.style.left = '0px'; div.style.top = '0px'; div.style.display = 'block'; document.body.appendChild(div); var horizontalFix = div.getBoundingClientRect().left; var verticalFix = div.getBoundingClientRect().top;Автор: Гость (не зарегистрирован), дата: 28 декабря, 2012 - 14:47
#permalinkУ меня IE 7. Функция getBoundingClientRect не учитывает свойство таблиц cellSpacing при подсчете.
Автор: Гость (не зарегистрирован), дата: 18 мая, 2013 - 10:18
#permalinkСупер!
Автор: pro777 (не зарегистрирован), дата: 13 сентября, 2013 - 19:57
#permalinkСпасибо, отличная статья - помогла!
Автор: Oleg1980 (не зарегистрирован), дата: 28 января, 2014 - 13:20
#permalinkчто делать, если имеется пустой div или br на странице, как вычислить его координаты, getBoundingClientRect возвращает прямоугольник с нулевыми координатами?
Автор: Гость (не зарегистрирован), дата: 10 мая, 2014 - 13:19
#permalinkПодскажите, пожалуйста. А можно решить такую задачу - имеется определенное слово, необходимо проверить есть ли оно на экране, если есть - выдать его координаты.
Автор: Гость (не зарегистрирован), дата: 24 июля, 2014 - 10:42
#permalinkАдреса страниц
Автор: Гость (не зарегистрирован), дата: 10 ноября, 2015 - 15:34
#permalinkДобрый день. Ситуация следующая. Есть множество елементов на странице, создаются они юезром, ограничения на создание по количеству нет. Можно удалять старые и создавать новые. Их можно ресайзить и перетаскивать. Все они имеют братски-сестринские связи. Как мне сравнить координаты одного елемента, выбранного юзером, с другими, для запрета пересечения элементов друг-другом?
Автор: mi.rafaylik, дата: 29 октября, 2019 - 21:31
#permalinkВ статье не учтено, что родитель элемента (или любой вверх по цепочке) может иметь position: sticky, и вся схема просто рухнет, хоть с offsetParent, хоть с getBoundingClientRect. При нулевой прокрутке всё сработает верно, а потом.. Впрочем предлагаю просмотреть пример с демонстрацией проблемы:
jsfiddle.net/rafaylik/sf5Lcrjp
Как можно поступить, если родитель имеет position: sticky:
И рабочий пример (спасибо Malleys за совет):
jsfiddle.net/Lk74do8u