浅谈开发一个Toast组件,有哪些小细节

1,465 阅读7分钟

前言

最近做项目过程中,发现如果文字过多的话,VantUI自带的Toast组件展示有点问题,不是我想要的效果。(UI老师的设计稿里文字需要两行展示...)

所以去翻VantUI的配置文档,一顿操作猛如虎之后,发现效果还是没到预期,Toast提示框出现了横向滚动条。

image.png image.png

于是起身,准备去找UI老师砍需求。走到半路,想着自己实现一个Toast不是很费时间,就一咬牙自己写了一个。效果如下~~~

image.png

正文

image.png

之前看过一个情感博主写的文章(狗头保命),把一件事情的第一步分解成自己很容易做到的事,会很大程度促使自己迈出第一步,而不是情绪持续内耗。

比如,我今天要开始学习了!十分钟后......是时候了,要开始学习了。但是要学的东西好多,还费脑子,再刷会某站吧......然后到晚上了还没开始学

所以,我们来分解下开发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>

image.png

上面的代码最终实现的效果就是三个子div在页面水平分布展示,垂直居中。

原子化CSS的概念说完了,以后写页面的时候,就不要在吭呲吭呲的写css了,直接class="xx xx"里定义好类名,一保存,页面的布局/字体大小/颜色/间距/宽高就和设计稿一样啦。是不是解决了前端不想写CSS的一大苦恼。

2 通过v-model传值+接收值

有的小伙伴会想,不就是传一个showToastToast组件,让他展示就行了呗,为什么还要emit给父,父来接收值呢?

  • Toast组件通过watch监听showToast属性的变化,同时会通过定时器让showToast2s后变为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组件里emitshowToast值也会改变父组件中的state.showToast

这个在vue3官网有介绍,父组件的v-model写法展开后如下

<toastCustom 
    :showToast="state.showToast" 
    @update:modelValue="newValue => state.showToast = newValue" />

子组件中的defineEmitsdefineProps是vue3写法,不熟悉的小伙伴就把他当成vue2中的propsemit就行啦。

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也就是我定义的动画名跑最后去了,导致我的动画失效。

image.png

最后,各种调试,如下,使用animation-duration: ${during}ms用animation-duration单独对动画的持续时间进行定义,效果达到啦~~~

image.png

完结

好啦,全剧终~~~

Toast组件开发下来,其实复杂度不高,但是里面的不少小细节以及为什么要这样做还是值得思考一下的。

就像之前一个大佬和我说的:每天CV大法能应付70%的工作,剩下的30%就去和产品商量把需求砍了。但是,这对你的技术没啥提升,技术都隐藏在细节里面。

就比如文中的那个hide,这就是个细节,我到现在还没百度到是为啥,后面知道了再更新到这里。

欢迎转载,但请注明来源。

最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

image.png