Algo de contexto:
Estoy tratando de lograr un efecto de desplazamiento similar al del carrusel de miniaturas de imágenes de productos de Etsy. Cuando pasa el cursor sobre la parte superior del div, se desplaza automáticamente hasta que revela la última imagen y lo mismo ocurre en la parte inferior.
Aquí hay un enlace a un producto aleatorio donde puede verificar la funcionalidad.
He estado tratando de lograr esto con reaccionar, así que decidí comenzar con el desplazamiento del mouse hacia abajo y detener el desplazamiento hacia arriba.
Encontré un gran ejemplo de esto aquí .
El problema es que está usando jquery. Traté de convertirlo a vanilla js y usarlo en mi aplicación de reacción, pero no tuve éxito.
Después de investigar un poco más, terminé con una solución funcional, pero la animación no es fluida en absoluto. Usé un gancho setInterval. Aquí está mi código.
import { useRef, useState, useLayoutEffect, useEffect } from "react"; import styled from "styled-components"; const Wrapper = styled.div` margin: 5% 40%; .test { height: 300px; width: 100px; overflow: scroll; .inner { background-image: linear-gradient(red, blue); height: 800px; width: 100%; } } `; function useInterval(callback, delay) { const savedCallback = useRef(callback); useLayoutEffect(() => { savedCallback.current = callback; }, [callback]); useEffect(() => { if (!delay) { return; } const id = setInterval(() => savedCallback.current(), delay); return () => clearInterval(id); }, [delay]); } const Scroll = () => { const scrollRef = useRef(); const [delay, setDelay] = useState(100); const [scrolling, setScrolling] = useState(false); useInterval( () => { console.log("Scrolling"); scrollRef.current.scrollBy({ top: 10, behavior: "smooth" }); }, scrolling ? delay : null ); return ( <Wrapper> <div ref={scrollRef} className="test"> <div className="inner"></div> </div> <button onMouseDown={() => setScrolling(true)} onMouseUp={() => setScrolling(false)} > HOLD DOWN TO SCROLL </button> </Wrapper> ); }; export default Scroll;Realmente agradecería algunas instrucciones y, si es posible, un ejemplo rápido sobre cómo puedo lograr un desplazamiento continuo suave
Parece que useRef y requestAnimationFrame junto con scrollTop podrían lograrlo.
Consulte los ejemplos (Stackblitz - https://stackblitz.com/edit/react-cuf5cl?file=src%2FApp.js ):
import React from "react"; import "./style.css"; import { useRef } from "react"; import styled from "styled-components"; const Wrapper = styled.div` margin: 5% 40%; .test { height: 300px; width: 100px; overflow: scroll; .inner { background-image: linear-gradient(red, blue); height: 800px; width: 100%; } } `; const Scroll = () => { const step = 10; const scrollRef = useRef(); const isScrollRef = useRef(); const setMove = (state) => isScrollRef.current = state; const move = () => { if (isScrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollTop + step; requestAnimationFrame(move); } }; return ( <Wrapper> <div ref={scrollRef} className="test"> <div className="inner"></div> </div> <button onMouseDown={() => { setMove(true); move();}} onMouseUp={() => setMove(false)} > HOLD DOWN TO SCROLL </button> </Wrapper> ); }; export default Scroll; export default function App() { return ( <div> <Scroll /> </div> ); }Aquí hay una versión con velocidad configurable y diferente precisión de velocidad. Tenga en cuenta que cuantos más pasos de velocidad tenga, menos suave resultará el desplazamiento (porque scrollTop no se puede aumentar con un número decimal).
import { useRef, useState, useLayoutEffect, useEffect } from "react"; import styled from "styled-components"; const Wrapper = styled.div` margin: 5% 40%; .test { height: 300px; width: 100px; overflow: scroll; .inner { background-image: linear-gradient(red, blue); height: 800px; width: 100%; } } `; function useInterval(callback, active, speed, speedRange = 10) { const savedCallback = useRef(callback); const intervalIdRef = useRef(null); useLayoutEffect(() => { savedCallback.current = callback; }, [callback]); useEffect(() => { clearInterval(intervalIdRef.current); if (speedRange < 1) { console.error(`Speed range must be >= 1`); return; } if (!(speed >= 1 && speed <= speedRange) || speed < 1) { console.error(`Speed must be in range [1...${speedRange}]`); return; } if (!active) { return; } intervalIdRef.current = setInterval( () => savedCallback.current(), speedRange / speed ); return () => clearInterval(intervalIdRef.current); }, [active, speed, speedRange]); } const Scroll = () => { const scrollRef = useRef(); const [speed, setSpeed] = useState(1); //Default speedRange is 10 = 10 different speeds [1..10] // (The higher this number the less smooth are the low speeds, // but there will be more speeds to chose from) const [speedRange, setSpeedRange] = useState(10); const [scrolling, setScrolling] = useState(false); useInterval( () => { scrollRef.current.scrollTop += 1; }, scrolling, speed, speedRange ); return ( <Wrapper> <div ref={scrollRef} className="test"> <div className="inner"></div> </div> <button onMouseDown={() => setScrolling(true)} onMouseUp={() => setScrolling(false)} > HOLD DOWN TO SCROLL </button> </Wrapper> ); }; export default Scroll;