[JS]scrollIntoView event handlers, bubbling & capturing, event delegation, DOM traversing

Hyodduru ·2021년 12월 22일
0

JavaScript

목록 보기
46/60
post-thumbnail

강의 출처 : The Complete JavaScript Course 2022 Jonas (Udemy)

Implementing Smooth Scrolling

scroll 관련 알아두면 좋은 정보들

  • getBoundingClientRect() : relative to visible viewport
  • window.screen : 모니터 size
  • window.outer : page를 넘어선 browser 전체 크기 ( url + tab + page)
  • window.inner : 스크롤바 포함한 viewport
  • document.documentElement.clientHeight, document.documentElement.clientWidth : viewport의 높이와 너비. scroll bar 제외
  • window.pageXOffset, window.pageYOffset : viewport의 가장 끝에서 top of the page의 길이

scrollIntoView()

const btnScrollTo = document.querySelector('.btn--scroll-to');
const section1 = document.querySelector('#section--1');

btnScrollTo.addEventListener('click', e => {
  const s1coords = section1.getBoundingClientRect();
  console.log(s1coords);

  console.log(e.target.getBoundingClientRect());
  console.log('Current scroll (X/Y)', window.pageXOffset, window.pageYOffset); 

  console.log(
    'height/width viewport',
    document.documentElement.clientHeight,
    document.documentElement.clientWidth 
  );

//scrolling
window.scrollTo(
  s1coords.left + window.pageXOffset,
  s1coords.top + window.pageYOffset
);

window.scrollTo({
  left: s1coords.left + window.pageXOffset,
  top: s1coords.top + window.pageYOffset,
  behavior: 'smooth',
});

// modern way
 section1.scrollIntoView({ behavior: 'smooth' });
});

Types of Events and Event Handlers

addEvenetListener, removeEventListener

const h1 = document.querySelector('h1');

const alertH1 = e => {
  alert('addEventListener : Great! You are reading the heading :D');

  h1.removeEventListener('mouseenter', alertH1); //event 가 한번만 실행되고 제거된다.
};

h1.addEventListener('mouseenter', alertH1);

setTimeout(() => h1.removeEventListener('mouseenter', alertH1), 3000);

//old way
h1.onmouseenter = e => {
  alert('addEventListener : Great! You are reading the heading :D');
};

Event Propagation : Bubbling and Capturing

Event 발생 원리

Event 가 발생하면 해당 element가 아닌 가장 상위에 있는 Document에 먼저 전달된다.

  • capturing phase : document에서 target phase의 부모 element들을 거쳐 target까지 가는 구간.
  • target phase : event가 발생한 target에서 callback 함수를 실행한다.
  • bubbling phase : 다시 target에서부터 거쳐왔던 parent element로 올라가 처음 event가 전달되었던 document까지 가는 구간. 이 구간에서 만약 parent Element에 target과 동일한 event가 있다면 함께 발생. 부모 컨테이너는 어떤 자식요소에서 이벤트가 발생하든 모든 이벤트를 다 들을 수 있다.

Event Propagation in Practice

rgb(255, 255, 255); 랜덤한 색깔 함수 만들기

const randomInt = (min, max) =>
  Math.floor(Math.random() * (max - min + 1) + min);
const randomColor = () =>
  `rgb(${randomInt(0, 255)},${randomInt(0, 255)},${randomInt(0, 255)})`;

document.querySelector('.nav__link').addEventListener('click', function (e) {
  this.style.backgroundColor = randomColor();
  console.log('LINK', e.target, e.currentTarget);

  // Stop propogation 사용하지 않는게 좋음
  //e.stopPropagation(); //bubbling 일어나지 않음
});

document.querySelector('.nav__links').addEventListener('click', function (e) {
  this.style.backgroundColor = randomColor();
  console.log('CONTAINER', e.target, e.currentTarget);
});

document.querySelector('.nav').addEventListener(
  'click',
  function (e) {
    this.style.backgroundColor = randomColor();
    console.log('NAV', e.target, e.currentTarget);
  },
  true // event handling이 bubbling을 멈추고 capturing event 시작한다.
  // capturing phase : nav -> link -> container 순서로 event 발생 (nav는 capturing, 나머지 둘은 bubbling phase 이므로)
);


'.navlink' event 실행시 parent Element인 .navlinks와 .nav 전부다 실행됌.
=> Event propagation

this === e.currentTarget / e.target은 실제로 event 가 일어난 element를 가리킨다. ex) bubbling으로 인해 .nav의 event가 발생하였어도 e.target은 .nav__link를 가리킨다!

Event Delegation

Implementing Page Navigation by using Event Delegation

이벤트 위임의 장점

여러 element들 동일한 event 실행시켜주어야 할 때 => 공통의 parent Element에 이벤트를 실행시켜(위임하여) 일일히 함수 만드는 것을 생략 가능. 즉 반복되어지는 이벤트를 처리할 때, 즉 부모 안의 자식들에게 공통적으로 무언가 처리를 해야될 때 일일히 이벤트리스너를 자식노드에 추가하는 것보다 부모에 등록하는 것이 좋다.

이벤트 위임 로직

  1. Add event listener to common parent element
  2. Determine what element originated the event
  3. Matching strategy => if(element.classList.contains('')){}
document.querySelector('.nav__links').addEventListener('click', e => {
  e.preventDefault();
  // Matching strategy
  if (e.target.classList.contains('nav__link')) {
    const id = e.target.getAttribute('href');
    if (id === '#') {
      return;
    }
    document.querySelector(id).scrollIntoView({ behavior: 'smooth' });
  }
});

참고) link href ='#id' => 해당하는 아이디로 자동 scroll을 해주는 기본 기능을 가지고 있다.

DOM Traversing

DOM Traversing : walking through the DOM, we can select an element based on another element

Going downwards : child

querySelector

const h1 = document.querySelector('h1');
console.log(h1.querySelectorAll('.highlight'));
console.log(h1.childNodes);
//NodeList(9) [text, comment, text, span.highlight, text, br, text, span.highlight, text]
// Nodes can be anything!
console.log(h1.children); //HTMLCollection(3) [span.highlight, br, span.highlight]
h1.firstElementChild.style.color = 'white';
h1.lastElementChild.style.color = 'orangered';

Going upwards : parents

closest

console.log(h1.parentNode); //<div class="header__title">...</div>
console.log(h1.parentElement); //<div class="header__title">...</div>

//closest(selectors) 지정한 선택자와 가장 가깝게 조건에 만족한 부모요소가 반환된다.
h1.closest('.header').style.background = 'var(--gradient-secondary)'; 
// header에 가장 가까운 h1의 부모요소에 효과 적용된다.
h1.closest('h1').style.background = 'var(--gradient-primary)'; 
// h1 그 자체


closest는 querySelector의 정 반대의 개념이라 생각하면 된다. 둘다 selector를 받지만 querySelector은 child element를 선택하고 closest는 parent element를 선택하므로!

Going side ways : siblings

console.log(h1.previousElementSibling); //바로 전 sibling elememnt
console.log(h1.nextElementSibling); // 바로 다음 sibling element

console.log(h1.previousSibling); //바로 전 sibling node
console.log(h1.nextSibling); //바로 다음 sibling node

console.log(h1.parentElement.children); // h1을 포함한 모든 siblings 출력.
[...h1.parentElement.children].forEach(function (el) {
  if (el !== h1) el.style.transform = 'scale(0.5)';
});

Tabbed component

tabsContainer.addEventListener('click', e => {
  const clicked = e.target.closest('.operations__tab'); //span 부분을 눌러도 button 선택된다!
  //Guard clause -modern way (빠른 return)
  if (!clicked) return;

  // Remove active classes
  tabs.forEach(t => t.classList.remove('operations__tab--active')); 
  // 흔한 logic! 하나의 element에만 classList를 추가하고 싶다면 추가 전에 모든 요소의 classList clear 해주기!
  tabsContent.forEach(c => c.classList.remove('operations__content--active'));

  //Active tab
  clicked.classList.add('operations__tab--active');

  //Active content area
  document
    .querySelector(`.operations__content--${clicked.dataset.tab}`)
    .classList.add('operations__content--active');
});
profile
꾸준히 성장하기🦋 https://hyodduru.tistory.com/ 로 블로그 옮겼습니다

0개의 댓글