如果我们的Vue项目中没有用到任何UI框架的话,为了更好的用户体验,肯定会用到toast。那么我们就自定义这个组件吧。
在 src/components下创建toast文件夹,并依此创建index.vue和index.ts
创建模版index.vue
一般toast会有如下功能:背景色、字体颜色、文本。停留时间。
<template>
<div class="toast-box" >
<p class="toast-value" :style="{background: background, color: color}">
{{ value }}
</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Toast',
props: {
value: {
type: String,
default: ''
},
duration: {
type: Number,
default: 3000
},
background: {
type: String,
default: '#000'
},
color: {
type: String,
default: '#fff'
}
}
})
</script>
<style>
.toast-box {
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
}
.toast-value {
max-width: 100px;
background: rgb(8, 8, 8);
padding: 8px 10px;
border-radius: 4px;
text-align: center;
display: inline-block;
animation: anim 0.5s;
}
@keyframes anim {
0% {opacity: 0;}
100%{opacity:1;}
}
.toast-value.reomve {
animation: reomve 0.5s;
}
@keyframes reomve {
0% {opacity: 1;}
100%{opacity:0;}
}
</style>
导出Toast方法
- 创建时
1.首先使用createVNode方法创建一个vNode独享;2.使用render方法转换成真实dom;3.添加到body上。
- 销毁时
1.首先添加一个淡入淡出效果;2.使用render将真实设置为null;3.移除创建的dom;
import { createVNode, render } from 'vue'
import toastTemplate from './index.vue'
export interface IProps {
value?: string;
duration?: number;
background?: string;
color?: string;
}
const defaultOpt = { // 创建默认参数
duration: 3000
}
export interface ResultParams {
destory?: () => void;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const Toast = (options: IProps):ResultParams => {
const container = document.createElement('div')
const opt = {...defaultOpt,...options}
const vm = createVNode(toastTemplate, opt) // 创建vNode
render(vm, container)
document.body.appendChild(container) // 添加到body上
const destory = ()=> {
const dom = vm.el as HTMLDivElement
if(dom.querySelector('.toast-value')) {
dom.querySelector('.toast-value')?.classList.add('reomve') // 销毁时添加淡入淡出效果
const t = setTimeout(() => { // 淡入淡出效果之后删除dom节点
render(null, container)
document.body.removeChild(container)
clearTimeout(t)
},500);
}
}
if(opt.duration) { // 如果传入的值为0可以持续保留在页面,需要手动销毁
const timer = setTimeout(()=> {
destory()
clearTimeout(timer)
}, opt.duration)
}
return {
destory
}
}
export default Toast
测试
<template>
<div class="home">
测试toast
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import Toast from '@/components/toast'; // @ is an alias to /src
export default defineComponent({
name: 'Home',
setup() {
onMounted(()=> {
const toast = Toast({
value: 'toast',
duration: 0, // 如果大于0则不必使用destory方法
background: '#000',
color: '#fff'
})
setTimeout(() => {
toast.destory && toast.destory()
}, 3000);
})
}
});
</script>
另一种写法,纯ts实现
import { provide, inject, reactive, createApp, h } from "vue";
const ToastSymbol = Symbol();
const globalConfig: any = {
duration: 2500
};
const state = reactive({
show: false, // toast元素是否显示
text: ""
});
let toastTimer: any = null,
toastVM: any = null,
toastWrapper: any = null;
const _toast = (text: string) => {
state.show = true;
state.text = text;
if (!toastVM) {
// 如果toast实例存在则不重新创建
toastVM = createApp({
setup() {
return () =>
h(
"span",
{
class: ["dialog-tips"],
style: {
display: state.show ? "block" : "none"
}
},
state.text
);
}
});
}
if (!toastWrapper) {
// 如果该节点以经存在则不重新创建
toastWrapper = document.createElement("div");
toastWrapper.id = "lx-toast";
document.body.appendChild(toastWrapper);
toastVM.mount("#lx-toast");
}
if (toastTimer) clearTimeout(toastTimer);
// 定时器,持续时长之后隐藏
toastTimer = setTimeout(() => {
state.show = false;
clearTimeout(toastTimer);
}, globalConfig.duration);
};
export function provideToast(config: any = {}) {
for (const key in config) {
globalConfig[key] = config[key];
}
provide(ToastSymbol, _toast);
}
export function useToast() {
const toast = inject(ToastSymbol);
if (!toast) {
throw new Error("error");
}
return toast;
}
let mNowToast: any = null;
export function setToast(mObj: any) {
mNowToast = mObj;
}
export function nowToast(mStr: string) {
if (mNowToast) mNowToast(mStr);
}
//全局定义样式//////////////////////////////////////
/* 提示 */
.dialog-tips {
position: fixed;
z-index: 2000;
left: 50%;
top: 45%;
transition: all 0.5s;
-webkit-transform: translateX(-50%) translateY(-50%);
-moz-transform: translateX(-50%) translateY(-50%);
-ms-transform: translateX(-50%) translateY(-50%);
-o-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
text-align: center;
border-radius: 5px;
color: #ffffff;
background: rgba(17, 17, 17, 0.6);
min-height: 100px;
font-size: 26px;
line-height: 40px;
padding: 30px 40px;
max-width: 500px;
}
//引用///////////////////////////////
//app.vue下设置
provideToast({
duration: 2500
});
const Toast = useToast();
setToast(Toast);
//界面中使用
const Toast = useToast();
Toast("Hello World");