Skip to content

Commit db6b55c

Browse files
committed
Improvement - Tooltip atom - Use smoother position transition
1 parent d5dba82 commit db6b55c

File tree

3 files changed

+79
-12
lines changed

3 files changed

+79
-12
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,4 @@
110110
"vitest": "^3.1.1",
111111
"vue": "^3.5.14"
112112
}
113-
}
113+
}

src/atoms/Tooltip.cy.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ describe('<Tooltip />', () => {
77
props: {
88
content: `<div data-cy="tooltip-content">Content</div>`,
99
show: true,
10+
disableSmoothing: true
1011
},
1112
slots: {
1213
default: {

src/atoms/Tooltip.vue

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script setup>
2-
import { computed, ref } from "vue";
2+
import { computed, ref, watch, onUnmounted, nextTick } from "vue";
33
import { calcTooltipPosition } from "../calcTooltipPosition";
44
import { useMouse } from "../useMouse";
5-
import { opacity, setOpacity } from "../lib";
5+
import { setOpacity } from "../lib";
66
77
const props = defineProps({
88
backgroundColor: {
@@ -64,28 +64,82 @@ const props = defineProps({
6464
isFullscreen: {
6565
type: Boolean,
6666
default: false
67+
},
68+
disableSmoothing: {
69+
type: Boolean,
70+
default: false
71+
}
72+
});
73+
74+
const tooltip = ref(null);
75+
76+
const { x, y } = useMouse(props.parent);
77+
const targetPosition = ref({ x: 0, y: 0 });
78+
const displayPosition = ref({ x: 0, y: 0 });
79+
80+
const smoothing = 0.18;
81+
let animationFrameId = null;
82+
83+
function animate() {
84+
if (props.disableSmoothing) {
85+
displayPosition.value.x = targetPosition.value.x;
86+
displayPosition.value.y = targetPosition.value.y;
87+
return;
88+
}
89+
displayPosition.value.x += (targetPosition.value.x - displayPosition.value.x) * smoothing;
90+
displayPosition.value.y += (targetPosition.value.y - displayPosition.value.y) * smoothing;
91+
animationFrameId = requestAnimationFrame(animate);
92+
}
93+
94+
watch([x, y], ([newX, newY]) => {
95+
targetPosition.value.x = newX;
96+
targetPosition.value.y = newY;
97+
if (props.disableSmoothing) {
98+
displayPosition.value.x = newX;
99+
displayPosition.value.y = newY;
67100
}
68101
});
69102
70-
const tooltip = ref(null)
103+
watch(() => props.show, async (show) => {
104+
if (show) {
105+
const initialX = x.value;
106+
const initialY = y.value;
107+
targetPosition.value.x = initialX;
108+
targetPosition.value.y = initialY;
109+
displayPosition.value.x = initialX;
110+
displayPosition.value.y = initialY;
111+
await nextTick();
112+
if (!animationFrameId) animate();
113+
} else {
114+
if (animationFrameId) {
115+
cancelAnimationFrame(animationFrameId);
116+
animationFrameId = null;
117+
}
118+
}
119+
});
71120
72-
const clientPosition = ref(useMouse(props.parent));
121+
onUnmounted(() => {
122+
if (animationFrameId) cancelAnimationFrame(animationFrameId);
123+
});
73124
74125
const position = computed(() => {
75-
return calcTooltipPosition({
126+
const pos = calcTooltipPosition({
76127
tooltip: tooltip.value,
77128
chart: props.parent,
78-
clientPosition: clientPosition.value,
129+
clientPosition: displayPosition.value,
79130
positionPreference: props.position,
80131
defaultOffsetY: props.offsetY,
81132
blockShiftY: props.blockShiftY
82133
});
83-
})
134+
return {
135+
top: Math.round(pos.top),
136+
left: Math.round(pos.left)
137+
};
138+
});
84139
85140
const convertedBackground = computed(() => {
86141
return setOpacity(props.backgroundColor, props.backgroundOpacity);
87-
})
88-
142+
});
89143
</script>
90144

91145
<template>
@@ -98,7 +152,15 @@ const convertedBackground = computed(() => {
98152
data-cy="tooltip"
99153
:class="{'vue-data-ui-custom-tooltip' : isCustom, 'vue-data-ui-tooltip': !isCustom}"
100154
v-if="show"
101-
:style="`pointer-events:none;top:${position.top}px;left:${position.left}px;${isCustom ? '' : `background:${convertedBackground};color:${color};max-width:${maxWidth};font-size:${fontSize}px`};border-radius:${borderRadius}px;border:${borderWidth}px solid ${borderColor};z-index:2147483647;`"
155+
:style="`
156+
pointer-events:none;
157+
top:${position.top}px;
158+
left:${position.left}px;
159+
${isCustom ? '' : `background:${convertedBackground};color:${color};max-width:${maxWidth};font-size:${fontSize}px`};
160+
border-radius:${borderRadius}px;
161+
border:${borderWidth}px solid ${borderColor};
162+
z-index:2147483647;
163+
`"
102164
>
103165
<slot name="tooltip-before"/>
104166
<slot/>
@@ -120,4 +182,8 @@ const convertedBackground = computed(() => {
120182
position: fixed;
121183
z-index: 3;
122184
}
123-
</style>
185+
.vue-data-ui-tooltip,
186+
.vue-data-ui-custom-tooltip {
187+
will-change: top, left;
188+
}
189+
</style>

0 commit comments

Comments
 (0)