第五集: 从零开始实现(toast组件)
本集定位:
toast组件的实现, 争取一章就说完, 更多弹出相关的东西会在alert组件里面.
下章loading组件.
很开心昨天去现场录制'脱口秀大会',心情愉悦😁.
一: toast需求分析
- 弹出提示信息的形式出现, 与alert不同, toast以简单提示为主.
- 不用用户操作dom, 通过js操作即可(比如封装axios的时候并没有template等标签).
- 优先级要可控, 默认要高一些, 毕竟提示要瞩目一些.
- 可自己消失, 可手动消失.
- 很常见的情况是用户疯狂点击, 或是事件多次触发, 处理好多弹出的状况与动画.
- 及时清除dom结构,比如跳转的时候, 不要造成性能的损耗.
二: 基础结构的搭建
话不多少, 导出文件 Toast/index.js.
导出默认的引入项, 是可以如下简写的.
export { default } from './main/toast.js';
// 由于这个js文件内容比较重要, 我们放在后面讲, 先写结构
toast.vue文件
<template>
// toast弹框没有动画的话就太寒掺了.
// 我这边定义的是一个从上到下掉出来的效果.
<transition name="cc-toast-fade">
// 命名规则还是老样子, bem原则.
<div class="cc-toast"
// 上面提到的 z-index属性, 对于这种组件来说, 用户必须可控.
:style="'zIndex:'+zIndex">
// 用户可以随意插入内容
// 写成变量形式也可以, 这里用slot是为了见名知意.
<slot />
// 下面的是关闭的 'X' icon,
<i class="cc-toast__closeButton"
@click="deleteEl"
v-if="showButton">
<ccIcon name='cc-close'/>
</i>
</div>
</transition>
</template>
js部分
import ccIcon from "@/components/Icon/main/icon.vue";
export default {
name: "toast",
components: { ccIcon },
props: {
zIndex: {
type: Number,
default: 10
},
showButton: {
// 是否要关闭的x
type: Boolean,
default: true
},
}
}
三: 本人z-index的一些看法
从z-index的数值设定上就能看出一个人的做事风格, 比如我之前工作时候带过的几个人, 我甚至看到有些人经常写 999999 , 我不让他这样写, 之后他还跟我反应说, 不这样写不习惯🤷♀️, 我说如果要写比他等级更高的怎么办? 他说: 肯定不可能有那种情况.
其综合分析也就明白了, 喜欢这样去书写的人,
一是没有太长远的规划, 没有想到未来的多样性,
二是并没有为团队配合而考虑, 没有为他人书写代码'留有余地'而只顾自己.
本人独立负责的实际项目中试验过, 1-10的z-index已经可以应付绝大多数工程了, 一个中小型项目分成10层真的算很多了, 这里面也有一些技巧, 比如说, 每次设置的时候要设置 1, 3, 5, 7, 9, 这是为了如果以后有特殊情况要放到某两个中间的, 可以留一个插入位, 随意约束好z-index是一个合格工程的开端.
四: scss基本样式的设定
vue-cc-ui/src/style/Toast.scss
@import './common/mixin.scss';
@import "./common/animation.scss";
@include b(toast) {
position: fixed;
display: inline-block;
background-color: white;
top: 6px;
// 上方, 居中
left: 50%;
padding: 6px 20px;
transition: all .3s;
transform: translateX(-50%);
// 本套ui的精髓😏黑边.
@include commonShadow($--color-black);
// 定义了动画的样子
@at-root {
@include commonType(cc-toast--);
.#{$namespace}-toast--big {
padding: 6px 35px 6px 20px;
}
.#{$namespace}-toast-fade-enter,
.#{$namespace}-toast-fade-leave-active {
opacity: 0;
transform: translate(-50%, -100%);
}
};
}
五: 提示框的type颜色
提示框需要让用户能够很直观的分辨它的意义, 比如用户没有看到提示文字的时候, 但他看到了红色的提示框, 那他也会知道自己不应该进行刚才的操作.
属性当然就是type了
<div class="cc-toast"
:class="[toastType]" // 这里接收
:style="'zIndex:'+zIndex">
props: {
type: {
type: String,
default: "nomal"
},
计算属性里面我们还要规定一些合法值, 以及用户传错了的时候的默认值.
computed: {
toastType() {
// 这里我做了 正常, 成功, 失败, 警告
let ary = ["nomal", "success", "warning", "danger"];
// 这里写三元也可以, 但不方便扩展, 万一以后要改那,,,,
// 其实无所谓, 我只是不想写那种长长的代码段
if (ary.includes(this.type)) {
return "cc-toast--" + this.type;
}
return "cc-toast--nomal";
}
},
scss
@at-root {
@include commonType(cc-toast--);
};
之前写的util类, 循环加上彩色边框
@mixin commonType($name) {
@each $type in (success, warning, danger) {
.#{$name}#{$type} {
@include commonShadow($type);
}
}
}
效果展示
6: 渲染到页面上(说了这个才能引出如何关闭它)
vue-cc-ui/src/components/Toast/main/toast.js
这里我会写很详细的注释!!!
// 1: 把写好的组件引进来, 根据打包的规则, 其实引得已经是处理好的js代码了
import toast from './toast.vue';
export default {
// 2: 配合use方法
install(Vue) {
// 3: 为了保证全局都能控制弹出toast, 我们只能原型编程
// 这个options就是将来我们调用他的时候, 传的参数.
Vue.prototype.$ccToast = function(options) {
// 4: 使用基础 Vue 构造器,创建一个“子类”。
let Constructor = Vue.extend(toast),node;
// 5: 我们传入了配置项
if (typeof options === 'object' && options instanceof Object) {
// 6: 实例化这个组件
node = new Constructor({
// 7: 相当于你在定义data里面的数据
propsData: options
});
// 8: 提取需要弹出的信息到默认插槽
// 这里为什么要用数组我解释一下
// 因为这里我们传入的是'结构'
// 所以他可能多个, 也可能是嵌套,
// 所以vue 分析它必须要用数组的形式
// 也可以说他要的是 '子元素的集合'.
// 这方面有兴趣可以去看render的实现.
node.$slots.default = [options.message];
// 5: 我们传入一串字符串
} else if (typeof options === 'string') {
// 6: 直接实例化即可
node = new Constructor();
node.$slots.default = [options];
}
// 9: 调用函数的生命周期
node.vm = node.$mount();
// 10: 让他显示出出来
node.vm.visible = true
// 11: 无情插入
// 如果有特殊需求的同学, 不想让他插入body, 而是通过传入来定也是不错的.
document.body.appendChild(node.$el);
};
}
};
7: 清除组件, 也可以说是关闭
- 用户点击x之后, 需要移除这个dom结构而不是隐藏
- 用户设置了不显示x 则需要倒计时关闭
- 元素采用后者遮盖前者的方式显示.
- 思前想后没有定义clearAll这个函数, 原因如下:
之前我做的工程总是出现这样的问题, 比如一个人很谨慎, 他在写完一个提示框之后, 会写一个clearAll(), 把所有的提示都清除, 这有涉及到了工程化, 组件定义的这个clearAll会无差别的清除所有显示的, 导致他清除了别人的提示信息, 而这种bug并不容易发现, 不是很喜欢这种太绝对函数.
x 绑定click事件.
<i class="cc-toast__closeButton"
@click="deleteEl"
v-if="showButton">
<ccIcon name='cc-close'/>
</i>
// 专门负责清理工作的函数
deleteEl() {
// 先触发动画效果, 不要直接rm, 不然效果会很差.
this.visible = false;
setTimeout(() => {
// 下这样才能清理干净一个组件👇
// 这里是去除元素
this.$el.remove();
// 完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
this.$destroy();
}, 300);
},
scss
// 这个'x'还需要调节一下样式,
// 并且浪一下, 赋予hover时候疯狂旋转的动画, 充满朝气
.#{$namespace}-toast__closeButton {
display: flex;
cursor: pointer;
position: absolute;
align-items: center;
justify-content: center;
top: 0;
right: 0;
bottom: 0;
padding: 0 5px;
&:hover {
animation: rotating .5s infinite linear;
}
}
用户设置了自动关闭, 过期时间,
props: {
closeTime: {
// 关闭的延时
type: Number,
default: 2000
},
}
// 初始化是否设置了时间的操作
initAutoClose() {
// 万一用户传了个负数, 还是替他abs一下吧.
if (this.autoClose && Math.abs(this.closeTime) > 0) {
// 这里要用变量接一下, 方便停止他
this.timer = setTimeout(() => {
this.deleteEl();
}, Math.abs(this.closeTime));
}
},
用户想详细看看提示内容, 把鼠标放在了toast上, 并不点击关闭
<div class="cc-toast"
:class="[
toastType,{
'cc-toast--big':showButton
}]"
:style="'zIndex:'+zIndex"
// 移入进来就别关闭倒计时了
@mousemove="clearTimer"
// 移出就从新走一遍倒计时初始化
@mouseleave="initAutoClose"
v-show="visible">
clearTimer() {
clearTimeout(this.timer);
},
用户想点击键盘的esc键, 关闭这个组件
mounted() {
this.initAutoClose();
// 开始就要添加对用户键盘事件的监听
document.addEventListener("keydown", this.keydown);
},
keydown(e) {
if (e.keyCode === 27) {
this.deleteEl();
}
}
// 既然有document的操作, 当然就要有去除的操作.
beforeDestroy() {
// 万一用户写了个很长的时间, 虽然element没有对此进行处理, 但我还是想写一下.
this.clearTimer()
document.removeEventListener('keydown', this.keydown);
}
end 到这里为止, 一个健康的toast也就可以正常使用了
感谢您的阅读, 喜欢的话就收藏吧, 让我们一起终生学习.
下章loading组件.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。