Vue Snackbar 消息条队列显示,依次动画消失的实现

效果预览

思路

  1. 封装 Snackbar 组件;
  2. 在根路由页面下建立全局 Snackbar 控制器,统一管理 Snackbar;
  3. 通过事件通知全局 Snackbar 控制器显示消息;

实现

1. 封装 Snackbar 组件

project/src/components/snackbar.vue

<template>
  <div class="component-snackbar" v-if="value">
    <div class="snackbar-content">{{ message }}</div>
    <div class="snackbar-close" v-if="closable" @click="close">关闭</div>
  </div>
</template>

<script>
export default {
  name: "component-snackbar",
  props: {
    value: Boolean, // 调用本组件v-model传入的值
    message: String, // 消息内容
    closable: { // 是否显示删除按钮
      type: Boolean,
      default: false
    }
  },
  data: function() {
    return {
      showTime: 6000, // snackbar显示的时长
      timer: null // 定时器
    }
  },
  mounted() {
    // 在 showTime 到期后自动关闭snackbar
    this.timer = setTimeout(() => {
      this.close()
    }, this.showTime)
  },
  methods: {
    close() {
      // 清除定时器
      clearTimeout(this.timer)
      // 不能直接在组件中修改props中的数据,因此不能直接修改this.value = false
      // 而是实现了在自定义组件中使用v-model,通过外传 input 事件通知调用者自动更新 v-model 传入的值
      this.$emit('input', false)
    }
  }
}
</script>

<style lang="less">
.component-snackbar {
  width: 400px;
  height: 60px;
  position: fixed;
  right: 10px;
  bottom: 10px;
  display: flex;
  flex-direction: row;
  align-items: center;
  border-radius: 8px;
  background-color: #333333;
  box-shadow: 2px 4px 6px 0 rgba(0,0,0,.4);
  transition: transform 500ms ease-in;
  .snackbar-content {
    flex: 1;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    padding: 0 16px;
  }
  .snackbar-close {
    margin: 0 16px;
    color: deeppink;
    cursor: pointer;
  }
}
</style>

2. 全局 Snackbar 控制器

注册事件总线 EventBus;

project/src/main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

// 创建 Vue 的实例作为事件总线使用,将其挂载到 $eventBus 上,
// 即可在组件中直接使用 this.$eventBus.$emit() 和 this.$eventBus.$on() 来触发/监听事件
Vue.prototype.$eventBus = new Vue()

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

全局 Snackbar 控制器

全局 Snackbar 控制器在根路由页面下监听 showSnackbar 事件,并维护一个消息列表,通过对事件的监听有序的推入、删除消息,从而达到控制 Snackbar 显示的目的;

project/src/App.vue

<template>
  <div id="app">
    <router-view />

    <!-- 全局 Snackbar 队列 -->
    <!-- 根据当前 Snackbar 的 index 动态计算 Y 方向的位移,达到依次排列的目的 -->
    <snackbar
      v-model="item.show"
      :style="{transform: 'translateY(' + -(60+10) * index + 'px)'}"
      :message="item.content"
      :closable="item.closable"
      v-for="(item, index) in messages"
      :key="item.id"
      @input="onSnackbarClose($event, index)"></snackbar>
  </div>
</template>

<script>
import Snackbar from './components/snackbar'

export default {
  name: "app",
  components: {
    Snackbar
  },
  data: function() {
    return {
      messages: [] // 消息队列
    }
  },
  mounted() {
    // 全局 Snackbar 控制器事件监听
    this.snackbarController()
  },
  methods: {
    snackbarController() {
      // 监听 showSnackbar 事件
      this.$eventBus.$on('showSnackbar', data => {
        // 将收到的message推入messages数组中
        this.messages.push({
          ...data,
          show: true
        })
      })
    },
    onSnackbarClose(value, index) {
      // value 为 Snackbar 组件内部传递出来的
      // index 为当前关闭 Snackbar 的 索引
      // 删除已关闭的 Snackbar 对应的消息数据
      this.messages.splice(index, 1)
    }
  }
}
</script>

<style lang="less">
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 0;
  background-color: #212121;
  color: #cecece;
  font-size: 14px;
}
</style>

3. 调用Snackbar

这是一个模拟触发消息的页面;

project/src/views/snackbar.vue

<template>
  <div class="view-snackbar">
    <div class="wrap">
      <input type="text" v-model="msg" class="msg-input" placeholder="说点什么...">
      <div class="btn" @click="showMessage">显示消息</div>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'view-snackbar',
    data: function () {
      return {
        msg: ''
      }
    },
    methods: {
      showMessage() {
        // 通过触发 showSnackbar 事件并传递消息参数,从而调用全局 Snackbar
        this.$eventBus.$emit('showSnackbar', {
          id: new Date().getTime(), // id 用于设置 Snackbar 在 v-for 循环中的 key 属性,避免排序混乱的问题
          content: this.msg,
          closable: true
        })
      }
    }
  }
</script>

<style lang="less">
.view-snackbar {
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-image: url(https://img.cc0.cn/pixabay/2019102201563350617.jpg/content);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  .wrap {
    width: 240px;
  }
  .msg-input {
    width: 100%;
    height: 40px;
    color: inherit;
    background-color: #333333;
    border: 2px solid #444444;
    border-radius: 8px;
    outline: none;
    padding: 0 8px;
  }
  .btn {
    width: 120px;
    margin: 20px auto;
    padding: 8px 0;
    text-align: center;
    border-radius: 8px;
    background-color: deeppink;
    cursor: pointer;
    user-select: none;
  }
}
</style>

4. 总结

通过上述4步就实现了简单的 Vue Snackbar 消息条队列显示,并且依次动画消失,思路还是很清晰的,主要有3个要点:

  • 使用了全局控制器,并通过事件总线 $eventBus 传递消息;
  • 在自定义组件中使用 v-model 实现 Snackbar 的删除;
  • 利用 Snackbar 索引动态计算 Y 方向上的位移实现 Snackbar 的有序排列;

本教程旨在描述思路,实现比较简单,未做过多封装和定制,希望能帮到有需要的童鞋,如发现任何问题,或有更多实现方式,欢迎一起讨论!

5. 改善

可在本版基础上做更多完善,有兴趣的童鞋可以自己玩玩;

  • Snackbar 渐变出现/消失;
  • 多种高度的 Snackbar 混合使用;

本文出处:https://www.cnblogs.com/zhenfengxun/
本文链接:https://www.cnblogs.com/zhenfengxun/p/12452814.html

posted @ 2020-03-10 10:53  大漠风起沙飞扬  阅读(1694)  评论(0编辑  收藏  举报