louzhedong/blog

做一个Vue的Toast组件

louzhedong opened this issue · 0 comments

本文来基于Vue实现一个Toast组件

要点

基于Vue.extend()方法,我们可以得到一个Vue组件的实例,并且操作这个实例

Toast.js

import Vue from 'vue';
import ToastCompent from './Toast.vue';

const instancePool = []; // 实例池
const ToastConstructor = Vue.extend(ToastCompent);

const getAnInstance = () => {
  if (instancePool.length > 0) {
    const instance = instancePool.shift();
    return instance;
  }
  return new ToastConstructor({
    el: document.createElement('div')
  });
}

const removeDom = (event) => {
  if (event.target.parentNode) {
    event.target.parentNode.removeChild(event.target);
  }
}

ToastConstructor.prototype.close = function () {
  this.visible = false;
  this.$el.addEventListener('transitionend', removeDom);
  this.closed = true;
  // 关闭后将实例存入实例池
  instancePool.push(this);
}

/**
 *
 *
 * @param {*} [options={
 * message
 * backgroundColor: rgba(0, 0, 0, 0.7)
 * position ['top', 'middle', 'bottom']
 * duration 3000 -1为不关闭
 * className
 * }]
 * options 也可以是一个字符串
 */
const Toast = (options = {}) => {
  const duration = options.duration || 3000;
  let instance = getAnInstance();
  instance.closed = false;
  clearTimeout(instance.timer);

  instance.message = typeof options === 'string' ? options : options.message;
  instance.backgroundColor = options.backgroundColor || 'rgba(0, 0, 0, 0.7)';
  instance.position = options.position || 'top';
  instance.duration = options.duration || 3000;
  instance.className = options.className || '';

  document.body.appendChild(instance.$el);

  Vue.nextTick(() => {
    instance.visible = true;
    instance.$el.removeEventListener('transitionend', removeDom);
    ~duration && (instance.timer = setTimeout(() => {
      if (instance.closed) return;
      instance.close();
    }, duration))
  });

  return instance;
}

export default Toast;

Toast.vue

<template>
  <transition name="fade">
    <div
      class="wheat-toast"
      :class="computedClass"
      :style="{'background': backgroundColor}"
      v-show="visible"
    >
      <span class="wheat-toast-message">{{message}}</span>
    </div>
  </transition>
</template>

<script type="text/babel">
export default {
  props: ["message", "backgroundColor", "position", "duration", "className"],

  data() {
    return {
      visible: false
    };
  },

  computed: {
    computedClass() {
      let classname = this.className;
      switch (this.position) {
        case "top":
          classname += " toast-position-top";
          break;
        case "middle":
          classname += " toast-position-middle";
          break;
        case "bottom":
          classname += " toast-position-bottom";
          break;
        default:
          break;
      }
      return classname;
    }
  }
};
</script>

<style lang="less">
.wheat-toast {
  position: fixed;
  max-width: 80%;
  border-radius: 5px;
  color: #fff;
  box-sizing: border-box;
  text-align: center;
  z-index: 1000;
  padding: 8px 12px;
  font-size: 16px;
  .wheat-toast-message {
    font-size: inherit;
  }
}
.toast-position-top {
  top: 50px;
  left: 50%;
  transform: translate(-50%, 0);
}

.toast-position-middle {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.toast-position-bottom {
  bottom: 50px;
  left: 50%;
  transform: translate(-50%, 0);
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.fade-enter-to,
.fade-leave {
  opacity: 1;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
</style>

使用方式

<template>
  <div class="toast-example">
    <div class="beautify-button" @click="handleShowToast">显示弹窗</div>
  </div>
</template>

<script>
import Toast from "wheat-ui/dist/Toast";
export default {
  methods: {
    handleShowToast() {
      Toast({
        message: "出现错误",
        position: "middle"
      });

      Toast("只有message");


      const instance = Toast({
        message: "不关闭",
        duration: -1
      });
      setTimeout(() => {
        instance.close();
      }, 4000);
    }
  }
};
</script>
​```