基于React的自定义ToolTips浮层组件编写

标签

React

前端

ToolTips

html

javascript

发布时间:

本文字数:815 字 阅读完需:约 2 分钟

基本思路

本文要实现自定义浮层组件,基于React框架实现。目标是实现类似于一个按钮或者图标,鼠标hover上去后,弹出一个提示浮层,位置在图标附近。同时鼠标移动到浮层后,浮层不会消失。鼠标移开图标或浮层,浮层消失。

实现步骤

借助ReactDOM.createPortal()

浮层一定要显示在所有元素的最上面,当我们使用各种组件库时,浮层不一定能建立在最上面。所以,要求浮层的dom基于document.body,同时z-index设置为足够大。

ReactDOM.createPortal 是 React 提供的一个 API,它允许你把子节点渲染到存在于父组件之外的 DOM 节点上。这个方法的签名如下:

ReactDOM.createPortal(child, container);

其中 child 是任何可以渲染的 React 子元素,例如一个元素、字符串或碎片(fragment),而 container 是一个 DOM 元素。

createPortal 的主要用处包括:

  1. 事件冒泡:使用 createPortal 渲染的子组件会在 DOM 树上处于不同的位置,但从 React 的角度来看,它仍然存在于 React 组件树中原来的位置,这意味着事件可以正常冒泡到 React 父组件,尽管这些元素在 DOM 层级结构中并不直接相连。

  2. 避免 CSS 约束:在某些情况下,你可能不希望子组件受到父组件 CSS 的影响,例如在一个设置了overflow: hidden或z-index的父元素内部渲染一个模态对话框(modal)或工具提示(tooltip)。通过使用 createPortal,模态或工具提示可以渲染到 DOM 树中的其他位置,例如直接渲染到document.body下,从而避免了这些 CSS 约束。

  3. 视觉上的“脱离”:当你需要组件在视觉上位于页面层次结构之外时(如模态框、通知、悬浮卡片等),createPortal 提供了一种将组件结构上与视觉结构分离的方法。这可能是因为这些组件需要在页面上占据最顶层,以避免被其他元素遮挡。

使用的代码样例如下:

    const toolTip = isShow && ReactDOM.createPortal(<>
        <div className={css.card} style={{ position: 'absolute', zIndex: 9999>
            <Card/>
        </div>
    </>, document.body);

浮层定位的计算

直接利用ref api 获取 dom元素的定位即可。具体代码如下:

    const [isShow, setIsShow] = useState(false);
    const [imgAbsolutePos, setImgAbsolutePos] = useState({});
    useEffect(() => {
        const getImgAbsolutePosition = () => {
            const imgDom = imgRef.current;
            const imgRect = imgDom.getBoundingClientRect();
            return {
                left: imgRect.left + imgRect.width / 2,
                top: imgRect.top + imgRect.height / 2
            }
        }
        const imgAbsolutePos = getImgAbsolutePosition();
        setImgAbsolutePos(imgAbsolutePos);
    }, [isShow]);
     const toolTip = isShow && ReactDOM.createPortal(<>
        <div className={css.card} style={{ position: 'absolute', zIndex: 9999, left: imgAbsolutePos.left - 45, top: imgAbsolutePos.top + 20 }}>
            <Card/>
        </div>
    </>, document.body);

其中left和top值可以根据实际情况微调位置。

鼠标hover浮层显示

要实现这个功能,只需要onMouseEnter onMouseLeave这两个api即可。但是要实现鼠标移动到浮层后,浮层不会消失。鼠标移开图标或浮层,浮层消失就比较困难了。我的实现思路是借助setTimeout延时器,给500ms的缓冲。

完整的代码如下


/**
 * 自定义提示浮动块
 */
export default function CusToolTip() {
    const imgRef = useRef(null);
    const [isShow, setIsShow] = useState(false);
    const [imgAbsolutePos, setImgAbsolutePos] = useState({});
    let mouseInCard = false
    useEffect(() => {
        const getImgAbsolutePosition = () => {
            const imgDom = imgRef.current;
            const imgRect = imgDom.getBoundingClientRect();
            return {
                left: imgRect.left + imgRect.width / 2,
                top: imgRect.top + imgRect.height / 2
            }
        }
        const imgAbsolutePos = getImgAbsolutePosition();
        setImgAbsolutePos(imgAbsolutePos);
    }, [isShow]);
    const onMouseEnterIcon = () => {
        setIsShow(true);
    }


    const onMouseLeaveIcon = () => {
        setTimeout(() => {
            if (!mouseInCard) {
                setIsShow(false);
            }
        }, 500);
    }
    const onCardMouseLeave = () => {
            mouseInCard = false
            setTimeout(() => {
                if (!mouseInCard) {
                    setIsShow(false);
                }
            }, 500);
        }
    

    const toolTip = isShow && ReactDOM.createPortal(<>
        <div className={css.card} style={{ position: 'absolute', left: imgAbsolutePos.left - 45, top: imgAbsolutePos.top + 20 }} onMouseEnter={() => mouseInCard = true} onMouseLeave={onCardMouseLeave}>
            <Card/>
        </div>
    </>, document.body);
    return <>
        <img ref={imgRef} className={css.quanwenhao} src={quanWenhao} alt="" onMouseEnter={onMouseEnterIcon} onMouseLeave={onMouseLeaveIcon} />
        {toolTip}
    </>
}