Skip to content

Commit e772544

Browse files
committed
Improvement - Tooltip - Improve performance; add config options for smooth mode
1 parent 99a17e5 commit e772544

File tree

3 files changed

+117
-46
lines changed

3 files changed

+117
-46
lines changed

src/atoms/Tooltip.vue

Lines changed: 109 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ const props = defineProps({
7373
type: Boolean,
7474
default: true
7575
},
76+
smoothForce: {
77+
type: Number,
78+
default: 0.18
79+
},
80+
smoothSnapThreshold: {
81+
type: Number,
82+
default: 0.25
83+
}
7684
});
7785
7886
const tooltip = ref(null);
@@ -81,52 +89,86 @@ const { x, y } = useMouse(props.parent);
8189
const targetPosition = ref({ x: 0, y: 0 });
8290
const displayPosition = ref({ x: 0, y: 0 });
8391
84-
const smoothing = 0.18;
8592
let animationFrameId = null;
8693
87-
function animate() {
94+
function stepAnimation() {
95+
if (!props.show) {
96+
cancelAnimation();
97+
return;
98+
}
99+
88100
if (!props.smooth) {
89101
displayPosition.value.x = targetPosition.value.x;
90102
displayPosition.value.y = targetPosition.value.y;
103+
cancelAnimation();
91104
return;
92105
}
93-
displayPosition.value.x += (targetPosition.value.x - displayPosition.value.x) * smoothing;
94-
displayPosition.value.y += (targetPosition.value.y - displayPosition.value.y) * smoothing;
95-
animationFrameId = requestAnimationFrame(animate);
106+
107+
const dx = targetPosition.value.x - displayPosition.value.x;
108+
const dy = targetPosition.value.y - displayPosition.value.y;
109+
110+
if (Math.abs(dx) <= props.smoothSnapThreshold && Math.abs(dy) <= props.smoothSnapThreshold) {
111+
displayPosition.value.x = targetPosition.value.x;
112+
displayPosition.value.y = targetPosition.value.y;
113+
cancelAnimation();
114+
return;
115+
}
116+
117+
displayPosition.value.x += dx * props.smoothForce;
118+
displayPosition.value.y += dy * props.smoothForce;
119+
120+
animationFrameId = requestAnimationFrame(stepAnimation);
121+
}
122+
123+
function ensureAnimationRunning() {
124+
if (animationFrameId == null && props.show && props.smooth) {
125+
animationFrameId = requestAnimationFrame(stepAnimation);
126+
}
127+
}
128+
129+
function cancelAnimation() {
130+
if (animationFrameId != null) {
131+
cancelAnimationFrame(animationFrameId);
132+
animationFrameId = null;
133+
}
96134
}
97135
98136
watch([x, y], ([newX, newY]) => {
99137
targetPosition.value.x = newX;
100138
targetPosition.value.y = newY;
139+
101140
if (!props.smooth) {
102141
displayPosition.value.x = newX;
103142
displayPosition.value.y = newY;
143+
} else {
144+
ensureAnimationRunning();
104145
}
105146
});
106147
107-
watch(() => props.show, async (show) => {
108-
if (show) {
109-
const initialX = x.value;
110-
const initialY = y.value;
111-
targetPosition.value.x = initialX;
112-
targetPosition.value.y = initialY;
113-
displayPosition.value.x = initialX;
114-
displayPosition.value.y = initialY;
115-
await nextTick();
116-
if (!animationFrameId) animate();
117-
} else {
118-
if (animationFrameId) {
119-
cancelAnimationFrame(animationFrameId);
120-
animationFrameId = null;
148+
watch(
149+
() => props.show,
150+
async (show) => {
151+
if (show) {
152+
const initialX = x.value;
153+
const initialY = y.value;
154+
targetPosition.value.x = initialX;
155+
targetPosition.value.y = initialY;
156+
displayPosition.value.x = initialX;
157+
displayPosition.value.y = initialY;
158+
159+
await nextTick();
160+
ensureAnimationRunning();
161+
} else {
162+
cancelAnimation();
121163
}
122164
}
123-
});
165+
);
124166
125167
onUnmounted(() => {
126-
if (animationFrameId) cancelAnimationFrame(animationFrameId);
168+
cancelAnimation();
127169
});
128170
129-
const position = computed(() => {
171+
const pixelPosition = computed(() => {
130172
const pos = calcTooltipPosition({
131173
tooltip: tooltip.value,
132174
chart: props.parent,
@@ -144,41 +186,61 @@ const position = computed(() => {
144186
const convertedBackground = computed(() => {
145187
return setOpacity(props.backgroundColor, props.backgroundOpacity);
146188
});
189+
190+
const tooltipStyle = computed(() => {
191+
const base = {
192+
pointerEvents: "none",
193+
position: "fixed",
194+
top: "0px",
195+
left: "0px",
196+
transform: `translate3d(${pixelPosition.value.left}px, ${pixelPosition.value.top}px, 0)`,
197+
borderRadius: `${props.borderRadius}px`,
198+
border: `${props.borderWidth}px solid ${props.borderColor}`,
199+
zIndex: 2147483647
200+
};
201+
202+
if (!props.isCustom) {
203+
Object.assign(base, {
204+
background: convertedBackground.value,
205+
color: props.color,
206+
maxWidth: props.maxWidth,
207+
fontSize: `${props.fontSize}px`
208+
});
209+
}
210+
211+
return base;
212+
});
147213
</script>
148214

149215
<template>
150216
<teleport :to="isFullscreen ? parent : 'body'">
151-
<div
152-
ref="tooltip"
153-
role="tooltip"
154-
:aria-hidden="!show"
155-
aria-live="polite"
156-
data-cy="tooltip"
157-
:class="{'vue-data-ui-custom-tooltip' : isCustom, 'vue-data-ui-tooltip': !isCustom, 'vue-data-ui-tooltip-backdrop': backdropFilter}"
158-
v-if="show"
159-
:style="`
160-
pointer-events:none;
161-
top:${position.top}px;
162-
left:${position.left}px;
163-
${isCustom ? '' : `background:${convertedBackground};color:${color};max-width:${maxWidth};font-size:${fontSize}px`};
164-
border-radius:${borderRadius}px;
165-
border:${borderWidth}px solid ${borderColor};
166-
z-index:2147483647;
167-
`"
217+
<div
218+
ref="tooltip"
219+
role="tooltip"
220+
:aria-hidden="!show"
221+
aria-live="polite"
222+
data-cy="tooltip"
223+
v-if="show"
224+
:class="{
225+
'vue-data-ui-custom-tooltip': isCustom,
226+
'vue-data-ui-tooltip': !isCustom,
227+
'vue-data-ui-tooltip-backdrop': backdropFilter
228+
}"
229+
:style="tooltipStyle"
168230
>
169-
<slot name="tooltip-before"/>
170-
<slot/>
171-
<div v-html="content"/>
172-
<slot name="tooltip-after"/>
231+
<slot name="tooltip-before" />
232+
<slot />
233+
<div v-html="content" />
234+
<slot name="tooltip-after" />
173235
</div>
174236
</teleport>
175237
</template>
176238

177239
<style>
178240
.vue-data-ui-tooltip {
179-
box-shadow: 0 6px 12px -6px rgba(0,0,0,0.2);
241+
box-shadow: 0 6px 12px -6px rgba(0, 0, 0, 0.2);
180242
position: fixed;
181-
padding:12px;
243+
padding: 12px;
182244
}
183245
184246
.vue-data-ui-tooltip-backdrop {
@@ -190,8 +252,9 @@ const convertedBackground = computed(() => {
190252
position: fixed;
191253
z-index: 3;
192254
}
255+
193256
.vue-data-ui-tooltip,
194257
.vue-data-ui-custom-tooltip {
195-
will-change: top, left;
258+
will-change: transform;
196259
}
197260
</style>

src/useConfig.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ export function useConfig() {
9494
offsetY: 24,
9595
smooth: true, // v3
9696
backdropFilter: true, // v3
97+
smoothForce: 0.18,
98+
smoothSnapThreshold: 0.25,
9799
}
98100

99101
const AXIS_DATE_FORMATTER = {
@@ -2868,6 +2870,8 @@ export function useConfig() {
28682870
tooltipPosition: POSITION.CENTER,
28692871
tooltipOffsetY: 24,
28702872
tooltipSmooth: true,
2873+
tooltipSmoothForce: 0.18,
2874+
tooltipSmoothSnapThreshold: 0.25,
28712875
tooltipBackdropFilter: true,
28722876
useCustomLegend: false,
28732877
valuePrefix: '',

types/vue-data-ui.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ declare module "vue-data-ui" {
293293
offsetY?: number;
294294
smooth?: boolean;
295295
backdropFilter?: boolean;
296+
smoothForce?: number;
297+
smoothSnapThreshold?: number;
296298
};
297299

298300
export type ZoomMinimap = {
@@ -6143,6 +6145,8 @@ declare module "vue-data-ui" {
61436145
tooltipPosition?: TooltipPosition;
61446146
tooltipOffsetY?: number;
61456147
tooltipSmooth?: boolean;
6148+
tooltipSmoothForce?: number;
6149+
tooltipSmoothSnapThreshold?: number;
61466150
tooltipBackdropFilter?: boolean;
61476151
useCustomLegend?: boolean;
61486152
valuePrefix?: string;

0 commit comments

Comments
 (0)