diff --git a/__tests__/integration/components/tooltip/tooltip-1.ts b/__tests__/integration/components/tooltip/tooltip-1.ts index 547f6cd6c..1c6a672ae 100644 --- a/__tests__/integration/components/tooltip/tooltip-1.ts +++ b/__tests__/integration/components/tooltip/tooltip-1.ts @@ -40,7 +40,7 @@ export const Tooltip1 = () => { document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); group.addEventListener('mousemove', (e: any) => { - tooltip.position = [e.offsetX, e.offsetY]; + tooltip.show(e.offsetX, e.offsetY); }); group.addEventListener('mouseenter', (e: any) => { tooltip.show(e.offsetX, e.offsetY); diff --git a/__tests__/integration/components/tooltip/tooltip-10.ts b/__tests__/integration/components/tooltip/tooltip-10.ts index 1d9eb7b87..19eca14f3 100644 --- a/__tests__/integration/components/tooltip/tooltip-10.ts +++ b/__tests__/integration/components/tooltip/tooltip-10.ts @@ -36,19 +36,20 @@ export const Tooltip10 = () => { { value: 1.2312323, name: '第四项', index: 1, color: 'green' }, { value: 1.2312323, name: '第五项', index: 1, color: 'blue' }, ], + enterable: true, }, }) ); document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); group.addEventListener('mousemove', (e: any) => { - tooltip.position = [e.offsetX, e.offsetY]; + tooltip.show(e.offsetX, e.offsetY); }); group.addEventListener('mouseenter', (e: any) => { tooltip.show(e.offsetX, e.offsetY); }); - group.addEventListener('mouseleave', () => { - tooltip.hide(); + group.addEventListener('mouseleave', (e: any) => { + tooltip.hide(e.clientX, e.clientY); }); return group; diff --git a/__tests__/integration/components/tooltip/tooltip-2.ts b/__tests__/integration/components/tooltip/tooltip-2.ts index 605ffe0b3..df2e3dbd3 100644 --- a/__tests__/integration/components/tooltip/tooltip-2.ts +++ b/__tests__/integration/components/tooltip/tooltip-2.ts @@ -56,26 +56,15 @@ export const Tooltip2 = () => { ); document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); - let isPointerInTooltip = false; group.addEventListener('mousemove', (e: any) => { - tooltip.position = [e.offsetX, e.offsetY]; - }); - tooltip.getContainer().addEventListener('mouseenter', () => { - isPointerInTooltip = true; - }); - tooltip.getContainer().addEventListener('mouseleave', () => { - isPointerInTooltip = false; + tooltip.show(e.offsetX, e.offsetY); }); group.addEventListener('mouseenter', () => { tooltip.show(); }); - group.addEventListener('mouseleave', () => { - timeout(() => { - if (!isPointerInTooltip) { - tooltip.hide(); - } - }); + group.addEventListener('mouseleave', (e: any) => { + tooltip.hide(e.clientX, e.clientY); }); return group; }; diff --git a/__tests__/integration/components/tooltip/tooltip-3.ts b/__tests__/integration/components/tooltip/tooltip-3.ts index 837f16ba0..b4e7f25e2 100644 --- a/__tests__/integration/components/tooltip/tooltip-3.ts +++ b/__tests__/integration/components/tooltip/tooltip-3.ts @@ -38,7 +38,7 @@ export const Tooltip3 = () => { document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); group.addEventListener('mousemove', (e: any) => { - tooltip.position = [e.offsetX, e.offsetY]; + tooltip.show(e.offsetX, e.offsetY); }); group.addEventListener('mouseenter', () => { tooltip.show(); diff --git a/__tests__/integration/components/tooltip/tooltip-4.ts b/__tests__/integration/components/tooltip/tooltip-4.ts index f4fc0dc51..429c5b54b 100644 --- a/__tests__/integration/components/tooltip/tooltip-4.ts +++ b/__tests__/integration/components/tooltip/tooltip-4.ts @@ -45,13 +45,13 @@ export const Tooltip4 = () => { document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); group.addEventListener('mousemove', (e: any) => { - tooltip.position = [e.offsetX, e.offsetY]; + tooltip.show(e.offsetX, e.offsetY); }); group.addEventListener('mouseenter', () => { tooltip.show(); }); - group.addEventListener('mouseleave', () => { - tooltip.hide(); + group.addEventListener('mouseleave', (e: any) => { + tooltip.hide(e.offsetX, e.offsetY); }); return group; }; diff --git a/__tests__/integration/components/tooltip/tooltip-5.tsx b/__tests__/integration/components/tooltip/tooltip-5.tsx index 099cd2517..2770bf0d4 100644 --- a/__tests__/integration/components/tooltip/tooltip-5.tsx +++ b/__tests__/integration/components/tooltip/tooltip-5.tsx @@ -43,7 +43,7 @@ export const Tooltip5 = () => { document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); group.addEventListener('mousemove', (e: any) => { - tooltip.position = [e.offsetX, e.offsetY]; + tooltip.show(e.offsetX, e.offsetY); }); group.addEventListener('mouseenter', () => { tooltip.show(); diff --git a/__tests__/integration/components/tooltip/tooltip-6.tsx b/__tests__/integration/components/tooltip/tooltip-6.tsx index 6bba5897b..10ed4c4e9 100644 --- a/__tests__/integration/components/tooltip/tooltip-6.tsx +++ b/__tests__/integration/components/tooltip/tooltip-6.tsx @@ -37,7 +37,7 @@ export const Tooltip6 = () => { document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); group.addEventListener('mousemove', (e: any) => { - tooltip.position = [e.offsetX, e.offsetY]; + tooltip.show(e.offsetX, e.offsetY); /** 1: 通过 React 渲染Tooltip节点 */ // ReactDOM.render( //
diff --git a/__tests__/integration/components/tooltip/tooltip-7.tsx b/__tests__/integration/components/tooltip/tooltip-7.tsx index 6fe1a1ace..6618ee52d 100644 --- a/__tests__/integration/components/tooltip/tooltip-7.tsx +++ b/__tests__/integration/components/tooltip/tooltip-7.tsx @@ -39,7 +39,7 @@ export const Tooltip7 = () => { document.getElementsByTagName('body')[0].appendChild(tooltip.HTMLTooltipElement); } - tooltip.position = [e.offsetX, e.offsetY]; + tooltip.show(e.offsetX, e.offsetY); }); group.addEventListener('mouseenter', (e: any) => { tooltip?.show(e.offsetX, e.offsetY); diff --git a/src/ui/tooltip/index.ts b/src/ui/tooltip/index.ts index c8dfb5b27..8bb1b273b 100644 --- a/src/ui/tooltip/index.ts +++ b/src/ui/tooltip/index.ts @@ -1,7 +1,7 @@ import { substitute, createDOM } from '@antv/util'; import { Component } from '../../core'; import { Group } from '../../shapes'; -import { applyStyleSheet, throttle } from '../../util'; +import { BBox, applyStyleSheet } from '../../util'; import { getClassNames, getDefaultTooltipStyle } from './constant'; import type { TooltipOptions, TooltipPosition, TooltipStyleProps } from './types'; @@ -10,6 +10,8 @@ export type { TooltipStyleProps, TooltipOptions }; export class Tooltip extends Component { public static tag = 'tooltip'; + private timestamp = -1; + public get HTMLTooltipElement() { return this.element; } @@ -18,12 +20,6 @@ export class Tooltip extends Component { return this.element; } - public set position([x, y]: [number, number]) { - this.attributes.x = x; - this.attributes.y = y; - this.updatePosition(); - } - private get elementSize() { const width = this.element.offsetWidth; const height = this.element.offsetHeight; @@ -89,19 +85,32 @@ export class Tooltip extends Component { * 如果设置了坐标值,显示过程中会立即更新位置并关闭过渡动画 */ public show(x?: number, y?: number) { - const disableTransition = x !== undefined && y !== undefined; - if (disableTransition) { - const transition = this.element.style.transition; - this.element.style.transition = 'none'; - this.position = [x ?? +this.attributes.x, y ?? +this.attributes.y]; - setTimeout(() => { - this.element.style.transition = transition; - }, 10); + if (x !== undefined && y !== undefined) { + const isToggle = this.element.style.visibility === 'hidden'; + const setPosition = () => { + this.attributes.x = x ?? this.attributes.x; + this.attributes.y = y ?? this.attributes.y; + this.updatePosition(); + }; + // 只有从 hidden 状态变为 visible 状态时才需要关闭过渡动画 + isToggle ? this.closeTransition(setPosition) : setPosition(); } this.element.style.visibility = 'visible'; } - public hide() { + /** + * 如果 hide 时传入了坐标值,那么只有当鼠标不在 tooltip 上时才会隐藏 + * 对于 enterable = true 的时候,需要传入 x y,为了避免问题,建议上层在使用的时候,都传入 x y + * @param x + * @param y + * @returns + */ + public hide(x = 0, y = 0) { + const { enterable } = this.attributes; + + // 如果当前鼠标在 tooltip 上,则不隐藏 + if (enterable && this.isCursorEntered(x, y)) return; + this.element.style.visibility = 'hidden'; } @@ -186,10 +195,15 @@ export class Tooltip extends Component { /** * 更新tooltip的位置 */ - @throttle(100, true) private updatePosition() { - // 尝试当前的位置使用默认position能否放下 - // 如果不能,则改变取溢出边的反向position + const { showDelay = 60 } = this.attributes; + + const currentTimestamp = Date.now(); + if (this.timestamp > 0 && currentTimestamp - this.timestamp < showDelay) return; + + this.timestamp = currentTimestamp; + // 尝试当前的位置使用默认 position 能否放下 + // 如果不能,则改变取溢出边的反向 position /** * 默认位置 * ⬇️ @@ -241,4 +255,29 @@ export class Tooltip extends Component { const correctedPositionString = correctivePosition.join('-'); return this.getRelativeOffsetFromCursor(correctedPositionString as TooltipPosition); } + + private isCursorEntered(clientX: number, clientY: number) { + // 是可捕获的,并且点在 tooltip dom 上 + if (this.element) { + const { x, y, width, height } = this.element.getBoundingClientRect(); + // const { container } = this.attributes; + // const { x: cx, y: cy } = container; + + // console.log(1113, [clientX, clientY], [x, y, width, height], [cx, cy], new BBox(x - cx, y - cy, width, height).isPointIn(cursorX, cursorY)); + + return new BBox(x, y, width, height).isPointIn(clientX, clientY); + } + return false; + } + + private closeTransition(callback: () => void) { + const transition = this.element.style.transition; + this.element.style.transition = 'none'; + + callback(); + + setTimeout(() => { + this.element.style.transition = transition; + }, 10); + } } diff --git a/src/ui/tooltip/types.ts b/src/ui/tooltip/types.ts index c08abb7a6..5a8249686 100644 --- a/src/ui/tooltip/types.ts +++ b/src/ui/tooltip/types.ts @@ -30,6 +30,8 @@ export type TooltipStyleProps = GroupStyleProps & { offset?: [number, number]; /** 指针是否可进入 */ enterable?: boolean; + /** 配合 enterable = true 使用,指定延迟显示的毫秒数,默认为 60ms */ + showDelay?: number; /** 画布的左上角坐标 */ container: { x: number; diff --git a/src/util/bbox.ts b/src/util/bbox.ts index 39d05c0f9..ecdb4fd8c 100644 --- a/src/util/bbox.ts +++ b/src/util/bbox.ts @@ -48,6 +48,14 @@ export class BBox { left: this.left, }; } + + /** + * 点是否在 bbox 中 + * @param p + */ + public isPointIn(x: number, y: number) { + return x >= this.left && x <= this.right && y >= this.top && y <= this.bottom; + } } export function getRenderBBox(element: DisplayObject) {