Координаты в JavaScript
Координаты в JavaScript — числовые значения, описывающие позицию на странице. Узнайте о двух системах координат браузера и ключевых методах DOM.
Координаты — это числовые значения, описывающие положение на странице. Практически каждая интерактивная функция — перетаскивание, рисование, подсказки, всплывающие меню, определение попадания — в итоге сводится к считыванию координаты и размещению или сравнению чего-либо относительно неё.
Сложность в том, что браузер предоставляет две разные системы координат, и их путаница является наиболее распространённой причиной ошибок вида «мой элемент перемещается не на то место». В этой главе объясняются обе системы, способы преобразования между ними, а также ключевые методы DOM (getBoundingClientRect() и elementFromPoint()), используемые для считывания позиций элементов.
Две системы координат
Существуют две системы отсчёта, которые необходимо различать:
- Координаты viewport (относительно окна) — отсчитываются от верхнего левого угла видимой части страницы. Они не изменяются при прокрутке. Точка в верхнем левом углу видимой области всегда равна
(0, 0). Свойства:clientX/clientY. - Координаты документа (относительно страницы) — отсчитываются от верхнего левого угла всего документа, включая часть, прокрученную за пределы экрана. Они увеличиваются при прокрутке вниз. Свойства:
pageX/pageY.
Связь между ними определяется смещением прокрутки:
pageX = clientX + window.scrollX;
pageY = clientY + window.scrollY;Когда страница не прокручена, window.scrollX и window.scrollY равны 0, поэтому обе системы дают одинаковые значения — именно поэтому ошибки, связанные с прокруткой, не проявляются до тех пор, пока пользователь не прокрутит страницу.
Используйте координаты viewport (clientX/Y) когда… | Используйте координаты документа (pageX/Y) когда… |
|---|---|
Позиционируете элемент с position: fixed | Позиционируете элемент с position: absolute |
| Проверяете попадание в то, что видно на экране | Сохраняете место клика для последующего воспроизведения |
Работаете с getBoundingClientRect() | Рисуете на высоком прокручиваемом canvas |
О том, как измеряются window.scrollX/Y и размер viewport, читайте в разделе Размеры окна и прокрутка в JavaScript, а о программном управлении прокруткой — в разделе Прокрутка в JavaScript.
Считывание координат из события мыши
Каждое событие мыши содержит информацию об обеих системах координат. Полная модель событий описана в разделе Основы событий мыши.
Координаты viewport: clientX / clientY
event.clientX и event.clientY возвращают положение указателя относительно viewport независимо от прокрутки. Пример ниже размещён внутри высокой страницы, чтобы вы могли прокрутить её и убедиться, что числа привязаны к видимой области:
<!-- snippet: html-result -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Client-Side Coordinates Example</title>
<style>
#container {
width: 100%;
height: 100%;
background-color: grey;
min-height: 40px;
}
</style>
</head>
<body style="height: 2000px;">
<div id="container">Click anywhere in the grey area to see Client-Side coordinates!</div>
<script>
const container = document.getElementById('container');
function showCoords(event) {
alert("Client-Side X: " + event.clientX + ", Y: " + event.clientY);
}
container.addEventListener('click', showCoords);
</script>
</body>
</html>Координаты документа: pageX / pageY
event.pageX и event.pageY возвращают положение указателя относительно всего документа, поэтому они уже включают смещение прокрутки. Они стандартны и широко поддерживаются — вычислять их вручную не нужно. Пример ниже показывает их рядом с ручным вычислением clientX + window.scrollX, чтобы вы могли убедиться в их совпадении:
<!-- snippet: html-result -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Page-Side Coordinates Example</title>
<style>
#container {
width: 100%;
height: 100%;
background-color: grey;
min-height: 40px;
}
</style>
</head>
<body style="height: 2000px;">
<div id="container">Click anywhere in this area to see Page-Side coordinates!</div>
<script>
const container = document.getElementById('container');
function showPageCoords(event) {
// pageX/pageY already include scroll; the manual version must match
const manualX = event.clientX + window.scrollX;
const manualY = event.clientY + window.scrollY;
alert("pageX/Y: " + event.pageX + ", " + event.pageY +
"\nclientX+scrollX: " + manualX + ", " + manualY);
}
container.addEventListener('click', showPageCoords);
</script>
</body>
</html>Экранные координаты: screenX / screenY
event.screenX и event.screenY измеряют положение указателя относительно всего физического экрана, а не только окна браузера. Они полезны для отслеживания курсора на нескольких мониторах или позиционирования всплывающего окна window.open(), но для работы внутри страницы почти всегда предпочтительнее использовать clientX/Y или pageX/Y.
Считывание позиции элемента: getBoundingClientRect()
Чтобы найти положение элемента (а не курсора мыши), вызовите getBoundingClientRect(). Метод возвращает объект DOMRect, свойства которого top, right, bottom, left, width и height указаны в координатах viewport — той же системе, что и clientX/Y.
const rect = element.getBoundingClientRect();
// rect.left, rect.top → top-left corner, relative to the viewport
// rect.width, rect.height → rendered size including borders/paddingПоскольку прямоугольник задан относительно viewport, он изменяется при прокрутке. Чтобы получить позицию элемента в координатах документа, добавьте смещение прокрутки:
function getDocumentCoords(el) {
const rect = el.getBoundingClientRect();
return {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
};
}Эта комбинация — считывание через getBoundingClientRect() и добавление scrollX/Y при необходимости получить координаты страницы — понадобится вам постоянно.
Поиск элемента по координатам: elementFromPoint()
На обратный вопрос — «что находится под этими координатами?» — отвечает метод document.elementFromPoint(x, y). Он принимает координаты viewport (соответствующие clientX/Y) и возвращает самый верхний элемент в указанной точке или null, если точка находится за пределами viewport.
document.addEventListener('click', (event) => {
const el = document.elementFromPoint(event.clientX, event.clientY);
console.log('You clicked on:', el.tagName);
});Распространённая ошибка: передать в метод pageX/pageY на прокрученной странице — тогда он вернёт неправильный элемент (или null), потому что ожидает координаты viewport, а не документа.
Управление позицией элементов с помощью координат
Вы можете комбинировать эти инструменты для перемещения элементов. Метод getBoundingClientRect() позволяет вычислить смещение между указателем и углом элемента, чтобы при начале перетаскивания элемент не «прыгал» к курсору. Вот пример перетаскиваемого квадрата:
Пример: перетаскиваемый HTML-элемент
<!-- snippet: html-result -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Draggable Element Example</title>
<style>
#draggable {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
cursor: pointer;
}
</style>
</head>
<body>
<div id="draggable"></div>
<script>
const elem = document.getElementById('draggable');
let shiftX, shiftY;
function onMouseDown(event) {
shiftX = event.clientX - elem.getBoundingClientRect().left;
shiftY = event.clientY - elem.getBoundingClientRect().top;
function moveAt(pageX, pageY) {
elem.style.left = pageX - shiftX + 'px';
elem.style.top = pageY - shiftY + 'px';
}
function onMouseMove(event) {
moveAt(event.clientX + window.scrollX, event.clientY + window.scrollY);
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', function stopDrag() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', stopDrag);
elem.removeEventListener('mousedown', onMouseDown);
});
}
elem.addEventListener('mousedown', onMouseDown);
elem.addEventListener('dragstart', function() { return false; });
</script>
</body>
</html>Для лучшей производительности при анимациях рассмотрите использование transform: translate() вместо left / top, так как это позволяет избежать пересчёта макета. Для совместимости с мобильными устройствами добавьте обработчики событий touchstart, touchmove и touchend наряду с событиями мыши.
Анимация с использованием координат
Вы также можете управлять движением, обновляя координаты элемента в каждом кадре с помощью requestAnimationFrame. Умножение на временну́ю дельту обеспечивает постоянную скорость независимо от частоты обновления экрана:
Пример: анимированный движущийся объект
<!-- snippet: html-result -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Animation Using Coordinates</title>
</head>
<body>
<div id="animateMe" style="width: 50px; height: 50px; background: blue; position: absolute;"></div>
<button id="stopBtn">Stop Animation</button>
<script>
const target = document.getElementById('animateMe');
const stopBtn = document.getElementById('stopBtn');
let pos = 0;
let isRunning = true;
let lastTime = performance.now();
function animate(currentTime) {
if (!isRunning) return;
const delta = (currentTime - lastTime) / 16; // Normalize to ~60fps
lastTime = currentTime;
if (pos >= 350) {
pos = 0;
}
pos += delta;
target.style.left = pos + 'px';
requestAnimationFrame(animate);
}
stopBtn.addEventListener('click', () => { isRunning = false; });
requestAnimationFrame(animate);
</script>
</body>
</html>Несмотря на мощные возможности JavaScript для создания динамических и интерактивных анимаций, CSS-анимации часто лучше подходят для простых эффектов. CSS-анимации обеспечивают более плавные переходы и, как правило, более эффективны с точки зрения производительности, поскольку обрабатываются непосредственно движком рендеринга браузера с меньшей нагрузкой на CPU. Это делает CSS-анимации идеальными для переходов, затуханий и базовых движений, особенно когда важны высокая производительность и низкое потребление ресурсов.
Заключение
Главное, что нужно запомнить: браузер использует две системы координат — viewport (clientX/Y, getBoundingClientRect(), elementFromPoint()) и документа (pageX/Y), и преобразование между ними выполняется путём прибавления или вычитания window.scrollX/Y. Разберитесь с этим — и большинство ошибок позиционирования исчезнут.
Далее изучите разделы Размеры окна и прокрутка, Прокрутка — для управления viewport, и Основы событий мыши — для событий, которые передают эти координаты.