前言
今天除夕,在这里祝大家新年快乐!!!今天在这个特别的日子里我们做一个消息通知组件,好,我们开始行动起来吧!!!
项目一览
效果很简单,就是这种的小卡片似的效果。

源码
我们先开始写UI页面,可自定义消息内容以及关闭按钮的样式。
Notification.vue
1<template> 2 <transition name="fade" @after-enter="handleAfterEnter"> 3 <div class="notification" :style="style" v-show="visible"> 4 <span class="notification__content"> 5 {{content}} 6 </span> 7 <span class="notification__btn" @click="handleClose">{{btn}}</span> 8 </div> 9 </transition> 10</template> 11<script> 12export default { 13 name: 'Notification', 14 props: { 15 content: { 16 type: String, 17 required: true 18 }, 19 btn: { 20 type: String, 21 default: 'close' 22 } 23 } 24} 25</script> 26<style lang="less" scoped> 27.fade-enter-active, .fade-leave-active{ 28 transition: opacity 1s; 29} 30.fade-enter, .fade-leave-to{ 31 opacity: 0; 32 transform: translateX(100px); 33} 34.notification{ 35 display: flex; 36 background-color: #303030; 37 color: rgba(255, 255, 255, 1); 38 align-items: center; 39 padding: 20px; 40 position: fixed; 41 min-width: 280px; 42 box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.3); 43 flex-wrap: wrap; 44 transition: all 0.3s; 45 border-radius:10px ; 46 &__content{ 47 padding: 0; 48 } 49 &__btn{ 50 color: #ff4081; 51 padding-left: 24px; 52 margin-left: auto; 53 cursor: pointer; 54 } 55} 56</style> 57 58
写完基本的样式组件,我们该赋予其灵魂了。好,我们开始写最重要的逻辑。
notify.js
1import Vue from "vue"; 2import Notification from "./Notification.vue"; 3 4const NotificationConstructor = Vue.extend(Notification); 5 6const instances = []; 7let seed = 1; 8// 消除Vue实例 9const removeInstance = (instance) => { 10 if (!instance) return; 11 const len = instances.length; 12 const index = instances.findIndex((ins) => instance.id === ins.id); 13 14 instances.splice(index, 1); 15 16 if (len <= 1) return; 17 const removeHeight = instance.height; 18 for (let i = index; i < len - 1; i++) { 19 instances[i].verticalOffset = 20 parseInt(instances[i].verticalOffset) - removeHeight - 16; 21 } 22}; 23 24const notify = (options = {}) => { 25 if (Vue.prototype.$isServer) return; 26 // 获取vue实例 27 let instance = new NotificationConstructor({ 28 propsData: options, // 这里是传进来一组props 29 data() { 30 return { 31 verticalOffset: 0, 32 timer: null, 33 visible: false, 34 height: 0, 35 }; 36 }, 37 computed: { 38 // 配置消息组件的位置 39 style() { 40 return { 41 position: "fixed", 42 right: "20px", 43 bottom: `${this.verticalOffset}px`, 44 }; 45 } 46 }, 47 mounted() { 48 this.createTimer(); 49 this.$el.addEventListener("mouseenter", () => { 50 if (this.timer) { 51 this.clearTimer(this.timer); 52 } 53 }); 54 this.$el.addEventListener("mouseleave", () => { 55 if (this.timer) { 56 this.clearTimer(this.timer); 57 } 58 this.createTimer(); 59 }); 60 }, 61 updated() { 62 this.height = this.$el.offsetHeight; 63 }, 64 beforeDestroy() { 65 this.clearTimer(); 66 }, 67 methods: { 68 // 创建计时器 69 createTimer() { 70 this.timer = setTimeout(() => { 71 this.visible = false; 72 document.body.removeChild(this.$el); 73 removeInstance(this); 74 this.$destroy(); 75 }, options.timeout || 5000); 76 }, 77 // 清除计时器 78 clearTimer() { 79 if (this.timer) { 80 clearTimeout(this.timer); 81 } 82 }, 83 // 关闭消息弹窗 84 handleClose() { 85 this.visible = false; 86 document.body.removeChild(this.$el); 87 removeInstance(this); 88 this.$destroy(true); 89 }, 90 // 过渡js钩子 91 handleAfterEnter() { 92 this.height = this.$el.offsetHeight; 93 }, 94 }, 95 }); 96 97 const id = `notification_${seed++}`; // 动态生成唯一Id 98 instance.id = id; 99 // 生成vue中的$el 100 instance = instance.$mount(); 101 // 将$el中的内容插入dom节点中去 102 document.body.appendChild(instance.$el); 103 instance.visible = true; 104 105 let verticalOffset = 0; 106 107 instances.forEach((item) => { 108 verticalOffset += item.$el.offsetHeight + 16; 109 }); 110 111 verticalOffset += 16; 112 instance.verticalOffset = verticalOffset; 113 114 instances.push(instance); 115 116 return instance; 117}; 118 119export default notify; 120 121
当消息组件组高度超过浏览器显示区域的时候,消息组件会依次按顺序消失。
在这里,我们使用了Vue.extend(),在这里我们简单地介绍下,官网上是这样介绍的。
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
data选项是特例,需要注意 - 在Vue.extend()中它必须是函数
1<div id="app"></div> 2
1// 创建构造器 2var Profile = Vue.extend({ 3 template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', 4 data: function () { 5 return { 6 firstName: 'Walter', 7 lastName: 'White', 8 alias: 'Heisenberg' 9 } 10 } 11}) 12// 创建 Profile 实例,并挂载到一个元素上。 13new Profile().$mount('#app') 14
@after-enter="handleAfterEnter",看到这很多小伙伴会有疑问,其实这是Vue过渡组件中 JavaScript 钩子。
官网的解释这样讲。
可以在 attribute 中声明 JavaScript 钩子
1<transition 2 v-on:before-enter="beforeEnter" 3 v-on:enter="enter" 4 v-on:after-enter="afterEnter" 5 v-on:enter-cancelled="enterCancelled" 6 7 v-on:before-leave="beforeLeave" 8 v-on:leave="leave" 9 v-on:after-leave="afterLeave" 10 v-on:leave-cancelled="leaveCancelled" 11> 12 <!-- ... --> 13</transition> 14
1// ... 2methods: { 3 // -------- 4 // 进入中 5 // -------- 6 7 beforeEnter: function (el) { 8 // ... 9 }, 10 // 当与 CSS 结合使用时 11 // 回调函数 done 是可选的 12 enter: function (el, done) { 13 // ... 14 done() 15 }, 16 afterEnter: function (el) { 17 // ... 18 }, 19 enterCancelled: function (el) { 20 // ... 21 }, 22 23 // -------- 24 // 离开时 25 // -------- 26 27 beforeLeave: function (el) { 28 // ... 29 }, 30 // 当与 CSS 结合使用时 31 // 回调函数 done 是可选的 32 leave: function (el, done) { 33 // ... 34 done() 35 }, 36 afterLeave: function (el) { 37 // ... 38 }, 39 // leaveCancelled 只用于 v-show 中 40 leaveCancelled: function (el) { 41 // ... 42 } 43} 44
1.这些钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用。2.当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。3.推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。
详情解释可以查看官方文档
整理完UI组件与逻辑文件,下一步做得工作是将它们整合起来,通过Vue命令的方式直接使用。
index.js
1import Notification from "./Notification.vue"; 2import notify from "./notify.js"; 3 4export default (Vue) => { 5 Vue.component(Notification.name, Notification); 6 Vue.prototype.$notify = notify; 7}; 8
下面,我们将使用它。
在main.js中引入index.js文件。
1import Notification from "../src/components/notification/index.js"; 2Vue.use(Notification); 3
然后,你在相应的组件中这样调用它即可。
1this.$notify({ 2 content: "Hello World", // 消息内容 3 btn: "关闭" // 关闭按钮内容 4}); 5
结语
谢谢大家阅读,希望没有浪费大家的时间。新的一年即将来临,2021年祝大家工作顺利,平平安安。


本文转转自微信公众号前端历劫之路原创https://mp.weixin.qq.com/s/SS50RmN0bo9F3P3zOSvZiA,如有侵权,请联系删除。
