textarea 높이 조절하기
REACT ·줄바꿈이 발생할 때 채팅 높이를 늘리기
textarea 태그를 이용해 채팅 작성 기능을 만들면서, 최대 세 줄까지 늘어나고, 줄어드는 기능이 가능하도록 구현하고 있었습니다. 이를 위해 기존에 만들어진 textarea 태그에 스크롤이 발생하는 경우, 특정 길이 이하일 때 높이가 늘어날 수 있도록 만들었습니다.

줄이 1개, 2개, 3개일 때의 각각의 높이가 57, 83, 110인 것도 함께 확인했습니다.
const setAutoSize = () => {
if (!textareaRef.current) return;
const { scrollHeight: curHeight } = textareaRef.current.style;
// 현재의 scroll 높이를 구함
// maximum height is 83px when textarea has 3 line
if (curHeight >= 90) return;
// 83보다 크고 110보다 작은 값으로 설정
textareaRef.current.style.height = `${curHeight}px`;
// 실제 textarea 높이를 스크롤 높이만큼 넓힘
};
하지만, 위 코드와 같이 작성할 경우 문제점이 발생하는데, 채팅창이 넓어지는 것은 가능하나, 이후 줄어드는 것이 불가능했습니다. 이는 이미 해당 요소의 값이 이미 커진 상태에서 글자를 제거해도 scroll height는 줄어들지 않는 데에서 발생하는 문제였습니다.

그림자 태그를 만들어 해결하기
이를 해결하기 위한 방법을 찾아보던 도중, 링크를 통해 ‘그림자’를 만들어 해결하는 방법을 알게 되었습니다.
실제 보여지는 textarea 1개, 내부에서 높이 계산을 위해 따로 존재하는 textarea 1개, 총 두 개를 만드는 형식이었습니다.
현재 문제가 되는 부분이 위에서 말한 바와 같이, textarea의 크기가 이미 커진 상태에서는 scroll height가 줄어들지 않는 것이 문제였는데, 그림자 태그를 두고 그 내부에 같은 글자들이 계속 들어올 수 있게 설정을 하면, 그림자 태그의 scroll height는 외부 height가 변하지 않았기 때문에 한 줄일 때부터 세 줄일 때의 높이를 지속적으로 알려줄 수 있게 됩니다.

export const ChatTextarea = styled.textarea<{ isShadow?: boolean }>`
${({ theme: { colors, fonts }, isShadow = false }) => css`
${fonts.main}
${fonts.largeRegular}
...
${isShadow &&
// 그림자로 설정된 경우, 높이를 없애 보이지 않도록 설정
css`
height: 0px;
opacity: 0;
`}
`}
`;
우선 위와 같이 그림자 옵션이 들어갈 시, 보이지 않는 태그가 만들어지도록 설정했습니다. 이 때, width
는 그대로 두고, height
값의 변화만 주어, 같은 길이의 text에서 높이가 변할 수 있도록 설정했습니다.
const textareaRef = useRef<HTMLTextAreaElement>(null);
const shadowTextareaRef = useRef<HTMLTextAreaElement>(null);
...
/** get height with shadow textarea */
const setAutoSize = () => {
if (!textareaRef.current || !shadowTextareaRef.current) return;
const { scrollHeight: shadowHeight } = shadowTextareaRef.current;
// 그림자의 scroll height를 이용해 높이를 구함
const { height: curHeight } = textareaRef.current.style;
// maximum height is 83px when textarea has 3 line
if (curHeight === `${shadowHeight}px` || shadowHeight >= 90) return;
// 그림자의 scroll height가 사용 가능할 시, 실제 textarea의 높이로 사용
textareaRef.current.style.height = `${shadowHeight}px`;
};
...
useEffect(() => {
setAutoSize();
}, [chatValue]);
<S.ChatTextareaWrapper>
<S.ChatTextarea
ref={textareaRef}
rows={1}
spellCheck={false}
value={chatValue}
onKeyDown={handleKeyDownChat}
onChange={handleChangeChatValue}
onScroll={handleScrollTextarea}
placeholder='메시지를 입력하세요.'
/>
// 그림자 태그를 만들어 뒤에 숨어있도록 설정
<S.ChatTextarea
ref={shadowTextareaRef}
isShadow={true}
value={chatValue}
onKeyDown={handleKeyDownChat}
onChange={handleChangeChatValue}
onScroll={handleScrollTextarea}
readOnly
tabIndex={-1}
/>
</S.ChatTextareaWrapper>
이후 기본 textarea, 그림자 textarea 두 개를 주어, scroll height가 바뀌는 상황(채팅 값이 써지면서 높이가 변할 때)에서 함수가 동작할 수 있도록 설정했습니다. 이를 통해, 채팅이 써지고 지워지는 상황에 채팅창의 높이가 올라갔다가 내려가는 것을 확인할 수 있었습니다.

하지만, 위 그림에서 보이듯 하나의 문제점이 더 존재하는데, 채팅이 써지고 이 채팅이 최종 입력되는 상황에서 기존 채팅이 가려지는 것이 문제가 되었습니다.
채팅창의 높이가 올라갈 때 채팅 내용도 올려주기
이 부분에 대해 진행을 하면서 가장 우선적으로 고민했던 것은 채팅창의 스타일 형태였습니다. 현재 만들고 있는 채팅창의 스타일의 경우, position: fixed
로 설정이 되어있었고, 이로 인해 채팅창 뒤에 존재하는 채팅 내용 일부가 가려지는 곳을 다시 올려주는 형태로 이루어져있었습니다. 하지만, 인스타그램의 DM과 같은 곳에서 확인해본 결과, 그 곳에서는 fixed
가 아닌 일반적인 position
설정을 하고 채팅 내용 부분에만 스크롤이 이루어질 수 있도록 설정이 되어있었습니다.
이 부분에서 저는 fixed
를 그대로 활용하는 쪽으로 진행하게 되었습니다. 그 이유는 모바일 브라우저의 경우 최상단으로 가기 위한 단축키로 화면 최상단을 더블탭하는 방법이 있는데, 이 방법이 작동하기 위해서는 자식 태그가 아닌 전체 앱 내에서 스크롤이 이루어지는 현재 방식이어야 하기 때문입니다.
position: fixed
로 결정한 이후,현재의 문제를 해결하기 위해 진행할 것은, 채팅창 내의 스크롤이 발생하는 시점마다 채팅 내용의 스크롤도 올려주는 것이었습니다. 이를 위해 기존 채팅창 컴포넌트 내에 onHeightChange
라는 높이가 바뀔 때에 처리할 함수를 받는 새로운 prop을 추가했습니다. 그리고 이 곳에 높이가 바뀔 때 채팅 내용 부분에 스크롤이 다시 이루어질 수 있도록 설정을 했습니다.
const [blockHeight, setBlockHeight] = useState(0);
...
const scrollToBottom = () => {
lastBlockRef.current?.scrollIntoView({ block: 'end' });
};
...
const changeBlockHeight = (height: number) => {
setBlockHeight(height);
scrollToBottom();
// 블록 높이를 바꾸고, 스크롤을 한 번 더 진행하도록 설정
};
...
return (
<>
<S.Wrapper>
{chatroomLogs}
<S.LastBottomBlock ref={scrollToBottomRef} blockHeight={blockHeight} />
// 채팅창 뒤 빈 영역을 채워주는 블록
</S.Wrapper>
<ChatroomBar chatroomId={Number(chatroomId)} onHeightChange={changeBlockHeight} />
// 채팅창의 높이가 변할 시 블록 높이를 바꾸도록 설정
</>
);
위 과정을 통해서 아래와 같이 자동으로 높이가 조절되는 채팅창을 만들어낼 수 있었습니다.
