Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useState, useEffect, useCallback } from "react";
interface UseScrollToTopOptions {
/** Distancia de desplazamiento (px) antes de que aparezca el botón. Por defecto: 300 */
threshold?: number;
/** Usa desplazamiento suave. Por defecto: true */
smooth?: boolean;
}
interface UseScrollToTopReturn {
/** Si la página se ha desplazado más allá del umbral */
isVisible: boolean;
/** Llama esto para desplazarse al inicio */
scrollToTop: () => void;
/** Posición de desplazamiento actual */
scrollY: number;
}
function useScrollToTop(
options: UseScrollToTopOptions = {}
): UseScrollToTopReturn {
const { threshold = 300, smooth = true } = options;
const [isVisible, setIsVisible] = useState(false);
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
const handleScroll = () => {
const y = window.scrollY;
setScrollY(y);
setIsVisible(y > threshold);
};
// Verifica la posición inicial
handleScroll();
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [threshold]);
const scrollToTop = useCallback(() => {
window.scrollTo({
top: 0,
behavior: smooth ? "smooth" : "instant",
});
}, [smooth]);
return { isVisible, scrollToTop, scrollY };
}Cuándo usarlo: Tienes una página larga y deseas dar a los usuarios una forma rápida de volver al inicio, con el botón apareciendo solo después de que hayan desplazado la página una distancia significativa.
"use client";
function BackToTopButton() {
const { isVisible, scrollToTop } = useScrollToTop({
threshold: 400,
smooth: true,
});
return (
<button
onClick={scrollToTop}
aria-label="Scroll to top"
style={{
position: "fixed",
bottom: 24,
right: 24,
width: 48,
height: 48,
borderRadius: "50%",
border: "none",
background: "#111",
color: "#fff",
fontSize: 20,
cursor: "pointer",
opacity: isVisible ? 1 : 0,
transform: isVisible ? "translateY(0)" : "translateY(16px)",
transition: "opacity 0.3s, transform 0.3s",
pointerEvents: isVisible ? "auto" : "none",
}}
>
↑
</button>
);
}
function LongPage() {
return (
<div>
<h1>Título del Artículo</h1>
{Array.from({ length: 50 }, (_, i) => (
<p key={i}>Párrafo {i + 1} del contenido...</p>
))}
<BackToTopButton />
</div>
);
}Lo que esto demuestra:
pointerEvents: "none" previene que el botón invisible bloquee clicsaria-label asegura accesibilidad del lector de pantallascroll (con { passive: true } para rendimiento) rastrea window.scrollY.isVisible se pone en true cuando scrollY supera el umbral, dando al componente consumidor un booleano reactivo para renderizar.scrollToTop llama a window.scrollTo con behavior: "smooth" para una animación de desplazamiento suave nativa.passive: true le dice al navegador que el handler no llamará a preventDefault, permitiendo optimizaciones de rendimiento de desplazamiento.| Parámetro | Tipo | Por Defecto | Descripción |
|---|---|---|---|
options.threshold | number | 300 | Píxeles desplazados antes de que isVisible sea true |
options.smooth | boolean | true | Si usar comportamiento de desplazamiento suave |
| Retorno | Tipo | Descripción |
|---|---|---|
isVisible | boolean | Si la posición de desplazamiento supera el umbral |
scrollToTop | () => void | Función para desplazarse al inicio |
scrollY | number | Posición de desplazamiento Y actual |
Handler de desplazamiento con throttle: Para páginas con renderizado pesado, envuelve el handler de desplazamiento con useThrottledCallback para reducir actualizaciones de estado:
const handleScroll = useThrottledCallback(() => {
setScrollY(window.scrollY);
setIsVisible(window.scrollY > threshold);
}, 100);Desplazarse a elemento: Extiende para desplazarse a cualquier ref de elemento en lugar del inicio:
const scrollToElement = useCallback((ref: React.RefObject<HTMLElement>) => {
ref.current?.scrollIntoView({ behavior: smooth ? "smooth" : "instant" });
}, [smooth]);requestAnimationFrame.window no está disponible durante la renderización del lado del servidor. Solución: El useEffect solo se ejecuta en el cliente, por lo que el hook es seguro para SSR tal como está escrito. El estado por defecto (isVisible: false) es correcto para SSR.behavior: "smooth". Solución: La página aún se desplaza instantáneamente, lo cual es un alternativa aceptable.bottom o añade z-index para capas correctamente.| Paquete | Hook/Componente | Notas |
|---|---|---|
react-scroll | animateScroll.scrollToTop() | Librería de desplazamiento completa con componentes de enlace |
usehooks-ts | useScrollPosition | Rastrea posición pero sin función de desplazamiento |
ahooks | useScroll | Retorna estado completo de desplazamiento para cualquier elemento |
framer-motion | useScroll | Rastreo de desplazamiento enfocado en animación |
| CSS Nativo | scroll-behavior: smooth | Solo CSS, sin lógica de botón |
isVisible es true cuando window.scrollY supera el valor threshold.false cuando el usuario desplaza de nuevo por encima del umbral.El indicador passive le dice al navegador que el handler nunca llamará a preventDefault(). Esto permite que el navegador optimice el rendimiento de desplazamiento sin esperar a que el handler finalice antes de desplazarse.
window.scrollTo({ behavior: "smooth" }) activa una animación de desplazamiento suave nativa.behavior y desplazan al instante.Esto maneja el caso donde el usuario actualiza la página mientras ya está desplazado hacia abajo. Sin la llamada inicial, isVisible permanecería false hasta el siguiente evento de desplazamiento.
Envuelve el handler con useThrottledCallback:
const handleScroll = useThrottledCallback(() => {
setScrollY(window.scrollY);
setIsVisible(window.scrollY > threshold);
}, 100);bottom para empujar el botón por encima de la barra de navegación.z-index para asegurar capas correctas.Si se establece opacity: 0 sin pointerEvents: "none", el botón aún recibe eventos de clic. El ejemplo funcional establece pointerEvents: isVisible ? "auto" : "none" para evitar esto.
Sí. El useEffect solo se ejecuta en el cliente, y los valores de estado por defecto (isVisible: false, scrollY: 0) son correctos para salida renderizada por servidor. No se necesita protección typeof window fuera del efecto.
Usa las interfaces nombradas directamente:
interface Props {
scrollOptions: UseScrollToTopOptions;
}
function MyComponent({ scrollOptions }: Props) {
const result: UseScrollToTopReturn = useScrollToTop(scrollOptions);
}Añade una función scrollToElement usando scrollIntoView:
const scrollToElement = useCallback(
(ref: React.RefObject<HTMLElement>) => {
ref.current?.scrollIntoView({
behavior: smooth ? "smooth" : "instant",
});
},
[smooth]
);Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥