jQuery / Método / .offset() - Obtener o establecer coordenadas del documento
En jQuery, .offset() devuelve { top, left } de la caja de borde del primer elemento seleccionado respecto a la esquina superior izquierda del documento. .offset({ top, left }) mueve los elementos seleccionados a esas coordenadas. No las confunda con posiciones del área visible o relativas al padre.
Diferencias entre offset, position y área visible
La API de .offset() mide la caja de borde, sin incluir el margen del elemento. top y left son números en píxeles CSS y pueden tener decimales. Consultarlos no mueve el elemento.
| Método | Referencia | Límite medido |
|---|---|---|
| .offset() | Documento | Caja de borde |
| .position() | Caja de relleno del padre de posicionamiento | Caja de margen del elemento |
| getBoundingClientRect() | Área visible del navegador | Rectángulo que incluye los bordes |
El padre de posicionamiento de .position() no es necesariamente el padre HTML inmediato. top/left de getBoundingClientRect() cambian con el desplazamiento; sumar window.scrollY/scrollX los convierte en coordenadas habituales del documento. No copie valores de otro sistema directamente en CSS top/left.
Ejemplo completo con tres referencias y desplazamiento
Guarde todo el código como offset-demo.html en UTF-8 y ábralo en el navegador. Se necesita Internet para cargar el archivo oficial de jQuery 4.0.0 desde su red de distribución de contenido (CDN), sin instalación. La lectura, el desplazamiento, el movimiento, la ocultación, el reinicio y la función de asignación de abajo se ejecutaron con 3.7.1 y 4.0.0.
stage está en top 500/left 20 del documento con un borde de 5 píxeles. Dentro, target utiliza top 40/left 50 y margin 10. El margen de body es cero y no hay transformaciones, por lo que puede calcularse directamente. Los controles permanecen fijos en la parte superior del área visible.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document and viewport coordinates</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<style>
body { margin: 0; min-height: 1800px; font: 16px/1.6 sans-serif; }
#controls { position: fixed; inset: 0 0 auto; padding: 12px; background: #fff; z-index: 10; max-height: 42vh; overflow: auto; }
button { font: inherit; margin: 3px; }
pre { white-space: pre-wrap; overflow-wrap: anywhere; }
#stage { position: absolute; top: 500px; left: 20px; width: 300px; height: 260px; border: 5px solid #bbc; }
#target { position: absolute; top: 40px; left: 50px; margin: 10px; border: 2px solid teal; padding: 8px; background: #d9f4ef; }
</style>
</head>
<body>
<div id="controls">
<strong>Measure the target border box</strong>
<button type="button" id="read">Read</button>
<button type="button" id="scroll">Scroll 200px</button>
<button type="button" id="move">Move to (top 620, left 130)</button>
<button type="button" id="visibility">Toggle visibility</button>
<button type="button" id="display">Toggle display</button>
<button type="button" id="reset">Reset</button>
<pre id="result" aria-live="polite"></pre>
</div>
<div id="stage"><div id="target">Target</div></div>
<script>
$(function () {
const $target = $("#target");
function n(value) { return Number(value.toFixed(2)); }
function report() {
const node = $target.get(0);
if (!node) { $("#result").text("Target missing"); return; }
if (!node.getClientRects().length) { $("#result").text("Not rendered; reveal before measuring"); return; }
const offset = $target.offset();
const rect = node.getBoundingClientRect();
const position = $target.position();
$("#result").text(`document top=${n(offset.top)}, left=${n(offset.left)}\nviewport top=${n(rect.top)}, left=${n(rect.left)}\nposition top=${n(position.top)}, left=${n(position.left)}\nscrollY=${n(window.scrollY)}`);
}
$("#read").on("click", report);
$("#scroll").on("click", function () { window.scrollTo(0, 200); });
$("#move").on("click", function () { if ($target.get(0).getClientRects().length) $target.offset({ top: 620, left: 130 }); report(); });
$("#visibility").on("click", function () { $target.css("visibility", $target.css("visibility") === "hidden" ? "visible" : "hidden"); report(); });
$("#display").on("click", function () { $target.toggle(); report(); });
$("#reset").on("click", function () { $target.css({ top: 40, left: 50, display: "", visibility: "" }); window.scrollTo(0, 0); report(); });
$(window).on("scroll.offsetLesson resize.offsetLesson", report);
report();
});
</script>
</body>
</html>
Esta es la salida inicial sin desplazamiento. Copiar solo un fragmento a otra página puede cambiar los números por la disposición del entorno.
document top=555, left=85
viewport top=555, left=85
position top=40, left=50
scrollY=0
- Read vuelve a medir. top del documento es 500 + 5 + 40 + 10 = 555; left es 20 + 5 + 50 + 10 = 85.
- Scroll 200px mantiene top 555 en el documento, pero top del área visible pasa a 355 y scrollY a 200. .position() sigue en 40/50.
- Move to (top 620, left 130) establece 620/130 en el documento. Con desplazamiento 200, el área visible da 420/130 y .position() 105/95: las posiciones CSS relativas al padre son distintas.
- Toggle visibility oculta target conservando su caja de diseño, por lo que sigue siendo medible.
- Toggle display aplica display:none. El ejemplo muestra Not rendered en lugar de medidas no admitidas. Pulse otra vez para mostrar y medir.
- Reset restaura la posición CSS y la visibilidad y vuelve al desplazamiento cero.
Aquí target es un elemento normal con posición absoluta. Si no cambia su posición dentro del documento, desplazar la ventana no cambia sus coordenadas del documento. No generalice a fixed/sticky ni a cambios del diseño. Vuelva a medir cuando corresponda tras cargar imágenes o fuentes, o cambiar las dimensiones de la ventana.
Asignar coordenadas y usar una función
.offset({ top: 620, left: 130 }) acepta coordenadas del documento, no valores CSS relativos al padre. jQuery calcula los valores necesarios y puede cambiar static por relative al asignar. Compruebe también el CSS y el diseño resultantes.
.offset(function(index, current)) recibe las coordenadas actuales de cada elemento y devuelve los nuevos top/left. Ejecute este fragmento completo en la consola de las herramientas de desarrollo. Crea dos elementos temporales, aplica incrementos verticales diferentes y los elimina al terminar.
const $items = $("<div>A</div><div>B</div>")
.css({ position: "absolute", top: 1000, left: 20 }).appendTo("body");
$items.offset(function (index, current) {
return { top: current.top + index * 30, left: current.left + 40 };
});
console.log($items.map(function () {
const point = $(this).offset();
return `${point.top},${point.left}`;
}).get().join(" | "));
console.log(`empty getter=${typeof $().offset()}`);
$items.remove();
1000,60 | 1030,60
empty getter=undefined
La consulta lee el primer elemento; la asignación afecta a todos los seleccionados. index empieza en cero dentro de la colección. El ejemplo solo redondea la salida mostrada, no las coordenadas usadas para calcular. No descarte automáticamente los decimales.
Elementos ocultos, selección vacía y cambios del diseño
display:none no conserva una caja de representación y no es una fuente admitida de coordenadas actuales significativas. Aunque alguna versión devuelva cero, no lo trate como una posición real: muestre primero el elemento. visibility:hidden conserva la caja y permite medir.
El ejemplo comprueba que exista el nodo y getClientRects().length. .offset() sobre una selección jQuery vacía devuelve undefined; acceder directamente a .offset().top fallaría. Tampoco presuponga que un nodo separado del modelo de objetos del documento (DOM) tenga la posición esperada dentro del documento.
Revise el zoom, las transformaciones, los cambios de margin/border y la disposición de los antecesores. La API no admite contabilizar los márgenes del elemento html. Haga coincidir primero las referencias y actualice en cambios relevantes, evitando lecturas geométricas y escrituras CSS innecesariamente repetidas.




