diff --git a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx index 270ea8d7..2a63ff4c 100644 --- a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx @@ -1,3 +1,4 @@ +// Tooltip.tsx import type { PropType } from 'vue' import { cloneVNode, defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue' @@ -21,27 +22,46 @@ export default defineComponent({ const createTooltip = () => { const el = document.createElement('div') - el.className = `tooltip tooltip-${props.placement}` // 初始隐藏,不加 visible - el.innerHTML = `${props.content}
` + el.className = `tooltip tooltip-${props.placement}` + + 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) 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 target = targetEl.value const tooltip = tooltipEl.value - if (!target || !tooltip) + const target = getTargetDom() + if (!tooltip || !target) return - let dom: HTMLElement | null = null - 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 rect = target.getBoundingClientRect() const tooltipRect = tooltip.getBoundingClientRect() const scrollX = window.scrollX || window.pageXOffset const scrollY = window.scrollY || window.pageYOffset @@ -75,53 +95,51 @@ export default defineComponent({ const show = () => { const tooltip = tooltipEl.value - if (!tooltip || !targetEl.value) + if (!tooltip || !getTargetDom()) return - visible.value = true + tooltip.style.visibility = 'visible' + tooltip.style.pointerEvents = 'auto' + nextTick(() => { updatePosition() - tooltip.classList.add('visible') // CSS 控制动画 + tooltip.style.opacity = '1' + tooltip.style.transform = 'scale(1)' }) + visible.value = true } const hide = () => { const tooltip = tooltipEl.value if (!tooltip) return - + tooltip.style.opacity = '0' + tooltip.style.transform = 'scale(0.95)' + tooltip.style.pointerEvents = 'none' + tooltip.style.visibility = 'hidden' visible.value = false - tooltip.classList.remove('visible') } - const toggle = () => { - visible.value ? hide() : show() - } + const toggle = () => (visible.value ? hide() : show()) onMounted(() => { createTooltip() - window.addEventListener('scroll', () => { - if (visible.value) - updatePosition() - }) - window.addEventListener('resize', () => { - if (visible.value) - updatePosition() - }) - }) - onBeforeUnmount(() => { - window.removeEventListener('scroll', () => { + const onUpdate = () => { if (visible.value) 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 () => { diff --git a/code/base-project/base-vue/packages/ao-design/src/popup/tooltip.scss b/code/base-project/base-vue/packages/ao-design/src/popup/tooltip.scss index b80994fd..7bc9393c 100644 --- a/code/base-project/base-vue/packages/ao-design/src/popup/tooltip.scss +++ b/code/base-project/base-vue/packages/ao-design/src/popup/tooltip.scss @@ -1,7 +1,6 @@ @use '../_styles/_variables.scss' as *; .tooltip { - position: absolute; z-index: 9999; padding: 6px 10px; background: #fff; @@ -9,13 +8,16 @@ color: #333; font-size: 12px; 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); + transition: + opacity 0.2s ease, + transform 0.2s ease; + pointer-events: none; + opacity: 0; + transform: scale(0.95); + visibility: hidden; + &.visible { opacity: 1; pointer-events: auto; @@ -27,30 +29,30 @@ height: 8px; background: #fff; 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 { bottom: -4px; - left: 50%; - transform: translateX(-50%) rotate(45deg); } - &.tooltip-bottom .tooltip-arrow { top: -4px; - left: 50%; - transform: translateX(-50%) rotate(45deg); } - &.tooltip-left .tooltip-arrow { right: -4px; - top: 50%; - transform: translateY(-50%) rotate(45deg); } - &.tooltip-right .tooltip-arrow { left: -4px; - top: 50%; - transform: translateY(-50%) rotate(45deg); } } diff --git a/code/base-project/base-vue/src/App.vue b/code/base-project/base-vue/src/App.vue index 4f34980b..60646ea1 100644 --- a/code/base-project/base-vue/src/App.vue +++ b/code/base-project/base-vue/src/App.vue @@ -21,7 +21,7 @@ function setLight() { 白色主题 - + 测试