jQuery / Criar um link suave para voltar ao topo

Crie um link real href="#top" e acrescente jQuery .animate({ scrollTop: 0 }) ao elemento que rola o documento. Evite o salto imediato mantendo a âncora para funcionar sem JavaScript. Respeite movimento reduzido e leve o foco ao título superior ao terminar.

Escolher destino e link básico

O exemplo move a página inteira. document.scrollingElement retorna seu elemento de rolagem real, a raiz no modo padrão. Mantenha <!doctype html>. Um alvo evita efeitos e callbacks separados em html e body.

Dê id="top" ao título e aponte o link para ele. tabindex="-1" permite foco por código sem acrescentar uma parada à navegação Tab normal. Só rolar pode deixar o foco embaixo, portanto trate-o separadamente.

Exemplo completo em um arquivo

Salve tudo em UTF-8 como back-top-demo.html e abra no navegador. A Internet carrega o arquivo oficial completo do jQuery 4.0.0. A animação precisa de effects, não slim. Não exige servidor nem instalação.

Go to 900px cria a posição de comparação; o link inferior direito continua acessível. A página tem altura para rolar. A barra fixa é didática: evite sobrepor cabeçalhos e anúncios em um site real.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Smooth back-to-top link</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    html { scroll-behavior: auto; }
    body { font: 16px/1.6 sans-serif; margin: 0; padding: 100px 20px; min-height: 2400px; box-sizing: border-box; }
    #top { max-width: 760px; margin: 0; }
    #top:focus { outline: 3px solid teal; }
    #toolbar { position: fixed; top: 0; left: 0; right: 0; background: #fff; padding: 12px; z-index: 2; }
    button, a { font: inherit; }
    button { margin: 4px; }
    #back { position: fixed; right: 16px; bottom: 16px; padding: 10px 14px; background: #075d55; color: #fff; border-radius: 6px; z-index: 2; }
    #status { position: fixed; left: 12px; bottom: 70px; max-width: calc(100% - 48px); padding: 8px; background: #fff; white-space: pre-wrap; overflow-wrap: anywhere; }
    .spacer { margin-top: 700px; }
  </style>
</head>
<body>
  <div id="toolbar"><button type="button" id="down">Go to 900px</button><button type="button" id="stop">Stop movement</button></div>
  <h1 id="top" tabindex="-1">Top of the example page</h1>
  <p>Use the fixed link after scrolling down.</p>
  <p class="spacer">Middle of the page</p>
  <p class="spacer">Lower content</p>
  <a id="back" href="#top">Back to top</a>
  <pre id="status" aria-live="polite"></pre>
  <script>
    $(function () {
      const root = document.scrollingElement;
      if (!root) return;
      const $root = $(root);
      let completed = 0, state = "ready";
      function report() { $("#status").text(`scrollY=${Math.round(window.scrollY)}\ncompleted=${completed}\nstate=${state}`); }
      function interrupt() {
        if (state !== "moving") return;
        $root.stop(true, false); state = "interrupted"; report();
      }
      $("#back").on("click.backTopLesson", function (event) {
        event.preventDefault();
        $root.stop(true, false);
        state = "moving"; report();
        const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
        $root.animate({ scrollTop: 0 }, { duration: reduce ? 0 : 600, complete: function () {
          completed++; state = "done";
          document.getElementById("top").focus({ preventScroll: true });
          report();
        } });
      });
      $("#down").on("click", function () { $root.stop(true, false).scrollTop(900); completed = 0; state = "ready"; report(); });
      $("#stop").on("click", interrupt);
      $(window).on("scroll.backTopLesson", report);
      $(window).on("wheel.backTopLesson touchstart.backTopLesson", interrupt);
      $(document).on("keydown.backTopLesson", function (event) {
        if (["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(event.key)) interrupt();
      });
      report();
    });
  </script>
</body>
</html>

Saída inicial no topo. Arredondar só facilita a leitura, sem limitar cálculos a inteiros.

scrollY=0
completed=0
state=ready
  1. Pressione Go to 900px. Se a área visível for alta demais para chegar a 900, reduza a altura.
  2. Back to top retorna normalmente em 600ms. scrollY cai até zero e mostra completed=1, state=done.
  3. Aparece o contorno do título focado. Confira o link com teclado e Enter.
  4. De 900, repetir cliques não acumula viagens: para a anterior e reinicia da posição atual.
  5. Stop movement interrompe no meio com state=interrupted. Reative o link ou restaure 900 para comparar.

Ações, teclado, repetições, parada e movimento reduzido foram executados com 3.7.1 e 4.0.0 no Edge para Windows. Outra verificação sem JavaScript seguiu a âncora #top.

Cliques, interrupção e movimento reduzido

O alvo de .animate() é scrollTop numérico zero. Sem event.preventDefault(), o salto nativo compete com a animação. .stop(true, false) limpa a fila e para sem concluir no destino. Novos cliques partem do ponto atual sem executar callbacks anteriores.

Roda, início de toque e teclas de rolagem chamam interrupt() para devolver controle. Não bloqueiam a rolagem com preventDefault. Stop usa a mesma função. Interromper não força foco no topo.

prefers-reduced-motion é conferido a cada ativação. Com essa preferência, a duração é zero e a mesma conclusão move o foco. Os efeitos globais do jQuery não mudam.

focus({ preventScroll: true }) evita uma segunda mudança de posição. Outros destinos exigem calcular coordenadas e possível cabeçalho fixo; só trocar o id não basta.

Adaptar a um site existente

Confira id="top" existente e correspondência do link. Restrinja seletores e namespaces para preservar outros manipuladores. Controle 900px, status e altura artificial são recursos de prática.

Não combine CSS scroll-behavior:smooth e animação jQuery no mesmo percurso. Aqui auto deixa o tempo com jQuery. Sem jQuery, window.scrollTo() com smooth é uma opção; adapte conclusão e foco.

Sem movimento, confira espaço de rolagem, contêiner interno e carregamento completo. Revise texto, contraste, área de toque e sobreposição de elementos fixos. Mantenha href útil em vez de javascript: ou endereço vazio.