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

修复国际区号选择器:点击容器外部隐藏下拉的反向Bug

问题分析与修复方案

核心问题

  1. 冗余且逻辑错误的事件监听:顶部提前添加的document.addEventListener不仅在container变量声明前就调用(会触发报错),而且判断逻辑完全反向——点击容器内部时关闭下拉,和需求完全相反。
  2. 未实现的外部点击关闭逻辑:handleDocumentClick函数为空,没有处理外部点击的判断逻辑。
  3. 事件冒泡冲突:点击切换按钮时,事件会冒泡到document,导致下拉刚打开就被关闭。

修复步骤

  • 移除顶部那段逻辑错误且提前执行的document.addEventListener代码块。
  • 完善handleDocumentClick函数,判断点击目标是否在**整个区号选择器容器(.country-code-selector)**之外,若是则关闭下拉。
  • 在handleButton函数中添加event.stopPropagation(),阻止按钮点击事件冒泡到document,避免下拉开关冲突。

修改后的完整代码

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');
const selectorWrapper = document.querySelector('.country-code-selector'); // 缓存整个选择器容器

// Add event listeners to the button, input, and list
xbutton.addEventListener('click', handleButton);
input.addEventListener('input', handleInput);
list.addEventListener('click', handleListClick);
document.addEventListener('click', handleDocumentClick);

// 处理外部点击关闭下拉
function handleDocumentClick(e) {
  // 判断点击目标是否不在选择器容器内
  if (!selectorWrapper.contains(e.target)) {
    container.classList.remove('show');
  }
}

// 国家区号数据
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' },
];

// 根据输入值过滤数据
function filterData(data, value) {
  return data.filter(obj => {
    return (
      obj.name.toLowerCase().startsWith(value.toLowerCase())
      || obj.code.toLowerCase().startsWith(value.toLowerCase())
    );
  });
}

// 生成列表项HTML
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('');
}

// 切换下拉菜单显示/隐藏
function handleButton(e) {
  e.stopPropagation(); // 阻止事件冒泡,避免触发document的click事件
  container.classList.toggle('show');
}

// 生成无数据提示项
function createNoDataHtml() {
  return '<li class="nodata">No data available</li>';
}

// 输入框变化时过滤并更新列表
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);
  }
}

// 生成按钮显示的HTML
function createButtonHtml(code, flag) {
  return `
    <div class="flag-icon flag-icon-${flag}"></div>
    <div class="code">+${code}</div>
  `;
}

// 更新选中的列表项样式
function updateSelected(list, item) {
  const items = list.querySelectorAll('.item');
  items.forEach(item => item.classList.remove('selected'));
  item.classList.add('selected');
}

// 点击列表项时更新按钮并关闭下拉
function handleListClick(e) {
  e.stopPropagation(); // 阻止事件冒泡,避免触发document的click
  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;
  border: 1px solid grey;
}
.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; /* 修复原CSS拼写错误:soldi → solid */
}
.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>

额外修复说明

  • 修复了CSS中的拼写错误:border: 1px soldi darkgray; → border: 1px solid darkgray;
  • 在handleListClick中添加了e.stopPropagation(),避免列表点击事件冒泡到document,逻辑更严谨

内容的提问来源于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.06 05:51:29