You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

国家代码选择器下拉框:点击外部隐藏功能反向bug修复求助

问题修复:点击容器外部隐藏下拉菜单

问题根源

你的代码存在三个核心问题:

  1. 监听document点击的逻辑完全写反:当前代码判断点击容器内部时关闭菜单,和需求的“点击外部关闭”正好相反。
  2. 同时绑定了两个document的click事件,其中handleDocumentClick是空函数,造成逻辑冗余。
  3. 点击触发下拉的按钮时,事件会冒泡到document,导致菜单刚打开就被关闭。

修复方案

  1. 删除无用的handleDocumentClick函数及其事件监听
  2. 修正document点击事件的判断逻辑:当点击目标不在容器内,也不在触发按钮内时,关闭菜单
  3. 在按钮点击事件中阻止冒泡,避免误触发关闭逻辑

修复后的完整代码

JavaScript

// Cache the elements
const xbutton = document.querySelector('.country-code-selector .telcode');
const container = document.querySelector('.country-code-selector .container');
const input = document.querySelector('.country-code-selector input');
const list = document.querySelector('.country-code-selector .list');

// Add event listeners to the button, input, and list
// We use a process called "event delegation" on the list
// to catch events from its children as they "bubble up" the DOM
xbutton.addEventListener('click', handleButton);
input.addEventListener('input', handleInput);
list.addEventListener('click', handleListClick);
document.addEventListener("click", (event) => {
  // 判断点击目标不在容器内,也不在触发按钮内,且菜单处于显示状态时关闭
  if (!container.contains(event.target) && !xbutton.contains(event.target)) {
    if (container.classList.contains('show')) {
      container.classList.remove('show');
    }
  }
});

// All of the data held as objects within an array
const data = [
  { name: 'Afganistan', code: '69', flag: 'afg' },
  { name: 'Barbados', code: '1-246', flag: 'brb' },
  { name: 'Bolivia', code: '591', flag: 'bol' },
  { name: 'Cuba', code: '53', flag: 'cub' },
  { name: 'Fiji', code: '679', flag: 'fji' },
];

// Filters the data based on the characters
// at the start of the provided name
function filterData(data, value) {
  return data.filter(obj => {
    return (
      obj.name.toLowerCase().startsWith(value.toLowerCase())
      || obj.code.toLowerCase().startsWith(value.toLowerCase())
    );
  });
}

// Create a series of list items based on the
// data passed to it
function createListHtml(data) {
  return data.map(obj => {
    const { name, code, flag } = obj;
    return `
      <li
        class="item"
        data-name="${name}"
        data-code="${code}"
        data-flag="${flag}"
      >
        <div class="flag-icon flag-icon-${flag}"></div>
        <div class="name">${name} (+${code})</div>
      </li>
    `;
  }).join('');
}

// Toggle the container on/off
function handleButton(e) {
  // 阻止事件冒泡,避免触发document的click事件
  e.stopPropagation();
  container.classList.toggle('show');
}

// No data available list item
function createNoDataHtml() {
  return '<li class="nodata">No data available</li>';
}

// When the input is changed filter the data
// according to the current value, and then
// create some list items using that filtered data
function handleInput(e) {
  const { value } = e.target;
  if (value) {
    const filtered = filterData(data, value);
    if (filtered.length) {
      list.innerHTML = createListHtml(filtered);
    } else {
      list.innerHTML = createNoDataHtml();
    }
  } else {
    list.innerHTML = createListHtml(data);
  }
}

// Create some button HTML
function createButtonHtml(code, flag) {
  return `
    <div class="flag-icon flag-icon-${flag}"></div>
    <div class="code">+${code}</div>
  `;
}

// Updates the selected list by removing the `selected`
// class from all items, and then adding one to the clicked
// item
function updateSelected(list, item) {
  const items = list.querySelectorAll('.item');
  items.forEach(item => item.classList.remove('selected'));
  item.classList.add('selected');
}

// When an item is clicked, grab the relevant data
// attributes, create the new button HTML, and then 
// close the container
function handleListClick(e) {
  const item = e.target.closest('li') || e.target;
  if (item.classList.contains('item')) {
    const { code, flag } = item.dataset;
    xbutton.innerHTML = createButtonHtml(code, flag);
    updateSelected(list, item);
    container.classList.remove('show');
  }
}

list.innerHTML = createListHtml(data);

CSS

.country-code-selector .telcode {
     margin-bottom: 1em;
     display: flex;
}
.country-code-selector .telcode div.code, .item div.name {
     margin-left: 0.25em;
}
.country-code-selector .container {
     display: none;
     width: 350px;
     border: 1px solid black;
}
.country-code-selector .show {
     display: block;
}
.country-code-selector .list {
     height: 100px;
     list-style: none;
     margin: 1em 0 0 0;
     padding: 0;
     overflow-y: scroll;
     border: 1px solid darkgray;
}
.country-code-selector .item {
     display: flex;
     padding: 0.25em;
     border: 1px solid lightgray;
}
.country-code-selector .item:hover {
     cursor: pointer;
}
.country-code-selector .item:hover, .item.selected {
     background-color: lightyellow;
}

HTML

<link href="https://amitdutta.co.in/flag/css/flag-icon.css" rel="stylesheet"/>

<div class="country-code-selector">
  <button type="button" class="telcode">Tel code</button>
  <section class="container">
    <input type="text" placeholder="Search for country" />
    <ul class="list"></ul>
  </section>
</div>

关键修复点说明

  • 修正document点击逻辑:使用!container.contains(event.target) && !xbutton.contains(event.target)判断点击位置是否在容器和按钮之外,符合需求。
  • 阻止按钮事件冒泡:在handleButton中调用e.stopPropagation(),避免点击按钮时触发document的click事件导致菜单刚打开就关闭。
  • 删除冗余代码:移除了空的handleDocumentClick函数及其事件监听,减少逻辑冲突。

内容的提问来源于stack exchange,提问作者Gupa Dutta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 00:01:07