如何使用Vue自定义组件?如何使用Vue动画?接下来讲为各位看官进行解答,通过封装一个简单的吐司组件,来贯穿讲解整个组件的构建。
初始化
- 创建EasyToast.vue文件
<template>
<div class="easy-toast">
<span>我是吐司</span>
</div>
</template>
<script>
export default {
name: 'EasyToast',
props: {
},
mounted(){
},
methods: {
}
}
</script>
- 在需要使用Toast组件的地方进行引入
<template>
<div id="app">
<EasyToast />
</div>
</template>
<script>
import EasyToast from './EasyToast';
export default {
name: 'app',
components:{
EasyToast
},
data(){
return {
}
}
}
配置
- 对于Toast,参数基本有三种,其中包括
提示文案
、动画持续时间
(下文讲解动画)、图标
(成功、失败或者自定义图标),我们将通过Props方式传递到组件内部,首先定义组件内接收配置的属性,如下
...
name: 'EasyToast',
props: {
// 标题
title: {
type: String,
default: '我是标题'
},
// 动画持续时间
duration: {
type: Number,
default: 3000
},
// 吐司模式
mode: {
type: String,
default: 'normal',
validator: function(value){
return ['normal','error','success'].indexOf(value) > -1
}
}
}
...
参数尽量要配置类型及默认值,如果是特殊参数,例如枚举,Vue并没有提供响应的类型配置,我们可以使用validator进行自定义验证。
- 传递参数 (为节约篇幅,我们将简化代码,结合上一章节查看)
<template>
...
<EasyToast :title="title" />
...
</template>
<script>
export default {
name: 'app',
...
data(){
return {
title: "提交成功"
}
}
}
</script>
样式
上一章节我们已经把基本参数进行了配置,目前的Toast还只是简单的一个文本显示,并且是可见的,下面我们对其进行一个样式配置。因为是组件,基于低耦合、高内聚的原则,通过scoped属性实现样式的模块化,仅作用于当前组件,防止造成全局污染。EasyToast.vue内增加样式代码如下:
<script>
...
</script>
<style lang="scss" scoped>
.easy-toast{
width: 140px;
background-color: #000000;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
text-align: center;
padding: 20px 10px;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
&__title{
color: white;
font-size: 16px;
line-height: 22px;
}
&__icon{
width: 32px;
height: 32px;
display: inline-block;
margin-bottom: 12px;
background-size: contain;
background-position: center;
&--error{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADfklEQVR4Xu2b7XEUMQyGpQqACqADoAKgAqACSAVABSQVABUQKgAqIB2QVAAlpAMx743N7OysbcmW1jeT89/zh97Hsuy1dUx3vPAd108nACcPuOMETktA6wAi8pKInqT6P5n5Wts2ul6y7T4R3VjtanqAiED0dyJ6tBJyzswX0eJq/YvIWyL6uLLtBxGdMfOtxrYqgDTA10pHl8x8phnIu46IwC4A2Cp/iei1xhuKABTi88C7Q2iIz3bBA160IGwCEBG4+x/DrO0GQSk+m444BQjF5VACcElEbwwAUDUcglF8Nh/xAHo2SwnA70XEt3AIg9ApHrZ/Yeb3VgBwmXsW5Yu6rhBEBNsbdqHnnfZcMPO5FUDPEliO4QIhif/V6Y3Znqe1QFgLggggvV5wiAlE9EG7H69nyEn8N2YubZWHIWvb4Kvkep2ed2jWjMJbnTuJv8GyaU3A6EFIA8cEYU/xVQ/IygwHohoMFYS9xasAoJKIYDlgTY/EhCqEGeLVABIEfBRdRUCYJd4EIArCTPFmAN4QUtAY3edV0d50EGqF9nRH4LEcMFS+ZGkNu/X7kPguD1jsDh4xoUd0bjMsfgiA43LogeAifhjAJAhu4l0A7AzBVbwbgJ0guIt3BRAMIUS8O4AgCGHiQwA4QwgVHwkA11ijJzzYp7ra7tlHc5vmy5C1c6ez/XLYUAiuAALEZxBhENwABIoPheACYAfxSwi4aC0+dFiX7DCAHcUvtVVfeywQhgBMEp/1uUDoBjBZvBuELgBO4nHIQXlscdmNukOeYAaQns7xVjd8k5PE4GZpGgQTAMeZ//9ik/qcBkENIEJ8dueZEFQAIsXPhtAEsIf4mRBaj6MeX3WmT1rH5YAsMaTMVUvteXx38c6eoPqAqgHozRPKOkwzv54mJ09AviAyRMxZYkgq+tRyn8rvQ+KdPaErSQo5guvUWC0PF/GOEG6Z+UHJ+FKOkGjVruq5iveCwMzFpV4C0JMmFyLeAQIyyIvH9hIA5NUhC1tbQsUPQqhuh7VdQJsruIv4Tgj9aXIYUERaEJoDaF3IUi9tkTjkPKu0U9mmOQp/JqJ3G8EOf5honrQswqx1RQRLFYmQD1dtq+mxy7pNAMkTEESQq4vT4RUz4/P1aErKWIGNOPhct5IjzQCORmmAISoPCBj3aLo8ATiaqZhkyMkDJoE/mmH/AUam2lCtvFZ1AAAAAElFTkSuQmCC');
}
&--success{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAEQElEQVR4Xu2bi3HUMBCGdyuAVEBSAUkFkAogFZBUQFIBpAJCBZAKSCogqYBLBSQVABUs83nkw/b5IUvy646d8Uwuc2fr/7SSdleWysBmZs9F5JWIHLqLz/vuKj79UUS4fovIyl33qsrnwUyHuLOZIfadiLx2omMeA4w7EblWVf5OaskAuJ5G9HlN76ZqNB5y5WAk8YxoAE74eycc9x7DEA+Iz7FDJAqAmdHjNGQs4VW4gDhX1etQ6kEAzIxJ7Isb46HPTvk75ogzVWWI9LLeAMzsVEQ+TdjrTQLxBiDc9CHQC4CZ0esAmLNdqeqFbwO9ASxEfK77q6qe+UDwAmBm30Tkrc8NZ/SdG1U96WpPJ4CF9XxVb6cntAJYuPgcBrECwVmtNQJwsz2T3jbYSdPqUAvArfM/ZrjUhXYGS+RRXZzQBOD7jIKcUNHV392p6nH1nxsAtsz1q3oJlL4W/1kC4BKbn1vk+lUADIWDYgJVBfBRRD6k8rmZ3udSVdGZ2RrADvR+rrnkBUUArJUkObtgF6pKGl/yAMY+ae4u2KOqHqwBuBoe6/4uGXHBKhsCZoY7UNZagv0RESYxvJXU/Flgo7MQOQdA71PJnbs9EKDly5jzXKpBIRBWqnqkbvb/NXflIlISn7fXzEjTSddDbA8AMTcIeWjIb2rFu+HL3gOhe4idAGDuwU+jeAcgplhzCQCKiG9C8I3wmy7xsTXKWwAwibB3F2vMzljIhFT37KHF88x7AKQIgNi3y6rFZpYiohxDPM1dAcAiu/5WVUsF08iUeizxmewUALKIqgoxEMKo4lMBKKWXRRA9IYwuPhUA0svjpr17TwiTiM8BsKH4InIeiIEwmXgReUq5DIZAmFL8ehlMGQj1gTC1eABkgVDqUNgHArHCOqurWUFiIzzfEZ2FwkMkQ60Q2lo38nZclgzxessQ6XBvCCOLpx/28oIIgcxLX7/p8T1vCBOIf1DVwzFKYp0QJhBPH5ZKYpTDhiyKNkKYSDwA/hVFXRaXIiBqGx0bECYU/6Sq2RbA2BsjQGBzkjmH9Jly1hRWuzHCaoAXpCpoTCHM55kUbvbzyvL/zdFK+rrtXlDq/dIckIPwTF99XG2O32l/QaIAIVWhdE4QOHyxMem2vSTFTL0tEyKuf+j9kpSLC4ZIkqbyiH6vyRWGwpJ2jZvghr0oWYBA4MLBiCXaer+iqfGd7wq74ZCyajQWyI39iroHewFwEJbkCZ09n8PwBuAgLGFOaB3zVS/oBaCwOuANc1siWepOBz0yU5gYSSWBkGJXOcWccO/ED39oqpI7kNIyLKbyBnqdY3Ol93/7EO09BKo3d0VVytxcY4FAOOA5IBV1gjQaQGFYkEniEYCI3Wpr6sQnJ5yjMFHCg1YBX9dyr6/lFZ/YajM7SCRniJ7v4ekmOG6I5KfIKb7mx+erXkLvVo/Pc8ghSU83te8vrv8NelTm5PcAAAAASUVORK5CYII=');
}
}
}
</style>
默认效果
失败
显示和隐藏
目前为止,样式已经补充上了,接下来便需要进行显示和隐藏的控制。一般来讲,显示和隐藏需要由使用者来控制,而实现控制的方式有多种。
Toast采用v-model
来进行控制显隐,可能并不是最优的方案,主要目的是学习一下v-model
的一些特殊用法。 v-model
我们通常会在input
标签中使用,进行双向数据绑定,使用它的优势在与通过双向数据绑定,能够同步组件内和外部使用者的状态。
实际上,v-model
等价与v-bind:value
属性,所以我们在组件内部增加一个value属性(Boolean类型,默认false),来接收绑定的数据,代码如下:
EasyToast.vue
name: 'EasyToast',
props: {
...
// 吐司模式
mode: {
type: String,
default: 'normal',
validator: function(value){
return ['normal','error','success'].indexOf(value) > -1
}
},
value: {
type: Boolean,
default: false
}
}
上一章节,我们制作好了Toast样式,但是是用户可见的, 实际上默认应该是不可见状态,我们通过增加一个修饰类class隐藏,并通过value属性来控制easy-toast--hide是否使用,代码如下: EasyToast.vue
<template>
<div :class="['easy-toast', value === false && 'easy-toast--hide']">
...
</div>
</template>
<style lang="scss" scoped>
.easy-toast{
...
&--hide{
display:none;
}
}
</style>
调用者通过v-model来进行显示的控制,代码如下
<template>
<div id="app">
<EasyToast
v-model="toastShow"
:title="title"
/>
</div>
</template>
<script>
export default {
name: 'app',
...
data(){
return {
toastShow: flase,
title: "提交成功",
}
}
}
对于Toast,一般是自动隐藏的,我们就需要在显示状态且duration时间结束后使其隐藏,可以通过监听value属性来增加控制隐藏的方法,代码如下: EasyToast.vue
<script>
export default {
name: 'EasyToast',
props: {
...
value: {
type: Boolean,
default: false
}
},
watch: {
value(){
if(this.value === true){
// Toast显示时
this.startTimer();
} else {
this.stopTimer();
}
}
},
// 组件销毁后,停止计时
destroyed(){
this.stopTimer();
},
methods: {
// 停止计时
stopTimer(){
this.timer && clearTimeout(this.timer);
this.timer = null;
},
// 开始计时
startTimer(){
this.stopTimer();
this.timer = setTimeout(()=>{
this.hide();
this.stopTimer();
}, this.duration);
},
// 隐藏
hide(){
this.$emit('input', false); // 正常Props不可以修改 通过emit “input”事件,来修改父组件状态,实现双向绑定
}
}
}
</script>
动画
上一章节,实现了显示和隐藏功能,但是太过单调,我们需要增加一些动画效果,使用Vue的transitions来实现,并配合css3动画,不再使用控制easy-toast--hide的方式,话不多说,直接上代码: EasyToast.vue
<template>
<transition name="easy-toast-scale">
<div v-show="value" class="easy-toast">
...
</div>
</transition>
</template>
<script>
...
</script>
<style lang="scss" scoped>
.easy-toast{
...
transform: translate(-50%,-50%) scale(1);
...
}
.easy-toast-scale-enter-active, .easy-toast-scale-leave-active {
transition: transform .3s;
}
.easy-toast-scale-enter, .easy-toast-scale-leave-to{
transform: translate(-50%,-50%) scale(0);
}
</style>
完整代码 EasyToast.vue
<template>
<transition name="easy-toast-scale">
<div v-show="value" class="easy-toast">
<i v-if="mode === 'success'" class="easy-toast__icon easy-toast__icon--success"></i>
<i v-if="mode === 'error'" class="easy-toast__icon easy-toast__icon--error"></i>
<span class="easy-toast__title">{{title}}</span>
</div>
</transition>
</template>
<script>
export default {
name: 'EasyToast',
props: {
// 标题
title: {
type: String,
default: '我是标题'
},
// 动画持续时间
duration: {
type: Number,
default: 3000
},
// 吐司类型
mode: {
type: String,
default: 'normal',
validator: function(value){
return ['normal','error','success'].indexOf(value) > -1
}
},
value: {
type: Boolean,
default: false
}
},
watch: {
value(){
if(this.value === true){
// Toast显示时
this.startTimer();
} else {
this.stopTimer();
}
}
},
// 组件销毁后,停止计时
destroyed(){
this.stopTimer();
},
methods: {
// 停止计时
stopTimer(){
this.timer && clearTimeout(this.timer);
this.timer = null;
},
// 开始计时
startTimer(){
this.stopTimer();
this.timer = setTimeout(()=>{
this.hide();
this.stopTimer();
}, this.duration);
},
// 隐藏
hide(){
this.$emit('input', false); // 正常Props不可以修改 通过emit “input”事件,来修改父组件状态,实现双向绑定
}
}
}
</script>
<style lang="scss" scoped>
.easy-toast{
width: 140px;
background-color: #000000;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%) scale(1);
text-align: center;
padding: 20px 10px;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
&__title{
color: white;
font-size: 16px;
line-height: 22px;
}
&__icon{
width: 32px;
height: 32px;
display: inline-block;
margin-bottom: 12px;
background-size: contain;
background-position: center;
&--error{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADfklEQVR4Xu2b7XEUMQyGpQqACqADoAKgAqACSAVABSQVABUQKgAqIB2QVAAlpAMx743N7OysbcmW1jeT89/zh97Hsuy1dUx3vPAd108nACcPuOMETktA6wAi8pKInqT6P5n5Wts2ul6y7T4R3VjtanqAiED0dyJ6tBJyzswX0eJq/YvIWyL6uLLtBxGdMfOtxrYqgDTA10pHl8x8phnIu46IwC4A2Cp/iei1xhuKABTi88C7Q2iIz3bBA160IGwCEBG4+x/DrO0GQSk+m444BQjF5VACcElEbwwAUDUcglF8Nh/xAHo2SwnA70XEt3AIg9ApHrZ/Yeb3VgBwmXsW5Yu6rhBEBNsbdqHnnfZcMPO5FUDPEliO4QIhif/V6Y3Znqe1QFgLggggvV5wiAlE9EG7H69nyEn8N2YubZWHIWvb4Kvkep2ed2jWjMJbnTuJv8GyaU3A6EFIA8cEYU/xVQ/IygwHohoMFYS9xasAoJKIYDlgTY/EhCqEGeLVABIEfBRdRUCYJd4EIArCTPFmAN4QUtAY3edV0d50EGqF9nRH4LEcMFS+ZGkNu/X7kPguD1jsDh4xoUd0bjMsfgiA43LogeAifhjAJAhu4l0A7AzBVbwbgJ0guIt3BRAMIUS8O4AgCGHiQwA4QwgVHwkA11ijJzzYp7ra7tlHc5vmy5C1c6ez/XLYUAiuAALEZxBhENwABIoPheACYAfxSwi4aC0+dFiX7DCAHcUvtVVfeywQhgBMEp/1uUDoBjBZvBuELgBO4nHIQXlscdmNukOeYAaQns7xVjd8k5PE4GZpGgQTAMeZ//9ik/qcBkENIEJ8dueZEFQAIsXPhtAEsIf4mRBaj6MeX3WmT1rH5YAsMaTMVUvteXx38c6eoPqAqgHozRPKOkwzv54mJ09AviAyRMxZYkgq+tRyn8rvQ+KdPaErSQo5guvUWC0PF/GOEG6Z+UHJ+FKOkGjVruq5iveCwMzFpV4C0JMmFyLeAQIyyIvH9hIA5NUhC1tbQsUPQqhuh7VdQJsruIv4Tgj9aXIYUERaEJoDaF3IUi9tkTjkPKu0U9mmOQp/JqJ3G8EOf5honrQswqx1RQRLFYmQD1dtq+mxy7pNAMkTEESQq4vT4RUz4/P1aErKWIGNOPhct5IjzQCORmmAISoPCBj3aLo8ATiaqZhkyMkDJoE/mmH/AUam2lCtvFZ1AAAAAElFTkSuQmCC');
}
&--success{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAEQElEQVR4Xu2bi3HUMBCGdyuAVEBSAUkFkAogFZBUQFIBpAJCBZAKSCogqYBLBSQVABUs83nkw/b5IUvy646d8Uwuc2fr/7SSdleWysBmZs9F5JWIHLqLz/vuKj79UUS4fovIyl33qsrnwUyHuLOZIfadiLx2omMeA4w7EblWVf5OaskAuJ5G9HlN76ZqNB5y5WAk8YxoAE74eycc9x7DEA+Iz7FDJAqAmdHjNGQs4VW4gDhX1etQ6kEAzIxJ7Isb46HPTvk75ogzVWWI9LLeAMzsVEQ+TdjrTQLxBiDc9CHQC4CZ0esAmLNdqeqFbwO9ASxEfK77q6qe+UDwAmBm30Tkrc8NZ/SdG1U96WpPJ4CF9XxVb6cntAJYuPgcBrECwVmtNQJwsz2T3jbYSdPqUAvArfM/ZrjUhXYGS+RRXZzQBOD7jIKcUNHV392p6nH1nxsAtsz1q3oJlL4W/1kC4BKbn1vk+lUADIWDYgJVBfBRRD6k8rmZ3udSVdGZ2RrADvR+rrnkBUUArJUkObtgF6pKGl/yAMY+ae4u2KOqHqwBuBoe6/4uGXHBKhsCZoY7UNZagv0RESYxvJXU/Flgo7MQOQdA71PJnbs9EKDly5jzXKpBIRBWqnqkbvb/NXflIlISn7fXzEjTSddDbA8AMTcIeWjIb2rFu+HL3gOhe4idAGDuwU+jeAcgplhzCQCKiG9C8I3wmy7xsTXKWwAwibB3F2vMzljIhFT37KHF88x7AKQIgNi3y6rFZpYiohxDPM1dAcAiu/5WVUsF08iUeizxmewUALKIqgoxEMKo4lMBKKWXRRA9IYwuPhUA0svjpr17TwiTiM8BsKH4InIeiIEwmXgReUq5DIZAmFL8ehlMGQj1gTC1eABkgVDqUNgHArHCOqurWUFiIzzfEZ2FwkMkQ60Q2lo38nZclgzxessQ6XBvCCOLpx/28oIIgcxLX7/p8T1vCBOIf1DVwzFKYp0QJhBPH5ZKYpTDhiyKNkKYSDwA/hVFXRaXIiBqGx0bECYU/6Sq2RbA2BsjQGBzkjmH9Jly1hRWuzHCaoAXpCpoTCHM55kUbvbzyvL/zdFK+rrtXlDq/dIckIPwTF99XG2O32l/QaIAIVWhdE4QOHyxMem2vSTFTL0tEyKuf+j9kpSLC4ZIkqbyiH6vyRWGwpJ2jZvghr0oWYBA4MLBiCXaer+iqfGd7wq74ZCyajQWyI39iroHewFwEJbkCZ09n8PwBuAgLGFOaB3zVS/oBaCwOuANc1siWepOBz0yU5gYSSWBkGJXOcWccO/ED39oqpI7kNIyLKbyBnqdY3Ol93/7EO09BKo3d0VVytxcY4FAOOA5IBV1gjQaQGFYkEniEYCI3Wpr6sQnJ5yjMFHCg1YBX9dyr6/lFZ/YajM7SCRniJ7v4ekmOG6I5KfIKb7mx+erXkLvVo/Pc8ghSU83te8vrv8NelTm5PcAAAAASUVORK5CYII=');
}
}
}
.easy-toast-scale-enter-active, .easy-toast-scale-leave-active {
transition: transform .3s;
}
.easy-toast-scale-enter, .easy-toast-scale-leave-to{
transform: translate(-50%,-50%) scale(0);
}
</style>