zhb 1 week ago
parent
commit
5148d8c899
2 changed files with 264 additions and 112 deletions
  1. 0 48
      static/scss/global/global.scss
  2. 264 64
      utils/useMouseTooltip.js

+ 0 - 48
static/scss/global/global.scss

@@ -2112,52 +2112,4 @@ uni-content.collapsed {
 }
 }
 .uni-picker-container{
 .uni-picker-container{
   z-index: 11201!important;
   z-index: 11201!important;
-}
-/* 基础样式 */
-.cursor-pointer {
-  position: relative;
-  cursor: pointer;
-}
-
-@media screen and (min-width: 768px) {
-  .cursor-pointer[data-tooltip]::after {
-    content: attr(data-tooltip);
-    position: fixed !important; /* 强制固定定位,相对视口 */
-    display: inline-block;
-    min-width: 20px;
-    width: max-content;
-    height: max-content;
-    overflow: visible !important; /* 不被裁 */
-    background-color: rgba(var(--bs-body-bg-rgb), 1) !important;
-    border-radius: px2rpx(8);
-    color: var(--bs-emphasis-color);
-    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
-    font-size: px2rpx(13);
-    text-align: left;
-    line-height: px2rpx(20);
-    padding: px2rpx(6);
-    white-space: nowrap;
-    /* 🔥 拉到最高级,覆盖绝大多数弹窗/模态框 */
-    z-index: 2147483647 !important; /* 最大安全整数 */
-    pointer-events: none;
-    left: calc(var(--mouse-x, 0px) + 15px);
-    top: calc(var(--mouse-y, 0px) + 15px);
-    opacity: 0;
-    visibility: hidden;
-    transform: scale(0.55) translate3d(0, 4px, 0);
-    transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 1);
-  }
-
-  .cursor-pointer[data-tooltip]:hover::after {
-    opacity: 1;
-    visibility: visible;
-    transform: scale(1) translate3d(0, 0, 0);
-  }
-
-  .cursor-pointer1[data-tooltip]::after {
-    // position: absolute ;
-    // left: 0;
-    // top: 150%;
-    // z-index: 2147483647 !important;
-  }
 }
 }

+ 264 - 64
utils/useMouseTooltip.js

@@ -1,98 +1,298 @@
-let t = null
-let r = null
-let l = null
+// useMouseTooltip.js
+
+let tooltipEl = null
+let currentTarget = null
+let isInitialized = false
+
+let moveHandler = null
+let overHandler = null
+let outHandler = null
+
+let rafId = null
+
+let showTimer = null
+let hideTimer = null
+
+let lastMouseX = 0
+let lastMouseY = 0
 
 
 export function useMouseTooltip() {
 export function useMouseTooltip() {
-    const TOOLTIP_WIDTH = 80
-    const TOOLTIP_HEIGHT = 40
     const PADDING = 16
     const PADDING = 16
+    const OFFSET = 15
+
+    const SHOW_DELAY = 300
+    const HIDE_DELAY = 80
+
+    function createTooltip() {
+        if (tooltipEl) return
+
+        tooltipEl = document.createElement('div')
+        tooltipEl.className = 'cursor-pointer-tooltip'
+
+        tooltipEl.style.cssText = `
+            position: fixed;
+            z-index: 2147483647;
+            pointer-events: none;
+
+            left: 0;
+            top: 0;
+
+            background: rgba(var(--bs-body-bg-rgb),1);
+            color: var(--bs-emphasis-color);
+
+            border-radius: .5rem;
+            box-shadow: 0 0 10px rgba(0,0,0,.1);
+
+            font-size: .8125rem;
+            line-height: 1.4;
+            padding: .375rem .5rem;
+
+            white-space: nowrap;
+
+            opacity: 0;
+            visibility: hidden;
+
+            transform: scale(.85);
+
+            transition:
+                opacity .18s ease,
+                transform .18s ease;
+
+            will-change:
+                opacity,
+                transform,
+                left,
+                top;
+        `
+
+        document.body.appendChild(tooltipEl)
+    }
+
+    function getTooltipSize() {
+        if (!tooltipEl) {
+            return {
+                width: 0,
+                height: 0
+            }
+        }
+
+        const rect = tooltipEl.getBoundingClientRect()
+
+        return {
+            width: rect.width,
+            height: rect.height
+        }
+    }
+
+    function updatePosition(clientX, clientY) {
+        if (!tooltipEl) return
 
 
-    const updateTooltipPosition = (clientX, clientY) => {
         const viewportWidth = window.innerWidth
         const viewportWidth = window.innerWidth
         const viewportHeight = window.innerHeight
         const viewportHeight = window.innerHeight
 
 
-        let x = clientX
-        let y = clientY
-        let flipX = false
-        let flipY = false
+        const { width, height } = getTooltipSize()
 
 
-        if (clientX + TOOLTIP_WIDTH + PADDING > viewportWidth) {
-            x = clientX - TOOLTIP_WIDTH - PADDING
-            flipX = true
+        let x = clientX + OFFSET
+        let y = clientY + OFFSET
+
+        if (x + width > viewportWidth - PADDING) {
+            x = clientX - width - OFFSET
         }
         }
 
 
-        if (clientY + TOOLTIP_HEIGHT + PADDING > viewportHeight) {
-            y = clientY - TOOLTIP_HEIGHT - PADDING
-            flipY = true
+        if (y + height > viewportHeight - PADDING) {
+            y = clientY - height - OFFSET
         }
         }
 
 
         x = Math.max(PADDING, x)
         x = Math.max(PADDING, x)
         y = Math.max(PADDING, y)
         y = Math.max(PADDING, y)
 
 
-        document.documentElement.style.setProperty('--mouse-x', x + 'px')
-        document.documentElement.style.setProperty('--mouse-y', y + 'px')
-        document.documentElement.style.setProperty('--tooltip-flip-x', flipX ? 'true' : 'false')
-        document.documentElement.style.setProperty('--tooltip-flip-y', flipY ? 'true' : 'false')
+        tooltipEl.style.left = `${x}px`
+        tooltipEl.style.top = `${y}px`
     }
     }
 
 
-    const u = (e, instant = false) => {
-        if (instant) {
-            updateTooltipPosition(e.clientX, e.clientY)
+    function showTooltip(e, target) {
+        const text = target.dataset.tooltip
+
+        if (!text) return
+
+        createTooltip()
+
+        if (hideTimer) {
+            clearTimeout(hideTimer)
+            hideTimer = null
         }
         }
 
 
-        clearTimeout(t)
-        t = setTimeout(() => {
-            updateTooltipPosition(e.clientX, e.clientY)
-        }, 200)
+        if (showTimer) {
+            clearTimeout(showTimer)
+            showTimer = null
+        }
+
+        currentTarget = target
+
+        tooltipEl.textContent = text
+
+        updatePosition(
+            e.clientX,
+            e.clientY
+        )
+
+        tooltipEl.style.opacity = '0'
+        tooltipEl.style.visibility = 'hidden'
+        tooltipEl.style.transform = 'scale(.85)'
+
+        showTimer = setTimeout(() => {
+            if (currentTarget !== target) return
+
+            tooltipEl.style.visibility = 'visible'
+
+            requestAnimationFrame(() => {
+                tooltipEl.style.opacity = '1'
+                tooltipEl.style.transform = 'scale(1)'
+            })
+        }, SHOW_DELAY)
     }
     }
 
 
-    const c = () => {
-        clearTimeout(t)
-        document.documentElement.style.removeProperty('--mouse-x')
-        document.documentElement.style.removeProperty('--mouse-y')
-        document.documentElement.style.removeProperty('--tooltip-flip-x')
-        document.documentElement.style.removeProperty('--tooltip-flip-y')
+    function hideTooltip() {
+        if (!tooltipEl) return
+
+        if (showTimer) {
+            clearTimeout(showTimer)
+            showTimer = null
+        }
+
+        if (hideTimer) {
+            clearTimeout(hideTimer)
+        }
+
+        hideTimer = setTimeout(() => {
+            tooltipEl.style.opacity = '0'
+            tooltipEl.style.transform = 'scale(.85)'
+
+            setTimeout(() => {
+                if (!tooltipEl) return
+
+                tooltipEl.style.visibility = 'hidden'
+            }, 180)
+
+            currentTarget = null
+        }, HIDE_DELAY)
     }
     }
 
 
-    const i = () => {
-        // 鼠标移动 → 节流更新
-        r = (e) => {
-            if (e.target.closest('.cursor-pointer')) {
-                u(e)
-            }
+    function init() {
+        if (isInitialized) return
+
+        isInitialized = true
+
+        moveHandler = (e) => {
+            if (!currentTarget) return
+
+            lastMouseX = e.clientX
+            lastMouseY = e.clientY
+
+            if (rafId) return
+
+            rafId = requestAnimationFrame(() => {
+                rafId = null
+
+                updatePosition(
+                    lastMouseX,
+                    lastMouseY
+                )
+            })
         }
         }
 
 
-        // 鼠标进入新元素 → 🔥 立刻刷新位置(解决你说的问题)
-        const enter = (e) => {
-            if (e.target.closest('.cursor-pointer')) {
-                u(e, true) // 立刻更新
-            }
+        overHandler = (e) => {
+            const target = e.target.closest('.cursor-pointer')
+
+            if (!target) return
+
+            if (target === currentTarget) return
+
+            showTooltip(e, target)
         }
         }
 
 
-        // 鼠标离开 → 延迟清除
-        l = (e) => {
-            if (e.target.closest('.cursor-pointer')) {
-                clearTimeout(t)
-                t = setTimeout(() => {
-                    c()
-                }, 100)
+        outHandler = (e) => {
+            const target = e.target.closest('.cursor-pointer')
+
+            if (!target) return
+
+            if (target !== currentTarget) return
+
+            // 在当前元素内部移动
+            if (
+                e.relatedTarget &&
+                target.contains(e.relatedTarget)
+            ) {
+                return
             }
             }
+
+            hideTooltip()
         }
         }
 
 
-        document.addEventListener('mousemove', r, true)
-        document.addEventListener('mouseenter', enter, true) // 新增
-        document.addEventListener('mouseout', l, true)
-    }
+        document.addEventListener(
+            'mouseover',
+            overHandler,
+            true
+        )
+
+        document.addEventListener(
+            'mouseout',
+            outHandler,
+            true
+        )
 
 
-    const d = () => {
-        clearTimeout(t)
-        c()
-        if (r) document.removeEventListener('mousemove', r, true)
-        if (l) document.removeEventListener('mouseout', l, true)
-        r = null
-        l = null
+        document.addEventListener(
+            'mousemove',
+            moveHandler,
+            true
+        )
     }
     }
 
 
-    i()
-    return d
+    function cleanup() {
+        if (rafId) {
+            cancelAnimationFrame(rafId)
+            rafId = null
+        }
+
+        if (showTimer) {
+            clearTimeout(showTimer)
+            showTimer = null
+        }
+
+        if (hideTimer) {
+            clearTimeout(hideTimer)
+            hideTimer = null
+        }
+
+        currentTarget = null
+
+        if (moveHandler) {
+            document.removeEventListener(
+                'mousemove',
+                moveHandler,
+                true
+            )
+        }
+        if (overHandler) {
+            document.removeEventListener(
+                'mouseover',
+                overHandler,
+                true
+            )
+        }
+        if (outHandler) {
+            document.removeEventListener(
+                'mouseout',
+                outHandler,
+                true
+            )
+        }
+        moveHandler = null
+        overHandler = null
+        outHandler = null
+
+        isInitialized = false
+    }
+    init()
+    return cleanup
 }
 }