Intersection Observer API란
Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다.
- MDN
화면 상의 Target Element
노출 여부를 감지하는 API
이다.
스크롤 이벤트 감지 방법과의 비교
- scroll event를 감지하여 구현한다. 이때
throttle
혹은debounce
과 같은 처리가 추가로 필요하며throttle
/debounce
를 사용하는 경우 쓰레드 메모리를 차지하고 성능에도 좋지않다. scrollTop
+window.clientHeight
등을 사용해서 끝에 도달했는지 계산한다. 이때clientHeight
이나offsetTop
같은 엘리먼트의 위치를 가져와서 계산해야 하는 값은 브라우저가 정확한 값을 구하기 위해 렌더링 큐에 쌓인 모든 작업을 수행하면서Reflow
를 발생시킬 수 있다.
Intersection Observer Syntax
new IntersectionObserver (callback[, options]);
callback 함수는 다음과 같이 2개의 매개변수를 받는다.
entries
👉 더 드러나거나 가려지면서 지정한 역치를 넘어가게 된 요소를 나타내는IntersectionObserverEntry
객체의 배열observer
👉 자신을 호출한IntersectionObserver
자세한 설명은 mdn 참고
무한 스크롤 구현
간단하게 구현하고자 하는 것은 아래와 같다.
- 리스트 목록 보여주기
- 스크롤 시 리스트 끝에 도달하면 새로운 리스트 추가
먼저, 리스트 목록을 보여줄 엘리먼트(ul
)와 Intersection Observer의 target 엘리먼트(div
)를 추가했다.
<ul id="list"></ul>
<div id="observer"></div>
다음으로, 리스트 목록을 추가하는 함수를 구현했다. 한 번 호출시마다 li
를 10개씩 추가한다.
let count = 0;
const $ul = document.getElementById('list');
function makeListElement() {
Array(10)
.fill(0)
.forEach(() => {
const $li = document.createElement('li');
$li.textContent = ++count;
$ul.appendChild($li);
});
}
마지막으로, 무한스크롤을 구현하는 부분이다. Intersection Observer의 타겟 엘리먼트가 화면에 보여질 때(isIntersecting
) 리스트의 목록을 추가했다. 이때 API 호출시 생기는 딜레마를 표현하기 위해서 1초의 timeout을 지정했다.
let timer;
const $observer = document.getElementById('observer');
const io = new IntersectionObserver((entries) => {
clearTimeout(timer);
if (entries[0].isIntersecting) {
timer = setTimeout(() => makeListElement(), 1000);
}
});
io.observe($observer); // observer에 target element 등록
즉, 무한스크롤이 일어나는 과정은 다음과 같다.
ul
태그 밑에 있는 target Element가 화면에 노출된다. (= 리스트의 끝에 도달했다)- 1초의 딜레마 후 리스트에 10개의 엘리먼트가 추가된다.
ul
태그에 자식 엘리먼트가 추가되면서 화면에는 새로 추가된 자식 엘리먼트가 보여진다. target Element 부분은 리스트의 맨 밑으로 이동하여 화면에 보여지지 않게 된다.- 다시 스크롤을 통해 맨 밑으로 내리면(=리스트에 끝에 도달하면) target Element가 화면에 노출되면서 리스트를 추가한다.
전체코드
전체코드는 다음과 같다. 실행결과는 Demo에서 확인해볼 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Infinite Scroll</title>
<style>
ul {
list-style-type: none;
display: flex;
flex-direction: column;
align-items: center;
padding: 0;
}
li {
background: #fff3bf;
border-radius: 2px;
margin: 1rem;
padding: 20% 0;
text-align: center;
font-size: 100px;
width: 80%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
li:nth-child(2n) {
background-color: #c3fae8;
}
li:hover {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25),
0 10px 10px rgba(0, 0, 0, 0.22);
}
#observer {
width: 50px;
height: 50px;
margin: 30px auto;
border: 5px solid rgba(0, 0, 0, 0.12);
border-radius: 50%;
border-top-color: #c3fae8;
animation: spin 1s ease-in-out infinite;
-webkit-animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
-webkit-transform: rotate(360deg);
}
}
@-webkit-keyframes spin {
to {
-webkit-transform: rotate(360deg);
}
}
</style>
</head>
<body>
<ul id="list"></ul>
<div id="observer"></div>
<script>
let count = 0;
const $ul = document.getElementById('list');
function makeListElement() {
Array(10)
.fill(0)
.forEach(() => {
const $li = document.createElement('li');
$li.textContent = ++count;
$ul.appendChild($li);
});
}
makeListElement();
let timer;
const $observer = document.getElementById('observer');
const io = new IntersectionObserver((entries) => {
clearTimeout(timer);
if (entries[0].isIntersecting) {
timer = setTimeout(() => makeListElement(), 1000);
}
});
io.observe($observer);
</script>
</body>
</html>
'Frontend' 카테고리의 다른 글
React-Query 정리 (0) | 2022.08.05 |
---|---|
[Javascript] Throttle 과 Debounce (0) | 2022.05.12 |
[Redux] 간단한 예제로 살펴보는 리덕스의 동작 원리 (0) | 2022.03.17 |
[React] ContextAPI & useContext Hook을 통한 Global State 값 관리하기 (0) | 2022.03.11 |
[Javascript] 변수, 호이스팅, TDZ (0) | 2022.03.07 |