JavaScript - 25

Doodream·2021년 5월 15일
0

코어 자바스크립트

목록 보기
29/36
post-thumbnail

Event

IntersectionObserver API

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 : element
      이 element에 해당하는 경계 사각형외에는 그 어느것도 viewPort라고 간주하지 않습니다. null 일경우 브라우저 viewPort 전체를 대상으로 합니다.
    • threshold: 숫자, []
      0 ~ 1 까지 의 숫자를 인수로 받으며 0.1 은 viewPort의 10% 라고 간주합니다. 따라서 threshold를 0.1로 지정하면 viewPort에 관찰 대상 element가 10퍼센트가 보이게 되면 callBackFunc을 실행합니다.
    • rootMargin : viewPort의 Margin을 나타냅니다. 값으로 '숫자px' 만을 받습니다. rem, em 등 다른 단위는 받지 않습니다. 동적으로 받기 위해서 숫자부분을 getBoundingClientRect()으로 계산합니다.
  • callBackFunc(entries) 에는 관찰되는 viewPort에 따라 변하는 변수들이 있습니다. 그중에서도 isIntersecting 프로퍼티는 viewPort에 threshold값에 따라 관찰 대상자가 그만큼 보이면 true, 보이지 않으면 false를 반환합니다.

    • entry에는 target이라는 프로퍼티도 있습니다. 관찰 대상으로서 element를 가르킵니다. 따라서 한번만 callBackFunc을 호출하고 더이상 호출할 필요가 없으면 observer객체.unobserve(entry.target)을 하면 해당 타겟은 더이상 observer 객체에서 모니터링 하지 않습니다.

lazy Img loading 구현

웹 페이지에서 고해상도의 이미지를 로딩할려면 시간이 걸린다. 하지만 사용자는 부정확하게나마 해당 이미지를 보다가 고해상도 이미지가 로딩이 되면 선명하게 볼수 있게 끔 하는 것이다.

순서를 보자면

  1. 사용자가 고해상도 사진을 보려고 할때 : 스크롤로 고해상도 이미지가 보여주는 근처에 다가갈때 고해상도 이미지 로딩을 시작한다. 그전까지는 해당 이미지는 로딩을 하지 않거나, 저해상도 이미지로 대체한다. ( 저해상도 이미지에 filter : blur(20px))
  2. 다가가는 것을 모니터링 해야하므로 IntersectionObserver로 이미지 element를 모니터링 한다.
.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));

Slider 구현

<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">&larr;</button>
        <button class="slider__btn slider__btn--right">&rarr;</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를 돌게한다는 것,
그리고 오른쪽 버튼이냐 왼쪽 버튼이냐에 따라 루프를 다르게 준다는 것만 구현하면 슬라이드 버튼은 끝난다.

Slider dots 상세 구현

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">&larr;</button>
        <button class="slider__btn slider__btn--right">&rarr;</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[]으로 속성으로 접근해야한다.

profile
일상을 기록하는 삶을 사는 개발자 ✒️ #front_end 💻

0개의 댓글