πŸ”₯ Develop/UI

[UI λ§Œλ“€κΈ°][Vanilla JS] μžλ™μ™„μ„± 검색창 κ΅¬ν˜„ν•˜κΈ°

μœ€λ„κΈ° 2024. 8. 2. 07:24
λ°˜μ‘ν˜•

 

μ›ν‹°λ“œ ν”„λ¦¬μ˜¨λ³΄λ”© FE μ±Œλ¦°μ§€ 사전 λ―Έμ…˜μœΌλ‘œ μžλ™μ™„μ„± 검색창을 κ΅¬ν˜„ν•΄ λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 이λ₯Ό μœ„ν•΄, ν‰μ†Œ μ‚¬μš©ν•˜λ˜ μ„œλΉ„μŠ€λ“€μ˜ 검색 κΈ°λŠ₯을 μœ μ‹¬νžˆ κ΄€μ°°ν•˜λ©΄μ„œ, μžλ™μ™„μ„±μ΄ μ–Όλ§ˆλ‚˜ 검색 κ²½ν—˜μ„ ν–₯μƒμ‹œν‚€λŠ”μ§€ μƒˆμ‚Ό κΉ¨λ‹«κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

μžλ™μ™„μ„± κ²€μƒ‰μ°½μ˜ μ£Όμš” κΈ°λŠ₯듀을 λ‹€μŒκ³Ό 같이 κ΅¬ν˜„ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

import dummy from "./data.js";

const searchInput = document.querySelector(".search-input");
const suggestionContainer = document.querySelector(".suggestionContainer");

let selectedIndex = -1;
let prevSelectedIndex = -1;

// 검색창에 μž…λ ₯μ‹œ μ œμ•ˆ λͺ©λ‘μ„ 필터링
searchInput.addEventListener("input", function () {
  const inputValue = this.value.toLowerCase();
  const filteredSuggestions = dummy.filter(
    (suggestion) =>
      suggestion.description.toLowerCase().includes(inputValue) || suggestion.key.toLowerCase().includes(inputValue)
  );

  renderSuggestions(filteredSuggestions);
});

// μ œμ•ˆ λͺ©λ‘μ„ λ Œλ”λ§
function renderSuggestions(filteredSuggestions) {
  if (filteredSuggestions.length === 0) {
    suggestionContainer.classList.remove("show");
    return;
  }

  suggestionContainer.innerHTML = "";
  let currentType = null;

  for (const suggestion of filteredSuggestions) {
    if (currentType !== suggestion.type) {
      const typeLi = document.createElement("li");
      typeLi.innerHTML = suggestion.type;
      typeLi.classList.add("suggestion-type");
      suggestionContainer.appendChild(typeLi);
      currentType = suggestion.type;
    }

    const li = document.createElement("li");
    li.innerHTML = `${suggestion.description}`;
    li.classList.add("suggestion-item");

    li.addEventListener("click", () => {
      searchInput.value = li.innerHTML;
      suggestionContainer.classList.remove("show");
    });
    suggestionContainer.appendChild(li);
  }

  suggestionContainer.classList.add("show");
  selectedIndex = -1;
}

// ν™”μ‚΄ν‘œ ν‚€λ₯Ό μ‚¬μš©ν•˜μ—¬ μ œμ•ˆ λͺ©λ‘μ„ νƒμƒ‰ν•˜κ³  Enter ν‚€λ₯Ό μ‚¬μš©ν•˜μ—¬ 선택
searchInput.addEventListener("keydown", function (e) {
  const items = suggestionContainer.querySelectorAll(":scope > .suggestion-item");

  if (e.key === "ArrowDown") {
    selectedIndex = selectedIndex < items.length - 1 ? selectedIndex + 1 : 0;
    updateSelection();
  } else if (e.key === "ArrowUp") {
    selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : items.length - 1;
    updateSelection();
  } else if (e.key === "Enter" && selectedIndex > -1) {
    searchInput.value = items[selectedIndex].innerHTML;
    suggestionContainer.classList.remove("show");
    selectedIndex = -1;
    prevSelectedIndex = -1;
  }
});

function updateSelection() {
  const items = suggestionContainer.querySelectorAll(":scope > .suggestion-item");

  if (prevSelectedIndex === selectedIndex || selectedIndex === -1) return;

  // 이전에 μ„ νƒλœ ν•­λͺ©μ˜ 클래슀 제거
  if (prevSelectedIndex !== -1) {
    items[prevSelectedIndex].classList.remove("selected");
  }

  // μƒˆλ‘œ μ„ νƒλœ ν•­λͺ©μ— 클래슀 μΆ”κ°€
  items[selectedIndex].classList.add("selected");
  items[selectedIndex].scrollIntoView({ behavior: "smooth", block: "nearest" });

  prevSelectedIndex = selectedIndex;
}

// 검색창 μ™ΈλΆ€ ν΄λ¦­μ‹œ μΆ”μ²œ λͺ©λ‘ 숨기기
document.addEventListener("click", function (e) {
  if (!searchInput.contains(e.target) && !suggestionContainer.contains(e.target)) {
    suggestionContainer.classList.remove("show");
  }
});

크게 총 4κ°€μ§€μ˜ κΈ°λŠ₯으둜 κ΅¬μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€:

1. 검색 μž…λ ₯ 처리

  • μ‚¬μš©μžκ°€ μž…λ ₯ ν•„λ“œμ— 타이핑할 λ•Œλ§ˆλ‹€ μ΄λ²€νŠΈκ°€ λ°œμƒν•©λ‹ˆλ‹€.
  • dummy λ°°μ—΄μ˜ 각 ν•­λͺ©μ— λŒ€ν•΄ filter ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ (λŒ€μ†Œλ¬Έμž ꡬ뢄 없이) κ²€μ‚¬ν•©λ‹ˆλ‹€.
  • ν•„ν„°λ§λœ κ²°κ³Όλ₯Ό renderSuggestions ν•¨μˆ˜μ— μ „λ‹¬ν•˜μ—¬ 화면에 ν‘œμ‹œν•©λ‹ˆλ‹€.

2. μ œμ•ˆ λͺ©λ‘ λ Œλ”λ§

ν•„ν„°λ§λœ μ œμ•ˆ λͺ©λ‘μ„ 화면에 ν‘œμ‹œν•˜κ³ , μ œμ•ˆ ν•­λͺ©λ“€μ„ νƒ€μž…λ³„λ‘œ κ·Έλ£Ήν™”ν•˜μ—¬ ν‘œμ‹œν•©λ‹ˆλ‹€.

  • ν•„ν„°λ§λœ μ œμ•ˆμ„ μˆœνšŒν•˜λ©΄μ„œ:
    • μƒˆλ‘œμš΄ νƒ€μž…μ˜ μ œμ•ˆμ΄ λ‚˜νƒ€λ‚˜λ©΄ νƒ€μž… 헀더λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
    • 각 μ œμ•ˆμ— λŒ€ν•΄ 리슀트 μ•„μ΄ν…œμ„ μƒμ„±ν•©λ‹ˆλ‹€.
    • 각 μ•„μ΄ν…œμ— 클릭 이벀트 λ¦¬μŠ€λ„ˆλ₯Ό μΆ”κ°€ν•˜μ—¬, 클릭 μ‹œ ν•΄λ‹Ή μ œμ•ˆμ„ μž…λ ₯ ν•„λ“œμ— μ±„μš°κ³  μ œμ•ˆ λͺ©λ‘μ„ μˆ¨κΉλ‹ˆλ‹€.

3. ν‚€λ³΄λ“œ λ„€λΉ„κ²Œμ΄μ…˜

μž…λ ₯ ν•„λ“œμ— μž…λ ₯된 킀에 따라 λ‹€λ₯Έ κΈ°λŠ₯을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.

  • μœ„/μ•„λž˜ ν™”μ‚΄ν‘œ ν‚€λ‘œ μ œμ•ˆ λͺ©λ‘μ„ 탐색할 수 있게 ν•©λ‹ˆλ‹€.
  • Enter ν‚€λ‘œ μ„ νƒν•œ μ œμ•ˆμ„ μž…λ ₯창에 μ μš©ν•©λ‹ˆλ‹€.
  • μ„ νƒλœ ν•­λͺ©μ„ μ‹œκ°μ μœΌλ‘œ ν‘œμ‹œν•˜κ³  μŠ€ν¬λ‘€ν•©λ‹ˆλ‹€.

4. μ™ΈλΆ€ 클릭 처리

κ²€μƒ‰μ°½μ΄λ‚˜ μ œμ•ˆ λͺ©λ‘ μ™ΈλΆ€λ₯Ό ν΄λ¦­ν•˜λ©΄ μ œμ•ˆ λͺ©λ‘μ„ μˆ¨κΉλ‹ˆλ‹€.

 


 

See the Pen Auto-complete search box by ‍곽윀철[학생](μ†Œν”„νŠΈμ›¨μ–΄μœ΅ν•©λŒ€ν•™ 컴퓨터곡학뢀) (@tkuffnrk-the-typescripter) on CodePen.

 

 

μ‚¬μš©μžμ˜ μž…λ ₯에 μ¦‰κ°μ μœΌλ‘œ λ°˜μ‘ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“€λ©΄μ„œ μ„±λŠ₯ μ΅œμ ν™”μ˜ μ€‘μš”μ„±μ„ μ²΄κ°ν–ˆμŠ΅λ‹ˆλ‹€. μ•žμœΌλ‘œλŠ” λ””λ°”μš΄μ‹±μ΄λ‚˜ μ“°λ‘œν‹€λ§ 기법을 μ μš©ν•˜μ—¬ λ”μš± 효율적인 검색 κΈ°λŠ₯을 κ΅¬ν˜„ν•΄λ³΄κ³ μž ν•©λ‹ˆλ‹€.

λ°˜μ‘ν˜•