Stuck Implementing Tricky Scroll Behavior

Hi all, I'm facing a pretty tricky problem in an app I'm working on and have been banging my head on my desk for a while now trying to solve it. Any tips would be greatly appreciated!

 

Basically, I have a container that contains many elements inside it; so many that they overflow and the container scrolls. There is one special element in the container that I want to always be in the middle of the container — even as items are continuously added above and below it. I have achieved this, however, here's the catch: as soon as the user manually scrolls this container, I would like to disable the previous behavior where that one special item remains in the center of the container, which I haven't been able to do.

 

Below is my best attempt at implementing an example of this and here is a link to a codesandbox of my attempt. The way my implementation works is I have a container which contains a list of items rendered from an array, then the "special item," and then a bunch more items rendered from a different array. When the component mounts I run two functions: center and updater. Center initally centers the special item, and updater starts adding items to both arrays every so often with setInterval. Every time an item is added to one of the arrays, center is run again to keep the special item centered in its parent container. So far this works perfectly to keep the special item centered.

 

To disable this centering behavior, I have a boolean that's initially false called hasScrolled in state, and an event listener attached to the main container's onWheel event called userScrollHandler that sets hasScrolled to true. My center function only centers the special item if hasScrolled is false, so theoretically once the user scrolls, hasScrolled will be true and then the centering behavior will stop. Unfortunately, this doesn't seem to work. I'm positive that both the event is behing called when the user scrolls the container (open the console to see a log on each scroll) and that hasScrolled is being set to true. For some reason the center function thinks that hasScrolled is false. I have tried numerous other things like passing these value to the center function, not keeping hasScrolled in state and just having it be a relgular variable, using context, etc. I'm a loss as to how to do this and why none of my implementations don't work. Anyone have an idea of how to do this or what I'm doing wrong?

 

My implementation:

import React, { useState, useEffect, useRef, useReducer } from "react"; import ReactDOM from "react-dom"; const items1 = []; for (let i = 0; i < 10; i++) { items1.push("top"); } const items2 = []; for (let i = 0; i < 10; i++) { items2.push("bottom"); } const App = () => { const [hasScrolled, setHasScrolled] = useState(false); const [tops, addTopItem] = useReducer(state => { const stateCopy = [...state]; stateCopy.push(Math.random()); return stateCopy; }, items1); const [bottoms, addBottomItem] = useReducer(state => { const stateCopy = [...state]; stateCopy.unshift(Math.random()); return stateCopy; }, items2); const middle = useRef(null); const container = useRef(null); const center = () => { if (hasScrolled) return; container.current.scrollTo( 0, middle.current.offsetTop - container.current.offsetTop - container.current.clientHeight / 2 ); }; const updater = () => { setInterval(() => { addTopItem(); if (container.current && middle.current) center(); }, 500); setInterval(() => { addBottomItem(); if (container.current && middle.current) center(); }, 700); }; const userScrollHandler = e => { console.log("user scrolled"); setHasScrolled(true); }; useEffect(() => { center(); updater(); }, []); return ( <div ref={container} onWheel={userScrollHandler} style={containerStyle}> {tops.map((item, idx) => { return ( <div key={idx} style={itemStyle}> {item} </div> ); })} <div ref={middle} style={middleItemStyle}> Middle </div> {bottoms.map((item, idx) => { return ( <div key={idx} style={itemStyle}> {item} </div> ); })} </div> ); }; const containerStyle = { margin: "0 auto", marginTop: "10vh", height: "400px", width: "300px", background: "#eee", overflow: "auto" }; const itemStyle = { width: "100%", height: "40px", borderBottom: "1px solid blue", display: "flex", justifyContent: "center", alignItems: "center" }; const middleItemStyle = { ...itemStyle }; middleItemStyle.background = "lime"; const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement); 

submitted by /u/Varauk
[link] [comments]
Source: Reddit