Vanilla js에서는 scorll event를 dom element 에 붙인다.
React js에서는 직접 element에 event를 붙일 수 없고, onScroll props를 붙이면 event가 붙는다.
(onClick 과 마찬가지라고 생각하면 됨)
codesandbox
key press를 아래로, 위로 하다가 scroll이 넘어가게 되면, scroll이 따라가도록 하는 것을 만들자
처음 화면 | 이동 시 |
---|---|
textInput.current
로 접근할 수 있게 된다. 예를 들면 textInput.current.focus()
를 할 수 있다.아래와 같이 쓰면, ref를 직접 current에 넘겨줄 수 있다.
const textInput = useRef(null);
return (
<input ref={(ref) => textInput.current = {myElement: ref, name: 'cho'} } />
)
If you pass a ref object to React with
<div ref={myRef} />
, React will set its .current property to the corresponding DOM node whenever that node changes.
아래 두 코드는 동일하다.
const textInput = useRef(null);
return (
<input ref={textRef} />
)
const textInput = useRef(null);
return (
<input ref={ref => textRef(ref)} />
)
const refContainer = useRef(initialValue);
function callbackFunc = (e) => {
console.log(e.target.scrollTop)
}
return (
<div onScroll={callbackFunc})> File.png </div>
)
Element.scrollTop
Element.scrollTo(x-coord, y-coord)
Element.scrollTo(options)
Element.scrollBy(x-coord, y-coord)
Element.scrollBy(options)
scroll을 일정 양만큼 이동시킬 수 있다.
Element.scrollIntoView(alignToTop) // boolean
Element.scrollIntoView(options)
viewport기준으로 scroll 위치를 정할 수 있다.
옵션에는 기본적으로 {block: 'start'}
, {block: 'end'}
가 있다. Element를 viewport기준의 첫번째, 끝으로 스크롤을 위치시킨다.
아래 index [1], [2], ...를 실제 코드에 주석으로 달아놨다.
맨 아래 example code에 있는 변수명을 괄호에 적음
currentScroll
)itemRef
)containerRef
)import "./styles.css";
import { useEffect, useRef, useState } from "react";
export default function App() {
const len = 60;
const listHeight = 300;
const [selectedItem, setSelectedItem] = useState(1); // item's index number
/****** [1] ******/
const itemRef = useRef({});
const currentScroll = useRef({ scrollTop: 0, scrollBottom: listHeight });
const containerRef = useRef();
useEffect(() => {
const keyPress = (e) => {
if (e.key === "ArrowLeft") { // 위로 이동할 때
if (selectedItem === 1) return;
setSelectedItem((prev) => Number(prev) - 1);
/****** [2] ******/
const elementTop = (Number(selectedItem) - 2) * 30;
/****** [4] ******/
if (elementTop < currentScroll.current.scrollTop) {
const prevItem = itemRef.current[selectedItem - 1];
prevItem && prevItem.scrollIntoView({ block: "start" });
}
}
if (e.key === "ArrowRight") { // 아래로 이동할 때
if (selectedItem >= len) return;
setSelectedItem((prev) => Number(prev) + 1);
/****** [2] ******/
const elementBottom = (Number(selectedItem) + 2) * 30;
/****** [3] ******/
if (elementBottom > currentScroll.current.scrollBottom) {
const nextItem = itemRef.current[selectedItem + 1];
nextItem && nextItem.scrollIntoView({ block: "end" });
}
}
};
window.addEventListener("keydown", keyPress);
return () => {
window.removeEventListener("keydown", keyPress);
};
}, [selectedItem]);
const onClickDiv = (e) => {
setSelectedItem(e.target.id);
};
const onScroll = (e) => {
currentScroll.current = {
scrollTop: e.target.scrollTop,
scrollBottom: e.target.scrollTop + listHeight
};
};
/****** [5] ******/
const goUp = () => {
setSelectedItem(1);
containerRef.current.scrollTo(0, 0);
};
/****** [5] ******/
const goDown = () => {
setSelectedItem(len);
containerRef.current.scrollTo(0, 5000);
};
const renderList = () => {
let items = [];
for (let i = 1; i <= len; i++) {
items.push(
<div
key={i}
className={`item ${Number(selectedItem) === i ? "active" : ""}`}
id={i}
onClick={onClickDiv}
ref={(ref) => {
itemRef.current = { ...itemRef.current, [i]: ref };
}}
/****** [2] ******/
style={{ height: "30px" }} // 계산할 수 있게 사이즈를 지정했다.
>
Item{i}.png
</div>
);
}
return items;
};
return (
<div className="App">
<button className="btn btn-up" onClick={goUp}>
UP
</button>
<button className="btn btn-down" onClick={goDown}>
DOWN
</button>
<div className="list-container" onScroll={onScroll} ref={containerRef}>
{renderList()}
</div>
</div>
);
}
.App {
font-family: sans-serif;
text-align: center;
}
.list-container {
height: 300px;
overflow: scroll;
margin-top: 30px;
}
.active {
background-color: orangered;
}
.btn {
padding: 10px;
}
.btn-down {
margin-left: 30px;
}
솔직히 이번에 stackoverflow의 도움이 없었다면.. 할 수 있었을지 모르겠다. 할 수 있더라도, 상당히 삽질을 했을 것 같다. 고민하다가 예제코드를 만들어서 질문을 올렸는데, 생각보다 답변도 빨리 올라오고 내용도 아주 만족스러웠다. stackoverflow 만세!
stackoverflow 질문 링크 GO