[JS] 스크롤 프로그레스 바 만들기

2023. 5. 4. 18:40·FE/Javascript
반응형

완성본

프로그레스 바 완성!


HTML

<div class="progressWrap">
  <div class="bar"></div>
</div>

 

CSS

body {
  margin: 0;
  padding: 0;
  height: 1000px; /* 스크롤을 위해 임시로 추가 */
}

.progressWrap {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background-color: #fff;
}

.bar {
  width: 0%;
  height: inherit;
  position: absolute;
  z-index: 100;
  background-color: #a9c4ff;
  border-top-right-radius: 3px;
  border-bottom-right-radius: 3px;
  transition: width 300ms;
}

 

JS

const progressBar = document.querySelector(".bar");

let scrollNum = 0;
let documentHeight = 0;

// 전체 문서에서 얼마나 스크롤되었는지 계산
const getPercent = (scroll, total) => {
  return (scroll / total) * 100;
};

window.addEventListener("scroll", () => {
  // scroll top이기 때문에 height가 5000px이면 5000에서 화면높이를 뺀 값까지만 나온다.
  scrollNum = document.documentElement.scrollTop; // 또는 window.scrollY 사용 가능
  
  // scroll top값을 기준으로 계산할 것이기 때문에 문서의 전체 길이에서 화면 크기만큼 빼준다.
  documentHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
  
  // 전체 문서에서 몇 %만큼 스크롤되었는지 계산해 progress bar의 width를 바꿔준다.
  progressBar.style.width = getPercent(scrollNum, documentHeight) + "%";
});

 


document.documentElement

document.documentElement는 <html>태그와 상응하는 요소로, 다양한 메서드를 지원한다. 이는 유용하지만 몇 가지 주의할 점이 있다.

 

1. window 객체보다는 document.documentElement 사용하기

창이 차지하는 너비와 높이를 알기 위해 document.documentElement의 clientWidth와 clientHeight를 사용했다. 그런데 window 객체에도 innerHeight, innerWidth 프로퍼티가 있기 때문에 이를 통해 창 크기를 구할 수 있다.

그런데 document.documentElement를 사용하는 이유가 무엇일까?

 

이는 window.innerHeight/innerWidth의 경우 화면에서 스크롤바가 차지하는 영역을 포함해 값을 계산하기 때문이다. 그에 반해 clientWidth, clientHeight는 스크롤바가 차지하는 공간을 제외해 너비, 높이 값을 계산한다.

 

보통 스크롤 바 안쪽에서 무언가를 그리거나 위치시킬 때 창의 사이즈를 필요로 하기 때문에 documentElement의 clientHeight/clientWidth를 쓰는 것이 좋다.

 

 

아래 예시를 통해 window.innerHeight를 사용했을 때 발생할 수 있는 문제에 대해 알아보자.

body {
  margin: 0;
  padding: 0;
  height: 1000px;
  width: 110vw; /* 화면 너비를 늘려 가로 스크롤바가 생기도록 함 */
}

먼저 화면 너비를 110vw로 늘려서 아래에 가로 스크롤바가 생기도록 한다.

 

 

그 후 document.documentElement.clientHeight와 window.innerHeight를 비교해보자.

console.log(document.documentElement.clientHeight, window.innerHeight);

console에 찍힌 값을 보면 window.innerHeight는 가로 스크롤바를 포함한 값이 나온다.

 

 

window.addEventListener("scroll", () => {
  // ...
  documentHeight = document.documentElement.scrollHeight - window.innerHeight;
  // ...
});

원래 코드에서 document.documentElement.clientHeight 부분을 window.innerHeight로 변경해보자. 가로 스크롤바가 없을 경우에는 동일하게 잘 동작하지만, 가로 스크롤바가 있는 경우 예상치 못한 결과가 나올 수 있다.

 

스크롤을 덜내렸는데도 프로그레스 바의 width가 100%로 되어있다.

 

2. '정확한' 문서 전체 높이 값을 알고 싶다면

document.documentElement는 문서의 루트 요소에 상응하고, 루트 요소엔 콘텐츠 전부가 들어가기 때문에 보통 문서 전체의 크기를 구할 때 document.documentElement의 scrollWidth/scrollHeight를 사용한다.

그런데 스크롤이 없는 경우 scrollHeight가 clientHeight보다 작을 때가 있어 예상대로 동작하지 않을 수 있다.

 

프로그레스 바 구현에서는 스크롤이 없을 경우 scrollTop이 계속 0이기 때문에 이런 상황은 고려하지 않아도 된다.

하지만 정확한 문서 전체 높이 값을 얻고 싶다면 아래와 같이 여섯개의 프로퍼티가 반환하는 값 중 최댓값을 골라야 한다.

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

 


 

scrollHeight 사용 시 주의할 점

페이지의 전체 높이를 구할 때 document.documentElement.scrollHeight 대신 document.body.scrollHeight를 사용할 수도 있다.

하지만 기본적으로 scrollHeight는 border과 margin을 제외한 컨텐츠의 전체 높이를 반환하기 때문에 이 역시 예상치 못한 결과가 나올 수 있다.

 

어떤 상황이 발생할 수 있는지 아래 예시를 통해 확인해보자.

HTML

<body>
  <div class="progressWrap">
    <div class="bar"></div>
  </div>
  <div class="box"></div>
</body>

 

프로그레스 바 밑에 box라는 클래스명을 가진 div를 추가해준다.

 

CSS

.box {
  margin-top: 500px;
}

그리고 box에 margin 값을 부여해준다.

 

JS

window.addEventListener("scroll", () => {
  // documentElement -> body
  documentHeight = document.body.scrollHeight - document.documentElement.clientHeight;
  
  console.log(document.body.scrollHeight, document.documentElement.scrollHeight);
  // ...
});

원래 코드에서 document.documentElement를 document.body로 변경해보자.

 

 

scrollHeight에 margin값이 측정되지 않아 스크롤을 다 내리지 않아도 프로그레스 바가 100%에 도달해버리는 것을 볼 수 있다.

 

콘솔에 찍어보면 document.body.scrollHeight와 document.documentElement.scrollHeight에 차이가 있다.

 

scrollHeight는 말 그대로 'content의 전체 길이'를 반환하기 때문에 margin과 border는 포함하지 않는다.

이처럼 body 안 요소의 border나 margin 때문에 의도치 않은 결과가 나올 수 있기 때문에 document.documentElement를 사용하는 것이 좋다.

 

 

References

몇 줄로 끝내는 인터랙티브 웹 개발 노하우[초급편]

https://developer.mozilla.org/ko/docs/Web/API/Element/clientHeight

scrollHeight, clientHeight, offsetHeight 의 차이

https://ko.javascript.info/size-and-scroll-window

https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively

 

 

반응형

'FE > Javascript' 카테고리의 다른 글

[JS] 클로저 - 클로저와 렉시컬 환경  (1) 2023.04.19
[JS] 실행 컨텍스트 - (2) 실행 컨텍스트의 생성과 식별자 검색 과정  (0) 2023.04.12
[JS] 실행 컨텍스트 - (1) 실행 컨텍스트의 역할, 실행 컨텍스트 스택  (0) 2023.04.11
[JS] 자바스크립트의 This  (0) 2022.12.27
[JS] ES6의 특징 (2) - 템플릿 리터럴, 반복자, for ... of  (0) 2022.03.07
'FE/Javascript' 카테고리의 다른 글
  • [JS] 클로저 - 클로저와 렉시컬 환경
  • [JS] 실행 컨텍스트 - (2) 실행 컨텍스트의 생성과 식별자 검색 과정
  • [JS] 실행 컨텍스트 - (1) 실행 컨텍스트의 역할, 실행 컨텍스트 스택
  • [JS] 자바스크립트의 This
SH_Roh
SH_Roh
  • SH_Roh
    혼자공부끄적끄적
    SH_Roh
  • 전체
    오늘
    어제
    • 분류 전체보기 (159)
      • FE (39)
        • HTML, CSS (3)
        • Javascript (17)
        • React (11)
        • Next.js (4)
      • Network (1)
      • DevOps (4)
      • Git (1)
      • Trouble Shooting (24)
      • Algorithm (41)
        • Python (2)
        • Data Structure, Algorithm (7)
        • Problem Solving (31)
      • Education (23)
        • Elice AI Track (4)
        • Wanted Pre-Onboarding FE Co.. (19)
      • TIL (25)
      • Etc. (1)
        • 회고 (1)
        • 그냥저냥 (0)
  • 링크

    • Github
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
SH_Roh
[JS] 스크롤 프로그레스 바 만들기
상단으로

티스토리툴바