如何在失去焦点时关闭Tailwind CSS+jQuery下拉菜单
问题描述
使用Tailwind CSS和jQuery开发下拉菜单,需要实现「失去焦点时自动关闭下拉菜单」的功能,但遇到以下问题:
- 给包含下拉菜单的外层div绑定了
focusout事件,打开菜单时将焦点设置到搜索框,此时菜单能正常保持打开; - 点击下拉列表中的用户项(
<a>标签)时,菜单会意外关闭——预期行为是选中项(显示复选标记)后菜单仍保持打开; - 原本认为子元素
<a>获取焦点时,父div会依然保持焦点,但实际并非如此。
核心需求:下拉菜单在下拉区域内进行输入、点击操作时保持打开,当点击、输入或切换到区域外时自动关闭。
解决思路与代码修改
问题根源
focusout事件的触发逻辑是:当元素或其任意子元素失去焦点时就会触发。点击<a>标签时,焦点从搜索框转移到<a>的瞬间,会先触发父div的focusout(因为搜索框先失焦),此时<a>还未获取焦点,导致代码错误执行关闭操作。
具体修改步骤
- 给下拉容器添加可聚焦属性:给
assigned_users_dropdown添加tabindex="-1",让容器本身可以成为焦点元素,维持子元素切换焦点时的容器焦点状态。 - 优化
focusout事件判断逻辑:通过event.relatedTarget检查失去焦点后,新焦点是否仍在下拉容器内部,避免误关闭。 - 点击
<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
相关产品推荐
相关产品推荐

