/** 基础 hooks: useTip */
const { useRef ,useState, useEffect, useCallback } = React;
export interface TipState<T> {
visible?: boolean;
data?: T;
}
const useTip = <T>(initialState: T = {} as T) => {
const [state, setState] = useState<TipState<T>>({
visible: false,
data: initialState,
});
const showTip = useCallback(
(data: T) => {
setState({ visible: true, data });
},
[setState],
);
const hideTip = useCallback(() => {
setState({
visible: false,
data: initialState,
});
}, [setState]);
return [state, setState, { showTip, hideTip }] as const;
};
/** useMouseMove */
const useMouseMove = (
ref: React.MutableRefObject<Element | null>,
eventName: string,
handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
) => {
useEffect(() => {
if (!ref?.current?.addEventListener) {
console.log('未匹配到正确的属性');
return;
}
if (!eventName) {
console.error('没有监听事件类型');
return;
}
ref.current.addEventListener(eventName, handler, options);
return () => {
ref.current?.removeEventListener(eventName, handler, options);
};
}, [ref, eventName]);
};
/** usePosTip: 计算错误 */
type OffsetPos = [number, number];
interface PosState extends Omit<DOMRect, 'toJSON'> {
offsetY?: number;
offsetX?: number;
}
const initState: PosState = {
x: 0,
y: 0,
top: 0,
left: 0,
right: 0,
width: 0,
bottom: 0,
height: 0,
/** 最终偏移量 */
offsetX: undefined,
offsetY: undefined,
};
const initOffset: OffsetPos = [16, 24];
const getContentRect = <T extends HTMLElement>(dom: T, pos: OffsetPos) => {
const rect = dom.getBoundingClientRect() as PosState;
const [x, y] = pos;
const offsetX = rect.x + x;
const offsetY = rect.y + y;
return Object.assign(rect, {
offsetX,
offsetY,
});
};
/** containerRef: 计算错误, 应使用 event.x 和 event.y */
const usePosTip = <T extends HTMLElement>(
containerRef: React.MutableRefObject<Element | null>,
offsetPos = initOffset,
) => {
const ref = React.useRef<T | null>(null);
const [state, setState, { showTip, hideTip }] = useTip<PosState>(initState);
useMouseMove(containerRef, 'mousemove', event => {
if (ref.current && containerRef.current) {
const dom = containerRef.current;
const contentDom = ref.current;
const { clientHeight, clientWidth } = dom;
const contentRect = getContentRect(contentDom, offsetPos);
const { width, height, offsetX, offsetY } = contentRect;
const newX = width + offsetX >= clientWidth ? clientWidth - offsetX + 10 : offsetX;
const newY = height + offsetY >= clientHeight ? clientHeight - offsetY + 10 : offsetY;
const rect = {
...contentRect,
offsetY: newX,
offsetX: newY,
};
showTip(rect);
}
});
useMouseMove(containerRef, 'mouseout', hideTip);
return [ref, state, setState, { showTip, hideTip }] as const;
};
const Demo = () => {
const containerRef = useRef<HTMLDivElement | null>(null);
const [ref, state, , { showTip, hideTip }] = usePosTip<HTMLSpanElement>(containerRef);
return (
<div className="wrapper" ref={containerRef}>
<pre ref={ref}>{JSON.stringify(state, null, 2)}</pre>
</div>
);
};
ReactDOM.render(<Demo />, mountNode);