This article records and imitates ael-tooltip
component details, which will help you better understand the specific working details of the corresponding components of Ele.me ui. This article is another article in the elementui source code learning and imitation series. I will continue to update and imitate other components when I am free. The source code is on github, you can pull it down, npm start to run, and comments are helpful for better understanding. The github repository address is as follows:
https://github.com/shuirongshuifu/elementSrcCodeStudy
foreword
what is programming
There are indeed many answers to the question of what programming is. A long time ago, when I first entered the industry, I was told this sentence:
编程就是:规则的学习,规则的使用,规则的理解、规则的自定义
Makes sense...
Background introduction
When we are packaging components, we often encounter some "pop-up components", taking Ele.me UI as an example, such as: el-tooltip组件
, el-popover组件
, el-popconfirm组件
, el-dropdown组件
, etc. When this type of component is operating, a pop-up box will often appear, for the trigger conditions (or suspension, or click) of these pop-up boxes and the control of the position (above, below , left, right), etc., the vue team specially encapsulated a vue-popper组件
, through props
pass parameters and some event methods to control to achieve the effect we want
So, how is vue-popper组件
implemented? What is the underlying principle? It is to popper.js
this excellent library
So, how is popper.js
implemented? What is the underlying principle? is controlled by js
弹出框dom
the location of ---0e7a9841e2b3962dec24f0b5bb3ae89d---
Since popper.js
there is not much domestic information, you can directly use the vue-popper组件
component to do some operations. After all, its underlying principle is also prpper.js
-
el-tooltip组件
is using the rule ofvue-popper组件
-
vue-popper组件
is using the rules ofpopper.js库
-
popper.js库
is the rule that usesjs和dom
- Infinite Rules Matryoshka...
Attach the portal
prpper.js
Official website: https://popper.js.org/, Chinese tutorial: https://www.jiyik.com/w/popperjs, good article: https://blog.csdn.net /jhzhahuaiyu/article/details/90213582If you are interested, you can do research in your spare time (such as
elementUI
andiview
andBootstrap
proper.js
Material UI
proper.js
) is also a secondary package另:
prpper.js
团队react
写React Popper
,vue
暂时没有,所以咱们就学习vue-popper
chant
This article focuses on the underlying principles of the middle layer vue-popper组件
, let's start learning
Tooltip component thinking
What is a tooltip component
- The tooltip component is a bubble box component used to make simple text with instructions (hints).
- The general interaction is that the mouse moves into the display, and the mouse moves out to disappear.
- Tooltip components generally do not do complex interactive operations and carry too much text content
- It can be understood as a specific supplement to the title attribute function of the dom element
tooltip component requirements
- Dark mode tooltip, white text on black background
- Highlight mode tooltip, black text on white background
- The position of the tooltip component, in the direction pointing to the reference element, is generally up, down, left and right, and there are 12 directions for expansion.
- The small triangle of the tooltip (usually displayed)
- It can be controlled to close and open, that is, the hover display meets the conditions, otherwise the hover is closed
- In general, the tooltip is a single line of content. If there is too much content, it supports text wrapping and even custom tooltip styles (supports slots)
- As for other requirements, such as: tooltip shows the expanded transition animation, whether the small arrow can be hidden, offset offset, delay appearing and disappearing, etc., it will not be changed much under normal circumstances, so this article focuses on the key common requirements to explain
Before using the library or some basic components, let's try it out and write it by hand
A simple tooltip demo
Mainly use attribute selectors to control, tooltips in four directions and small triangular arrows.
The label's whichPlacement属性值为"top"时
, let it be on the top, it is left时
, let it be on the left, and the same is true for other directions
demo renderings
demo code
Copy and paste to use
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
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;
}
</style>
</head>
<body>
<div class="item" whichPlacement="top" tooltipContent="上方出现tooltip内容">悬浮上方</div>
<br>
<div class="item" whichPlacement="bottom" tooltipContent="tooltip内容在下方出现">悬浮下方</div>
<br>
<br>
<br>
<div class="item" whichPlacement="left" tooltipContent="左侧出现tooltip内容">悬浮左侧</div>
<br>
<div class="item" whichPlacement="right" tooltipContent="tooltip内容出现在右侧">悬浮右侧</div>
</body>
</html>
About CSS attribute selectors and the attr() function
The attribute selector and attr() function are used in the above code, which is briefly mentioned here
attribute selector
Q: What is an attribute selector?
Answer 1: 通过选取带有指定标签属性的dom元素,进行样式的设置
Answer 2: 通过标签的属性名key和属性值value来匹配元素,从而进行样式的设置
Q: Give an example
answer:
-
[attr]
attr
,不用管其值是什么,如:input[type]{ ... }
,意为:input
标签, including thetype
attribute (ignoringtype
attribute value), select them, and set...
style -
[attr='val']
match allattr
attribute value equal toval
, exact match.如:input[type='text']{ ... }
,意为:只要input
标签中,有type
,text
,才去选中, and match...
style -
[attr^='val']
match allattr
attribute values starting withval
(used in the demo case above, but its attributes are custom). fuzzy matching -
[attr$='val']
, similar to above,^=
matches what starts with what,$=
matches what ends. fuzzy matching -
[attr*='val']
, similar to the above,*=
is a fuzzy match as long as it is included
For details, please refer to the introduction of the official attribute selector: https://www.w3school.com.cn/css/css_attribute_selectors.asp
attr() function
attr
is attribute
abbreviation for the word 属性
, as the name suggests, so this thing is related to attributes
- The function of
css
attr()
can get the属性值
of the selected element, and use it in the style file. Can be used in pseudo-elements, used in pseudo-class elements, it gets the value of the original element of the pseudo-element. -
attr()
function can be used with anycss
property, but exceptcontent
, the rest are experimental, so it is recommended: except with pseudo elementscontent
don't use anything else
As in the above case:
<div class="item" tooltipContent="上方出现tooltip内容">悬浮上方</div>
.item::after {
/* 使用选中标签的tooltipContent属性值作为content的内容 */
content: attr(tooltipContent);
}
Official attr function introduction: https://developer.mozilla.org/zh-CN/docs/Web/CSS/attr
Why do you say attribute selectors? Because it can be used in the encapsulated code.
Use vue-popper
to package components
Install
// CDN
<script src="https://unpkg.com/@ckienle/k-pop"></script>
// NPM
npm install vue-popperjs --save
// Yarn
yarn add vue-popperjs
// Bower
bower install vue-popperjs --save
Official case demo
<template>
<popper
trigger="clickToOpen"
:options="{
placement: 'top',
modifiers: { offset: { offset: '0,10px' } }
}">
<div class="popper">
Popper Content
</div>
<button slot="reference">
Reference Element
</button>
</popper>
</template>
<script>
import Popper from 'vue-popperjs';
import 'vue-popperjs/dist/vue-popper.css';
export default {
components: {
'popper': Popper
},
}
</script>
Official demo renderings
The author's secondary packaging renderings
code to use
There is a lot of code below, it is recommended to open the editor, copy and paste the code, run it, and read it
<template>
<div class="showTooltip">
<h3>暗色模式</h3>
<br />
<div class="darkMode">
<div class="topBox">
<my-tooltip placement="top-start" content="top-start">
<span class="topReferenceDom">上方左侧上方左侧</span>
</my-tooltip>
<my-tooltip placement="top" content="top">
<span class="topReferenceDom">上方中间</span>
</my-tooltip>
<my-tooltip placement="top-end" content="top-end">
<span class="topReferenceDom">上方右侧上方右侧</span>
</my-tooltip>
</div>
<div class="leftAndRightBox">
<div class="leftBox">
<my-tooltip placement="left-start" content="left-start">
<div class="leftReferenceDom">左侧上方</div>
</my-tooltip>
<my-tooltip placement="left" content="left">
<div class="leftReferenceDom">左侧中间</div>
</my-tooltip>
<my-tooltip placement="left-end" content="left-end">
<div class="leftReferenceDom">左侧下方</div>
</my-tooltip>
</div>
<div class="rightBox">
<my-tooltip placement="right-start" content="right-start">
<div class="rightReferenceDom">右侧上方</div>
</my-tooltip>
<my-tooltip placement="right" content="right">
<div class="rightReferenceDom">右侧中间</div>
</my-tooltip>
<my-tooltip placement="right-end" content="right-end">
<div class="rightReferenceDom">右侧下方</div>
</my-tooltip>
</div>
</div>
<div class="bottomBox">
<my-tooltip placement="bottom-start" content="bottom-start">
<span class="bottomReferenceDom">下方左侧下方左侧</span>
</my-tooltip>
<my-tooltip placement="bottom" content="bottom">
<span class="bottomReferenceDom">下方中间</span>
</my-tooltip>
<my-tooltip placement="bottom-end" content="bottom-end">
<span class="bottomReferenceDom">下方右侧下方右侧</span>
</my-tooltip>
</div>
</div>
<br />
<h3>亮色模式</h3>
<br />
<div class="lightMode">
<div class="topBox">
<my-tooltip light placement="top-start" content="top-start">
<span class="topReferenceDom">上方左侧上方左侧</span>
</my-tooltip>
<my-tooltip light placement="top" content="top">
<span class="topReferenceDom">上方中间</span>
</my-tooltip>
<my-tooltip light placement="top-end" content="top-end">
<span class="topReferenceDom">上方右侧上方右侧</span>
</my-tooltip>
</div>
<div class="leftAndRightBox">
<div class="leftBox">
<my-tooltip light placement="left-start" content="left-start">
<div class="leftReferenceDom">左侧上方</div>
</my-tooltip>
<my-tooltip light placement="left" content="left">
<div class="leftReferenceDom">左侧中间</div>
</my-tooltip>
<my-tooltip light placement="left-end" content="left-end">
<div class="leftReferenceDom">左侧下方</div>
</my-tooltip>
</div>
<div class="rightBox">
<my-tooltip light placement="right-start" content="right-start">
<div class="rightReferenceDom">右侧上方</div>
</my-tooltip>
<my-tooltip light placement="right" content="right">
<div class="rightReferenceDom">右侧中间</div>
</my-tooltip>
<my-tooltip light placement="right-end" content="right-end">
<div class="rightReferenceDom">右侧下方</div>
</my-tooltip>
</div>
</div>
<div class="bottomBox">
<my-tooltip light placement="bottom-start" content="bottom-start">
<span class="bottomReferenceDom">下方左侧下方左侧</span>
</my-tooltip>
<my-tooltip light placement="bottom" content="bottom">
<span class="bottomReferenceDom">下方中间</span>
</my-tooltip>
<my-tooltip light placement="bottom-end" content="bottom-end">
<span class="bottomReferenceDom">下方右侧下方右侧</span>
</my-tooltip>
</div>
</div>
<br />
<h3>可禁用</h3>
<br />
<my-tooltip :disabled="disabled" placement="top" content="disabled属性禁用">
<span class="item">悬浮出现</span>
</my-tooltip>
<button @click="disabled = !disabled">点击启用或禁用</button>
<br />
<br />
<h3>当tooltip内容多的时候,使用content插槽</h3>
<br />
<my-tooltip placement="top">
<span slot="content">
<div class="selfContent">
内容过多时,使用插槽更便于控制样式,比如换行
</div>
</span>
<span class="item">悬浮出现</span>
</my-tooltip>
<br />
<br />
</div>
</template>
<script>
export default {
data() {
return {
disabled: false,
};
},
};
</script>
<style lang='less' scoped>
.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;
}
</style>
mytooltip
Package code
<template>
<!--
1. :appendToBody="true"是否把位置加到body外层标签上
饿了么UI和antD是true,iview和vuetifyjs是false
2. trigger属性触发方式,常用hover悬浮触发、clickToOpen鼠标点击触发
3. :visibleArrow="true"默认显示三角形小箭头,但是可以修改
也可以使用伪元素自定义其对应样式,这样更加自由灵活一些
4. :options="{ ... } 其实就是popper.js的配置项,可看对应官方文档
5. placement: placement 即为tooltip出现的位置,有12个位置,即:placementArr
6. modifiers: { ... } 此修饰符配置对象主要是控制定位的相关参数
7. offset即偏移量在原有位置上进行移动微调,这里暂时不设置了,直接使用
给.popper加上外边距即可margin: 12px !important;
8. computeStyle.gpuAcceleration = false 关闭css3的transform定位,因为要自定义
9. preventOverflow.boundariesElement = 'window' 防止popper元素定位到边界外
如:当左侧距离不够用的时候,即使设置placement='left'但是tooltip依旧会在右侧
10. <div class="popper" /> 此标签是tooltip的容器,所以我们可以设置对应想要的样式
11. rootClass="selfSetRootClass"搭配transition="fade"实现淡入淡出过渡效果
12. slot="reference"命名插槽是触发tooltip打开/关闭的dom元素
13. disabled是否关闭这个tooltip
-->
<popper
:appendToBody="true"
trigger="hover"
:visibleArrow="true"
:options="{
placement: placement,
modifiers: {
offset: {
offset: 0,
},
computeStyle: {
gpuAcceleration: false,
},
preventOverflow: {
boundariesElement: 'window',
},
},
}"
rootClass="selfSetRootClass"
transition="fade"
:disabled="disabled"
>
<!-- 内容过多的时候,建议使用content插槽,便于自定义样式 -->
<div
v-if="$slots.content"
:class="{ isLightPopper: light }"
ref="popperRef"
class="popper"
>
<slot name="content"></slot>
</div>
<!-- 内容少的话,直接content属性 -->
<div
v-else
:class="{ isLightPopper: light }"
ref="popperRef"
class="popper"
>
{{ content }}
</div>
<!-- 把外界传递的普通插槽当做具名插槽传递给子组件使用 -->
<slot slot="reference"></slot>
</popper>
</template>
<script>
// 基于vue-popperjs的二次封装
import popper from "vue-popperjs"; // vue-popperjs基于popper.js二次封装
import "vue-popperjs/dist/vue-popper.css";
// 总共12个位置
const placementArr = [
"top-start",
"top",
"top-end",
"left-start",
"left",
"left-end",
"right-start",
"right",
"right-end",
"bottom-start",
"bottom",
"bottom-end",
];
export default {
name: "myTooltip",
components: { popper }, // 注册并使用vue-popperjs插件组件
props: {
// 12个tooltip位置
placement: {
type: String,
default: "top-start", // 默认
validator(val) {
return placementArr.includes(val); // 位置校验函数
},
},
// 内容(同内容插槽,不过内容插槽的权重高一些)
content: {
type: String,
default: "",
},
// 是否是亮色模式,默认是暗色模式
light: {
type: Boolean,
default: false,
},
// 是否禁用即关掉tooltip
disabled: {
type: Boolean,
default: false,
},
},
};
</script>
<style lang="less">
// 覆盖部分默认的样式(不用加/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;
}
</style>
Summarize
Because the mytooltip
component needs to use the vue-popper
attributes and methods are not many, so you can follow the author's way and take a look at the code of the vue-popper
component, Then combine with your company's business needs to package some suitable for your company 弹框组件
vue-popper
: https://github.com/RobinCK/vue-popperOf course, if you have more time, you can take a look at this library
popper.js
Regarding the application of other secondary packaging of vue-popper
components, such as packaging el-popover组件
, el-popconfirm组件
, el-dropdown组件
etc. will be updated successively. Different components use vue-popper
different properties and methods
The wall crack suggests that everyone, after reading it, write it by hand. Just watch it once, the learning effect is not very good
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。