Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | 59x 59x 83x 83x 83x 83x 83x 82x 83x 83x 83x 82x 82x 82x 82x 83x 83x | import React, { useRef, useEffect } from "react"; import { ReferenceObject } from "popper.js"; import { Bubble, BubbleContentProps } from "../bubble"; import { TriggerProps } from "../popover"; /** * 使用 Tooltip 的内容需要使用容器包裹。 * * 默认会使用 span 包裹内容,如有需要,可以改为使用 div 包裹。 */ export interface TooltipProps { /** * Tooltip 放置位置 * @default "bottom-start" */ placement?: BubbleContentProps["placement"]; /** * 文本提示内容 */ title?: React.ReactNode; /** * 要提示的对象 */ children?: React.ReactNode; /** * 出现延迟时间(ms) * @default 600 */ openDelay?: number; /** * 距上次 Tooltip 出现 1s 内再次出现延迟时间(ms) * @default 300 */ openDelayWhenHasInstance?: number; } let visibleTooltipCount = 0; /** * 控制 Tooltip 的触发机制 * * Tooltip 跟普通的 Bubble 不一样: * * 1. 出现位置不参考元素,参考鼠标 * 2. 如果场上无 Tooltip 实例,则激活后 0.6s 后弹出 * 3. 如果场上有 Tooltip 实例,则激活后 0.3s 后弹出 * * 设计上面的规则,是为了对齐系统 Tooltip 的交互 */ const TooltipTrigger = ({ delay, openDelay = delay, closeDelay = 0, setVisible, render, openDelayWhenHasInstance, }: TriggerProps & { delay: number; openDelayWhenHasInstance: number }) => { // 当前鼠标的位置,用作 Tooltip 弹出位置的参考 const mouseElement = useRef< ReferenceObject & { scrollTop: number; scrollLeft: number } >(null); // Tooltip 确定要激活,在延时出来期间,需要监听鼠标新位置,这个引用保存监听的取消方法 const mouseWatching = useRef<() => void>(null); // 从鼠标事件获得的坐标信息,构造鼠标参考位置 const mark = useRef((evt: MouseEvent) => { const width = 0; const height = 0; const clientX = Math.round(evt.clientX); const clientY = Math.round(evt.clientY); mouseElement.current = { clientWidth: width, clientHeight: height, getBoundingClientRect: () => ({ left: clientX, top: clientY, right: clientX + width, bottom: clientY + height, width, height, }), // IE10 下 Popper 读取这两个值计算 scrollTop: 0, scrollLeft: 0, }; }); // 监听鼠标位置变化,更新位置 const watch = () => { document.addEventListener("mousemove", mark.current); mouseWatching.current = () => document.removeEventListener("mousemove", mark.current); return mouseWatching.current; }; // 取消当前的位置变化监听 const unwatch = () => { Iif (mouseWatching.current) { mouseWatching.current(); mouseWatching.current = null; } }; // 用户鼠标进入目标元素 const enter = async (evt: React.MouseEvent) => { // 记录鼠标位置并跟踪 mark.current(evt.nativeEvent); watch(); // Tooltip 出来后,更新实例数量,并且可以取消跟踪了(Tooltip 出来的时候,鼠标在哪儿,就在哪儿,无需跟随) await setVisible( true, visibleTooltipCount > 0 ? openDelayWhenHasInstance : openDelay ); visibleTooltipCount += 1; unwatch(); }; // 用户鼠标离开目标元素 const leave = async () => { // 取消原有的跟踪 unwatch(); // 完全隐藏后,延时 1 秒更新实例数量 // 这里延时 1 秒是给用户留足够的空间,不至于由于鼠标走的慢导致下次 Tooltip 又要等很久 await setVisible(false, closeDelay); // CD 1 秒 await new Promise(resolve => setTimeout(resolve, 1000)); visibleTooltipCount -= 1; }; // 组件 unmount 的时候,清理一切 useEffect( () => () => { mouseElement.current = null; mark.current = null; unwatch(); }, [] ); return render({ childrenProps: { onMouseEnter: enter, onMouseLeave: leave, }, overlayProps: {}, referenceElement: mouseElement.current, }); }; export function Tooltip({ title, placement = "bottom-start", children, openDelay = 600, openDelayWhenHasInstance = 300, }: TooltipProps) { return ( <Bubble tooltip trigger={[TooltipTrigger, { delay: openDelay, openDelayWhenHasInstance }]} closeOnScroll placement={placement} placementOffset={20} content={title} overlayStyle={{ pointerEvents: "none" }} updateOnDimensionChange={false} > {children} </Bubble> ); } |