自定义 tootip

63 阅读1分钟

html

  <!-- 指示 -->
  <transition name="tooltip">
    <div v-show="tooltipShow" class="zc-tooltip" :style="tooltipStyle">
      <span class="zc-tooltip-text" v-html="text"></span>
      <div
        class="zc-tooltip-arrow"
        :class="[
          { left: placements == 'left' },
          { bottom: placements == 'bottom' },
          { right: placements == 'right' },
          { top: placements == 'top' },
        ]"
      ></div>
    </div>
  </transition>
</template>

<script lang="ts">
export default {
  name: 'Tooltip',
}
</script>
### ts
<script setup lang="ts">
import { ref, computed } from 'vue'

// 显示弹框
const tooltipShow = ref(false)

// 提示内容
const text = ref()

// 方向
const placements = ref('left')

// 显示
function showTip() {
  tooltipShow.value = true
}
function hiddenTip() {
  tooltipShow.value = false
}

// 位置
const tooltipPostiton = ref({
  x: 0,
  y: 0,
})
const tooltipStyle = computed(() => {
  return {
    transform: `translate3d(${tooltipPostiton.value.x}px,${tooltipPostiton.value.y}px,0)`,
  }
})

defineExpose({
  tooltipShow,
  showTip,
  hiddenTip,
  tooltipPostiton,
  tooltipStyle,
  text,
  placements,
})
</script>
// tooltip
.zc-tooltip {
  padding: 10px;
  font-size: 12px;
  // line-height: 1.2;
  width: 180px;
  word-wrap: break-word;
  position: fixed;
  left: 0;
  top: 0;
  background: #303133;
  color: #fff;
  z-index: 1000;
  display: inline-block;
  border-radius: 8px;
  font-weight: 500;
  pointer-events: none;
}

// 小箭头
.zc-tooltip-arrow {
  position: absolute;
  width: 0;
  height: 0;
  border-width: 8px;
  border-style: solid;
}

// 如果在左侧
.zc-tooltip-arrow.left {
  border-color: transparent transparent transparent #303133;
  right: -15px;
  top: 50%;
  transform: translate3d(0, -50%, 0);
}
// 如果在下侧
.zc-tooltip-arrow.bottom {
  top: -15px;
  border-color: transparent transparent #303133 transparent;
  left: 50%;
  transform: translate3d(-50%, 0, 0);
}
// 如果在右侧
.zc-tooltip-arrow.right {
  left: -15px;
  top: 50%;
  transform: translate3d(0, -50%, 0);
  border-color: transparent #303133 transparent transparent;
}
// 如果在上侧
.zc-tooltip-arrow.top {
  bottom: -15px;
  border-color: #303133 transparent transparent transparent;
  left: 50%;
  transform: translate3d(-50%, 0, 0);
}

/* 动画 */
.tooltip-enter-from,
.tooltip-leave-to {
  opacity: 0;
  transition: opacity 0.3s ease;
}
.tooltip-leave-from,
.tooltip-enter-to {
  transition: opacity 0.1s ease;
}
</style>

使用

定义指令

<script lang="ts">
// 引入组件
import { nextTick, createApp } from 'vue'
import Tooltip from './tooltip.vue'
// 清除监听
function clearEvent(el) {
  if (el._tipHandler) {
    el.removeEventListener('mouseenter', el._tipHandler)
  }
  if (el._tipMouseleaveHandler) {
    el.removeEventListener('mouseleave', el._tipMouseleaveHandler)
  }
  delete el._tipHandler
  delete el._tipMouseleaveHandler
  delete el._tipOptions
  delete el._tipInstance
}

// 位置定位
function calculationLocation(el, target, placements) {
  if (!el || !target) return
  el.tooltipPostiton.y = 0
  el.tooltipPostiton.x = 0
  const el_dom = el.$el.nextElementSibling.getBoundingClientRect()
  const target_dom = target.getBoundingClientRect()

  if (placements === 'left') {
    el.tooltipPostiton.x = target_dom.x - el_dom.width - 10
    el.tooltipPostiton.y =
      target_dom.y - el_dom.height / 2 + target_dom.height / 2
  } else if (placements === 'bottom') {
    el.tooltipPostiton.x =
      target_dom.x + target_dom.width / 2 - el_dom.width / 2
    el.tooltipPostiton.y = target_dom.y + el_dom.height + 10
  } else if (placements === 'right') {
    el.tooltipPostiton.x = target_dom.x + target_dom.width + 10
    el.tooltipPostiton.y =
      target_dom.y - el_dom.height / 2 + target_dom.height / 2
  } else if (placements === 'top') {
    el.tooltipPostiton.x =
      target_dom.x + target_dom.width / 2 - el_dom.width / 2
    el.tooltipPostiton.y = target_dom.y - el_dom.height - 10
  }
}

// 方向
const allPlacements = ['left', 'bottom', 'right', 'top']
export default {
  directives: {
    tooltip: {
      mounted(el, binding) {
        clearEvent(el)
        el._tipOptions = binding.value
        el._tipHandler = () => {
          const limitPlacementQueue = allPlacements.filter(
            (placement) => binding.modifiers[placement]
          )
          const placements = limitPlacementQueue.length
            ? limitPlacementQueue
            : allPlacements
          if (!el._tipInstance) {
            el._synopsis = createApp(Tooltip)
            el._root = document.createElement('div')
            document.body.appendChild(el._root)
            // el._root.id = `tooltip_${tokenFun()}`
            el._tipInstance = el._synopsis.mount(el._root)
          }
          el._tipInstance.placements = placements[0]
          el._tipInstance.showTip()
          el._tipInstance.text = el._tipOptions
          nextTick(() => {
            calculationLocation(el._tipInstance, el, placements[0])
          })
          el._scrollHandler = () => {
            if (el._tipInstance.tooltipShow)
              calculationLocation(el._tipInstance, el, placements[0])
          }
          window.addEventListener('scroll', el._scrollHandler)
        }
        el._tipMouseleaveHandler = () => {
          if (el._tipInstance) {
            el._tipInstance.hiddenTip()
          }
        }
        el.addEventListener('mouseenter', el._tipHandler)
        el.addEventListener('mouseleave', el._tipMouseleaveHandler)
      },
      updated(el, binding) {
        el._tipOptions = binding.value
      },
      unmounted(el) {
        if (el._tipInstance) {
          el._synopsis.unmount()
          document.body.removeChild(el._root)
        }
        window.removeEventListener('scroll', el._scrollHandler)
      },
    },
  },
}
</script>
### htm显示
 <span v-tooltip.top="显示的内容">
                <QuestionFilled />
              </span>