(封面图片来自于基于 CC0 协议的 shopify )
使用过 Antd
的小伙伴应该深有体会,但是我们用的多了,也就想了解它怎么做的(:跟我一样吧
乍一看实现方式并不难,但是其实本篇文章更想表述一些细节的内容。
"use client";
import {FC, PropsWithChildren, RefObject, useCallback, useEffect, useRef, useState} from "react";
import { createPortal } from "react-dom";
import { cloneElement } from "react";
const getStyle = (ref: RefObject<HTMLElement>, position: 'top' | 'bottom' | 'right' | 'left') => {
const styles = ref.current?.getBoundingClientRect();
const top = styles!.top + styles.height + 30 + 'px';
const left = styles!.left + 'px';
return {
top,
left,
}
}
const Popover: FC<PropsWithChildren> = ({ children }) => {
const ref = useRef(null);
const [styles, setStyles] = useState({});
const [opacity, setOpacity] = useState(0);
useEffect(() => {
const _ = getStyle(ref)
setStyles(_);
}, []);
const onMouseEnter = useCallback(() => {
setOpacity(1)
}, [])
const onMouseLeave = useCallback(() => {
setOpacity(0)
}, [])
const newChild = cloneElement(children as any, {
ref,
onMouseEnter,
onMouseLeave
})
return <>
{newChild}
{
createPortal(<div className="popover-wrapper bg-amber-300 w-[100px] h-[80px] absolute" style={{...styles, opacity }}>
Hello )_________
</div>, document.body)
}
</>
}
export default Popover;
其实大家大可关注到精髓点:
createPortal
getBoundingClientRect
createPortal
的目的是脱离文档流,getBoundingClientRect
是为了拿到目标元素的具体位置 坐标
、宽高
。
我们可以设置 Popover
的模式,大体分为两种 hover
和 click
模式:
click
onClick
事件即可hover
onMouseEnter
onMouseLeave
到这里,你觉得结束了吗?
答案是:没有,因为我们要考虑,点击模式常驻屏幕,滚动跟随移动的场景。
在这里我们借助 ResizeObserver
来实现:
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const _ = getStyle(ref)
setStyles(_);
}
});
resizeObserver.observe(document.target || document.body);
兼容性还是可以的,但是我们依旧要考虑我们程序的鲁棒性:
async function handleWatch() {
if(!window.ResizeObserver) {
await import ('resize-observer-polyfill')
}
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const _ = getStyle(ref)
setStyles(_);
}
});
resizeObserver.observe(document.target || document.body);
}
到此,一个简单的跟随移动 Popover
就实现了,如果你有更好的想法 ,欢迎交流~