완성본
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로 변경해보자. 가로 스크롤바가 없을 경우에는 동일하게 잘 동작하지만, 가로 스크롤바가 있는 경우 예상치 못한 결과가 나올 수 있다.
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
'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 |