[JavaScript] 22. Following Highlight

💡 메뉴바에서 내가 선택한 메뉴를 표시할 때
`hover`나 `classList.add` & `classList.remove`를 통해 해당 요소 자체에 CSS 값을 적용하는 방식이 아닌,
`getBoundingClientRect()`메서드를 통해 해당 요소의 위치와 크기를 구한 후, 따로 만들어놓은 배경이 이를 따라다니도록 하는 방식이다.

로직

  1. 미리 HTML 내에 배경 역할의 <span>태그 생성 (createElement, append)
  2. 모든 <a>태그에 마우스 이벤트로 함수 호출하도록 설정 (mouseenter)
  3. 이벤트가 발생한 <a>태그의 뷰포트 기준 위치를 구함 (getBoundingClientRect())
  4. 스크롤로 인한 위치 변화값을 자동 계산하도록 수정 (window.scrollX, window.scrollY)
  5. 해당 위치값을 그대로 <span>태그에 적용하여 해당 <a>태그의 배경 역할을 하도록 함

코딩 과정

1. 배경 역할을 하는 <span>태그 생성

const highlight = document.createElement("span");
//배경 역할을 할 수 있도록 CSS가 적용돼있는 class를 추가
highlight.classList.add("highlight");
//이벤트가 발생하기전에 우선 body안에 넣어둠
document.body.append(highlight);

createElement()

const highlight = document.createElement("span");

HTML 문서에서 document.createElement() 메서드는 지정한 tagName의 HTML 요소를 생성한다.

💡 이는 단순히 요소를 생성했을 뿐, HTML document 내의 무엇인가에 append 하기전에는 구현되지 않는다.

append()

document.body.append(highlight);

Element.append() 메서드는 부모 요소에 자식 요소를 추가한다.

이때 문자열 또한 추가 가능하며 여러 개의 자식 요소를 한번에 추가할 수도 있다.

💡 기본 width값과 height값을 설정 안해줬으므로 마우스 이벤트가 일어나기전에는 화면에 표시 ❌

2. 모든 <a>태그에 이벤트리스너 설정

const triggers = document.querySelectorAll("a");
triggers.forEach((a) => a.addEventListener("mouseenter", highlightLink));

mouseenter와 mouseover 비교

둘 다 공통적으로 해당 요소에 마우스가 올라가는 것을 감지한다.

둘의 차이점은

mouseenter : 자식 요소의 영역에 들어가면 감지 x

⇒ 해당 요소에서 자식 요소의 영역으로 마우스가 움직여도 따로 이벤트 발생하지 않음

mouseover : 자식 요소의 영역까지 감지 o

⇒ 마우스가 해당 요소와 자식 요소의 영역 경계를 넘나들때마다 이벤트 발생함

3. 이벤트가 발생한 <a>태그의 뷰포트 기준 위치 구하기

function highlightLink() {
  const linkCoords = this.getBoundingClientRect();

  //구한 뷰포트 기준 위치값을 'highlight' span에 적용
  highlight.style.width = `${linkCoords.width}px`;
  highlight.style.height = `${linkCoords.height}px`;
  highlight.style.transform = `translate(${linkCoords.left}px, ${linkCoords.top}px)`;
}

getBoundingClientRect()

const linkCoords = this.getBoundingClientRect();

DOMRect를 뷰포트 기준으로 반환해주는 메서드이다.

❓ DOMRect 란?

요소의 각종 좌표값이 들어있는 객체이며
요소를 감싸는 사각형 형태로 사이즈와 위치 정보를 나타낸다.
이 사각형은 요소의 contexts, padding, border를 포함한다.

4. 스크롤로 인한 위치 변화 계산

getBoundingClientRect()를 통해 구한 값은 뷰포트를 기준으로 하는 요소의 상대좌표

⇒ 화면을 스크롤 할 경우 요소의 위치값이 달라진다.

'highlight' span의 경우 뷰포트 기준으로 이동하는게 아니라 스크롤을 포함한 맨 위, 맨 왼쪽 부분을 기준으로 이동

⇒ 스크롤 한 상태에서 이동할 경우 스크롤 한 만큼 요소와 위치가 어긋나게 된다.

따라서!

뷰포트 기준 위치값에 window.scrollX, window.scrollY로 스크롤한 X, Y 값을 가져와서 더해주어야 정확한 요소의 위치로 'highlight' span이 이동한다.

function highlightLink() {
  const linkCoords = this.getBoundingClientRect();
  const coords = {
    width: linkCoords.width,
    height: linkCoords.height,
    //뷰포트 값에 스크롤된 길이를 더해준다.
    top: linkCoords.top + window.scrollY,
    left: linkCoords.left + window.scrollX,
  };

  highlight.style.width = `${coords.width}px`;
  highlight.style.height = `${coords.height}px`;
  highlight.style.transform = `translate(${coords.left}px, ${coords.top}px)`;
}

최종 완성 코드

const triggers = document.querySelectorAll("a");
const highlight = document.createElement("span");
highlight.classList.add("highlight");
document.body.append(highlight);

function highlightLink() {
  const linkCoords = this.getBoundingClientRect();
  const coords = {
    width: linkCoords.width,
    height: linkCoords.height,
    top: linkCoords.top + window.scrollY,
    left: linkCoords.left + window.scrollX,
  };
  highlight.style.width = `${coords.width}px`;
  highlight.style.height = `${coords.height}px`;
  highlight.style.transform = `translate(${coords.left}px, ${coords.top}px)`;
}

triggers.forEach((a) => a.addEventListener("mouseenter", highlightLink));

좋은 웹페이지 즐겨찾기