Advanced DOM and Events

vancouver·2023년 6월 27일
0

javascript이해하기

목록 보기
18/22

How the DOM Really Works

  1. JavaScript가 브라우저와 상호 작용하도록 허용합니다.

  2. JavaScript를 작성하여 HTML 요소를 생성, 수정 및 삭제할 수 있습니다.
    스타일, 클래스 및 속성을 설정합니다. 이벤트를 듣고 응답합니다.

  3. DOM 트리는 HTML 문서에서 생성됩니다. 상호 작용하다;

  4. DOM은 많은 메소드와 DOM 트리와 상호 작용하는 속성 (API)
    .querySelector() / .addEventListener() / .createElement() / .innerHTML / .textContent / .children / etc ...


Selecting, Creating, and Deleting Elements

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

const header = document.querySelector(`.header`);
const allSection = document.querySelectorAll(`.section`);

console.log(allSection); // NodeList(4) [section#section--1.section, section#section--2.section, section#section--3.section, section.section.section--sign-up]

document.getElementById(`section--1`);
const allButtons = document.getElementsByTagName(`button`); // 태그의 button이 들어간 모든 개체를 불러옴
console.log(
  allButtons
); /* HTMLCollection(9) [button.btn--text.btn--scroll-to, button.btn.operations__tab.operations__tab--1.operations__tab--active, 
button.btn.operations__tab.operations__tab--2, button.btn.operations__tab.operations__tab--3, button.slider__btn.slider__btn--left, button.slider__btn.slider__btn--right, 
button.btn.btn--show-modal, button.btn--close-modal, button.btn]*/

console.log(
  document.getElementsByClassName(`btn`) // 클래스의 btn이 들어간 모든 개체를 불러옴
); /*HTMLCollection(5) [button.btn.operations__tab.operations__tab--1.operations__tab--active, 
  button.btn.operations__tab.operations__tab--2, button.btn.operations__tab.operations__tab--3, button.btn.btn--show-modal, button.btn] */ 

// Creating and inserting elements
const message = document.createElement(`div`);
message.classList.add(`cookie-message`);

// message.textContent = `We use cookied for improved functionality and anlytics.`
message.innerHTML = `We use cookied for improved functionality and anlytics. <button class="btn btn--close--cookie">Got it!</button>`;
// header.prepend(message);
header.append(message); // (prepend 와 append는 생명요소이기에 한번만 사용 가능하다.)
// header.prepend(message.cloneNode(true)); // (cloneNode를 사용하면 중복사용 가능)

// header.before(message);
// header.after(message);

// Delete elments (요소 삭제)
document
  .querySelector(`.btn--close--cookie`)
  .addEventListener(`click`, function () {
    message.remove();
  });

Styles,Attributes and Classes

// Styles
message.style.backgroundColor = `#37383d`;
message.style.width = `120%`;

console.log(message.style.height);
console.log(message.style.backgroundColor);

console.log(getComputedStyle(message).color);
console.log(getComputedStyle(message).height);

message.style.height =
  Number.parseFloat(getComputedStyle(message).height, 10) + 30 + `px`;

document.documentElement.style.setProperty(`--color-primary`, `orangered`);

// Attributes
const logo = document.querySelector(`.nav__logo`);
console.log(logo.alt); // Bankist logo

console.log(logo.className); // nav__logo

logo.alt = `Beautiful minimalist logo`;

// Non-standard
console.log(logo.designer); // undefined
console.log(logo.getAttribute(`designer`)); // vancouver
logo.setAttribute(`company`, `Bankist`); // (creating Attribute)

console.log(logo.src); // http://127.0.0.1:5500/img/logo.png
console.log(logo.getAttribute(`src`)); // img/logo.png

const link = document.querySelector(`.nav__link--btn`);
console.log(link.href); // http://127.0.0.1:5500/index.html#
console.log(link.getAttribute(`href`)); // # (html의 있는 그대로를 출력)

// Data attributes
console.log(logo.dataset.versionNumber); // 3.0

//classes
logo.classList.add(`c`, `j`);
logo.classList.remove(`c`, `j`);
logo.classList.toggle(`c`, `j`);
logo.classList.contains(`c`, `j`);

// Don't use
logo.className = `vancouver`;

Impelementing Smooth Scrolling

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

  console.log(`Current scroll (X/Y)`, window.pageXOffset, window.pageYOffset); // 가시 뷰로 스크롤을 상하좌우 할때 현재 X,Y좌표를 찍어줌

  console.log(
    `heigth/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`,
  // });

  section1.scrollIntoView({ behavior: `smooth` });
});

Types of Events and Event Handles

const h1 = document.querySelector(`h1`);
const alertH1 = function (e) {
  alert(`addEventListener: Great! You are reading the heading :D`);

  // h1.removeEventListener(`mouseenter`, alertH1); (이벤트 리스너를 삭제, 한번만 허용)
};

h1.addEventListener(`mouseenter`, alertH1); // h1의 마우스를 눌렀을때의 이벤트리스너

setTimeout(() => h1.removeEventListener(`mouseenter`, alertH1), 3000);
// 3초가 지난뒤 eventListener를 삭제

// h1.onmouseenter = function (e) {
//   alert(`onmouseenter: Great! You are reading the heading :D`);
// };

Event Propagation: Bubbling and Capturing (이벤트 전파: 버블링 및 캡처)

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)})`;
console.log(randomColor(0, 255));

document.querySelector(`.nav__link`).addEventListener(`click`, function (e) {
  this.style.backgroundColor = randomColor(); // Features
   console.log(`LINK`, e.target);
});

document.querySelector(`.nav__links`).addEventListener(`click`, function (e) {
  this.style.backgroundColor = randomColor(); // container (parents element)
   console.log(`LINK`, e.target);
});

document.querySelector(`.nav`).addEventListener(`click`, function (e) {
  this.style.backgroundColor = randomColor(); // NAV (parents element)
   console.log(`LINK`, e.target); 
});

Event Delegation: Implementing Page Navigation (이벤트 위임: 페이지 탐색 구현)

// Page Navigation
// 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` });
//   });
// });

// 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` });
  }
});// Features / Operations / Testimonials 버튼을 각각 눌렀을때 부드럽게 창으로 넘어가는 효과 부여

DOM traversing

console.log(h1.querySelectorAll(`.highlight`));
console.log(h1.childNodes);
console.log(h1.children);
h1.firstElementChild.style.color = `white`;
h1.lastElementChild.style.color = `orangered`;

//  Going upwards: parents
console.log(h1.parentNode);
console.log(h1.parentElement);

h1.closest(`.header`).style.background = `var(--gradient-secondary)`;
h1.closest(`h1`).style.background = `var(--gradient-primary)`;

// Going sideways: siblings
console.log(h1.previousElementSibling);
console.log(h1.nextElementSibling);

console.log(h1.previousSibling);
console.log(h1.nextSibling);

console.log(h1.parentElement.children);

console.log(h1.parentElement.children);
[...h1.parentElement.children].forEach(function (el) {
  if (el !== h1) el.style.transform = `scale(0.5)`;
});

Building a Tabbed Component (tab animation)

// Tabed component
const tabs = document.querySelectorAll(`.operations__tab`);
const tabsContainer = document.querySelector(`.operations__tab-container`);
const tabsContents = document.querySelectorAll(`.operations__content`);

tabsContainer.addEventListener(`click`, function (e) {
  const clicked = e.target.closest(`.operations__tab`);

  // Guard clasuse
  if (!clicked) return; // target 범위밖에 클릭을 했을때 오류를 방지.

  // Remove active classes
  tabs.forEach((t) => t.classList.remove("operations__tab--active"));
  tabsContents.forEach((c) =>
    c.classList.remove(`operations__content--active`)
  );

  // Activate tab
  clicked.classList.add(`operations__tab--active`);

  // Activate content area
  console.log(clicked.dataset.tab);
  document
    .querySelector(`.operations__content--${clicked.dataset.tab}`)
    .classList.add(`operations__content--active`);
});

Passing Arguments to Event Handlers (mouseover, mouseout)

// Menu fade animation
const handleHover = function (e) {
  if (e.target.classList.contains(`nav__link`)) {
    const link = e.target;
    const siblings = link.closest(`.nav`).querySelectorAll(`.nav__link`);
    const logo = link.closest(`.nav`).querySelector(`img`);

    siblings.forEach((el) => {
      if (el !== link) el.style.opacity = this;
    });
    logo.style.opacity = this;
  }
};


// Passing "argument" into handler
nav.addEventListener(`mouseover`, handleHover.bind(0.5));

nav.addEventListener(`mouseout`, handleHover.bind(1));

Implementing a Sticky navigation : The scroll Event (hard Code)

// Sticky navigation
const initialCoords = section1.getBoundingClientRect();

window.addEventListener(`scroll`, function () {
  if (window.scrollY > initialCoords.top) nav.classList.add(`sticky`);
  else nav.classList.remove(`sticky`);
});

A Better Way: The Intersection Observer API (soft Code, 동적으로 전환)

// Sticky navigation: Intersection Observer API

const header = document.querySelector(`.header`);
const navHeight = nav.getBoundingClientRect().height;
console.log(navHeight);

const stickyNav = function (entries) {
  const [entry] = entries;
  console.log(entry);
  if (!entry.isIntersecting) nav.classList.add(`sticky`);
  else nav.classList.remove(`sticky`);
};

const headerObserver = new IntersectionObserver(stickyNav, {
  root: null,
  threshold: 0,
  rootMargin: `-${navHeight}px`, // navigation의 header margin의 맞춰 동적으로 전환
});
headerObserver.observe(header);

// Reveal sections
const allSections = document.querySelectorAll(`.section`);

const revealSection = function (entries, observer) {
  const [entry] = entries;
  console.log(entry);
  if (!entry.isIntersecting) return;

  entry.target.classList.remove(`section--hidden`);
  observer.unobserve(entry.target);
};
const sectionObserver = new IntersectionObserver(revealSection, {
  root: null,
  threshold: 0.15,
});
allSections.forEach(function (section) {
  sectionObserver.observe(section);
  section.classList.add(`section--hidden`);
});

// Lazy loading images
const imgTargets = document.querySelectorAll(`img[data-src]`);

const loadImg = function (entries, observer) {
  const [entry] = entries;
  console.log(entry);
  if (!entry.isIntersecting) return;

  // Replace src with data-src
  entry.target.src = entry.target.dataset.src;

  entry.target.addEventListener(`load`, function () {
    entry.target.classList.remove(`lazy-img`);
  });
  observer.unobserve(entry.target);
};

const imgObserver = new IntersectionObserver(loadImg, {
  root: null,
  threshold: 0,
  rootMargin: `-200px`,
});

imgTargets.forEach((img) => imgObserver.observe(img));


// EXAMPLE
// const obsCallback = function (entries, observer) {
//   entries.forEach((entry) => {
//     console.log(entry);
//   });
// };

// const obsOptions = {
//   root: null,
//   threshold: [0, 0.2],
// };

// const observer = new IntersectionObserver(obsCallback, obsOptions);
// observer.observe(section1);

Building a Slider Component: Part 1,2

// Slider Component : Part 1 , 2 
const slider = function () {
  const slides = document.querySelectorAll(`.slide`);
  const btnLeft = document.querySelector(`.slider__btn--left`);
  const btnRight = document.querySelector(`.slider__btn--right`);
  const dotContainer = document.querySelector(`.dots`);

  const maxSlide = slides.length;
  let curSlide = 0;

  // Functions
  const createDots = function () {
    slides.forEach(function (_, i) {
      dotContainer.insertAdjacentHTML(
        `beforeend`,
        `<button class="dots__dot" data-slide="${i}"></button>`
      );
    });
  };

  const activateDot = function (slide) {
    document
      .querySelectorAll(`.dots__dot`)
      .forEach((dot) => dot.classList.remove(`dots__dot--active`));

    document
      .querySelector(`.dots__dot[data-slide="${slide}"]`)
      .classList.add(`dots__dot--active`);
  };

  const goToSlide = function (slide) {
    slides.forEach(
      (s, i) => (s.style.transform = `translateX(${100 * (i - slide)}%)`)
    );
  };

  // Next slide
  const nextSlide = function () {
    if (curSlide === maxSlide - 1) {
      curSlide = 0;
    } else {
      curSlide++;
    }
    goToSlide(curSlide);
    activateDot(curSlide);
  };

  const prevSlide = function () {
    if (curSlide === 0) {
      curSlide = maxSlide - 1;
    } else {
      curSlide--;
    }
    goToSlide(curSlide);
    activateDot(curSlide);
  };
  const init = function () {
    goToSlide(0);
    createDots();

    activateDot(0);
  };
  init();

  // Event Handler
  btnLeft.addEventListener(`click`, prevSlide);
  btnRight.addEventListener(`click`, nextSlide);

  document.addEventListener(`keydown`, function (e) {
    console.log(e);
    if (e.key === `ArrowLeft`) prevSlide();
    e.key === `ArrowRight` && nextSlide();
  });

  dotContainer.addEventListener(`click`, function (e) {
    if (e.target.classList.contains(`dots__dot`)) {
      const { slide } = e.target.dataset;
      goToSlide(slide);
      activateDot(slide);
    }
  });
};
slider();

Lifecycle DOM Events

document.addEventListener(`DOMContentLoaded`, function (e) {
  console.log(`HTML parsed and DOM tree built!`, e);
});

window.addEventListener(`load`, function (e) {
  console.log(`Page fully loaded`, e);
});

window.addEventListener(`beforeunload`, function (e) {
  e.preventDefault();
  console.log(e);
  e.returnValue = `message`;
});

Efficient Script Loading: defer and async

END OF BODY

  • 스크립트는 HTML이 완전히 파싱된 후에 가져와서 실행됩니다.
  • 이전 브라우저를 지원해야 하는 경우 사용


    ⛔ 물론, 다른 스크립트에 대해 다른 전략을 사용할 수 있습니다. 보통 완전한 웹 애플리케이션에는 단 하나의 스크립트만 있는 것이 아니라 여러 개의 스크립트가 포함됩니다.

ASYNK IN HEAD

  • 스크립트는 비동기적으로 가져와서 즉시 실행됩니다.
  • 일반적으로 DOMContentLoaded 이벤트는 모든 스크립트의 실행을 기다리지만, async 스크립트는 제외됩니다. 따라서 DOMContentLoaded는 async 스크립트의 실행을 기다리지 않습니다.
  • 스크립트의 실행 순서는 보장되지 않습니다
  • 스크립트의 실행 순서가 중요하지 않은 경우에는 (예: Google Analytics와 같은) 제3자 스크립트에 사용합니다.

DEFER IN HEAD

  • 스크립트는 비동기적으로 가져오며, HTML이 완전히 파싱된 후에 실행됩니다.
  • DOMContentLoaded 이벤트는 defer 속성이 있는 스크립트가 실행된 후에 발생합니다.
  • 스크립트는 일반적으로 순서대로 실행됩니다.
  • 이것은 전반적으로 가장 좋은 해결책입니다! 이것은 자체 스크립트와 같이 스크립트의 순서가 중요한 경우 (예: 라이브러리 포함)에 사용됩니다.

0개의 댓글