jQuery / Crear un enlace suave para volver arriba

Cree un enlace real href="#top" y mejórelo con jQuery .animate({ scrollTop: 0 }) sobre el elemento que desplaza el documento. Evite el salto inmediato conservando el ancla para funcionar sin JavaScript. Respete movimiento reducido y lleve el foco al título superior al terminar.

Elegir destino y enlace básico

El ejemplo mueve toda la página. document.scrollingElement devuelve el elemento que realmente desplaza, la raíz en modo estándar. Mantenga <!doctype html>. Un destino evita efectos y callbacks separados sobre html y body.

Dé id="top" al título y apunte el enlace allí. tabindex="-1" permite foco por código sin añadir un paso a Tab normal. Desplazar solo puede dejar el foco abajo, así que trátelo por separado.

Ejemplo completo en un archivo

Guarde todo en UTF-8 como back-top-demo.html y ábralo en el navegador. Internet carga el archivo oficial completo de jQuery 4.0.0. La animación necesita effects, no slim. No requiere servidor ni instalación.

Go to 900px crea la posición de comparación; el enlace inferior derecho permanece accesible. La página tiene altura para desplazar. La barra fija es didáctica: evite solapar cabeceras y anuncios en un sitio 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>

Salida inicial arriba. El redondeo solo facilita leer, sin limitar los cálculos a enteros.

scrollY=0
completed=0
state=ready
  1. Pulse Go to 900px. Si el área visible es demasiado alta para llegar a 900, reduzca su altura.
  2. Back to top vuelve normalmente en 600ms. scrollY baja hasta cero y muestra completed=1, state=done.
  3. Aparece el contorno del título enfocado. Compruebe el enlace con teclado y Enter.
  4. Desde 900, repetir clics no acumula viajes: detiene el anterior y reinicia desde la posición actual.
  5. Stop movement interrumpe en un punto intermedio con state=interrupted. Reactive el enlace o restaure 900 para comparar.

Se ejecutaron acciones, teclado, repeticiones, parada y movimiento reducido con 3.7.1 y 4.0.0 en Edge para Windows. Otra verificación sin JavaScript siguió el ancla #top.

Clics, interrupción y movimiento reducido

El destino de .animate() es scrollTop numérico cero. Sin event.preventDefault(), el salto nativo compite con la animación. .stop(true, false) vacía la cola y detiene sin terminar en destino. Nuevos clics parten del punto actual sin ejecutar callbacks anteriores.

Rueda, inicio táctil y teclas de desplazamiento llaman a interrupt() para devolver control. No se bloquea su desplazamiento con preventDefault. Stop usa la misma función. Interrumpir no fuerza foco arriba.

prefers-reduced-motion se revisa al activar. Con esa preferencia, la duración es cero y se conserva la misma finalización y foco. No se alteran efectos globales de jQuery.

focus({ preventScroll: true }) evita un segundo cambio de posición. Otros destinos requieren calcular coordenadas y posible cabecera fija; cambiar solo el id no basta.

Adaptar a un sitio existente

Revise id="top" existente y coincidencia del enlace. Limite selectores y espacios de nombres para respetar otros controladores. El control 900px, estado y altura artificial son ayudas de práctica.

No combine CSS scroll-behavior:smooth y animación jQuery en el mismo recorrido. Aquí auto deja controlar el tiempo a jQuery. Sin jQuery, window.scrollTo() con smooth es una opción; adapte finalización y foco.

Si no se mueve, revise espacio desplazable, contenedor interno y carga completa. Compruebe texto, contraste, área táctil y elementos fijos superpuestos. Conserve href útil en lugar de javascript: o dirección vacía.