elementui源码学习之仿写一个el-tooltip

本篇文章记录仿写一个el-tooltip组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节。本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件。源码在github上,大家可以拉下来,npm start运行跑起来,结合注释有助于更好的理解。github仓库地址如下:
https://github.com/shuirongsh...
前言 什么是编程
关于什么是编程这个问题,的确有很多答案。在很久以前,在笔者刚入行的时候,被告知了这样一句话:
编程就是:规则的学习,规则的使用,规则的理解、规则的自定义
有一定的道理...
背景介绍
我们在做组件的封装时,常常会遇到一些“弹框组件”,以饿了么UI为例,比如:el-tooltip组件el-popover组件el-popconfirm组件el-dropdown组件等,这类组件在操作的时候,常常会有一个弹框出现,对于这些弹框的触发条件(或悬浮、或点击)以及位置的控制(上方、下方、左侧、右侧)等,vue团队专门封装了一个vue-popper组件,通过props传参以及一些事件方法的方式,去控制以达到我们想要的效果
那么,vue-popper组件是如何实现的呢?底层原理是啥?是把popper.js这个很优秀的库做了一层封装
那么,popper.js是如何实现的呢?底层原理是啥?是通过js控制弹出框dom的位置
由于popper.js国内资料不多,所以大家可以直接使用vue-popper组件组件去做一些操作即可,毕竟其底层原理,也是prpper.js
  • el-tooltip组件是使用了vue-popper组件的规则
  • vue-popper组件是使用了popper.js库的规则
  • popper.js库是使用了js和dom的规则
  • 无限规则套娃...
附上传送门
prpper.js 官网:https://popper.js.org/、中文...
感兴趣的道友,可以空闲时间研究研究(像elementUIiviewBootstrapMaterial UI等都用到了proper.js)也是做的二次封装
【elementui源码学习之仿写一个el-tooltip】另:prpper.js团队专门给react写了一套React Poppervue暂时没有,所以咱们就学习vue-popper
本篇文章着眼于,中层底层原理vue-popper组件,让我们开始学习吧
tooltip组件思考 什么是tooltip组件
  1. tooltip组件是用来做简单的文字附带说明(提示)的气泡框组件
  2. 一般交互是鼠标移入显示,鼠标移出消失
  3. tooltip组件一般不会做复杂的交互操作,以及承载过多的文本内容
  4. 可以理解为是dom元素title属性功能的具体补充
tooltip组件需求
  1. 暗黑模式tooltip,黑底白字
  2. 高亮模式tooltip,白底黑字
  3. tooltip组件的位置,在指向引用reference元素的那个方向,一般是上下左右,拓展共有12个方向
  4. tooltip的小三角形(一般是显示的)
  5. 可控制关闭开启,即符合条件hover展示,反之hover关闭
  6. 一般情况下tooltip都是单行内容,若内容过多,支持文字换行乃至自定义tooltip一些样式(支持插槽)
  7. 至于其他的需求如:tooltip显示展开的过渡动画、小箭头是否可以隐藏、以及偏移量offset、延迟出现消失等,一般情况下不会怎么更改,所以本文着眼于重点常见需求,来进行说明
在使用库或者一些基础组件之前,我们先尝试一下,手写一下
一个简单的tooltip的demo 主要是使用属性选择器去控制,四个方向的tooltip和三角形小箭头。
标签的whichPlacement属性值为"top"时,就让其在上方,为left时,就让其在左侧,其他方位同理
demo效果图
elementui源码学习之仿写一个el-tooltip
文章图片

demo代码
复制粘贴即可使用
Documentbody { box-sizing: border-box; padding: 60px 240px; }/* 设置基本样式 */ .item { width: fit-content; box-sizing: border-box; padding: 12px; border: 2px solid #aaa; /* 搭配伪元素,用相对定位 */ position: relative; }/* 使用伪元素创建tooltip */ .item::after { /* 内容为 使用 tooltipContent的属性值 */ content: attr(tooltipContent); position: absolute; background-color: #000; width: fit-content; height: auto; padding: 6px 12px; color: #fff; border-radius: 12px; /* 文字不换行 */ word-break: keep-all; display: none; }/* 使用伪元素创建小三角形 */ .item::before { content: ""; position: absolute; border-width: 6px 6px 0 6px; border-style: solid; border-color: transparent; border-top-color: black; display: none; }/* 上下左右四个方位,使用css的属性选择器控制tooltip和小三角形 */ /* 当whichPlacement的属性值为top时,做...样式 */ /* 上方 */ [whichPlacement='top']::after { left: 50%; transform: translateX(-50%); top: -100%; }[whichPlacement='top']::before { top: -26%; left: 50%; transform: translateX(-50%); }/* 下方 */ /* 当whichPlacement的属性值为bottom时,做...样式 */ /* 关于四个方向的小三角形,可以使用旋转更改即可 */ [whichPlacement='bottom']::after { left: 50%; transform: translateX(-50%); bottom: -100%; }[whichPlacement='bottom']::before { bottom: -28%; left: 50%; transform: rotate(180deg); }/* 左侧 */ /* 当whichPlacement的属性值为left时,做...样式 */ [whichPlacement='left']::after { top: 50%; transform: translateY(-50%); right: 108%; }[whichPlacement='left']::before { top: 50%; transform: translateY(-50%) rotate(270deg); left: -10.5px; }/* 右侧 */ /* 当whichPlacement的属性值为right时,做...样式 */ [whichPlacement='right']::after { top: 50%; transform: translateY(-50%); left: 108%; }[whichPlacement='right']::before { top: 50%; transform: translateY(-50%) rotate(90deg); right: -10px; }.item:hover::after { display: block; }.item:hover::before { display: block; } 悬浮上方
悬浮下方


悬浮左侧
悬浮右侧

关于css属性选择器和attr()函数
上述代码中用到了属性选择器和attr()函数,这里简单的提一下
属性选择器
问:什么是属性选择器?
答1:通过选取带有指定标签属性的dom元素,进行样式的设置
答2:通过标签的属性名key和属性值value来匹配元素,从而进行样式的设置
问:举个例子呗
答:
  • [attr] 匹配所有具有attr属性的元素,不用管其值是什么,如:input[type]{ ... },意为:只要input标签中,包含type属性(忽略type属性值),都选中,并设置 ... 样式
  • [attr='val'] 匹配所有attr属性值等于val,完全精准匹配。如:input[type='text']{ ... },意为:只要input标签中,有type属性,且属性值为text,才去选中,并匹配 ... 样式
  • [attr^='val']匹配所有attr属性值以val开头的(上述demo案例中就用到了,只不过其属性是我们自定义的)。模糊匹配
  • [attr$='val'],同上类似,^=是以什么什么开头匹配,$=是以什么什么结尾匹配。模糊匹配
  • [attr*='val'],同上类似,*=是只要包含即可,也是模糊匹配
详见官方属性选择器介绍:https://www.w3school.com.cn/c...
attr()函数
attrattribute单词属性的缩写,顾名思义,所以这个东西和属性有关
  • css的函数attr()可获取被选中元素的属性值,并且在样式文件中使用。可用在伪元素里,在伪类元素里使用,它得到的是伪元素的原始元素的值。
  • attr()函数可以和任何css属性一起使用,但是除了content外,其余都还是试验性的,所以建议:除了搭配伪元素的content别的都不要用
如上述案例:
悬浮上方.item::after { /* 使用选中标签的tooltipContent属性值作为content的内容 */ content: attr(tooltipContent); }

官方attr函数介绍:https://developer.mozilla.org...
为什么要说属性选择器呢?因为封装的代码中能够用到啊
使用vue-popper做组件的封装 安装
// CDN// NPM npm install vue-popperjs --save // Yarn yarn add vue-popperjs // Bower bower install vue-popperjs --save

官方案例demo

官方demo效果图
elementui源码学习之仿写一个el-tooltip
文章图片

笔者的二次封装效果图
elementui源码学习之仿写一个el-tooltip
文章图片

使用之代码
下方代码较多,建议打开编辑器,复制粘贴代码,跑起来,阅读之
.showTooltip { width: 100%; height: 100%; box-sizing: border-box; padding: 60px; padding-top: 0; padding-bottom: 120px; .topBox { .topReferenceDom { border: 1px solid #999; box-sizing: border-box; padding: 4px 8px; border-radius: 4px; width: 60px; text-align: center; margin-right: 6px; } } .leftAndRightBox { width: 100%; display: flex; padding-right: 120px; .leftBox { margin-right: 250px; } .leftReferenceDom, .rightReferenceDom { width: 72px; height: 60px; line-height: 60px; text-align: center; border: 1px solid #999; box-sizing: border-box; margin: 12px 0; } } .bottomBox { .bottomReferenceDom { border: 1px solid #999; box-sizing: border-box; padding: 4px 8px; border-radius: 4px; width: 60px; text-align: center; margin-right: 6px; } } .item { border: 1px solid #333; padding: 4px; } } .selfContent { width: 120px; color: #baf; font-weight: 700; }

mytooltip封装代码
// 覆盖部分默认的样式(不用加/deep/ ) .popper { box-sizing: border-box; padding: 6px 12px; border-radius: 3px; color: #fff; background-color: #333; border: none; } // 设置一个tootip的外边距(也可以使用offset) .popper[x-placement^="top"] { margin-bottom: 12px !important; } .popper[x-placement^="bottom"] { margin-top: 12px !important; } .popper[x-placement^="left"] { margin-right: 12px !important; } .popper[x-placement^="right"] { margin-left: 12px !important; } // 覆盖原有的默认三角形背景色样式 .popper[x-placement^="top"] .popper__arrow { border-color: #333 transparent transparent transparent; } .popper[x-placement^="bottom"] .popper__arrow { border-color: transparent transparent #333 transparent; } .popper[x-placement^="right"] .popper__arrow { border-color: transparent #333 transparent transparent; } .popper[x-placement^="left"] .popper__arrow { border-color: transparent transparent transparent #333; } // 加上过渡效果(搭配transition="fade") .selfSetRootClass { transition: all 0.6s; } .fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active { transition: opacity 0.6s; } // 亮色模式样式 .isLightPopper { color: #333; background-color: #fff; filter: drop-shadow(0, 2px, 12px, 0, rgba(0, 0, 0, 0.24)); box-shadow: 0, 2px, 12px, 0, rgba(0, 0, 0, 0.24); } .isLightPopper[x-placement^="top"] .popper__arrow { border-color: #fff transparent transparent transparent; } .isLightPopper[x-placement^="bottom"] .popper__arrow { border-color: transparent transparent #fff transparent; } .isLightPopper[x-placement^="right"] .popper__arrow { border-color: transparent #fff transparent transparent; } .isLightPopper[x-placement^="left"] .popper__arrow { border-color: transparent transparent transparent #fff; }

总结 因为mytooltip组件,需要使用到的vue-popper属性和方法并不多,所以大家可以仿照笔者的方式,去看一下vue-popper组件的代码,然后结合自己公司的业务需求,去封装一些适合自己公司的弹框组件
vue-popper:https://github.com/RobinCK/vu...
当然,时间较为充裕的可以看一下popper.js这个库
关于vue-popper组件的其他二次封装的应用,如封装el-popover组件el-popconfirm组件el-dropdown组件等,笔者会陆续更新的。不同的组件用到vue-popper不同的属性和方法
墙裂建议大家,看完以后,自己手写一下。只是看一遍,学习效果不太好

    推荐阅读