2021년 12월 15일

브라우저 창 사이즈와 스크롤

브라우저 창이 차지하는 너비와 높이를 어떻게 구할 수 있을까요? 스크롤 때문에 보이지 않는 영역을 포함하여 문서 전체가 차지하는 너비와 높이는 어떻게 구할 수 있을까요? 자바스크립트를 사용해서 페이지를 스크롤 할 수 있을까요?

이번 챕터에선 위와 같은 물음에 답을 주는 루트 문서 요소인 document.documentElement를 살펴볼 예정입니다. document.documentElement<html> 태그와 상응하는 요소로 다양한 메서드를 지원합니다. 유용한 메서드이긴 하지만 몇 가지 주의할 점이 있어 같이 살펴봅시다.

브라우저 창의 너비와 높이

창이 차지하는 너비와 높이를 알려면 document.documentElementclientWidthclientHeight를 사용하면 됩니다.

아래 버튼을 눌러 직접 창 높이를 출력해보세요.

window 객체가 아닌 document.documentElement를 쓰는 이유

브라우저의 window 객체 역시 innerWidthinnerHeight 프로퍼티를 지원합니다. 이 프로퍼티를 써도 원하는 대로 창 크기를 구할 수 있을 것 같은데 왜 document.documentElementclientWidthclientHeight를 쓰는 걸까요?

스크롤바가 생기면 스크롤바 역시 공간을 차지하는데, clientWidthclientHeight는 스크롤바가 차지하는 공간을 제외해서 너비나 높이 값을 계산합니다. 눈에 보이는 문서에서 콘텐츠가 실제로 들어가게 될 영역의 너비와 높이 값을 반환하는 것이죠.

그런데 window.innerWidth/innerHeight는 스크롤바가 차지하는 영역을 포함해 값을 계산합니다.

스크롤바가 있는 경우 스크롤 바 역시 공간을 차지하는데, 이럴 때 window객체와 document.documentElement의 해당 프로퍼티들은 다른 값을 반환합니다.

alert( window.innerWidth ); // 전체 창 너비
alert( document.documentElement.clientWidth ); // 스크롤바가 차지하는 영역을 제외한 창 너비

창 사이즈가 필요한 경우는 스크롤 바 안쪽에 무언가를 그리거나 위치시킬 때가 대다수입니다. 따라서 documentElementclientHeight/clientWidth를 써야 합니다.

DOCTYPE을 꼭 써주세요.

기하 관련 프로퍼티는 HTML에 문서에 <!DOCTYPE HTML>이 명시되어있지 않으면 이상하게 동작할 때가 있습니다. 정확하지 않거나 근거를 알 수 없는 값이 툭 튀어나올 수 있죠.

그러니 앞으로는 항상 HTML에 DOCTYPE을 명시하도록 합시다.

문서의 너비와 높이

이론상 document.documentElement는 문서의 루트 요소에 상응하고, 루트 요소엔 콘텐츠 전부가 들어가기 때문에 우리는 문서의 전체 크기를 document.documentElementscrollWidthscrollHeight를 사용해 재면 되지 않냐고 생각합니다.

그런데 전체 페이지를 대상으로 했을 때, document.documentElement의 프로피터들은 우리가 예상한 대로 동작하지 않습니다. Chrome이나 Safari, Opera에서 스크롤이 없는 경우 documentElement.scrollHeightdocumentElement.clientHeight보다 작을 때가 있죠. 예상하기엔 같은 값이어야 하는데도 말입니다.

정확한 문서 전체 높이 값을 얻으려면 아래 여섯 프로퍼티가 반환하는 값 중 최댓값을 골라야 합니다.

let scrollHeight = Math.max(
  document.body.scrollHeight, document.documentElement.scrollHeight,
  document.body.offsetHeight, document.documentElement.offsetHeight,
  document.body.clientHeight, document.documentElement.clientHeight
);

alert('스크롤에 의해 가려진 분을 포함한 전체 문서 높이: ' + scrollHeight);

그렇다면 왜 이런 방식으로 문서 전체 높이를 구해야 하는 걸까요? 이유는 알아보지 않는 게 낫습니다. 이런 이상한 계산법은 아주 오래 전부터 있었고 그다지 논리적이지 않은 이유로 만들어졌기 때문입니다.

스크롤 정보 얻기

DOM 요소의 현재 스크롤 상태(스크롤에 의해 가려진 영역에 대한 정보)는 scrollLeftscrollTop 프로퍼티를 통해 구할 수 있습니다.

대부분의 브라우저에서 문서의 스크롤 상태는 document.documentElementscrollLeftscrollTop을 이용해 구할 수 있습니다. 다만 구버전 WebKit을 기반으로 하는 브라우저에선 버그(5991) 때문에 document.documentElement가 아닌 document.body를 사용해야 원하는 값을 구할 수 있습니다.

이쯤 되면 스크롤 포지션 정보를 구하기 위해 브라우저별 예외처리까지 다 해야 하나 라는 생각이 들 수 있을 겁니다. 다행히도 window객체의 pageXOffsetpageYOffset을 사용하면 브라우저 상관없이 스크롤 정보를 구할 수 있어서 이런 예외 상황을 외워두지 않아도 됩니다.

alert('세로 스크롤에 의해 가려진 위쪽 영역 높이: ' + window.pageYOffset);
alert('가로 스크롤에 의해 가려진 왼쪽 영역 너비: ' + window.pageXOffset);

참고로 이 두 프로퍼티는 읽기만 가능합니다.

scrollTo, scrollBy로 스크롤 상태 변경하기

중요:

자바스크립트를 사용해 스크롤을 움직이려면 DOM이 완전히 만들어진 상태이어야 합니다.

<head>에 있는 스크립트에서 페이지 전체의 스크롤을 움직이려 하면 잘 동작하지 않을 수 있습니다.

일반 요소의 스크롤 상태는 scrollTop이나 scrollLeft로 쉽게 변경할 수 있습니다.

페이지 전체의 스크롤 상태 역시 document.documentElementscrollTop/scrollLeft를 사용해 변경 가능하죠(다만, Safari는 document.bodyscrollTop/scrollLeft를 써야 합니다).

그런데 이보다 더 편하고 브라우저 상관없이 쓸 수 있는 대안이 있긴합니다. 바로 window.scrollBy(x,y)window.scrollTo(pageX,pageY)입니다.

  • scrollBy(x,y)메서드를 사용하면 페이지의 스크롤 상태를 현재 포지션을 기준으로 상대적으로 조정합니다. scrollBy(0,10)는 문서의 스크롤 상태를 현재를 기준으로 스크롤을 10px아래로 내린것 처럼 움직여주죠.

    아래 버튼을 눌러 직접 실습해봅시다.

  • 반면 scrollTo(pageX,pageY)메서드는 절대 좌표를 기준으로 페이지 스크롤 상태를 변경합니다. 따라서 눈에 보이는 영역의 왼쪽 위 모서리의 좌표가 문서 전체의 왼쪽 위 모서리를 기준으로 (pageX, pageY)가 됩니다. 마치 scrollLeftscrollTop 값을 변경한 것처럼 움직이는 거죠.

    그래서 scrollTo(0,0)을 호출하면 문서 스크롤 상태를 처음 상태로 되돌릴 수 있습니다.

그리고 이 두 메서드는 브라우저 종류에 상관없이 동일한 동작을 보장합니다.

scrollIntoView

추가 메서드 elem.scrollIntoView(top)를 머릿속에 추가해 스크롤 상태를 완벽히 마스터 해봅시다.

elem.scrollIntoView(top)를 호출하면 전체 페이지 스크롤이 움직여 elem이 눈에 보이는 상태로 변경됩니다. elem.scrollIntoView는 인수를 하나 받는데, 인수에 따라 다음과 같이 동작합니다.

  • toptrue(디폴트)인 경우, elem이 창 제일 위에 보이도록 스크롤 상태가 변경됩니다. elem의 위쪽 모서리가 창의 위쪽 모서리와 일치하게 되죠.
  • topfalse인 경우, elem이 창 가장 아래에 보이도록 스크롤 상태가 변경됩니다. elem의 아래쪽 모서리가 창의 아래쪽 모서리와 일치하게 변합니다.

버튼을 눌러 직접 실습해봅시다. 첫 번째 버튼을 누르면 버튼 상단이 창 제일 꼭대기로 붙는 것을 확인할 수 있습니다.

두 번째 버튼을 누르면 버튼의 아래 모서리가 창 밑으로 붙는 것을 확인할 수 있습니다.

스크롤 막기

때에 따라 문서 스크롤바를 ‘고정’ 해야 하는 경우가 생기곤 합니다. 사용자에게 반드시 전달해야 하는 중요한 메시지가 있어서 이 메시지를 화면에 크게 띄우고, 사용자가 스크롤을 움직여 다른 콘텐츠를 보지 못하게 한 상태에서 메시지를 읽게 하려는 경우가 대표적인 예가 될 수 있습니다.

이럴 때 document.body.style.overflow = "hidden"를 사용할 수 있습니다. 해당 스크립트가 동작하면 페이지의 스크롤바 위치가 ‘고정’ 됩니다.

직접 실습해봅시다.

위쪽 버튼을 누르면 스크롤바가 고정되었다가, 아래 버튼을 누르면 고정이 해제되는 것을 확인할 수 있습니다.

이 방법은 document.body요소 뿐만 아니라 다른 요소의 스크롤을 고정시킬 때도 사용할 수 있습니다.

그런데 이 방법은 스크롤바가 사라진다는 단점이 있습니다. 스크롤바는 일정 공간을 차지하는데, 스크롤바가 사라지면 해당 공간을 채우기 위해 콘텐츠가 갑자기 ‘움직이는’ 현상이 발생합니다.

이렇게 페이지 전체의 스크롤 상태가 갑자기 변경되면 사용자 입장에선 이상해 보일 수 있기 때문에 개발자는 스크롤바를 고정시키기 전과 후의 clientWidth값을 비교해서 해당 증상을 보정해야 합니다. 스크롤바가 사라질 땐 clientWidth값이 커지는데 이때 스크롤바가 차지했던 영역만큼 document.bodypadding을 줘서 콘텐츠 전체의 너비를 스크롤바가 사라지기 전과 같은 값으로 유지할 수 있습니다.

요약

기하 프로퍼티:

  • 사용자 눈에 보이는 문서(콘텐츠가 실제 보여지는 영역)의 너비와 높이: document.documentElement.clientWidth/clientHeight

  • 스크롤에 의해 가려진 영역을 포함한 문서 전체의 너비와 높이:

    let scrollHeight = Math.max(
      document.body.scrollHeight, document.documentElement.scrollHeight,
      document.body.offsetHeight, document.documentElement.offsetHeight,
      document.body.clientHeight, document.documentElement.clientHeight
    );

스크롤 관련 프로퍼티:

  • 현재 스크롤 정보 읽기: window.pageYOffset/pageXOffset.

  • 스크롤 상태 변경시키기:

    • window.scrollTo(pageX,pageY) – 절대 좌표
    • window.scrollBy(x,y) – 현재 스크롤 상태를 기준으로 상대적으로 스크롤 정보 변경
    • elem.scrollIntoView(top)elem이 눈에 보이도록 스크롤 상태 변경(인수에 따라 창 최상단, 최하단에 해당 요소가 노출되도록 처리)
튜토리얼 지도