模仿Vant4的Toast轻提示,原生js + uniapp版

167 阅读2分钟

发现 Vant4 的Toast组件的内容发生改变的时候提示框切换是有个过渡效果的,感觉很优雅,就想着实现一个自己用,因为公司项目不一定是会用 Vant 的。写了一个原生版本和一个uni-app版本。
不废话上代码。

原生js

class toast {  
    node; // 节点  
    mask; // 遮罩  
    content; // 内容div  
    span; // 文字  
    svg; // loading 动画  
    options; // 配置项目  
    showTimer; // show 定时器  
    activeTimer; // 准备消失 定时器  
    closeTimer; // 关闭 定时器  

    constructor() {  
        if (document.querySelector(".Toast")) return; // 如果已经存在 不在执行  
        this.node = document.createElement("div");  
        this.node.className = "Toast";  
        this.node.innerHTML = `<div class="mask" style="display: none;">  
            <div class="content">  
                <svg style="display: none;enable-background:new 0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" width="30px" height="30px" viewBox="0 0 50 50">  
                    <path fill="#fff" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z" transform="rotate(275.098 25 25)">  
                        <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"></animateTransform>  
                    </path>
                </svg>  
                <span></span>  
            </div>  
        </div>`;  
        document.body.append(this.node); // 将节点插入 body  
        // 赋值各个 dom  
        this.mask = document.querySelector(".Toast .mask");  
        this.content = document.querySelector(".Toast .mask .content");  
        this.svg = document.querySelector(".Toast .mask .content svg");  
        this.span = document.querySelector(".Toast .mask .content span");  
    }  

    show(argument = {}) {  
        this.clearAllTimer(); // 显示前先清除所有定时器  
        this.options = argument; // 获取配置项  
        this.span.innerText = argument.message; // 显示的文字  
        this.mask.style.display = "flex"; // 遮罩显示  
        this.showTimer = setTimeout(() => {  
            !this.content.className.includes("active") && this.content.classList.add("active") 
        }); // 添加 active 类 实现过渡效果  
        // 如果不存在loading类名并且配置项里面type为loading  
        if (!this.content.className.includes("loading") && argument.type === "loading") {  
            this.content.classList.add("loading"); // 添加loading类名  
            this.svg.style.display = "block"; // 显示加载动画  
        // 如果存在loading类名并且配置项里面type不为loading  
        } else if (this.content.className.includes("loading") && argument.type !== "loading") { 
            this.svg.style.display = "none"; // 隐藏动画  
            this.content.classList.remove("loading"); // 去除loading类名  
        }  
        argument.duration && this.close(); // duration为假的时候不关闭提示  
    }  

    close() {  
        this.activeTimer = setTimeout(() => { // 根据传来的 duration 决定多长时间后关闭  
        this.content.className.includes("active") && this.content.classList.remove("active"); // 删除 active 类名  
            this.closeTimer = setTimeout(() => { // 过渡结束后隐藏遮罩  
                this.mask.style.display = "none";  
            }, 300);  
        }, this.options.duration || 0);  
    }  

    clearAllTimer() { // 清除所有用到的定时器  
        clearTimeout(this.showTimer);  
        clearTimeout(this.activeTimer);  
        clearTimeout(this.closeTimer);  
    }  
}  
  
const Toast = new toast(); // 直接就导出 new 之后的 toast 对象  
  
export default Toast;

css

.Toast .mask {  
    width: 100%;  
    height: 100vh;  
    background-color: rgba(0, 0, 0, 0);  
    position: absolute;  
    top: 0;  
    left: 0;  
    align-items: center;  
    justify-content: center;  
}  
  
.Toast .content {  
    width: fit-content;  
    min-width: 96px;  
    padding: 8px 12px;  
    font-size: 14px;  
    background-color: rgba(0, 0, 0, 0.7);  
    color: #fff;  
    border-radius: 8px;  
    opacity: 0;  
    transition: all 0.3s;  
    text-align: center;  
    line-height: 140%;  
    display: flex;  
    flex-direction: column;  
    justify-content: center;  
    align-items: center;  
    position: relative;  
    z-index: 2007;  
}  
  
.Toast .content svg {  
    margin-bottom: 5px;  
}  
  
.Toast .content.loading {  
    padding: 20px 16px;  
}  
  
.Toast .content.active {  
    opacity: 1;  
}

uni-app 组件

<template>
  <view class="Toast">
    <view class="mask" :style="`display:${display};`">
      <view :class="classes">
        <svg v-show="options.type==='loading'" xmlns="http://www.w3.org/2000/svg"
          xmlns:xlink="http://www.w3.org/1999/xlink" width="30px" height="30px" viewBox="0 0 50 50"
          style="enable-background:new 0 0 50 50" xml:space="preserve">
          <path fill="#fff"
            d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"
            transform="rotate(275.098 25 25)">
            <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25"
              dur="0.6s" repeatCount="indefinite"></animateTransform>
          </path>
        </svg>
        <text>{{options.message}}</text>
      </view>
    </view>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        display: "none",
        classes: ["content"],
        options: {
          duration: 1500
        },
        showTimer: null,
        activeTimer: null,
        closeTimer: null,
        isLoading: false,
        num: 0,
        isShow: false
      }
    },
    methods: {
      show(argument) {
        this.clearAllTimer();
        this.options = argument;
        this.display = "flex";
        this.showTimer = setTimeout(() => !this.classes.includes("active") && this.classes.push("active"));
        if (!this.classes.includes("loading") && argument.type === "loading") {
          this.classes.push("loading");
        } else if (this.classes.includes("loading") && argument.type !== "loading") {
          this.classes.splice(this.classes.indexOf("loading"), 1);
        }
        argument.duration && this.close();
      },
      close() {
        this.activeTimer = setTimeout(() => {
          this.classes.includes("active") && this.classes.splice(this.classes.indexOf("active"), 1);
          this.closeTimer = setTimeout(() => {
            this.display = "none";
          }, 300);
        }, this.options.duration || 0);
      },
      clearAllTimer() {
        clearTimeout(this.showTimer);
        clearTimeout(this.activeTimer);
        clearTimeout(this.closeTimer);
      }
    }
  }
</script>

<style lang="scss" scoped>
  .Toast {
    .mask {
      width: 100%;
      height: 100vh;
      background-color: rgba(0, 0, 0, 0);
      position: absolute;
      top: 0;
      left: 0;
      align-items: center;
      justify-content: center;
    }

    .content {
      width: fit-content;
      min-width: 96px;
      padding: 8px 12px;
      font-size: 14px;
      background-color: rgba(0, 0, 0, .7);
      color: #fff;
      border-radius: 8px;
      opacity: 0;
      transition: all .3s;
      text-align: center;
      line-height: 140%;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      position: relative;
      z-index: 2007;

      svg {
        margin-bottom: 5px;
      }

      &.loading {
        padding: 20px 16px;
      }

      &.active {
        opacity: 1;
      }
    }
  }
</style>

演示

20230609_175713.gif