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

如何在失去焦点时关闭Tailwind CSS+jQuery下拉菜单

问题描述

使用Tailwind CSS和jQuery开发下拉菜单,需要实现「失去焦点时自动关闭下拉菜单」的功能,但遇到以下问题:

  • 给包含下拉菜单的外层div绑定了focusout事件,打开菜单时将焦点设置到搜索框,此时菜单能正常保持打开;
  • 点击下拉列表中的用户项(<a>标签)时,菜单会意外关闭——预期行为是选中项(显示复选标记)后菜单仍保持打开;
  • 原本认为子元素<a>获取焦点时,父div会依然保持焦点,但实际并非如此。

核心需求:下拉菜单在下拉区域内进行输入、点击操作时保持打开,当点击、输入或切换到区域外时自动关闭。

解决思路与代码修改

问题根源

focusout事件的触发逻辑是:当元素或其任意子元素失去焦点时就会触发。点击<a>标签时,焦点从搜索框转移到<a>的瞬间,会先触发父div的focusout(因为搜索框先失焦),此时<a>还未获取焦点,导致代码错误执行关闭操作。

具体修改步骤

  1. 给下拉容器添加可聚焦属性:给assigned_users_dropdown添加tabindex="-1",让容器本身可以成为焦点元素,维持子元素切换焦点时的容器焦点状态。
  2. 优化focusout事件判断逻辑:通过event.relatedTarget检查失去焦点后,新焦点是否仍在下拉容器内部,避免误关闭。
  3. 点击<a>后重置焦点到搜索框:确保下拉容器始终处于焦点关联状态,防止触发关闭逻辑。

修改后的完整代码

HTML部分

<div id="select_users">
  <div class="w-60">
    <div class="relative mt-2">
      <input id="assigned_users_selected" name="assigned_users" type="hidden">
      <button type="button" id="assigned_users_button" name="assigned_users_button" class="relative w-full cursor-default rounded-md bg-white 
               py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
               focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6">
        <span class="flex items-center">
          <span class="ml-1 block truncate"> Assign Users</span>
        </span>
        
        <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-500">
          <svg class="w-2.5 h-2.5 ms-3" fill="none" viewBox="0 0 10 6">
            <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
          </svg>
        </span>
      </button>
      <!-- 添加tabindex="-1"让容器可聚焦 -->
      <div style="display: none;" id="assigned_users_dropdown" name="assigned_users_dropdown" tabindex="-1" class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md 
               bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
        <div class="p-3">
          <input type="text" id="assigned_users_dropdown_search" name="assigned_users_dropdown_search" class="block w-full p-2 ps-2 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 
               rk:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Search user">
        </div>
        <ul>
          <li class="text-gray-900 relative cursor-default select-none py-2 pl-1 pr-9" id="listbox-option-0" role="option">
            <a href="#" data-index="0" id="assigned_users_item_link_0" name="assigned_users_item_link_0">
              <div class="flex items-center">
                <span class="font-normal ml-3 block truncate">
                  Angela  Law
                </span>
              </div>
              <span style="display:none" id="assigned_users_item_check_0" name="assigned_users_item_check_0" class="text-indigo-600 absolute inset-y-0 right-0 flex items-center pr-4">
                <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                  <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clip-rule="evenodd"></path>
                </svg>
              </span>
            </a>
          </li>
        </ul>
      </div>
    </div>
  </div>
</div>

JavaScript部分

function fn_assigned_users() {
  var element_id = 'assigned_users';
  var link_id  = element_id + "_item_link";

  var open = false;
  var selected = [];
  var textField ='text';
  var valueField = 'value';
  var list = JSON.parse('[{"selected": false, "text": "Angela  Law", "value": 1}]');
  var button_elment = $(`#${element_id}_button`);
  var dropdown_elment = $(`#${element_id}_dropdown`);
  var selectedValues = $(`#${element_id}_selected`);
  selectedValues.value = selected.join(",");
  var searchElement = $(`#${element_id}_dropdown_search`);
  
  // 修改focusout事件逻辑:判断新焦点是否在容器内
  dropdown_elment.focusout(function(event) {
    // 检查relatedTarget是否是下拉容器的子元素
    if (!dropdown_elment.has(event.relatedTarget).length) {
      open = false;
      dropdown_elment.hide();
    }
  });

  searchElement.on("keyup", function multi_dropdown_search_keyup(sender, arg){
    for (let idx = 0; idx < list.length; idx++){
      if (list[idx][textField].toLowerCase().includes(searchElement[0].value.toLowerCase()))
        $(`#${link_id}_${idx}`).show();
      else
        $(`#${link_id}_${idx}`).hide();
    }
  });

  button_elment.on("click", function multi_dropdown_button_click (sender, args){
    open = !open;
    if (open){
      dropdown_elment.show();
      searchElement.focus();
    }
    else 
      dropdown_elment.hide();
  });

  $(`[id^='${link_id}']`).each(
    (idx, linkElm)=>{ 
      $(linkElm).on('click', function multi_dropdown_link_click (sender, args){
        let idx = $(linkElm).data('index');
        list[idx].selected = !list[idx].selected;
        itmElement =  $(`#${element_id}_item_check_${idx}`);
        if (list[idx].selected) {
          itmElement.show();
          selected.push(list[idx].value);
          selectedValues.value = selected.join(",");
        }
        else {
          itmElement.hide();
          selected = selected.filter(f=> f != list[idx].value);
          selectedValues.value = selected.join(",");
        }
        // 点击后将焦点重置到搜索框,维持容器焦点状态
        searchElement.focus();
        return false;
      });
    }
  );

  function clearSelection (){
    for (let itm of list){
      itm.selected = false;
    }
    selected = [];
    selectedValues.value = "";
    $(`[id^='${link_id}']`).each((idx, linkElm)=>{
      itmElement =  $(`#${element_id}_item_check_${idx}`);
      itmElement.hide();
    });
  }

  return {
    clear: clearSelection
  }
}

$(function (){
  assigned_users_instance = fn_assigned_users();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 03:24:52