const header = document.querySelector('.header');
const navHeight = nav.getBoundingClientRect().height;
const sticky = function (entries) {
//console.log(entries);
// [IntersectionObserverEntry]
const [entry] = entries;
console.log(entry);
//IntersectionObserverEntry {time: 1219.8249999783002, rootBounds: DOMRectReadOnly, boundingClientRect: DOMRectReadOnly, intersectionRect: DOMRectReadOnly, isIntersecting: true, …}
/*boundingClientRect: DOMRectReadOnly {x: 0, y: 0, width: 1440, height: 402, top: 0, …}
intersectionRatio: 0.48320895433425903
intersectionRect: DOMRectReadOnly {x: 90, y: 90, width: 1260, height: 222, top: 90, …}
isIntersecting: true
isVisible: false
rootBounds: DOMRectReadOnly {x: 90, y: 90, width: 1260, height: 222, top: 90, …}
target: header.header
time: 1219.8249999783002
__proto__: IntersectionObserverEntry
*/
!entry.isIntersecting
? nav.classList.add('sticky')
: nav.classList.remove('sticky');
};
const options = {
root: null,
threshold: 0,
rootMargin: `-${navHeight}px`,
};
const observer = new IntersectionObserver(sticky, options);
observer.observe(header);
IntersectionObserver
은 일종의 webAPI이다. 이 객체는 사용자의 스크롤링을 모니터링하는데 일반적으로 if 구문으로 해당 객체에 대한 스크롤 위치를 계산하면서 코딩을 하기에는 매우 많은 연산이 진행되기 때문에 웹브라우저에 부담이 된다.
따라서 이러한 모니터링이 가능한 webAPI를 사용하면 모니터링을 가볍게 할 수 있다.
IntersectionObserver(callBackFunc, options)
options
에는
root
: elementthreshold
: 숫자, []rootMargin
: viewPort의 Margin을 나타냅니다. 값으로 '숫자px' 만을 받습니다. rem, em 등 다른 단위는 받지 않습니다. 동적으로 받기 위해서 숫자부분을 getBoundingClientRect()으로 계산합니다.callBackFunc(entries)
에는 관찰되는 viewPort에 따라 변하는 변수들이 있습니다. 그중에서도 isIntersecting
프로퍼티는 viewPort에 threshold값에 따라 관찰 대상자가 그만큼 보이면 true
, 보이지 않으면 false
를 반환합니다.
entry
에는 target이라는 프로퍼티도 있습니다. 관찰 대상으로서 element를 가르킵니다. 따라서 한번만 callBackFunc을 호출하고 더이상 호출할 필요가 없으면 observer객체.unobserve(entry.target)
을 하면 해당 타겟은 더이상 observer 객체에서 모니터링 하지 않습니다. 웹 페이지에서 고해상도의 이미지를 로딩할려면 시간이 걸린다. 하지만 사용자는 부정확하게나마 해당 이미지를 보다가 고해상도 이미지가 로딩이 되면 선명하게 볼수 있게 끔 하는 것이다.
순서를 보자면
filter : blur(20px)
).lazy-img {
filter: blur(20px);
}
<img
src="img/digital-lazy.jpg"
data-src="img/digital.jpg"
alt="Computer"
class="features__img lazy-img"
/>
const lazyImgs = document.querySelectorAll('img[data-src]');
console.log(lazyImgs);
const imgLoading = function (entries) {
const [entry] = entries;
entry.target.src = entry.target.dataset.src;
entry.target.classList.remove('lazy-img');
imgObserver.unobserve(entry.target);
};
const imgObserver = new IntersectionObserver(imgLoading, {
root: null,
threshold: 0,
rootMargin: '200px',
});
lazyImgs.forEach(el => imgObserver.observe(el));
<div class="slider">
<div class="slide slide--1">
...
</div>
<div class="slide slide--2">
...
</div>
<div class="slide slide--3">
...
</div>
<button class="slider__btn slider__btn--left">←</button>
<button class="slider__btn slider__btn--right">→</button>
<div class="dots"></div>
</div>
//slider 구현
const slider = document.querySelector('.slider');
const slides = document.querySelectorAll('.slide');
const btnsSlider = document.querySelectorAll('.slider__btn');
let currentSlide = 0;
const moveSlide = function (e) {
if (e.target === btnRightSlider) {
currentSlide = currentSlide === slides.length - 1 ? 0 : currentSlide + 1;
} else {
currentSlide = currentSlide === 0 ? slides.length - 1 : currentSlide - 1;
}
console.log(currentSlide);
slides.forEach((slide, index) => {
slide.style.transform = `translateX(${100 * (index - currentSlide)}%)`;
});
};
// slider.style.overflow = 'visible';
// slider.style.transform = 'scale(0.4) translateX(-800px)';
slides.forEach((slide, index) => {
slide.style.transform = `translateX(${100 * index}%)`;
});
btnsSlider.forEach(el => el.addEventListener('click', moveSlide));
슬라이더를 구현하는 코드이다. 생각보다 그리 길지 않다. 핵심은 각 슬라이더 들은 가장 처음에는 모두 겹쳐 있다는 것과 스크립트에서 해당 코드들을
.transform = `translateX(${100 * index}%)`;
옆으로 길게 퍼뜨린다는 것, 그리고 현재 슬라이드의 index에 따라 loop를 돌게한다는 것,
그리고 오른쪽 버튼이냐 왼쪽 버튼이냐에 따라 루프를 다르게 준다는 것만 구현하면 슬라이드 버튼은 끝난다.
html
<div class="slider">
<div class="slide slide--1">
...
</div>
<div class="slide slide--2">
...
</div>
<div class="slide slide--3">
...
</div>
<button class="slider__btn slider__btn--left">←</button>
<button class="slider__btn slider__btn--right">→</button>
<div class="dots"></div>
</div>
css
.dots {
position: absolute;
bottom: 5%;
left: 50%;
transform: translateX(-50%);
display: flex;
}
.dots__dot {
border: none;
background-color: #b9b9b9;
opacity: 0.7;
height: 1rem;
width: 1rem;
border-radius: 50%;
margin-right: 1.75rem;
cursor: pointer;
transition: all 0.5s;
/* Only necessary when overlying images */
/* box-shadow: 0 0.6rem 1.5rem rgba(0, 0, 0, 0.7); */
}
.dots__dot:last-child {
margin: 0;
}
.dots__dot--active {
/* background-color: #fff; */
background-color: #888;
opacity: 1;
}
js
const slider = function () {
const slides = document.querySelectorAll('.slide');
const btnsSlider = document.querySelectorAll('.slider__btn');
const dotsContainer = document.querySelector('.dots');
let currentSlide = 0;
const createDots = function () {
slides.forEach((_, index) => {
dotsContainer.insertAdjacentHTML(
'beforeend',
`<button class="dots__dot" data-index='${index}'></button>`
);
});
};
createDots();
const goToSlide = function (order) {
slides.forEach((slide, index) => {
slide.style.transform = `translateX(${100 * (index - order)}%)`;
});
};
const moveSlide = function (e) {
if (e.target === btnsSlider[1]) {
currentSlide = currentSlide === slides.length - 1 ? 0 : currentSlide + 1;
} else {
currentSlide = currentSlide === 0 ? slides.length - 1 : currentSlide - 1;
}
goToSlide(currentSlide);
activeDots(currentSlide);
};
const activeDots = function (index) {
document.querySelectorAll('.dots__dot').forEach((dot, index) => {
dot.classList.remove('dots__dot--active');
});
document
.querySelector(`.dots__dot[data-index='${index}']`)
.classList.add('dots__dot--active');
};
const init = function () {
activeDots(0);
slides.forEach((slide, index) => {
slide.style.transform = `translateX(${100 * index}%)`;
});
btnsSlider.forEach(el => el.addEventListener('click', moveSlide));
dotsContainer.addEventListener('click', function (e) {
if (e.target.classList.contains('dots__dot')) {
const { index } = e.target.dataset;
goToSlide(index);
activeDots(index);
}
});
};
init();
};
slider();
activeDots 함수를 구현하고 goToSlide 함수를 구현하여 함수별로 하나의 기능을 담은 함수를 구현한다.
init() 함수를 구현해서 가독성을 높인다.
element을 새로 넣을 때에는 data-index 속성을 넣는데 querySelector에는 className[]으로 속성으로 접근해야한다.