This commit is contained in:
oneao committed 2025-09-04 21:44:55 +08:00
1 parent 1a33497c5d
commit 48dac36685
3 files changed
+78 -58

No files matched your search

@@ -1,3 +1,4 @@
// Tooltip.tsx
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { cloneVNode, defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue' import { cloneVNode, defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
@@ -21,27 +22,46 @@ export default defineComponent({
const createTooltip = () => { const createTooltip = () => {
const el = document.createElement('div') const el = document.createElement('div')
el.className = `tooltip tooltip-${props.placement}` // 初始隐藏,不加 visible el.className = `tooltip tooltip-${props.placement}`
el.innerHTML = `${props.content}<div class="tooltip-arrow"></div>`
const contentEl = document.createElement('div')
contentEl.className = 'tooltip-content'
contentEl.textContent = props.content
const arrowEl = document.createElement('div')
arrowEl.className = 'tooltip-arrow'
el.appendChild(contentEl)
el.appendChild(arrowEl)
// 初始状态
el.style.position = 'absolute'
el.style.top = '0'
el.style.left = '0'
el.style.opacity = '0'
el.style.visibility = 'hidden'
el.style.pointerEvents = 'none'
document.body.appendChild(el) document.body.appendChild(el)
tooltipEl.value = el tooltipEl.value = el
} }
const getTargetDom = (): HTMLElement | null => {
if (!targetEl.value)
return null
let dom = targetEl.value
if ('$el' in dom)
dom = (dom as any).$el
return dom instanceof HTMLElement ? dom : null
}
const updatePosition = () => { const updatePosition = () => {
const target = targetEl.value
const tooltip = tooltipEl.value const tooltip = tooltipEl.value
if (!target || !tooltip) const target = getTargetDom()
if (!tooltip || !target)
return return
let dom: HTMLElement | null = null const rect = target.getBoundingClientRect()
if (target instanceof HTMLElement)
dom = target
else if ((target as any)?.$el instanceof HTMLElement)
dom = (target as any).$el
if (!dom)
return
const rect = dom.getBoundingClientRect()
const tooltipRect = tooltip.getBoundingClientRect() const tooltipRect = tooltip.getBoundingClientRect()
const scrollX = window.scrollX || window.pageXOffset const scrollX = window.scrollX || window.pageXOffset
const scrollY = window.scrollY || window.pageYOffset const scrollY = window.scrollY || window.pageYOffset
@@ -75,53 +95,51 @@ export default defineComponent({
const show = () => { const show = () => {
const tooltip = tooltipEl.value const tooltip = tooltipEl.value
if (!tooltip || !targetEl.value) if (!tooltip || !getTargetDom())
return return
visible.value = true tooltip.style.visibility = 'visible'
tooltip.style.pointerEvents = 'auto'
nextTick(() => { nextTick(() => {
updatePosition() updatePosition()
tooltip.classList.add('visible') // CSS 控制动画 tooltip.style.opacity = '1'
tooltip.style.transform = 'scale(1)'
}) })
visible.value = true
} }
const hide = () => { const hide = () => {
const tooltip = tooltipEl.value const tooltip = tooltipEl.value
if (!tooltip) if (!tooltip)
return return
tooltip.style.opacity = '0'
tooltip.style.transform = 'scale(0.95)'
tooltip.style.pointerEvents = 'none'
tooltip.style.visibility = 'hidden'
visible.value = false visible.value = false
tooltip.classList.remove('visible')
} }
const toggle = () => { const toggle = () => (visible.value ? hide() : show())
visible.value ? hide() : show()
}
onMounted(() => { onMounted(() => {
createTooltip() createTooltip()
window.addEventListener('scroll', () => {
if (visible.value)
updatePosition()
})
window.addEventListener('resize', () => {
if (visible.value)
updatePosition()
})
})
onBeforeUnmount(() => { const onUpdate = () => {
window.removeEventListener('scroll', () => {
if (visible.value) if (visible.value)
updatePosition() updatePosition()
})
window.removeEventListener('resize', () => {
if (visible.value)
updatePosition()
})
if (tooltipEl.value && tooltipEl.value.parentNode) {
tooltipEl.value.parentNode.removeChild(tooltipEl.value)
} }
window.addEventListener('scroll', onUpdate)
window.addEventListener('resize', onUpdate)
onBeforeUnmount(() => {
window.removeEventListener('scroll', onUpdate)
window.removeEventListener('resize', onUpdate)
if (tooltipEl.value && tooltipEl.value.parentNode) {
tooltipEl.value.parentNode.removeChild(tooltipEl.value)
}
})
}) })
return () => { return () => {
@@ -1,7 +1,6 @@
@use '../_styles/_variables.scss' as *; @use '../_styles/_variables.scss' as *;
.tooltip { .tooltip {
position: absolute;
z-index: 9999; z-index: 9999;
padding: 6px 10px; padding: 6px 10px;
background: #fff; background: #fff;
@@ -9,13 +8,16 @@
color: #333; color: #333;
font-size: 12px; font-size: 12px;
white-space: nowrap; white-space: nowrap;
pointer-events: none;
/* ---------------- 动画 ---------------- */
opacity: 0;
transition: opacity 0.2s ease-in-out;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition:
opacity 0.2s ease,
transform 0.2s ease;
pointer-events: none;
opacity: 0;
transform: scale(0.95);
visibility: hidden;
&.visible { &.visible {
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
@@ -27,30 +29,30 @@
height: 8px; height: 8px;
background: #fff; background: #fff;
transform: rotate(45deg); transform: rotate(45deg);
z-index: 0; }
&.tooltip-top .tooltip-arrow,
&.tooltip-bottom .tooltip-arrow {
left: 50%;
transform: translateX(-50%) rotate(45deg);
}
&.tooltip-left .tooltip-arrow,
&.tooltip-right .tooltip-arrow {
top: 50%;
transform: translateY(-50%) rotate(45deg);
} }
&.tooltip-top .tooltip-arrow { &.tooltip-top .tooltip-arrow {
bottom: -4px; bottom: -4px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
} }
&.tooltip-bottom .tooltip-arrow { &.tooltip-bottom .tooltip-arrow {
top: -4px; top: -4px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
} }
&.tooltip-left .tooltip-arrow { &.tooltip-left .tooltip-arrow {
right: -4px; right: -4px;
top: 50%;
transform: translateY(-50%) rotate(45deg);
} }
&.tooltip-right .tooltip-arrow { &.tooltip-right .tooltip-arrow {
left: -4px; left: -4px;
top: 50%;
transform: translateY(-50%) rotate(45deg);
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ function setLight() {
白色主题 白色主题
</AButton> </AButton>
</div> </div>
<APopup content="2222222222222222222222" trigger="click"> <APopup content="123456" trigger="hover">
<AButton>测试</AButton> <AButton>测试</AButton>
</APopup> </APopup>
</div> </div>