過年了,基于Vue做一個消息通知組件

前言

今天除夕,在這里祝大家新年快樂!??!今天在這個特別的日子里我們做一個消息通知組件,好,我們開始行動起來吧?。?!
項目一覽

效果很簡單,就是這種的小卡片似的效果。



















我們先開始寫UI頁面,可自定義消息內(nèi)容以及關(guān)閉按鈕的樣式。

Notification.vue

<template>
 <transition name="fade" @after-enter="handleAfterEnter">
  <div class="notification" :style="style" v-show="visible">
   <span class="notification__content">
    {{content}}
   </span>
   <span class="notification__btn" @click="handleClose">{{btn}}</span>
  </div>
 </transition>
</template>
<script>
export default {
 name: 'Notification',
 props: {
  content: {
   type: String,
   required: true
  },
  btn: {
   type: String,
   default: 'close'
  }
 }
}
</script>
<style lang="less" scoped>
.fade-enter-active, .fade-leave-active{
 transition: opacity 1s;
}
.fade-enter, .fade-leave-to{
 opacity: 0;
 transform: translateX(100px);
}
.notification{
 display: flex;
 background-color: #303030;
 color: rgba(255, 255, 255, 1);
 align-items: center;
 padding: 20px;
 position: fixed;
 min-width: 280px;
 box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.3);
 flex-wrap: wrap;
 transition: all 0.3s;
 border-radius:10px ;
 &__content{
  padding: 0;
 }
 &__btn{
  color: #ff4081;
  padding-left: 24px;
  margin-left: auto;
  cursor: pointer;
 }
}
</style>



寫完基本的樣式組件,我們該賦予其靈魂了。好,我們開始寫最重要的邏輯。

notify.js

import Vue from "vue";
import Notification from "./Notification.vue";

const NotificationConstructor = Vue.extend(Notification);

const instances = [];
let seed = 1;
// 消除Vue實例
const removeInstance = (instance) => {
  if (!instance) return;
  const len = instances.length;
  const index = instances.findIndex((ins) => instance.id === ins.id);

  instances.splice(index, 1);

  if (len <= 1) return;
  const removeHeight = instance.height;
  for (let i = index; i < len - 1; i++) {
    instances[i].verticalOffset =
      parseInt(instances[i].verticalOffset) - removeHeight - 16;
  }
};

const notify = (options = {}) => {
  if (Vue.prototype.$isServer) return;
  // 獲取vue實例
  let instance = new NotificationConstructor({
    propsData: options, // 這里是傳進來一組props
    data() {
      return {
        verticalOffset: 0,
        timer: null,
        visible: false,
        height: 0,
      };
    },
    computed: {
      // 配置消息組件的位置
      style() {
        return {
          position: "fixed",
          right: "20px",
          bottom: `${this.verticalOffset}px`,
        };
      }
    },
    mounted() {
      this.createTimer();
      this.$el.addEventListener("mouseenter", () => {
        if (this.timer) {
          this.clearTimer(this.timer);
        }
      });
      this.$el.addEventListener("mouseleave", () => {
        if (this.timer) {
          this.clearTimer(this.timer);
        }
        this.createTimer();
      });
    },
    updated() {
      this.height = this.$el.offsetHeight;
    },
    beforeDestroy() {
      this.clearTimer();
    },
    methods: {
      // 創(chuàng)建計時器
      createTimer() {
        this.timer = setTimeout(() => {
          this.visible = false;
          document.body.removeChild(this.$el);
          removeInstance(this);
          this.$destroy();
        }, options.timeout || 5000);
      },
      // 清除計時器
      clearTimer() {
        if (this.timer) {
          clearTimeout(this.timer);
        }
      },
      // 關(guān)閉消息彈窗
      handleClose() {
        this.visible = false;
        document.body.removeChild(this.$el);
        removeInstance(this);
        this.$destroy(true);
      },
      // 過渡js鉤子
      handleAfterEnter() {
        this.height = this.$el.offsetHeight;
      },
    },
  });

  const id = `notification_${seed++}`; // 動態(tài)生成唯一Id
  instance.id = id;
  // 生成vue中的$el
  instance = instance.$mount();
  // 將$el中的內(nèi)容插入dom節(jié)點中去
  document.body.appendChild(instance.$el);
  instance.visible = true;

  let verticalOffset = 0;

  instances.forEach((item) => {
    verticalOffset += item.$el.offsetHeight + 16;
  });

  verticalOffset += 16;
  instance.verticalOffset = verticalOffset;

  instances.push(instance);

  return instance;
};

export default notify;


當(dāng)消息組件組高度超過瀏覽器顯示區(qū)域的時候,消息組件會依次按順序消失。

在這里,我們使用了Vue.extend(),在這里我們簡單地介紹下,官網(wǎng)上是這樣介紹的。

    使用基礎(chǔ) Vue 構(gòu)造器,創(chuàng)建一個“子類”。參數(shù)是一個包含組件選項的對象。

    data 選項是特例,需要注意 - 在 Vue.extend() 中它必須是函數(shù)

<div id="app"></div>



// 創(chuàng)建構(gòu)造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 創(chuàng)建 Profile 實例,并掛載到一個元素上。
new Profile().$mount('#app')



@after-enter="handleAfterEnter",看到這很多小伙伴會有疑問,其實這是Vue過渡組件中 JavaScript 鉤子。

官網(wǎng)的解釋這樣講。

    可以在 attribute 中聲明 JavaScript 鉤子

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>


// ...
methods: {
  // --------
  // 進入中
  // --------

  beforeEnter: function (el) {
    // ...
  },
  // 當(dāng)與 CSS 結(jié)合使用時
  // 回調(diào)函數(shù) done 是可選的
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------
  // 離開時
  // --------

  beforeLeave: function (el) {
    // ...
  },
  // 當(dāng)與 CSS 結(jié)合使用時
  // 回調(diào)函數(shù) done 是可選的
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // leaveCancelled 只用于 v-show 中
  leaveCancelled: function (el) {
    // ...
  }
}



    這些鉤子函數(shù)可以結(jié)合 CSS transitions/animations 使用,也可以單獨使用。
    當(dāng)只用 JavaScript 過渡的時候,在 enter 和 leave 中必須使用 done 進行回調(diào)。否則,它們將被同步調(diào)用,過渡會立即完成。
    推薦對于僅使用 JavaScript 過渡的元素添加 v-bind:css="false",Vue 會跳過 CSS 的檢測。這也可以避免過渡過程中 CSS 的影響。

    詳情解釋可以查看官方文檔

整理完UI組件與邏輯文件,下一步做得工作是將它們整合起來,通過Vue命令的方式直接使用。

index.js

import Notification from "./Notification.vue";
import notify from "./notify.js";

export default (Vue) => {
  Vue.component(Notification.name, Notification);
  Vue.prototype.$notify = notify;
};



下面,我們將使用它。

在main.js中引入index.js文件。

import Notification from "../src/components/notification/index.js";
Vue.use(Notification);



然后,你在相應(yīng)的組件中這樣調(diào)用它即可。

this.$notify({
   content: "Hello World", // 消息內(nèi)容
   btn: "關(guān)閉" // 關(guān)閉按鈕內(nèi)容
});

作者:Vam的金豆之路

主要領(lǐng)域:前端開發(fā)

我的微信:maomin9761

微信公眾號:前端歷劫之路