Get better at JavaScript with just JavaScript

6 minute read

Web Bos의 Get better at JavaScript with just JavaScript 강연 정리

Intersection Observer

한 요소가 화면 안으로 들어왔는지 확인할 때 사용한다.

  • 스크롤 시 요소에 애니메이션 넣기
  • 스크롤 시 비디오 재생
  • 이미지 지연로딩
  • 광고 노출수 세기
  • 스티키 헤더

등에 사용할 수 있다.

1. 옵션 설정

const options = {
  root: document.querySelector(".scrollingDiv"),
  rootMargin: "100px",
  // 화면에 없을 때, 반만, 완전히 들어왔을 때 알려줌
  threshold: [0, 0.5, 1.0]
};

2. 빈 Observer 생성

const observer = new IntersectionObserver(callback, options);

3. 콜백 전달하기

const callback = (entries, observer) => {
  entries.forEach(entry => {
    console.log(entry);
    // threshold가 1일 때(요소가 완전히 들어왔을 때)만 보이게 하기
    if (entry.isIntersecting && entry.intersectionRatio >= 1) {
      entry.target.classList.add("visible");
    } else {
      entry.target.classList.remove("visible");
    }
    observer.unobserve(entry.target);
  });
};

4. 관찰 시작

const boxes = document.querySelectorAll(".box");
boxes.forEach(box => observer.observe(box));

Resize Observer

요소의 크기 변경을 확인할 수 있다.

기본 예제

const callback = (entries, observer) => {
  entries[0].target.innerHTML = `
    <pre>
      ${JSON.stringify(entries[0].contentRect, null, " ")}
    </pre>
  `;
};

const observer = new ResizeObserver(callback);

const element = document.querySelector(".resize");
observer.observe(element);

요소 크기를 Viewport에 맞추는게 아니라 다른 요소가 얼마나 큰지, 어디에 있는 지 등, 내가 원하는 대로 지정하고 싶을 때 사용할 수 있다.

const callback = (entries, observer) => {
  const { width } = entries[0].contentRect;
  if (width > 400) {
    size = "large";
  } else if (width > 300) {
    size = "medium";
  } else {
    size = "small";
  }

  entries[0].target.classList.remove("small", "medium", "large");
  entires[0].target.classList.add(size);
};

const observer = new ResizeObserver(callback);
const element = document.querySelector(".videos__list");
observer.observe(element);

DOM Element Methods

1. .closest()

입력받은 선택자에 일치하는 가장 가까운 조상요소를 찾는다.

<div class="cards">
  <div class="card">
    <p>I'm a Card</p>
    <button>Delete</button>
  </div>
  <div class="card">
    <p>I'm a Card</p>
    <button>Delete</button>
  </div>
</div>

2개의 카드가 있는 div요소가 있고 각 카드는 삭제 버튼이 있다.

document.querySelectorAll(".card button").forEach(button => {
  button.addEventListener("click", e => {
    e.currentTarget.closest(".card").remove();
  });
});

closest 메서드로 버튼의 조상인 .card div를 찾아서 삭제한다.

const p = document.querySelector("p");

document.addEventListener("click", e => {
  const isOutsize = !e.target.closest(".modal-inner");
  p.textContent = `You Clicked ${isOutsize ? "Outsize" : "Insize"}!`;
});

요소 밖을 클릭했는지 안을 클릭했는지 확인할 때도 유용하다.

2. .matches()

한 요소가 선택자와 일치하는지 확인한다.

button.addEventListener("click", e => {
  if (e.currentTarget.matches(".available[data-price]")) {
    // .. 처리
  }
});

버튼이 data-price 라는 애트리뷰트를 갖고 있는지 확인한다.

// list는 JS로 아이템을 추가/삭제하는 빈 요소임
list.addEventListener("click", e => {
  if (e.target.matches("button")) {
    deleteItem(parseFloat(e.target.value));
  }
});

Event Delegation에도 사용한다.

모든 리스트 내의 아이템에 이벤트 리스너를 붙일 필요가 없다.

3. .contains()

const modal = document.querySelector(".modal");
modal.contains(button); // true

modal.querySelector("button"); // <button></button>
// contains 메서드와 같음
!!modal.querySelector("button"); // true

Bling.js

바닐라 JS앱을 만들 때 있으면 유용한 11줄짜리 라이브러리

window.$ = document.querySelector.bind(document);
window.$$ = document.querySelectorAll.bind(document);

Node.prototype.on = window.on = function(name, fn) {
  this.addEventListener(name, fn);
};

NodeList.prototype.__proto__ = Array.prototype;

NodeList.prototype.on = function(name, fn) {
  this.forEach(function(elem, i) {
    elem.on(name, fn);
  });
};

입력하기 힘든 document.querySelector를 $ 한 문자로 줄임

NodeList에서 Array의 메서드를 사용할 수 있게 함


데이터 다루기

1. Array.from()

Array.from({ length: 3 });
// => [undefined, undefined, undefined]

Array.from({ length: 3 }, () => `💰`);
// => ["💰", "💰", "💰"]

iterable을 array로 변환한다.

Array.from({ length: 3 }, (_, i) => `day-${i + 1}`);
// => [ "day-1", "day-2", "day-3" ]

하드코딩을 피하고 원하는 데이터가 담긴 리스트 생성할 때 유용하다.

Array.from({ length: 12 }, (x, index) =>
  new Date(0, index + 1, 0).toLocaleDateString("en-US", { month: "short" })
);
// => [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

Array.from({ length: 12 }, (x, index) =>
  new Date(0, index + 1, 0).toLocaleDateString("ko-KR", { month: "short" })
);

// => [ "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"]

달 이름 생성할 때 사용할 수 있다.


중복 요소 제거하기

const values = [1, 2, 3, 1, 2]

Array.from(new Set(values)) // [1, 2, 3]

[...new Set(values)] // [1, 2, 3]

…spread and …rest

// 1:1 복사
const copyOfHuman = { ...human };

// 복사 후 덮어쓰기
const copyNewId = {
  ...human,
  id: "das-vader"
};

// id를 뺀 모든 것
const { id, ...withoutId } = human;

객체 다루기

// 기존 배열를 변경하지 않고 배열 뒤집기
const reversed = [...names].reverse();

에러 핸들링

process.on("unhandledRejection", error => {
  console.log("unhandledRejection", error);
});

근데 위와 같이 에러를 무시할 바엔 unhandledRejection 발생 시 프로세스를 종료하고 다시 시작하는게 낫다고 노드 베스트 프랙티스에 나와있다.


Web 음성인식

SpeechRecognition API로 음성인식이 가능하다.


Shape Detection

얼굴, 바코드, 텍스트를 인식할 수 있다.


출처

https://youtu.be/pws4qzGn5ak