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
  1. Read mede novamente. top do documento é 500 + 5 + 40 + 10 = 555; left é 20 + 5 + 50 + 10 = 85.
  2. 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.
  3. 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.
  4. Toggle visibility oculta target mantendo sua caixa de layout, que continua mensurável.
  5. Toggle display aplica display:none. O exemplo mostra Not rendered em vez de medidas não suportadas. Pressione novamente para exibir e medir.
  6. 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.