本文是 Element 的组件源码学习系列。
项目源码:ElemeFE/element | GitHub,badge:v2.13.0
Badge 组件使用文档:badge 标记
.vue 文件:/packages/badge
.scss 文件:/packages/theme-chalk/badge.scss
.d.ts 文件:/types/badge.d.ts
Props
我们先来看一下 el-badge 这个组件的入参
1 | props: { |
is-dot
is-dot 这个属性比较简单,当这个属性赋值为 true 的时候,右上角的标记是一个小圆点。我们来看一下是如何实现的:
1 | <div class="el-badge"> |
首先,el-badge 的用法,是 <el-badge> 标签,包裹住需要右上角有标签的元素,所以 <template> 中的实现,是返回了一个 <div>,包裹住被需要有标签的元素 <slot>,在外面形成一个壳。然后通过 CSS,将标签定位到该元素的右上角。
动效
首先,标签被包裹在一个动效里面,这个动效是 ElementUI 设计的动效,具体的效果可以参考 内置过渡动画。
动效大致的实现思路是,利用 Vue 提供的原生组件 <transition>,然后实现了具体的类。比如这里的 name = el-zoom-in-center,就需要实现 el-zoom-in-center-enter、el-zoom-in-center-active、el-zoom-in-center-leave 等。具体要实现哪些类,在 Vue 文档中可以找到:过渡 & 动画 | 过渡的类名
在 ElementUI 中,这些类的实现,可以在 transition.scss 这里找到,大家有兴趣可以去看一下 ~
CSS
在不考虑动效之后,我们来看一下 is-dot 是如何实现的。
首先,当用户传入 is-dot 的时候,此时 content 为 undefined
1 | computed: { |
所以 <sup> 的 class 为 el-badge__content 和 is-dot。
然后 :class 的逻辑里面,还有一句是 'is-fixed': $slots.default。啥意思呢?$slots.default 指的是被 el-badge 包裹住的组件,如果 el-badge 包裹了某个组件,比如一个文本,则 $slots.default 的值就不为空,calss 就还需要加上 is-fixed。
我们来看一下这几个类的实现逻辑:
1 | // badge.scss |
大体的实现思路如下:
- 使用
<sup>标签。<sup>标签在浏览器中,有一个默认的 CSS 属性是vertical-align: super,该属性的作用是,将<sup>垂直对齐文本的上标,也就是放在右上角 - 设置
position: relative包裹住需要有角标的 HTML 元素,方便把角标通过position: absolute定位到右上角;设置display: inline-block,让被包裹元素和角标在一行 - 如果被包裹元素存在,则触发
@include when(fixed)函数,设置position: absolute; top: 0; right: #{1 + $--badge-size / 2};,将角标调整到右上角向左10px。 - 针对元素的宽度和高度,做一些调整:
transform: translateY(-50%) translateX(100%);。这句话大概的意思是:把元素沿着横向(x轴)移动自身宽度的100%,一般是从左侧为开始点也就是0点。而数值是100%,所以是从右侧0点向左移动自身宽度的100%。同时,向上移动自身高度的 50%
所以,整体实现的效果是,如果设置 right: 0px; transform: translateY(-50%) translateX(100%);。则 badge 放在被定位元素的右上角,从 X 轴上面没有重合,Y 轴上面,badge 上移了 50% 的高度。然后为了美观,将 right 设置为 #{1 + $--badge-size / 2};。通过 var.scss 文件,我们可以得到 $--badge-size 默认是 18px。所以默认情况下,badge 在被包裹元素的右上角,朝左移动 10px,高度有 50% 的重合。
当然,如果有 is-Dot 的时候,直接将 <sup> 通过 width = height = 8px; border-radius: 50%,画成一个小圆球就好。
max 和 value
value 有值的时候,content 就有值。此时赋值 v-text="content",就是让 <sup> 元素包裹一个文本就好。
我们可以看一下 max 是怎么实现的:
1 | content() { |
如果 max 和 value 都存在并且是数字类型,则比较 value 和 max 的大小,如果 value 小于 max,则 badge 的内容就是 value 的值。如果 value 大于 max,则 badge 的内容就是 ${max}+ 字符串的值。比如 value = 100; max = 10,则 badge 的内容就是 10+