前言
最近做项目过程中,发现如果文字过多的话,VantUI
自带的Toast
组件展示有点问题,不是我想要的效果。(UI老师的设计稿里文字需要两行展示...)
所以去翻VantUI
的配置文档,一顿操作猛如虎之后,发现效果还是没到预期,Toast
提示框出现了横向滚动条。
于是起身,准备去找UI老师砍需求。走到半路,想着自己实现一个Toast
不是很费时间,就一咬牙自己写了一个。效果如下~~~
正文
之前看过一个情感博主写的文章(狗头保命),把一件事情的第一步分解成自己很容易做到的事,会很大程度促使自己迈出第一步,而不是情绪持续内耗。
比如,我今天要开始学习了!十分钟后......是时候了,要开始学习了。但是要学的东西好多,还费脑子,再刷会某站吧......然后到晚上了还没开始学
所以,我们来分解下开发Toast组件的步骤。
- Toast的排版
- 通过v-model传值+接收值
- watch监听值+setTimeout改值
- 来点动画(最费时间的地方)
1, Toast的排版
排版这里其实比较简单,相信小伙伴们不在话下。只要一个fixed
定位,然后配合transform
让文本在X+Y轴都居中展示。Toast
的文本区域一个背景色一个padding
,上代码,给小伙伴省一点时间。
.fix-center {
position: fixed;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
text-align: center;
z-index: 1
}
.toast {
background: rgba(0, 0, 0, 0.7);
text-align: center;
padding: 10px;
color: #ffffff;
font-size: 13px;
z-index: 3000;
width: 270px;
line-height: 18px;
}
这里延伸一个概念(和Toast组件无紧密关系,可跳过),平时项目中写页面的时候,可以给项目引入原子化CSS
的概念,又简单又能装逼,还能提升写页面时候的效率。下面给小伙伴简单说下我这边项目中的原子化CSS
,想了解更高大上可以可以了解下Tailwind CSS
1.1 公共样式文件里定义好项目中需要使用的样式
项目的公共样式文件中加入如下代码(uniapp),一般目录路径是src=>styles=>公共样式文件.scss
.flex {
display: -webkit-flex;
display: -ms-flexbox;
display: flex
}
.flex1 {
-webkit-flex: 1;
-ms-flex: 1;
flex: 1
}
.fix-center {
position: fixed;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
text-align: center;
z-index: 1
}
.jcs {
justify-content: flex-start
}
.jcc {
justify-content: center
}
.jce {
justify-content: flex-end
}
.jcsb {
justify-content: space-between
}
.jcsa {
justify-content: space-around
}
.acs {
align-content: start
}
.acc {
align-content: center
}
.ace {
align-content: flex-end
}
.acsb {
align-content: space-between
}
.acsa {
align-content: space-around
}
.ais {
align-items: flex-start
}
.aic {
align-items: center
}
...
其他还有很多,就不一一类举例了,小伙伴可以自己定义自己需要的样式。
注意:
- 公共样式一般都是和UI老师沟通过的。大家约定好的一些样式,约定好的写法最好不要私自修改,免得影响项目中其他开发人员的页面样式。
- 自定义样式时,类名要和里面的样式内容对应上,别掺杂额外的。不要影响了使用
原子化CSS
的初衷。比如.fs24{font-sze:24px} 里面就写一个字体大小设为24px,不要画蛇添足.fs24{font-sze:24px;font-weight:bold;},再加一个帮文字加粗。
1.2 在项目开发中使用
<div class="flex jcsb aic">
<div>标题一</div>
<div>标题二</div>
<div>标题三</div>
</div>
上面的代码最终实现的效果就是三个子div在页面水平分布展示,垂直居中。
原子化CSS
的概念说完了,以后写页面的时候,就不要在吭呲吭呲的写css了,直接class="xx xx"里定义好类名,一保存,页面的布局/字体大小/颜色/间距/宽高就和设计稿一样啦。是不是解决了前端不想写CSS
的一大苦恼。
2 通过v-model传值+接收值
有的小伙伴会想,不就是传一个showToast
给Toast
组件,让他展示就行了呗,为什么还要emit
给父,父来接收值呢?
Toast
组件通过watch
监听showToast
属性的变化,同时会通过定时器让showToast
2s后变为false,如果不把结果emit
给父,父下次还会传true过来,watch
函数就监听不到了。- 而且由于组件封装的目的就是降低父组件使用时的成本,所以
showToast
的状态变更逻辑只能写在Toast
组件中。
延伸一个watch和conputed的区别:
watch: 开门 => 关门 => 开门,有变化了,watch能监听到
computed: 开门 => 关门 => 开门, 最终还是开门,computed监听不到,认为它没变化
因为笔者使用的是vue3,此处用vue3具体说下自定义组件里使用v-model的用法,vue2自定义组件的v-model和这八九不离十,vue官网也有说明。
// 父
<toastCustom
v-model:showToast="state.showToast"
content="明月万年无前身,照见古今独醒人。公子王孙何必问,虚度我青春春春春春春春春。"
/>
// 子 - Toast组件
<script setup>
const emits = defineEmits(['update:showToast'])
const props = defineProps({
showToast: {
type: Boolean,
default: false,
},
})
watch(
() => props.showToast,
(val) => {
emits('update:showToast', false)
}
)
</script>
下面对核心点进行一下解释:
父组件中,使用v-model:showToast="state.showToast"
, 实际作用就是把父组件里的state.showToast
的值通过v-model
传给Toast组件, Toast组件里emit
的showToast
值也会改变父组件中的state.showToast
。
这个在vue3官网有介绍,父组件的v-model写法展开后如下
<toastCustom
:showToast="state.showToast"
@update:modelValue="newValue => state.showToast = newValue" />
子组件中的defineEmits
和defineProps
是vue3写法,不熟悉的小伙伴就把他当成vue2中的props
和emit
就行啦。
props
接收父传过来的showToast
,最后通过emits('update:showToast', false)
把值同步给父组件
再延伸一个知识点,自定义组件要是想新增修饰符,比如trim的写法:
- 父:
v-model:showToast.trim="state.showToast"
- 子:如下
const props = defineProps({
showToastModifiers: {
type: Object,
default: () => ({}),
},
})
watch(
() => props.showToast,
(val) => {
if (props.showToastModifiers.trim) {
// 自定义逻辑
value = value.trim()
}
emits('update:showToast', false)
}
)
xxxModifiers
在props中接收,然后使用props.xxxModifiers.trim
做为条件去自定义逻辑,为true就说明使用了trim
修饰符,这个是vue3的固定写法。
3 watch监听值+setTimeout改值
watch(
() => props.showToast,
(val) => {
console.log(val)
state.show = val
clearTimeout(timer)
if (props.showToast) {
timer = setTimeout(() => {
emits('update:showToast', false)
}, props.during)
}
}
)
这一块在上一章节提了一下,watch
的作用在于同步父子组件的状态机制。
主要以下几件事:
- 监听值改变
Toast
组件的文字展示状态 - 回收执行的定时器,让一切重新开始
showToast
属性为true时,设置定时器,默认2s后通过emit
同步值给父组件
4 来点动画
其实到这里,功能开发方面已经结束了。但是,我看了一下VantUI
,它的Toast
提示其实结束的时候是有动画效果的。为了尽善尽美点,所以我开始了一系列踩坑。
因为是优化系列的,节省大家时间,我直接上总结:
4.1 监听到showToast
为true时,就给div绑定一个showPage
的class类名,开始执行文本慢慢消失的动画
因为等到定时器的读秒结束,再执行动画,实际是没效果的,因为定时器的回调中执行了改变文本状态的逻辑,文本不展示了。
所以此处需要showToast
为true时就开始执行慢慢消失的动画,这里把父传过来的定时器持续时间(后面直接以during
代替)和动画的持续时间保持为一致就行啦,保证动画结束,文本消失~~~
.showPage {
animation: hide 2s linear;
}
@keyframes hide {
0% {
opacity: 1;
}
50% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
4.2 style中写animation的坑
这里是整个开发过程中最耗时间的地方。
因为需要把父传过来的during
时长和动画的持续时间保持为一致,所以此处CSS中animation: hide 2s linear
的2s得写为动态的,值为during。
起初,笔者的写法是在div中的style里写animation: hide ${during}ms linear;
,按理说没啥问题,但是页面实际展示如下,during
的值倒是没啥问题,但是hide
也就是我定义的动画名跑最后去了,导致我的动画失效。
最后,各种调试,如下,使用animation-duration: ${during}ms
用animation-duration单独对动画的持续时间进行定义,效果达到啦~~~
完结
好啦,全剧终~~~
Toast
组件开发下来,其实复杂度不高,但是里面的不少小细节以及为什么要这样做
还是值得思考一下的。
就像之前一个大佬和我说的:每天CV大法能应付70%的工作,剩下的30%就去和产品商量把需求砍了。但是,这对你的技术没啥提升,技术都隐藏在细节里面。
就比如文中的那个hide
,这就是个细节,我到现在还没百度到是为啥,后面知道了再更新到这里。
欢迎转载,但请注明来源。
最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。