前言
端午节大家粽子可吃的开心?你问我为啥又要自己造轮子?那当然不是闲的蛋疼了。因为不想引入一个大而全的UI库,虽然有些也提供了插件进行按需加载,不过试了下Element-ui和iView,一个和最新版Nuxt在ts模式下有不兼容问题,另一个则影响了全局样式,况且自己的场景很简单,不需要什么复杂的功能,那还是自己撸一个更快点。
定需求
- 只要hover时展示一句提示
- 提供简单几种不同方向的定位
开搞
因为用的.vue后缀格式文件,所以实际上都放在一起的,为了方便说明,分开清晰一些。
第一版
因为很简单,直接上代码吧
模板:
<template>
<div
:class="{ 'm-tooltip': !disabled }"
:data-content="content"
>
<slot />
</div>
</template>
<script>
export default {
props: {
content: String, // 提示内容
disabled: Boolean, // 是否关闭提示
},
}
</script>
样式:
<style lang="scss">
.m-tooltip {
position: relative;
$y-space: 2px;
$bg-color: rgba(70, 76, 91, 0.9);
$shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
@mixin position() {
position: absolute;
left: 50%;
bottom: 100%;
transform: translateX(-50%);
opacity: 0;
pointer-events: none;
}
&::before {
content: attr(content); // 使用自定义属性的内容作为文本,这里直接使用了不标准的content属性
@include position();
line-height: 1.5;
font-size: 12px;
max-width: 250px;
margin-bottom: 8px + $y-space;
padding: 8px 12px;
color: #fff;
text-align: left;
text-decoration: none;
background-color: $bg-color;
border-radius: 4px;
box-shadow: $shadow;
white-space: nowrap;
}
&::after {
content: '';
@include position();
display: inline-block;
width: 8px;
height: 8px;
margin-bottom: $y-space;
border-color: transparent;
border-style: solid;
border-width: 5px 5px 0;
border-top-color: $bg-color;
}
&[right] {
&::before {
transform: translateX(-70%);
}
}
&[left] {
&::before {
transform: translateX(-30%);
}
}
&[show],
&:hover {
&::before,
&::after {
opacity: 100;
pointer-events: unset;
transition: all 0.2s ease-in-out;
}
}
}
</style>
使用方式:
<Tooltip
:disabled="!isLiked"
right
content="您已喜欢过该文章啦~"
>
<div
:class="{ 'c-red': true, active: isLiked }"
@click="like(detail.id)"
>
<i class="iconfont icon-xin" />
</div>
</Tooltip>
是不是很简单?只有两个属性,一个提示内容,另一个是否禁用。还有一个样式上的api,如果传了show属性,则会一直显示,用于手动控制显隐的情况。
样式上参考了iView的,因为懒得自己去画了,还是相信人家设计师的功底的:)。暂时只用到上面的方向,所以通过传left\right属性来控制是否偏左或偏右。
显示效果:
咦,好像哪里不对劲?想了想,原来是样式有问题,hover时内部子元素应该有边框高亮才对。(我就知道事情没那么简单)
这样实现会造成中间加了一层元素,可能会影响原有子类选择器的样式,那怎么把包裹的这一层当做空气一样透明的去掉呢?
第二版
翻了下Vue官网文档,真不错哟,看到了jsx。好家伙,这就是本大爷的地盘了啊,自己控制渲染,嘿嘿嘿,那不是想怎么来就怎么来?
Tooltip进化~~
// 注意这里要把template删掉,不然还是会用模板渲染
<script>
export default {
props: {
disabled: Boolean, // 把其他几个透传的属性去掉,直接用就好了
},
render(createElement) {
const vnodes = this.$slots.default
if (vnodes.length === 1) {
const node = vnodes[0]
if (node.tag === 'div' || node.tag === 'span') {
node.data.class = node.data.class || {}
// 根据disabled控制是否添加样式类
node.data.class['m-tooltip'] = !this.disabled
// 其他属性直接透传到子元素上
node.data.attrs = Object.assign(node.data.attrs || {}, this.$attrs)
}
} else {
console.warn('Tooltip只接受一个子节点')
}
return this.$slots.default[0] // 直接返回第一个子节点
},
}
</script>
中间发现不知道传的自定义属性是在什么位置,比如传了content是在放在子节点哪里呢?于是在子节点上写了一个自定义属性,打印后发现是在node.data.attrs对象里。
效果图:
dom结构:
嗯,这才对嘛,这样就把包裹元素上的属性都移到了子节点上,当然会产生属性覆盖的问题,不过自己用的话,倒是不太担心会有这个问题。至于其他子节点是否支持其他自定义组件,emmm,那就由各位自由发挥了。
样式和使用方式没变化,至于为啥使用没有data-前缀的非标准属性,那当然是因为懒啦,程序猿天生想少打几个字。
好了,自己写一个虽然比较简单,不过也够用了。
后记
嘛,想想现在,想做点自己的小东西吧,总是要用到一堆的框架,还没写代码呢,就开始各种痛苦的配置(你知道我在说什么)和各种库的踩坑。但你说不用吧,那只能翻一翻小的库,至于有多少坑,好不好用,那自然用了才知道啊。
反正就是爱折腾,不仅折腾别人,也折腾自己,哈哈哈。
ps: 这里不支持vue和scss格式的高亮嘛?