手写一个类似ElementUI的tooltip组件。

425 阅读2分钟
  • toolTip很常见,其实就是一个方框,为了更好的显示自己的内容。
  • 并且写这个的方式有很多,可以使用css伪类来实现,也可以使用自己创建一个div绝对定位来实现。
  • 后来因为在写项目中遇到一些bug: 自己写的绝对定位的div充当的tooltip,把自己的box盒子撑大了(因为你的绝对定位虽然针对你的父级,不占用你的父级的空间,但是你的父级的内容却也会变大,撑开他的父级的空间),然后就变得很尴尬,
  • 后来参考elementUI等一些UI框架的写法,发现他们制作的tooltip, select等等,都是创作在body里的,这样就可以规避,所有空间被撑大的问题都被规避了。
  • 但是当时不知道怎么做到的,后来随着不断的学习,发现其实很简单,就是使用 slotdirectives实现的。现在我们就简单做一个 超出box宽度,文字显示...,并且toolTip 显示完整文字

toolTip.vue

<template>
    <div class="tooltip-container" v-tooltipShow="contentStr">
        <slot></slot>
    <div>
<template/>
<script>
export default {
    name:'tooltip',
    props: {
        content: {
            type:String,
            required:true
        }
    },
    computed: {
        contentStr() {
            return this.content;
        }
    },
    directives: {
        tooltipShow: {
            inserted (el,binding) {
                // 创建一个tootip Dom
                const tooltipDom = document.createElement('div');
                toolTipDom.innerText = binding.value;
                toolTipDom.setAttribute('class', 'tool-tip-cus');
                toolTipDom.display = 'none';
                toolTipDom.position = 'absolute';
                toolTipDom['z-index'] = '10000';
                toolTipDom.style[''max-width'] = '500px';
                toolTipDom.style['background-color'] = '#fff';
                toolTipDom.style['word-break'] = 'break-all';
                toolTipDom.style.border = '1px solid #eee';
                toolTipDom.style['border-radius'] = '8px';
                toolTipDom.style.padding = '4px';
                toolTipDom.style['font-size'] = '12px';
                toolTipDom.style.color = '#616161';
                toolTipDom.style.boxShadow = '0px 3px 10px rgba(0,0,0,0.1)';
                el.___Mouseover__ = () => {
                    const {bottom,left,width} = el.getBoundingClientRect();
                    if(width > el.scrollWidth) return;
                    toolTipDom.style.display = 'block';
                    const { width: tooltipWidth } = toolTipDom.getBoundingClientRect();
                    toolTipDom.style.left = left + width / 2 - (tooltipWidth / 2) + 'px';
                    toolTipDom.style.top = bottom + 4 + 'px';
                };
                el.__Mouseout__ = () => {
                    toolTipDom.style.display = 'none';
                };
                el.__tooltipDom = toolTipDom;
                el.addEventListener('mouseover', el.__Mouseover__);
                el.addEventListener('mouseout', el.__Mouseout__);
                document.body.appendChild(el.__tooltipDom);
            },
            unbind (el) {
                // 解绑时,去除监听事件和dom
                el.removeEventListener('mouseover', el.__Mouseover__);
                el.removeEventListener('mouseout', el.__Mouseout__);
                document.body.removeChild(el.__tooltipDom);
            }
        }
    }
}
<script/>

view.vue 使用

...
<tool-tip :content="contentText">
    <div class="file-box">
        {{contentText}}
    </div>
</tool-tip>
...
export defualt {
    components: {
        tooltip: () => import('./tooltip.vue')
    }
}
<style scoped lang="scss">
.file-box {
    width: 100px;
    display:block;
    overflow:hidden;
    text-overflow:ellipsis;
    white-space: nowrap;
}
<style/>
  • 需要注意的点:我们为了图省事,使用el.scrollWidth来判断是否box内容超出了box的width。但是el.scrollWidth是精确到整数位的,但是css判断是否出现...是精确到小数位的,所以存在一些误差,最精准的做法是,模拟一个和[file-box]一摸一样的dom, 添加完文字使其完全撑开后,使用dom.getBoundingClientRect().width(精确到小数位的dom宽度)与原先的[file-box]的clientWidth来对比,就可以精准的计算文字是否超出了box的宽度。