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

仿CoinMarketCap搜索框onBlur异常触发问题求助

解决搜索框容器onBlur异常触发的问题

问题原因

  1. div容器默认无焦点能力:你给search-box这个div加了onBlur,但div默认无法获得焦点,除非设置tabIndex属性,否则onBlur事件根本不会按预期工作。
  2. 内部元素抢焦点触发onBlur:就算给div加了tabIndex,当点击容器内的输入框、列表项时,焦点会从容器转移到内部元素,导致容器触发onBlur,错误关闭搜索框。
  3. 当前onBlur逻辑完全错误:你用setTimeout返回的定时器ID判断if(temp),定时器ID永远是正数,所以不管怎样都会执行setSearch(!search),直接导致搜索框一触发onBlur就关闭。

正确解决方案

方案1:用点击外部检测替代onBlur(推荐)

这种方式更可靠,不会受内部元素焦点影响,步骤如下:

  1. 给组件添加useRef获取搜索框容器:
import { useState, useRef, useEffect } from 'react';

// ...
const searchBoxRef = useRef(null);
  1. 监听document的点击事件,判断点击区域是否在搜索框外部:
useEffect(() => {
  const handleClickOutside = (e) => {
    if (searchBoxRef.current && !searchBoxRef.current.contains(e.target)) {
      setSearch(false);
    }
  };

  document.addEventListener('mousedown', handleClickOutside);
  return () => {
    document.removeEventListener('mousedown', handleClickOutside);
  };
}, [search]);
  1. 修改真实搜索框的代码,把ref绑定到容器上,同时移除原来错误的onBlur:
{search ? (
  <div 
    ref={searchBoxRef}
    id="search-box"
    className="absolute bg-white w-full h-fit border border-slate-200 rounded-2xl shadow-sm"
  >
    <!-- 内部内容保持不变 -->
  </div>
) : (
  ""
)}

方案2:优化onBlur逻辑(仅作参考,不推荐)

如果一定要用onBlur,需要给容器加tabIndex,并且处理内部元素的焦点转移:

<div 
  tabIndex="0"
  onBlur={(e) => {
    // 检查焦点是否转移到了容器内部的元素
    if (!e.currentTarget.contains(e.relatedTarget)) {
      setSearch(false);
    }
  }}
  id="search-box"
  className="absolute bg-white w-full h-fit border border-slate-200 rounded-2xl shadow-sm"
>
  <!-- 内部内容保持不变 -->
</div>

优化自动聚焦逻辑

原来的setInterval容易导致内存泄漏,改用useEffect更安全:

useEffect(() => {
  if (search) {
    const input = document.getElementById("input-search");
    input?.focus();
  }
}, [search]);

// 假输入框的onClick里移除setInterval,只保留状态切换:
onClick={() => setSearch(!search)}

最终关键代码片段

import { useState, useRef, useEffect } from 'react';

function SearchComponent() {
  const [search, setSearch] = useState(false);
  const searchBoxRef = useRef(null);

  // 点击外部关闭搜索框
  useEffect(() => {
    const handleClickOutside = (e) => {
      if (searchBoxRef.current && !searchBoxRef.current.contains(e.target)) {
        setSearch(false);
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [search]);

  // 自动聚焦输入框
  useEffect(() => {
    if (search) {
      const input = document.getElementById("input-search");
      input?.focus();
    }
  }, [search]);

  return (
    <>
      {/* 假输入框(内部内容不变) */}
      <div
        className="bg-slate-300 rounded-lg flex w-96 h-14 items-center justify-between cursor-pointer"
        onClick={() => setSearch(!search)}
      >
        <div className="flex m-3">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="16"
            height="16"
            fill="currentColor"
            className="bi bi-search"
            viewBox="0 0 16 16"
          >
            <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" />
          </svg>
          <div className="font-bold mx-5 text-slate-600">Search</div>
        </div>
        <div className="bg-slate-400 w-8 text-center rounded text-white mr-3">
          /
        </div>
      </div>

      {/* 真实搜索框(内部内容不变) */}
      {search ? (
        <div 
          ref={searchBoxRef}
          id="search-box"
          className="absolute bg-white w-full h-fit border border-slate-200 rounded-2xl shadow-sm"
        >
          <div className="m-5">
            <div className="flex justify-between ">
              <i className="mx-2 bi bi-search"></i>
              <input
                id="input-search"
                className="w-full focus:outline-none "
                type="text"
                placeholder="Search coin, pair, contract address or exchange"
                maxLength={200}
              ></input>
              <i className="mx-2 bi bi-x-circle cursor-pointer"></i>
            </div>
            <div className=" p-5">
              <div className="flex text-xl">
                <div>Trending</div>
                <i
                  onClick={() => {
                    let temp = document.getElementById("input-search");
                    console.log(temp);
                  }}
                  className="bi bi-fire text-orange-600"
                ></i>
              </div>
              <div>
                {rowData ? (
                  rowData.map((coin) => {
                    return (
                      <div onClick={() => {
                        navigate("coin/" + coin?.symbol);
                      }} className="flex justify-between cursor-pointer">
                        <div className="flex items-center">
                          <img
                            className="m-2 w-10 h-10"
                            src={coin?.logo}
                            alt="logo"
                          />
                          <div className="m-2 font-bold">
                            {coin?.name}
                          </div>
                          <div className="m-2 text-slate-400">
                            {coin?.symbol}
                          </div>
                        </div>
                        <div>
                          <div className="text-slate-400 text-lg">
                            # {coin?.id}
                          </div>
                        </div>
                      </div>
                    );
                  })
                ) : (
                  <Loading />
                )}
              </div>
            </div>
            <div className="border-t-2 border-slate-200 p-5">
              <div className="text-xl">Recent searches</div>
            </div>
          </div>
        </div>
      ) : null}
    </>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 22:57:22