jQuery / Método / .offset() - Obter ou definir coordenadas do documento
No jQuery, .offset() retorna { top, left } da caixa de borda do primeiro elemento selecionado em relação ao canto superior esquerdo do documento. .offset({ top, left }) move os elementos selecionados para essas coordenadas. Não confunda esses valores com posições da área visível ou relativas ao pai.
Diferenças entre offset, position e área visível
A API de .offset() mede a caixa de borda sem incluir a margem do elemento. top e left são números em pixels CSS e podem ter casas decimais. A leitura não movimenta o elemento.
| Método | Referência | Limite medido |
|---|---|---|
| .offset() | Documento | Caixa de borda |
| .position() | Caixa de preenchimento do pai de posicionamento | Caixa de margem do elemento |
| getBoundingClientRect() | Área visível do navegador | Retângulo incluindo as bordas |
O pai de posicionamento de .position() não é necessariamente o pai HTML imediato. top/left de getBoundingClientRect() mudam com a rolagem; somar window.scrollY/scrollX fornece coordenadas comuns do documento. Não transfira diretamente valores de outra referência para CSS top/left.
Exemplo completo com três referências e rolagem
Salve todo o código em UTF-8 como offset-demo.html e abra no navegador. É preciso Internet para carregar o arquivo oficial do jQuery 4.0.0 pela rede de distribuição de conteúdo (CDN), sem instalação. Leitura, rolagem, movimento, ocultação, restauração e a função de definição abaixo foram executados com 3.7.1 e 4.0.0.
stage fica em top 500/left 20 do documento, com borda de 5 pixels. Dentro dele, target usa top 40/left 50 e margin 10. A margem de body é zero e não há transformações, permitindo calcular diretamente. Os controles ficam fixos no topo da área visível.
<!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 é a saída inicial com rolagem zero. Copiar apenas um trecho para outra página pode alterar os números devido ao layout ao redor.
document top=555, left=85
viewport top=555, left=85
position top=40, left=50
scrollY=0
- Read mede novamente. top do documento é 500 + 5 + 40 + 10 = 555; left é 20 + 5 + 50 + 10 = 85.
- Scroll 200px mantém top 555 no documento, mas top da área visível passa a 355 e scrollY a 200. .position() continua em 40/50.
- Move to (top 620, left 130) define 620/130 no documento. Com rolagem 200, a área visível dá 420/130 e .position() 105/95: posições CSS relativas ao pai são diferentes.
- Toggle visibility oculta target mantendo sua caixa de layout, que continua mensurável.
- Toggle display aplica display:none. O exemplo mostra Not rendered em vez de medidas não suportadas. Pressione novamente para exibir e medir.
- Reset restaura a posição CSS e a visibilidade e volta à rolagem zero.
Aqui target é um elemento comum com posicionamento absoluto. Se sua posição no documento não mudar, apenas rolar a janela não altera as coordenadas do documento. Não generalize para fixed/sticky ou mudanças de layout. Meça novamente quando necessário após imagens, fontes ou mudanças nas dimensões da janela.
Definir coordenadas e usar uma função
.offset({ top: 620, left: 130 }) recebe coordenadas do documento, não valores CSS relativos ao pai. O jQuery calcula os valores necessários e pode trocar static por relative durante a definição. Confira também o CSS e o layout resultantes.
.offset(function(index, current)) recebe as coordenadas atuais de cada elemento e retorna novos top/left. Execute este trecho completo no Console das ferramentas do desenvolvedor. Ele cria dois elementos temporários, aplica incrementos verticais diferentes e os remove ao 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
A consulta lê o primeiro elemento; a definição afeta todos os selecionados. index começa em zero na coleção. O exemplo arredonda apenas a saída exibida, não as coordenadas usadas nos cálculos. Não descarte automaticamente as casas decimais.
Elementos ocultos, seleção vazia e mudanças de layout
display:none não mantém uma caixa de renderização e não é uma fonte suportada de coordenadas atuais significativas. Mesmo que uma versão retorne zero, não considere isso uma posição real: exiba primeiro o elemento. visibility:hidden mantém a caixa e permite a medição.
O exemplo verifica a existência do nó e getClientRects().length. .offset() em uma seleção jQuery vazia retorna undefined; acessar diretamente .offset().top falharia. Também não suponha que um nó separado do modelo de objetos do documento (DOM) forneça a posição esperada no documento.
Verifique zoom, transformações, mudanças de margin/border e layout dos ancestrais. A API não oferece suporte ao cálculo das margens do elemento html. Alinhe primeiro as referências e atualize nas mudanças relevantes, evitando leituras geométricas e escritas CSS repetidas sem necessidade.




