The Tooltip component is usually used to interpret elements on a page, and I think it is usually used to quickly interpret objects that can be summarized in a nutshell, which is lightweight.
There is a UI component library
First of all, I would like to pay tribute to my predecessors, who have provided a lot of inspiration for my component packaging. 🖖🖖🖖(salute)
However, some mainstream UI libraries, such as ElementUI and Bootstrap, require component packages for Tooltip components, which I find troublesome, so TODAY I use vue3.x custom instructions to package a simple Tooltip component.
Effect and usage
As shown in figure,Tooltip
Yes support similari18n
This may update parameters dynamically, and is much easier to use:
<template>
<! Create tooltip tooltip tooltip tooltip tooltip tooltip tooltip
<button v-tooltip.left="t('like')">give a like<button>
</template>
<script>
import { useI18n } from 'vue-i18n'
export default {
setup(){
const { t } = useI18n();
return {
t
}
}
}
</script>
<! The i18N single file mode used here is not explained here, if you have any questions can be discussed in the comment section.
<i18n>{" zh_CN ": {" like" : "three even a key"}, "en_US" : {" like ":" like me "}}</i18n>
Copy the code
Other positions
<button v-tooltip.left="' Displayed on the left '">give a like<button>
<button v-tooltip.right="' Displayed on the right '">give a like<button>
<button v-tooltip.top="' Shown on top '">give a like<button>
<button v-tooltip.bottom="' Shown below '">give a like<button>
Copy the code
Directory format
Tooltip | | - directive. Js / / registered here custom instructions & positioning tooltip | | - tooltip. Vue / / write tooltip template hereCopy the code
tooltip.vue
There is nothing to say here, but define templates that expose properties that can be used to control positions.
<template>
<! - instructions -- -- >
<div class="zc-tooltip" v-show="tooltipShow" :style="tooltipStyle">
<! -- Directive content -->
<span class="zc-tooltip-text" v-html="text"></span>
<! -- Small arrow -->
<div class="zc-tooltip-arrow" :class="[{'left':placements=='left'}, {'bottom':placements=='bottom'}, {'right':placements=='right'}, {'top':placements=='top'}]"></div>
</div>
</template>
<script>
import {ref, computed} from 'vue'
export default {
setup(){
// Display the popbox
const tooltipShow = ref(false);
// Prompt content
const text = ref()
// Display direction
const placements = ref('left')
/ / show
function showTip(){
tooltipShow.value = true
}
/ / hide
function hiddenTip(){
tooltipShow.value = false
}
/ / position
const tooltipPostiton = ref({
x: 0.y: 0
})
const tooltipStyle = computed(() = >{
return {
transform: `translate3d(${tooltipPostiton.value.x}px,${tooltipPostiton.value.y}px,0)`}})return {
tooltipShow,
showTip,
hiddenTip,
tooltipPostiton,
tooltipStyle,
text,
placements,
}
}
}
</script>
Copy the code
🌟🌟🌟 Solve the problem that if the mouse moves over the edge of the Tooltip, 🌟 port 🌟 may flash
The reason for the flash is simple:
Graph TB A[mouse over button] --> B[display Tooltip] --> C{mouse over button edge} C== yes ==>D Hover over the Tooltip] - > [button loses focus] -- > G F [Tooltip disappear] = = = = > infinite loop A C = = n = = > E [normal display]
The most common way to prevent jitter is to give a delay of about 150ms, but TODAY I am more simple and violent. CSS has a property pointers-events: Specifies under what circumstances (if any) a particular element can become a mouse event. If set to None, the browser ignores mouse events for the current element.
/* causes the element to ignore mouse events */
.zc-tooltip{...pointer-events: none;
}
Copy the code
directive.js
Directive.js mainly registers directives. Note the difference in component registration between vue3 and vue2.
export default {
install(app) {
// The lifecycle hooks here are different from vue2
app.directive('tooltip', {
mounted(el, binding){... },updated(el, binding){... },unmounted(el){... }}}}Copy the code
mounted()
In Mounted we mainly:
- create
Tooltip
Instance and mount to the page. - Receive the prompt content and direction from the command.
- Evaluates based on the bound element
Tooltip
Position on the page.
// Control the direction
const allPlacements = ['left'.'bottom'.'right'.'top']...mounted(el, binding) {
// Get the prompt content
el._tipOptions = binding.value
// When the mouse moves over the target element
el._tipHandler = () = > {
Get the correct display direction from the command modifiers
const limitPlacementQueue = allPlacements.filter(placement= > binding.modifiers[placement])
const placements = limitPlacementQueue.length ? limitPlacementQueue : allPlacements
// If no instance exists
if(! el._tipInstance) {// Create a tooltip instance
el._synopsis = createApp(tooltip)
// Create root element
el._root = document.createElement('div')
// Mount to the page
document.body.appendChild(el._root)
el._tipInstance = el._synopsis.mount(el._root)
}
// Set the orientation of the tooltip display
el._tipInstance.placements = placements[0]
// Make the tooltip display
el._tipInstance.showTip()
// Set the tooltip display
el._tipInstance.text = el._tipOptions
nextTick(() = > {
// Calculate the position of the tooltip on the page
calculationLocation(el._tipInstance, el, placements[0])})// When a scroll event is sent
el._scrollHandler = () = > {
if (el._tipInstance.tooltipShow)
// Reposition the position
calculationLocation(el._tipInstance, el, placements[0])}// Add page scroll listener
window.addEventListener('scroll', el._scrollHandler)
}
// Mouse over the target element
el._tipMouseleaveHandler = () = > {
if (el._tipInstance) {
// Make tooltip hidden
el._tipInstance.hiddenTip()
}
}
// Add mouse listener to the target element
el.addEventListener('mouseenter', el._tipHandler)
el.addEventListener('mouseleave', el._tipMouseleaveHandler)
},
Copy the code
updated()
In the updated, we mainly update the prompt content to cope with possible parameter changes.
updated(el, binding) {
// Update the prompt
el._tipOptions = binding.value
},
Copy the code
unmounted()
Unmounted we mainly:
- uninstall
Tooltip
Instance. - Remove each listening event.
unmounted(el) {
if (el._tipInstance) {
// Uninstall the Tooltip instance.
el._synopsis.unmount()
document.body.removeChild(el._root)
}
// Remove all listener events
window.removeEventListener('scroll', el._scrollHandler)
}
Copy the code
The source code to show
tooltip.vue
<template>
<! - instructions -- -- >
<transition name="tooltip">
<div class="zc-tooltip" v-show="tooltipShow" :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>
import {ref, computed} from 'vue'
export default {
setup(){
// Display the popbox
const tooltipShow = ref(false);
// Prompt content
const text = ref()
/ / direction
const placements = ref('left')
/ / show
function showTip(){
tooltipShow.value = true
}
function hiddenTip(){
tooltipShow.value = false
}
/ / position
const tooltipPostiton = ref({
x: 0.y: 0
})
const tooltipStyle = computed(() = >{
return {
transform: `translate3d(${tooltipPostiton.value.x}px,${tooltipPostiton.value.y}px,0)`}})return {
tooltipShow,
showTip,
hiddenTip,
tooltipPostiton,
tooltipStyle,
text,
placements,
}
}
}
</script>
<style lang="scss" scoped>
// tooltip
.zc-tooltip{
padding: 10px;
font-size: 12px;
line-height: 1.2;
min-width: 10px;
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; } // small arrow.zc-tooltip-arrow{
position: absolute;
width: 0;
height: 0;
border-width: 8px;
border-style: solid; } // If on the left.zc-tooltip-arrow.left{
border-color: transparent transparent transparent # 303133;
right: -15px;
top: 50%;
transform: translate3d(0, -50%.0); } // If on the lower side.zc-tooltip-arrow.bottom{
top: -15px;
border-color: transparent transparent # 303133 transparent;
left: 50%;
transform: translate3d(-50%.0.0); } // If it is on the right.zc-tooltip-arrow.right{
left: -15px;
top: 50%;
transform: translate3d(0, -50%.0);
border-color: transparent # 303133transparent transparent; } // If on the top side.zc-tooltip-arrow.top{
bottom: -15px;
border-color: # 303133 transparent transparent transparent;
left: 50%;
transform: translate3d(-50%.0.0);
}
/ * * / animation
.tooltip-enter-from..tooltip-leave-to{
opacity: 0;
transition: opacity .3s ease;
}
.tooltip-leave-from..tooltip-enter-to{
transition: opacity .1s ease;
}
</style>
Copy the code
directive.js
// Import components
import { nextTick, createApp } from "vue";
import tooltip from './tooltip.vue'
import { tokenFun } from '.. /.. /utils/token'
// Clear the listener
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
}
// The location is fixed
function calculationLocation(el, target, placements) {
if(! el || ! target)return;
el.tooltipPostiton.y = 0;
el.tooltipPostiton.x = 0;
let el_dom = el.$el.nextElementSibling.getBoundingClientRect()
let 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}}/ / direction
const allPlacements = ['left'.'bottom'.'right'.'top']
export default {
install(app) {
app.directive('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)
}
})
}
}
Copy the code
token.js
Used to assign a unique ID to each tooltip and can be ignored
function node() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
export function tokenFun() {
return (node() + node() + node());
}
Copy the code
main.js
import { createApp } from 'vue'
import App from './App.vue'
/ / install the tooltip
import tooltip from './components/tooltip/directive'
const app = createApp(App)
app.use(tooltip).mount('#app')
Copy the code
Quick to use
<button v-tooltip.left="' Displayed on the left '">O praise<button>
<button v-tooltip.right="' Displayed on the right '">O praise<button>
<button v-tooltip.top="' Shown on top '">O praise<button>
<button v-tooltip.bottom="' Shown below '">O praise<button>
Copy the code
Urls you might use
i18n
Vue3 instruction lifecycle hook
Places to expand
v-tooltip.left=”Obejct”: The tooltip component in this article has a lot to add. For example, you can pass in data of type Obejct to configure more projects by checking whether the type of binding.value is a string or an object in directive.js.
The Tooltip component designed in this article creates a unique display of tooltip elements after the first hover over the target element, and the next hover simply updates the existing tooltip for maximum performance.
Of course, you can change billions of bits of code so that the page displays only one tooltip at a time.
Your likes are my biggest motivation, and if you have any better suggestions for tooltip component optimization, feel free to comment in the comments section.