자바스크립트 DOM 심화 [1] – 요소/스타일/속성/클래스 조작하기, 이벤트와 이벤트 핸들러

DOM 동작 원리

  • DOM 트리는 여러개의 노드(Node: JS object)로 이루어져 있다.
  • 노드는 Element, Text, Comment, Document 타입으로 구분된다.
  • 각각의 노드는 저마다 다른 프로퍼티와 메소드를 가지고 있으며, 상위 요소들의 프로퍼티와 메소드를 상속한다.
  • 자주 사용하는 addEventListener 메소드는 가장 상위에 있는 EventTarget의 메소드이므로 하위 노드들에 해당 메소드를 사용할 수 있다.

Element 선택, 생성, 삭제하기

Select

console.log(document.documentElement);
console.log(document.head);
console.log(document.body);

document.querySelector('.header'); // mached first element 
const allSections = document.querySelectorAll('.header');
console.log(allSections) // NodeList

document.getElementById('section--1'); // # 안써도 됨
const allButtons = document.getElementsByTagName('button');
console.log(allButtons); // HTMLCollection

document.getElementsByClassName('btn');
  • HTMLCollection은 NodeList와 달리 수정하는 대로 바로 업데이트 되는 live 객체이다.

Create & Insert

// .insertAdjacentHTML

const message = document.createElement('div');
message.classList.add('cookie-message');
// message.textContent = 'We use cookies for improved functionality and analytics.';
message.innerHTML = 'We use cookies for improved functionality and analytics. <button class="btn btn--close-cookie">Got It!</button>';

header.prepend(message); // 내부 가장 위에 삽입됨
header.append(message); // 내부 가장 아래에 삽입됨
// header.append(message.cloneNode(true)); // 똑같은 것을 사용하려면 복제해서 사용

header.before(message);
header.after(message);
  • DOM Element는 unique한 특성이 있기 때문에 같은 요소를 여러번 삽입한다고 해도 한 번만 삽입된다.
  • 똑같은 것을 반복적으로 사용해야할 경우 cloneNode 메소드를 사용해야 한다.

Delete

document.querySelector('.btn--close-cookie').addEventListener('click', function () {
  // message.remove();
  message.parentElement.removeChild(message);
});

스타일, 속성, 클래스

Styles

message.style.backgroundColor = '#37383d';
message.style.width = '120%';

console.log(message.style.height); // style 프로퍼티를 사용해서 설정한 값만 출력된다.(only inline style)
console.log(getComputedStyle(message).height); 

message.style.height = Number.parseFloat(getComputedStyle(message).height, 10) + 40 + 'px';
console.log(message.style.height)

document.documentElement.style.setProperty('--color-primary', 'orangered'); // CSS 변수 바꾸기
  • getComputedStyle을 사용하면 CSS sheet에서 설정한 값들도 가져올 수 있다.

Attributes

const logo = documnt.querySelector('.nav__logo');
console.log(logo.alt);
console.log(logo.src);
console.log(logo.className);

logo.alt = 'Beautiful minimalist logo';
logo.setAttribute('company', 'Bankist');

console.log(logo.designer); // 이 방식은 standard attribute 지원한다.
console.log(logo.getAttribute('designer'));

const link = document.querySelector('.twitter-link');
console.log(link.href); // 절대 경로를 출력 http://127.0.0.1:8080/#
console.log(link.getAttribute('href')); // 실제 설정된 값을 출력

// Data attributes: data로 시작하는 속성 값
console.log(logo.dataset.versionNumber); // .data-version-number 클래스는 dataset 객체에 저장되어 있다.

Classes

logo.classList.add('c', 'j');
logo.classList.remove('c');
logo.classList.toggle('c');
logo.classList.contains('c'); // not includes

// Don't use - override 되기 때문에
logo.className = 'jonas'

Smooth Scroll

const btnScrollTo = document.querySelector('.btn--scroll-to');
const section1 = document.querySelector('#section--1');
btnScrollTo.addEventListener('click', function (e) {
  const s1coords = section1.getBoundingClientRect();
  console.log(s1coords);

  console.log(e.target.getBoundingClientRect()); // visible viewport

  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
  // );

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

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

Event와 Event Handler

const h1 = document.querySelector('h1');
const alertH1 = function (e) {
  alert('addEventLister: Great! You are reading the heading :D');
  h1.removeEventListener('mouseenter', alertH1);
};
h1.addEventListener('mouseenter', alertH1);

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

// old
// h1.onmouseenter = function (e) {
//   alert('onmouseenter: Great! You are reading the heading :D');
// };
  • Event의 종류 MDN
  • addEventLister 메소드로 이벤트를 설정하는 이유는 이벤트리스너 삭제 등의 편의성 때문이다.

Event Propagation: Bubbling & Capturing

  • 캡쳐링 단계에서는 이벤트가 Document 상위에서부터 하위의 여러 자식 요소들을 지나 타겟 요소에 도달한다.
  • 버블링 단계에서는 이벤트가 타겟 요소부터 부모 요소들을 거쳐 상위까지 도달하게 된다.
  • 이렇게 이벤트가 전달되는 과정을 Event Propagation이라고 부른다.
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);
});

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);
});
  • .nav__link에 해당하는 요소만 클릭되어도 부모 요소인 .nav__links, .nav에도 이벤트가 전달되기 때문에 같이 동작한다.
    • e.target은 세 이벤트 모두 동일하게 .nav__link가 출력되며, e.currentTarget은 현재 이벤트가 도달한 요소가 출력된다.
    • e.stopPopagation()으로 버블링이 동작하지 않도록 할 수 있다.
document.querySelector('.nav').addEventListener('click', function (e) {
  this.style.backgroundColor = randomColor();
  console.log('NAV', e.target, e.currentTarget);
}, true);
  • addEventListener 세번째 인자로 true를 전달하게 되면, capturing phase에 이벤트가 전달되게 된다. 그래서 가장 상위 요소인 .nav의 이벤트 리스너 코드가 먼저 동작한다.
  • 이 방식은 이제는 거의 사용하지 않는다.

Event Delegation(이벤트 위임)

document.querySelectorAll('.nav__link').forEach(
  function (el) {
    el.addEventListener('click', function (e) {
      e.preventDefault();
      const id = this.getAttribute('href');
      console.log(id);
      document.querySelector(id).scrollIntoView({ behavior: 'smooth' });
    });
  });
  • 똑같은 이벤트 리스너 함수를 forEach로 수십개를 붙이게 된다면 성능이 저하될 수 있다.
  • 이럴때는 아래와 같이 버블링을 이용해 이벤트 위임을 활용할 수 있다.
// 1. Add event listener to common parent element
// 2. Determine what element originated the event
document.querySelector('.nav__links')
  .addEventListener('click', function (e) {
    e.preventDefault();

    // matching strategy
    if (e.target.classList.contains('nav__link')) {
      const id = e.target.getAttribute('href');
      console.log(id);
      document.querySelector(id).scrollIntoView({ behavior: 'smooth' });
    }
  });

Leave a Reply

Your email address will not be published. Required fields are marked *