Reputation: 2930
I make a list of div tags for listing filename. and After selecting a div, then I can change focus up and down using Arrow keys
Because I have a long file list, I add overflow: scroll
to the container
but Scroll does not move along with my focus(so active div disappear from the viewport),
How can I make scroll behavior move down along with active div?
I create an example in codesandbox
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [selectedItem, setSelectedItem] = useState(0);
useEffect(() => {
const keyPress = (e) => {
if (e.key === "ArrowLeft") {
setSelectedItem((prev) => Number(prev) - 1);
}
if (e.key === "ArrowRight") {
setSelectedItem((prev) => Number(prev) + 1);
}
};
window.addEventListener("keydown", keyPress);
return () => {
window.removeEventListener("keydown", keyPress);
};
}, [selectedItem]);
const onClickDiv = (e) => {
setSelectedItem(e.target.id);
};
const renderList = () => {
let items = [];
console.log(selectedItem);
for (let i = 0; i < 60; i++) {
items.push(
<div
key={i}
className={`item ${Number(selectedItem) === i ? "active" : ""}`}
id={i}
onClick={onClickDiv}
>
Item{i}.png
</div>
);
}
return items;
};
return (
<div className="App">
<div className="list-container">{renderList()}</div>
</div>
);
}
.list-container {
height: 300px;
overflow: scroll;
}
.active {
background-color: orangered;
}
I finally complete this example, I sincerely thank you guys for answering my question.
Here is code sandbox final code
Upvotes: 2
Views: 2622
Reputation: 1167
Here's my take on it.
I am using refs
as well along with scrollIntoView
.This way we don't have to scroll by a fixed amount and also we only are scrolling when we are at the end of the viewport.
Here's the demo
I am storing refs of each element.
ref={(ref) => {
elementRefs.current = { ...elementRefs.current, [i]: ref };
}}
And then we will use scrollIntoView
when focus changes.
const prevItem = elementRefs.current[selectedItem - 1];
prevItem && prevItem.scrollIntoView({ block: "end" });
Notice the {block:"end"}
argument here. It makes sure we only scroll if the element is not in the viewport.
You can learn more about scrollIntoView here.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import { useEffect, useState, useRef } from "react";
export default function App() {
const [selectedItem, setSelectedItem] = useState(0);
const elementRefs = useRef({});
useEffect(() => {
const keyPress = (e) => {
if (e.key === "ArrowLeft") {
setSelectedItem((prev) => Number(prev) - 1);
const prevItem = elementRefs.current[selectedItem - 1];
prevItem && prevItem.scrollIntoView({ block: "end" });
}
if (e.key === "ArrowRight") {
console.log(elementRefs.current[selectedItem]);
// if (selectedItem < elementRefs.current.length)
const nextItem = elementRefs.current[selectedItem + 1];
nextItem && nextItem.scrollIntoView({ block: "end" });
setSelectedItem((prev) => Number(prev) + 1);
}
};
window.addEventListener("keydown", keyPress);
return () => {
window.removeEventListener("keydown", keyPress);
};
}, [selectedItem]);
const onClickDiv = (e) => {
setSelectedItem(e.target.id);
};
const renderList = () => {
let items = [];
console.log(selectedItem);
for (let i = 0; i < 60; i++) {
items.push(
<div
key={i}
className={`item ${Number(selectedItem) === i ? "active" : ""}`}
id={i}
onClick={onClickDiv}
ref={(ref) => {
elementRefs.current = { ...elementRefs.current, [i]: ref };
}}
>
Item{i}.png
</div>
);
}
return items;
};
return (
<div className="App">
<div className="list-container">{renderList()}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
Upvotes: 6
Reputation: 6395
One way to approach this problem is to use ref
in React.
First define a ref using
const scrollRef = useRef(null);
Then, assign it to your scrolling element like this,
<div className="list-container" ref={scrollRef}>
What this does is, gives you a reference to the html element inside your code.
ref.current
is the HTML Div element now.
Now, you can use scrollBy
method on HTML element to scroll up or down.
Like this,
useEffect(() => {
const keyPress = (e) => {
if (e.key === "ArrowLeft") {
setSelectedItem((prev) => Number(prev) - 1);
scrollRef.current.scrollBy(0, -18); // <-- Scrolls the div 18px to the top
}
if (e.key === "ArrowRight") {
setSelectedItem((prev) => Number(prev) + 1);
scrollRef.current.scrollBy(0, 18); // <-- Scrolls the div 18px to the bottom
}
};
window.addEventListener("keydown", keyPress);
return () => {
window.removeEventListener("keydown", keyPress);
};
}, [selectedItem]);
I have given 18
because I know the height of my list item.
I have updated your Sandbox. Check it out.
Upvotes: 1