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>