부족한 부분이 많으니, 좀 더 좋은 방법이 있다면 알려주시면 감사하겠습니다!
"프론트엔드 신입으로 회사에 입사해 모바일 대응만 하면 되는 페이지를 만들고 있던 중, 발목을 잡은 디자인이 있었는데..."
디자인 팀장님께서는 세줄까지 나온 뒤 더보기 버튼을 클릭하면 밑으로 내용이 나올 수 있도록 요청 하셨고, 처음엔 별거 아니라 생각했다.
먼저 처음 생각한 방법은
자바스크립트로 글자 수 제한을 둬서 그 글자 수 이상이 넘어가면 잘리도록 하는 방법이였다.
( 인스타그램 등 많은 곳이 이방식으로 처리하는 것 같아 보였다. )
const Box = () => {
const [limit, setLimit] = useState(50);
const toggleEllipsis = (str, limit) => {
return {
string: str.slice(0, limit),
isShowMore: str.length > limit
}
};
const onClickMore = (str) => () => {
setLimit(str.length);
};
return (
<Wrapper>
<Description>
{toggleEllipsis(text, limit).string}
{toggleEllipsis(text, limit).isShowMore && <MoreButton onClick={onClickMore(text)}>...더보기</MoreButton>}
</Description>
</Wrapper>
)
}
문제
적당한 모바일 화면에서는 잘 되는 것 같지만, 반응형 대응이 잘 안되어 마음에 썩 들지 않았다.
화면이 큰 모바일의 경우 텍스트들이 한줄이 되는데도 불구하고 더보기 버튼이 생성되게 되는데, 이런 경우에는 더보기 버튼은 없어지고 내용이 다 보여야 한다고 생각했다.
사용할만한 상황: 텍스트 줄 수에 크게 상관이 없고, 적당히 텍스트의 글이 보일 만큼 보이고 더보기로 가려놓길 바란다면 사용하기 좋을 것 같다.
근데 이게 디자인과 가장 비슷하게 나온다..(예고)
두번째 방법은,
여러줄을 말줄임 처리 할 수 있는 css를 이용해 세줄 이상이 되면 텍스트를 자르고 버튼을 오른쪽 맨 끝에 띄워 버튼에 background linear-gradient
를 주는 트릭을 이용하는 방법이였다.
아래는 완성 모습이다.
const Box = () => {
const contentRef = useRef(null);
const onClick = (e) => {
contentRef.current.classList.add("show");
e.currentTarget.classList.add("hide");
};
return (
<Wrap>
<Ellipsis ref={contentRef}>{data}</Ellipsis>
<Button>...더보기</Button>
</Wrap>
)
}
const Ellipsis = styled.div`
position: relative;
display: -webkit-box;
max-height: 6rem;
line-height: 2rem;
overflow: hidden;
-webkit-line-clamp: 3;
&.show {
display: block;
max-height: none;
overflow: auto;
-webkit-line-clamp: unset;
}
`;
const Button = styled.button`
position: absolute;
bottom: 0;
right: 0;
max-height: 2rem;
line-height: 2rem;
padding-left: 20px;
background: rgb(2, 0, 36);
background: linear-gradient(
90deg,
rgba(2, 0, 36, 1) 0%,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 18%
);
&.hide {
display: none;
}
`;
문제
일단 디자인과 다르다. (이게 제일 큰 문제)
백그라운드 때문에 자연스러운듯 자연스럽지 않다.😳
마지막으로는 텍스트 세 줄을 꽉 채워 css로 자른 뒤,
더보기 버튼을 텍스트 밑으로 내려 사용하기
이때, 가장 신경쓰였던 resize 될 때의 상황인데,
계속해서 resize되는 모습을 지켜보고 있어야 scrollHeight
값이 height
값보다 커지는지 아닌지를 알 수 있다. (텍스트 내용이 세줄이 되지 않을 경우, 더보기 버튼 안보이게 하기 위해)
구글 검색하다가 찾아낸 ResizeObserver
와 이를 활용해 만든 useResizeObserver
커스텀훅.
검색으로 찾아낸 커스텀훅을 이용해 세 줄이 되지 않거나 텍스트가 짧을 경우 더보기 버튼이 보이지 않도록, 반대라면 세 줄 아래로 더보기 버튼이 보이도록 구현했다.
아래는 완성된 모습이다.
// useResizeObserver.js
import { useEffect, useRef } from "react";
import ResizeObserver from "resize-observer-polyfill";
const useResizeObserver = ({ callback, element }) => {
const current = element && element.current;
const observer = useRef(null);
useEffect(() => {
if (observer && observer.current && current) {
observer.current.unobserve(current);
}
const observe = () => {
if (element && element.current && observer.current) {
observer.current.observe(element.current);
}
};
const resizeObserverOrPolyfill = ResizeObserver;
observer.current = new resizeObserverOrPolyfill(callback);
observe();
return () => {
if (observer && observer.current && element && element.current) {
observer.current.unobserve(element.current);
}
};
}, [callback, current, element]);
};
export default useResizeObserver;
// main
const App = () => {
const contentRef = useRef(null);
const [isShowReadMore, setIsShowReadMore] = useState(false);
const observeCallback = (entries) => {
for (let entry of entries) {
if (entry.target.scrollHeight > entry.contentRect.height) {
setIsShowReadMore(true);
} else {
setIsShowReadMore(false);
}
}
};
useResizeObserver({ callback: observeCallback, element: contentRef });
const onClick = (e) => {
contentRef.current.classList.add("show");
setIsShowReadMore(false);
};
return (
<Wrap>
<Ellipsis ref={contentRef}>{data}</Ellipsis>
{isShowReadMore && <Button onClick={onClick}>...더보기</Button>}
</Wrap>
);
}
const Wrap = styled.div``;
const Ellipsis = styled.div`
position: relative;
display: -webkit-box;
max-height: 6rem;
line-height: 2rem;
overflow: hidden;
-webkit-line-clamp: 3;
&.show {
display: block;
max-height: none;
overflow: auto;
-webkit-line-clamp: unset;
}
`;
const Button = styled.button`
max-height: 2rem;
line-height: 2rem;
&.hide {
display: none;
}
`;
디자인 팀장님께 말씀드리고 두번째 혹은 세번째 방법으로 처리 하기로 했는데 세번째가 나을 것 같다고 하셔서 최종적으로는 마지막 방법으로 처리 했다.
나중에 찾아보니 npm에 등록된 패키지들도 꽤 있는것 같아 저장소에 가서 코드를 봤는데, 텍스트들의 각 줄들을 span
태그로 감싸서 텍스트들을 자르고 붙이는 식으로 처리 하는 것 같았다.😨
나중에 시간이 난다면 나만의 패키지 처럼 만들어 봐도 좋을 것 같고, 급한 경우엔 생산성을 위해 잘 만들어진 패키지를 이용하는 방법이 좋을 것 같다. 🙃
지금 생각해보면 많은 곳들이 글자 수 제한으로 처리하는 이유가 있는 듯 하다.. 🤔
React Hooks — useObserve (use ResizeObserver Custom Hook)
ResizeObserver - Web APIs | MDN