본문 바로가기
Developer/TIL

TIL : 250401화 (:active, 즐겨찾기 기능 css 변경 진행중-event.target, menuRefs, white-space, 프로그래머스 문자열 내림차순으로 배치하기)

by 청량리 물냉면 2025. 4. 1.
반응형

1. `:active`

`:active` CSS 의사 클래스는 사용자가 활성화한 요소(버튼 등)를 나타낸다.

예를 들어 마우스를 사용하는 경우, "활성"이란 보통 마우스 버튼을 누르는 순간부터 떼는 시점 까지를 의미한다.

/* 활성화된 모든 <a> 태그를 선택 */
a:active {
  color: red;
}

주로 아래와 같은 요소에서 사용된다.

  • <a>: 링크
  • <button>: 버튼
  • <label>: 폼 요소와 연결된 라벨
  • <input>, <textarea>: 입력 필드

즉, 사용자가 클릭하거나 포커스를 줄 수 있는 요소들에 자주 사용된다.

 

🚧 주의할 점

`:active`는 다른 링크 관련 의사 클래스에 덮어씌워질 수 있다.

링크에 스타일을 적용할 때는 `:link`, `:visited`, `:hover`, `:active` 순서를 지켜야 한다.(LVHA 순서)

a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: green; }
a:active { color: red; } /* 이건 맨 마지막에 있어야 작동함! */

 

만약 :active가 앞에 있고, :hover가 뒤에 있다면?

👉 사용자가 클릭하는 순간 :active가 적용돼도, 바로 :hover가 덮어버린다.

(출처: Mdn `:active` 문서)

 

 

 

웹사이트 즐겨찾기 기능 로직 수정

선임분의 요청에 따라 사이드바의 메뉴에 hover 시, `⋮` 버튼 생성 -> 버튼 클릭 시 옵션 리스트가 나타나고, 그 옵션 리스트 중 즐겨찾기 기능이 포함되도록 수정

 

1. 메뉴 외의 다른 곳을 클릭시, 메뉴가 닫히게 하는 기능 추가 (eventListener 이용)

 

1) sidebar 전체에 ref="menuRefs"를 거는 것만으로는 메뉴 각각의 옵션 리스트를 불러올 수 없는 에러 발생.

해결: ref="(el) => menuRefs[index] = el" 사용!

 

🎯 왜 ref="menuRefs" 대신 ref="(el) => menuRefs[index] = el"를 사용해야 할까?

ref="menuRefs"

  • Vue는 배열을 자동으로 채우지 않음 → `menuRefs.value`는 `{}`(빈 객체)로 남아 있음.
  • ` menuRefs.value.some(menu => menu?.contains(event.target)) ` 코드 진행 시, `menuRefs.value`가 비어 있어서 `contains()`를 호출할 수 없음.

ref="(el) => menuRefs[index] = el"

  • `el`(각 <li> 요소)을 직접 `menuRefs[index]`에 할당 → 배열이 정상적으로 채워짐.
  • `menuRefs.value`가 `[{ li1 }, { li2 }, { li3 }, ...]` 이런 형태가 됨.
  • 이제 `menuRefs.value.some(menu => menu?.contains(event.target))`가 정상적으로 동작함

결론

✔ `ref="menuRefs"`는 `v-for`에서 자동으로 배열을 채우지 않음 → {}(빈 객체).
✔ `ref="(el) => menuRefs[index] = el"`로 직접 할당해야 함.
✔ 이 방법을 쓰면 `menuRefs.value`가 `<li>` 요소들을 올바르게 참조할 수 있음.

 

 

 

위와 같이 코드를 짜고 메뉴 하나를 클릭한 뒤,`menuRefs.value`를 출력해 보니 아래와 같이 값이 출력되었다.

  1. 0: li.menu
  2. 1: li.menu.active (활성화(클릭)된 메뉴)
  3. 2: li.menu
  4. 3: li.menu
  5. 4: li.menu

 

 

2) event.target에 대해 공부하기

    const closeMenuOnClickOutside = (event) => {
      console.log("Clicked outside the menu", event.target);
      console.log("Menu refs", menuRefs.value);
      if (!menuRefs.value.some((menu) => menu === event.target)) {
        console.log("Clicked outside the menu");
        openedMenuIndex.value = null;
      }
    };

 

`event.target`은 사용자가 클릭한 실제 HTML 요소 (DOM 요소) 를 반환한다.
즉, 어디를 클릭했는지에 대한 정보를 담고 있다.

 

📌 예제: 클릭 위치에 따른 `event.target` 값

<ul>
  <li class="menu-item">Menu 1</li>
  <li class="menu-item">Menu 2</li>
  <li class="menu-item">Menu 3</li>
</ul>
<button class="hover-menu">⋮</button>
document.addEventListener("click", (event) => {
  console.log(event.target);
});

👀 클릭 위치에 따른 `event.target` 값

클릭한 요소 ` event.target` 값
"Menu 1" ` <li class="menu-item">Menu 1</li>`
"⋮" 버튼 ` <button class="hover-menu">⋮</button>`
빈 공간 (body) ` <body>...</body>`

 

 

🔥 내가 작성한 코드에서 `event.target` 값

const closeMenuOnClickOutside = (event) => {
  console.log("Clicked outside the menu", event.target);
};
  • 🔹 메뉴 아이템을 클릭하면 → `event.target`은 `<li>` 요소.
  • 🔹 점 세 개 버튼을 클릭하면 → `event.target`은 `<button class="hover-menu">⋮</button>`.
  • 🔹 빈 공간을 클릭하면 → `event.target`은 `<body>` 또는 `<div>` 같은 컨테이너.

👉 즉, event.target은 사용자가 클릭한 요소를 그대로 반환!

 

 

3) `!menuRefs.value.some(menu => menu?.contains(event.target))`로는 원하는 동작(외부 클릭 시 메뉴창 닫힘)을 하는데, `!menuRefs.value.some(menu => menu === event.target)`로는 원하는 동작을 하지 않는 현상 발생

✅ `menuRefs.value.some(menu => menu === event.target)`는 왜 안 되나?

if (!menuRefs.value.some((menu) => menu === event.target)) { 
  openedMenuIndex.value = null;
}

📌 여기서 문제가 발생하는 이유

  • `menuRefs.value`에는 `<li>` 요소들이 저장됨.
  • `event.target`은 사용자가 클릭한 정확한 요소인데, `menuRefs.value`와 완전히 일치하는 요소가 아닐 수도 있음.
    • 예를 들어, `<li>` 안의 `<button>`을 클릭하면 `event.target`은` <button>`이지만, `menuRefs.value`에는 `<li>`만 있음.
    • 그러면 `menuRefs.value.some(menu => menu === event.target)`가 false가 되고, 메뉴가 닫혀버림.

 

🎯 해결 방법 → `contains()` 사용!

const closeMenuOnClickOutside = (event) => {
  if (!menuRefs.value.some(menu => menu?.contains(event.target))) {
    openedMenuIndex.value = null;
  }
};

contains(event.target)가 하는 일

  • `menu.contains(event.target)`은 menu 안에 `event.target`이 있는지 확인함.
  • 즉, 메뉴 내부의 버튼을 클릭해도 true를 반환해서 메뉴가 안 닫힘.

 

🔥 결론

✔ `event.target`은 사용자가 클릭한 실제 HTML 요소.
✔ `menuRefs.value.some(menu => menu === event.target)` → 내부 버튼 클릭 시 실패함.
✔` menu.contains(event.target)`를 사용해야 버튼 클릭 시에도 닫히지 않음.

 

 

 

위처럼 코드를 짜니 여러 개의 메뉴창이 동시에 열릴 수가 있었다.

하나의 창이 켜지면 나머지 창은 닫히게 만들고 싶어서, 아래와 같이 메뉴창이 하나만 열릴 수 있도록 코드를 수정했다. 

   const closeMenuOnClickOutside = (event) => {
      if (!menuRefs.value[openedMenuIndex.value]?.contains(event.target)) {
        openedMenuIndex.value = null;
      }
    };

 

 

 

웹 사이트 스타일링 에러 변경 요청 들어와서 수정함

`white-space: nowrap`을 이용해 텍스트 overflow 시 줄바꿈되며 스타일링이 깨지던 문제를 수정했다.

https://developer.mozilla.org/ko/docs/Web/CSS/white-space

 

white-space - CSS: Cascading Style Sheets | MDN

CSS white-space 속성은 요소가 공백 문자를 처리하는 법을 지정합니다.

developer.mozilla.org

 

 

 

👨‍💻 [프로그래머스] 문자열 내림차순으로 배치하기

function solution(s) {
    return s.split('').sort().reverse().join('');
}

reverse(): 배열을 뒤집는 함수

 

 

 

👨‍💻 [프로그래머스] 핸드폰 번호 가리기

function solution(phone_number) {
    return '*'.repeat(phone_number.length - 4) + phone_number.slice(-4);
}

repeat(): 특정 문자를 반복하는 함수

반응형