개요
오늘 원래 <검색 페이지 관련>에서 나머지 하나도 같이 풀어보려고했는데
<스크롤 페이징 구현>이 생각보다 오래걸려서 나눠서 작성해볼게요!
Q.lazy load 개념을 이용하여, 이미지가 화면에 보여야 할 시점에 load 되도록 처리해야 합니다.
점점 난이도가 높아지네요!
우선 이미지의 지연 로딩(lazy loading)은 이미지가 화면에 보여야 할 시점에 동적으로 로드되도록 처리하는 기술입니다
그런데 이게 결국 스크롤 페이징 기법이랑 이어진다고 생각이 드는데요..
그니까 지금은 fetchCats를 이용하면 32개의 이미지를 다 받아오고 띄어놓는 상태인데
lazy load 기법을 사용하면 사용자가 보고있는 view포트에 따라서 지금 화면 크기상 5개만 보이면
5개만 이미지를 받아오고, 나머지는 사용자가 view를 이동할때 보이게 한다는 것입니다
이렇게하면 사용자 경험을 개선하고 페이지 로딩 시간을 단축시킨다는 장점이 있다고해요~
일단 지금 보면 searchResult에서 img태그 안에 src 프로퍼티를 이용해서 받아오고있는데요
이 속성을 data-src 속성으로 바꾸어야 합니다
// searchResult.js
this.$searchResult.innerHTML = this.data
.map(
cat => `
<div class="item">
<img data-src=${cat.url} alt=${cat.name} />
</div>
`
)
이거야 뭐 간단하게 바꾸어주면되구요
그러면 검색을 했을때 이렇게 됩니다 (저는 버튼을 눌렀어요)
허허
이렇게되면 안보이는 영역때문에 뭔가 화면이 깨질수있겠죠?
지금으로선 레이아웃 처리를하거나 로딩 이미지를 넣는 방법이 있겠네요
저는 로딩 이미지를 넣었습니다 ㅎ
그냥 favicon이미지를 넣었어요 ㅎ (따로 로딩 이미지를 가져오는건 오바같아서..)
// searchResult.js
this.$searchResult.innerHTML = this.data.items
.map(
cat => `
<div class="item">
<img data-src=${cat.url} alt=${cat.name} src="favicon.ico" />
</div>
`
)
.join("");
그리고 이제 스크롤에서 보이는 이미지에 대해서만 가져오도록 처리를 할건데요
이건 저도 처음 사용해보는거라 일단 무작정 따라해봤어요
// lazyLoad.js
export default {
checkImageInView($images){
console.log("checkImageInView 수행")
$images.forEach($image =>{
console.log($image, "불러와")
const rect = $image.getBoundingClientRect();
const isVisible = (rect.top >= 0 && rect.bottom <= window.innerHeight);
if (isVisible) {
const src = $image.getAttribute('data-src');
$image.setAttribute('src', src);
}
})
}
}
util에 새로운 파일을 팠구요, checkImageInView라는 함수를 만들어봤어요
input으로 이미지 파일의 배열형태를 받게끔 설계했구요
배열을 forEach로 돌립니다
그리고 getBoundingClientRect()라는 메소드를 처음 사용해봤어요
getBoundingClientRect() 메서드는 모든 DOM 요소에서 호출할 수 있는 메소드라고 하더라구요
반환으로 받아오는 rect라는 객체는 이런 정보를 갖고있다고해요
- top: 요소의 상단 경계선의 위치
- right: 요소의 우측 경계선의 위치
- bottom: 요소의 하단 경계선의 위치
- left: 요소의 좌측 경계선의 위치
- width: 요소의 너비
- height: 요소의 높이
해당 요소의 위치와 크기에 대한 정보들이네요
이를 통해 요소의 위치와 크기를 동적으로 계산하거나 다른 요소와의 상대적인 위치를 비교할 수 있겠죠?
비교는 이렇게 이루어지는데요
isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight
rect의 top이 0보다 크고, bottom이 window.innerHeight보다 작으면 보이고있는 상태(isVisible)로 보고있습니다
저는 rect와 window.innerHeight 기준이 뷰포트 기준인지 윈도우 기준인지 좀 헷갈려서 한번 console.log로 나오게 해봤어요
여기서 출력한 이미지는 오른쪽의 마우스 위에있는 "시베리안" 고양이이구요 src를 보시면아시겠지만 favicon.ico으로 되어있는 상태로 lazy-load 처리가 되어있어 아직 이미지를 불러온 상태가 아닙니다
또한 스크롤로 인하여 아직 화면(뷰포트)에 나오지 않은 상태이구요 (하단에 있습니다)
보면 window.innerHeight는 711이죠
이것은 화면 크기를 조정하지않는 한 거의 고정값입니다 (뷰포트의 세로 길이에요)
rect.top은 711보다 큰 982이죠
여기서 rect의 상단은 뷰포트 상단 경계선에서 얼마나 떨어져 있는지를 기준으로
상단과 얼마나 차이가 나는지에 대한 값인거같습니다
화면에 보이지 않는 상태이기때문에 711을 벗어난 982 인거죠
rect.bottom값은 당연히 rect.top보다 훨씬 밑인(이미지 크기만큼 밑인거겠죠)
1003값이네요
여기서 이제 스크롤을 움직여 화면에 나오게끔 해보겠습니다
이만치 내렸는데도 lazy-load는 수행되지않네요 ㅜㅜ 그래서 아직 favicon으로 나옵니다
이쯤에서 아까그 조건문을 다시보면 보이는것이 있습니다
isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight
rect.top이 0 보다 크다는 것은 스크롤 하위에 이미지 상단이 보일수 있다는 것을 의미합니다
rect.top이 0보다 작으면 상단이 화면위로 갔겠죠
rect.bottom은 window.innerHeight보다 작은 값일때 보인다고 설정되어있습니다
이 경우에 지금 캡쳐화면처럼 이미지가 빼꼼 보이는 상태일때 계속 로딩화면이 나오겠죠
완전히 뷰포트화면 안에 이미지 전체가 나오는 상황이어야지 로드를 시작하게 되어있는 겁니다
이렇게되면 사용자입장에서 불편할 수 있겠죠
조금이라도 이미지가 보이면 로드받아오면 될것같다고 생각이 드는데요
그럼 두가지 경우로 나올 수 있겠네요
1. 상단이 조금 보일 경우
2. 하단이 조금 보일 경우
사실 2번같은 경우는 스크롤을 엄청 빨리 확 내려야 해당되긴하겠죠
1번을 먼저 만들어보면
rect.top이 0보다 같거나 크고, window.innerHeight보다는 작으면 될거같네요
2번도 거의 마찬가지로
rect.bottm이 0보다 같거나 크고, window.innerHeight보다 작으면 된다..
그럼 이걸 아예 좌우로도 적용시켜버릴수 있을거같네요
코드로 작성해봤습니다
// utils/lazyLoad.js
export default {
checkImageInView($images){
$images.forEach($image =>{
const rect = $image.getBoundingClientRect();
const isVisibleHeight = (rect.top >= 0 && rect.top <= window.innerHeight)
|| (rect.bottom >= 0 && rect.bottom <= window.innerHeight)
const isVisibleWidth = (rect.right >= 0 && rect.right <= window.innerWidth)
|| (rect.left >= 0 && rect.left <= window.innerWidth)
if (isVisibleHeight && isVisibleWidth) {
const src = $image.getAttribute('data-src');
$image.setAttribute('src', src);
}
})
}
}
가로와 세로를 따로 측정하고 (원래 호리젠탈 등이 맞겠지만 그냥 Height,Width로 했습니다 ㅎ)
보이는 상황일때 불러오도록 해습니다
확실히 아까보다 바로바로 불러와서 불편함이 덜하네요!
searchResult에는 이렇게 적용해줬어요
// searchResult.js
document.addEventListener("scroll", e=>{
const $images = this.$searchResult.querySelectorAll("img")
lazyLoad.checkImageInView($images)
})
...
const $images = this.$searchResult.querySelectorAll("img")
lazyLoad.checkImageInView($images)
scroll에 이벤트로만 등록해주면 처음에 스크롤하기전에 모든사진이 로드가안되어서 render에서도 한번 수행하게 했습니다
배운것도 많고 재미있네요 ㅎㅎ
getBoundingClientRect()!!
rect나 client rect 등 용어가 낯설어서 외울수있을까싶은데 잘 숙지해봐야겠어요 ㅎㅎ
여기서 마무리하고 다음 포스팅에서 뵙겠습니다~
'기술 > 과제테스트 연습' 카테고리의 다른 글
[프로그래머스 과제테스트 연습] 고양이 사진 검색 사이트 (+) - 프론트엔드 (리팩토링) (0) | 2023.06.26 |
---|---|
[프로그래머스 과제테스트 연습] 고양이 사진 검색 사이트 (+) - 프론트엔드 (기타) (0) | 2023.06.26 |
[프로그래머스 과제테스트 연습] 고양이 사진 검색 사이트 (4) - 프론트엔드 (HTML, CSS 관련) (0) | 2023.06.23 |
[프로그래머스 과제테스트 연습] 고양이 사진 검색 사이트 (3) - 프론트엔드 (검색 페이지 관련, 스크롤 페이징 구현) (0) | 2023.06.23 |
[프로그래머스 과제테스트 연습] 고양이 사진 검색 사이트 (2) - 프론트엔드 (이미지 상세 보기 모달 관련) (0) | 2023.06.22 |