Coordenadas en JavaScript
Aprende los dos sistemas de coordenadas del navegador en JavaScript: viewport y documento, cómo convertir entre ellos y usar getBoundingClientRect().
Las coordenadas son valores numéricos que describen una posición en la página. Casi cualquier función interactiva — arrastrar, dibujar, tooltips, menús emergentes, detección de colisiones — se reduce a leer una coordenada y colocar o comparar algo con respecto a ella.
El problema es que el navegador expone dos sistemas de coordenadas diferentes, y confundirlos es la causa más común de los errores de "mi elemento salta al lugar equivocado". Este capítulo explica ambos sistemas, cómo convertir entre ellos y los métodos DOM clave (getBoundingClientRect() y elementFromPoint()) que se usan para leer las posiciones de los elementos.
Los dos sistemas de coordenadas
Hay dos marcos de referencia que debes tener claros:
- Coordenadas de viewport (relativas a la ventana) — medidas desde la esquina superior izquierda de la parte visible de la página. Estas no cambian al desplazarse. Un punto en la esquina superior izquierda de lo que puedes ver es siempre
(0, 0). Propiedades:clientX/clientY. - Coordenadas de documento (relativas a la página) — medidas desde la esquina superior izquierda del documento completo, incluida la parte desplazada fuera de vista. Estas aumentan al desplazarse hacia abajo. Propiedades:
pageX/pageY.
La relación entre ellas es simplemente el desplazamiento del scroll:
pageX = clientX + window.scrollX;
pageY = clientY + window.scrollY;Cuando la página no está desplazada, window.scrollX y window.scrollY son ambos 0, por lo que los dos sistemas dan números idénticos — lo cual explica exactamente por qué los errores de scroll se ocultan hasta que alguien desplaza la página.
Usa viewport (clientX/Y) cuando… | Usa documento (pageX/Y) cuando… |
|---|---|
Posicionas un elemento position: fixed | Posicionas un elemento position: absolute |
| Detectas colisiones con lo que está actualmente en pantalla | Almacenas una ubicación de clic para recrearla más tarde |
Trabajas con getBoundingClientRect() | Dibujas en un canvas alto con scroll |
Consulta Tamaños de ventana y desplazamiento en JavaScript para saber cómo se miden window.scrollX/Y y el tamaño del viewport, y Desplazamiento en JavaScript para mover la posición del scroll de forma programática.
Leer coordenadas desde un evento de ratón
Cada evento de ratón lleva ambos sistemas de coordenadas. Para el modelo de eventos completo, consulta Conceptos básicos de eventos de ratón.
Coordenadas de viewport: clientX / clientY
event.clientX y event.clientY dan la posición del puntero relativa al viewport, independientemente del scroll. El ejemplo siguiente está dentro de una página alta para que puedas desplazarte y confirmar que los números permanecen anclados al área visible:
<!-- 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>Coordenadas de documento: pageX / pageY
event.pageX y event.pageY dan la posición del puntero relativa al documento completo, por lo que ya incluyen el desplazamiento del scroll. Son estándar y ampliamente compatibles — no es necesario calcularlos manualmente. El ejemplo siguiente los muestra junto al cálculo manual clientX + window.scrollX para que puedas confirmar que ambos coinciden:
<!-- 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>Coordenadas de pantalla: screenX / screenY
event.screenX y event.screenY miden el puntero relativo a la pantalla física completa, no solo a la ventana del navegador. Son útiles para rastrear el cursor en varios monitores o posicionar un popup de window.open(), pero para el trabajo dentro de la página casi siempre querrás usar clientX/Y o pageX/Y en su lugar.
Leer la posición de un elemento: getBoundingClientRect()
Para encontrar dónde está un elemento (en lugar de dónde está el ratón), llama a getBoundingClientRect(). Devuelve un DOMRect cuyos valores top, right, bottom, left, width y height están todos en coordenadas de viewport — el mismo marco que 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/paddingDado que el rect es relativo al viewport, cambia al desplazarse. Para obtener la posición del elemento en coordenadas de documento, añade el desplazamiento del scroll:
function getDocumentCoords(el) {
const rect = el.getBoundingClientRect();
return {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
};
}Esta combinación — leer con getBoundingClientRect(), añadir scrollX/Y cuando necesitas coordenadas de página — es la conversión que usarás constantemente.
Encontrar el elemento en un punto: elementFromPoint()
La pregunta inversa — "¿qué hay bajo estas coordenadas?" — la responde document.elementFromPoint(x, y), que toma coordenadas de viewport (correspondientes a clientX/Y) y devuelve el elemento más superior en ese punto, o null si el punto está fuera del viewport.
document.addEventListener('click', (event) => {
const el = document.elementFromPoint(event.clientX, event.clientY);
console.log('You clicked on:', el.tagName);
});Un error común: si le pasas pageX/pageY en una página desplazada, devuelve el elemento incorrecto (o null), porque espera coordenadas de viewport, no de documento.
Manipular posiciones de elementos usando coordenadas
Puedes combinar estas piezas para mover objetos. El método getBoundingClientRect() nos permite calcular el desplazamiento entre el puntero y la esquina del elemento para que el elemento no "salte" al cursor cuando comienza el arrastre. Aquí hay un cuadrado que puedes arrastrar:
Ejemplo: Elemento HTML arrastrable
<!-- 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>Para un mejor rendimiento, considera usar transform: translate() en lugar de left / top para las animaciones, ya que evita los recálculos de diseño. Además, para compatibilidad con dispositivos móviles, añade los listeners de eventos touchstart, touchmove y touchend junto a los eventos de ratón.
Animar con coordenadas
También puedes controlar el movimiento actualizando las coordenadas de un elemento en cada fotograma con requestAnimationFrame. Multiplicar por un delta de tiempo mantiene la velocidad constante independientemente de la tasa de refresco del monitor:
Ejemplo: Objeto animado en movimiento
<!-- 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>Aunque JavaScript ofrece potentes capacidades para crear animaciones dinámicas e interactivas, las animaciones CSS suelen ser más adecuadas para animaciones sencillas. Las animaciones CSS pueden proporcionar transiciones más fluidas y generalmente son más eficientes en cuanto al rendimiento, ya que las gestiona directamente el motor de renderizado del navegador, utilizando menos CPU. Esto hace que las animaciones CSS sean ideales para efectos como transiciones, fundidos y movimientos básicos, especialmente cuando el alto rendimiento y el bajo consumo de recursos son críticos.
Conclusión
La conclusión clave es que el navegador tiene dos marcos de coordenadas: viewport (clientX/Y, getBoundingClientRect(), elementFromPoint()) y documento (pageX/Y), y se convierte entre ellos sumando o restando window.scrollX/Y. Teniendo esto claro, la mayoría de los errores de posicionamiento desaparecen.
A partir de aquí, profundiza en Tamaños de ventana y desplazamiento, Desplazamiento para mover el viewport, y Conceptos básicos de eventos de ratón para los eventos que entregan estas coordenadas.