Javascript.RU

Координаты элемента на странице

Update: Более новый материал по этой теме находится по адресу https://learn.javascript.ru/coordinates.

Для кросс-браузерного вычисления координат элемента давно используется суммирование 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)}
}

Основных проблем с этим кодом две.

  1. Он слегка глючит, в разных браузерах - по-разному. Есть проблемы с border'ами элементов, ошибки при прокрутке внутри элементов и некоторые другие.
  2. Он медленный. Каждый раз приходится пройти всю цепочку 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) }
}
  1. Получить ограничивающий прямоугольник для элемента.
  2. Задать две переменных для удобства
  3. Вычислить прокрутку документа. Все браузеры, кроме IE, поддерживают pageXOffset/pageYOffset, а в IE, при наличии DOCTYPE прокрутка вычисляется либо на documentElement(<html>), иначе на body - что есть то и берем
  4. Документ(html или body) бывает сдвинут относительно окна (IE). Получаем этот сдвиг.
  5. Прибавляем к координатам относительно окна прокрутку и вычитаем сдвиг 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) как там реализованно вычисление координат?


Автор: Илья Кантор, дата: 7 мая, 2009 - 21:26
#permalink

Так, как здесь описано, только хуже.
За исключением jQuery/dojo toolkit, где тоже реализовано так, как здесь описано, но в dojo вариант offsetParent лучше проработан для Firefox 2 и Safari/Chrome, а jQuery вообще использует parentNode и делает ряд дополнительных измерений для обнаружения багов браузера.


Автор: Гость (не зарегистрирован), дата: 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".


Автор: Ленивец (не зарегистрирован), дата: 7 декабря, 2011 - 14:35
#permalink

Отличный код. так лень собирать было самому, просто взял этот. Работает. Спс )


Автор: Гость (не зарегистрирован), дата: 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
#permalink
function left (elem) 
{
 var left=0;
	 alert(elem.offsetLeft)
	 while(elem)
	 {
	 
	left += parseFloat(elem.offsetLeft);
	
	 elem= elem.offsetParent;
	 }
	 return Math.round(left);
	 

}

как насчет такого варианта


Автор: ОВА (не зарегистрирован), дата: 30 ноября, 2016 - 20:06
#permalink

Гениально !!!


Автор: Гость (не зарегистрирован), дата: 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:

  1. Сохранить position родителя в переменную
  2. Назначить родителю position: relative
  3. Получить координаты элемента
  4. Вернуть родителю его первоначальный position

И рабочий пример (спасибо Malleys за совет):
jsfiddle.net/Lk74do8u


 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние темы на форуме
Forum