게시글
velca
벨바카
3주 (수정됨)
<textarea> 에서 현재 커서가 화면 중앙에 위치하게 하는 방법

최근 개발을 진행하다 내용에 따라 높이가 가변적으로 변하거나 화면 높이보다 긴 <textarea>를 사용할 때 sticky 헤더나 푸터가 있으면 커서 영역이 헤더나 푸터에 가려지는 현상을 겪었다.

이를 해결하기 위해 TypeScript로 작성한 코드를 소개할까 한다.

코드에 앞서 전체적인 흐름을 보면 다음과 같다.

  1. <textarea> 요소의 keydown 이벤트를 감지한다.
  2. keydown 이벤트 감지 시, selectionStart 를 이용해 현재 커서의 위치를 체크한다.
  3. <textarea>의 모든 스타일을 상속 받은 임시 <pre> 요소를 만들어, 커서 이전의 모든 문자열을 넣어준다. <pre> 요소를 만드는 이유는 <textarea>에 작성 된 내용의 height 를 알기 위함이다.
  4. 알 수 있는 값들을 이용해 스크롤 위치를 조정한다.

이제 코드를 알아보자.

재사용성을 위해 클래스를 이용했다. 이름은 TextAreaCursorLens 라고 지어봤다. 이 클래스는 <textarea> 요소를 파라미터로 받는다.

class TextAreaCursorLens {
  private _textarea: HTMLTextAreaElement;

  constructor(textarea: HTMLTextAreaElement) {
    this.focusToCursor = this.focusToCursor.bind(this);

    this._textarea = textarea;
    this._textarea.addEventListener('keydown', this.focusToCursor);
  }

  focusToCursor(): void {
    const cursorPosition = this._textarea.selectionStart;
    const textBeforeCursor = this._textarea.value.substring(0, cursorPosition);
    const pre = document.createElement('pre');

    const textareaDomRect = this._textarea.getBoundingClientRect();
    
    // textarea의 모든 CSS 복사
    pre.style.cssText = window.getComputedStyle(this._textarea, null).cssText;

    // pre의 역할은 textarea에 작성된 글의 길이, 즉, 커서의 위치를 계산하기 위함이므로
    // 그에 맞게 적절한 스타일 추가
    pre.style.height = 'auto';
    pre.style.width = textareaDomRect .width+ 'px';
    pre.style.position = 'fixed';
    pre.style.visibility = 'hidden';
    pre.style.textWrap = 'wrap';

    // 커서 이전의 모든 문자열 추가
    pre.textContent = textBeforeCursor;

    document.body.appendChild(pre);

    const cursorBottom = pre.offsetHeight;
    const textareaTop = textareaDomRect .top;
    const windowScrollY = window.scrollY;

    document.body.removeChild(pre);

    const targetScrollTop = 
        windowScrollY + textareaTop + cursorBottom // 현재 스크롤 위치 기반 커서의 위치
        - window.innerHeight / 2; // 화면 높이의 중앙

    window.scrollTo({
      top: targetScrollTop,
      behavior: 'instant', // 'smooth' 사용 시 글 입력 시 마다 스크롤 위치가 재조정 되므로 'instant' 사용
    });
  }

  destroy(): void {
    this._textarea.removeEventListener('keydown', this.focusToCursor);
  }
}

스크롤 위치를 조정하는 모든 과정은 keydown 이벤트에서 발생하기 때문에 구조는 간단하다.

#textarea #커서 #중앙 #위치 #scroll #window #스크롤 #조정 #타입스크립트 #커서 #위치 #계산 #Range #객체 #DOM #조작 #사용자 #경험 #개선 #부드러운 #스크롤 #이벤트 #처리 #웹 #개발 #기술 #커서 #포커스 #관리 #입력 #요소 #편집 #기능