手动实现 Tooltip 效果

258 阅读1分钟

1. 实现思路

首先,将 Tooltip 框固定在要悬浮的内容上面(这里仅做了悬浮在上面的效果,其他位置暂未实现),根据 getBoundingClientRect 获取当前元素的 top、left;然后,在页面滚动时,动态获取元素的位置,更新 Tooltip 的位置;最后,在点击空白区域时,关闭 Tooltip 框。

2. 具体步骤

2.1 获取悬浮位置

const [tooltipVisible, setTooltipVisible] = useState(false)
const [tooltipStyle, setTooltipStyle] = useState({})
const buttonRef = useRef<HTMLButtonElement | null>(null)

const handleButtonClick = () => {
    if (!buttonRef.current) return
    const buttonRect = buttonRef.current.getBoundingClientRect()
    setTooltipStyle({
      top: `${buttonRect.top + window.scrollY - 55}px`,
      left: `${buttonRect.left + buttonRect.width / 2}px`,
      transform: 'translateX(-50%)'
    })
    setTooltipVisible(!tooltipVisible)
  }
  
  return (
    <>
      <div className='content'>
        <p>
          Hello world!
        </p>
        <Button
          ref={buttonRef}
          type='link'
          onClick={handleButtonClick}
        >
          Tooltip
        </Button>
        {
          tooltipVisible && (
            <div ref={tooltipRef} className="custom-tooltip" style={tooltipStyle}>
              This is Tooltip hover content
            </div>
          )
        }
      </div>
    </>
  )
  
.content {
  width: 200px;
  height: 3000px;
  background-color: grey;
  padding: 20px;
}

.custom-tooltip {
  position: absolute;
  background: #fff;
  border: 1px solid #ddd;
  padding: 10px;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 1000;
  pointer-events: none;
}

.custom-tooltip::after {
  content: '';
  position: absolute;
  bottom: -8px;
  left: 50%;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 8px 8px 0 8px;
  border-color: #fff transparent transparent transparent; 
  z-index: 1001;
}

.custom-tooltip::before {
  content: '';
  position: absolute;
  bottom: -9px;
  left: 50%;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 9px 9px 0 9px;
  border-color: #ddd transparent transparent transparent;
  z-index: 1000;
}

2.2 元素滚动更新位置

const handleScroll = () => {
    if (tooltipVisible && buttonRef.current) {
      const buttonRect = buttonRef.current.getBoundingClientRect()
      setTooltipStyle({
        top: `${buttonRect.top + window.scrollY - 55}px`,
        left: `${buttonRect.left + buttonRect.width / 2}px`,
        transform: 'translateX(-50%)',
      });
    }
  }
  
  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, [tooltipVisible]);

2.3 点击空白区域关闭 Tooltip 框

// click the blank container to close tooltip
  const handleClickOutside = (e: MouseEvent) => {
    if (
      tooltipRef.current && buttonRef.current &&
      !tooltipRef.current.contains(e.target as Node) &&
      !buttonRef.current.contains(e.target as Node)
    ) {
      setTooltipVisible(false);
    }
  };
  
  useEffect(() => {
    document.addEventListener('click', handleClickOutside);
    return () => {
      document.removeEventListener('click', handleClickOutside);
    };
  }, [tooltipVisible]);

3. 完整代码

具体详见