发现 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>
演示