全局通知组件
全局通知组件
- Notify
- _src
- index.vue
- instances.ts
- notify.ts
- props.ts
- index.ts
- _src
1.index.vue
<template>
<Teleport :to="appendTo">
<div
v-show="visible"
absolute
w-800px
h-30px
mx-auto
left-0
right-0
text="16px #ECAE4F"
class="font-YOUHEI wrapper"
:style="{ top: `${offset}px` }"
>
<SvgIcon name="fa6-solid:bullhorn" :size="20" mr-10px />
<ScrollTransform
w-600px
origin="h"
:duration="duration"
:data="message"
timing-func="linear"
:ani-count="repeatNum"
@finish-ani="onHidden"
>
<template #default="{ data }">
<div whitespace-nowrap h-full>
<div inline-block w-600px></div>
<div inline-block text-nowrap>{{ data }}</div>
<div inline-block w-600px></div>
</div>
</template>
</ScrollTransform>
<div ml-20px>>></div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import type { NotifyProps } from "./props";
const visible = ref(false);
function hidden() {
visible.value = false;
}
function show() {
visible.value = true;
}
withDefaults(defineProps<NotifyProps>(), {
appendTo: ".layout-main",
message: "请输入文字",
repeatNum: 3,
offset: 180,
duration: 10,
onHidden: () => {},
});
onMounted(() => {
show();
});
defineExpose({
hidden,
show,
visible,
});
</script>
<style lang="scss" scoped>
.wrapper {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
background:
linear-gradient(
to right,
rgb(50 20 0 / 0%) 5%,
rgb(50 20 0 / 30%) 30%,
rgb(50 20 0 / 40%) 50%,
rgb(50 20 0 / 30%) 70%,
rgb(50 20 0 / 0%) 95%
);
}
</style>
2.instances.ts
import type { VNode } from 'vue'
import type { NotifyProps } from './props'
import type { NotifyHandler } from './notify'
export type NotifyContext = {
vnode: VNode
handler: NotifyHandler
props: NotifyProps
}
const instance = shallowRef<NotifyContext | null>(null)
export const getInstance = (): NotifyContext | null => {
return instance.value
}
export const setInstance = (inst: NotifyContext): NotifyContext => {
instance.value = inst
return instance.value
}
export const clearInstance = () => {
instance.value = null
}
3.notify.ts
import { createVNode, render } from "vue"
import NotifyConstructor from './index.vue'
import { getInstance, setInstance,clearInstance } from './instances'
import type { NotifyContext } from './instances'
import type { UserNotifyProps } from './props'
interface NotifyHandler {
hidden: () => void,
destroy: () => void,
show: () => void,
}
const DEFAULT_USER_OPTIONS = {
appendTo: ".layout-main",
message: "请输入通知",
repeatNum: 3,
offset: 180,
duration: 10,
}
const normalizeOptions = (userOptions: UserNotifyProps): Required<UserNotifyProps> => {
const options = {
...DEFAULT_USER_OPTIONS,
...userOptions,
}
return options
}
const hiddenNotify = () => {
const cur = getInstance()
if (!cur) return;
const { handler } = cur
handler.hidden()
}
const createNotify = (userOptions: UserNotifyProps) => {
let cur = getInstance()
const options = normalizeOptions(userOptions)
if (cur) {
cur.props.message = options.message
cur.props.duration = options.duration
cur.props.offset = options.offset
cur.props.repeatNum = options.repeatNum
cur.props.appendTo = options.appendTo
cur.handler.show()
} else {
const container = document.createElement('div')
const props = {
...options,
onHidden: () => {
hiddenNotify()
}
}
const vnode = createVNode(NotifyConstructor, props, null)
render(vnode, container)
const handler: NotifyHandler = {
show: () => {
instance.vnode.component!.exposed!.show()
},
hidden: () => {
instance.vnode.component!.exposed!.hidden()
},
destroy: () => {
clearInstance()
render(null, container)
}
}
const instance: NotifyContext = {
vnode,
handler,
props: (vnode.component as any).props,
}
cur = setInstance(instance)
}
return cur
}
const notify = (options: UserNotifyProps) => {
const instance = createNotify(options)
const userHandler = instance.handler.destroy
return { destory: userHandler }
}
export default notify
export type {
NotifyHandler
}
4.props.ts
interface UserNotifyProps {
message: string;
appendTo?: string; // 挂载dom位置
repeatNum?: number; // 循环次数,循环结束会隐藏组件
offset?: number; // 距离top距离
duration?:number; // 单次循环动画的时间
}
interface NotifyProps extends Required<UserNotifyProps> {
onHidden: () => void;
}
export type {
UserNotifyProps,
NotifyProps
};
5.index.ts
import notify from './_src/notify'
export default notify
export type * from './_src/props'
/**
* * 调用方法
* * 引入
* * import Notify from "@/components/Notify";
* * 其他参数可以参考源码 notify.ts
* * 调用方法
* * const { destroy } =Notify({
* * message: "xxxxxxxxxxxxxxxx",
* * });
* * 离开当前大屏页面时请注意调用destroy函数
* *
*/
6.ScrollTransform.vue
<script setup lang="ts" generic="T extends Object">
const props = withDefaults(
defineProps<{
origin?: "v" | "h";
duration?: number;
data: T;
timingFunc?: string;
aniCount?: number | "infinite";
}>(),
{
origin: "v",
duration: 3,
timingFunc: "ease-in-out",
aniCount: "infinite",
}
);
const emits = defineEmits<{
(eventName: "finishAni"): void;
}>();
const viewer = ref<HTMLDivElement | null>();
const container = ref<HTMLDivElement | null>();
const aniListenerManage = (() => {
let aniListenerDom: HTMLDivElement | null = null;
const aniListener = () => {
emits("finishAni");
};
const add = (dom: HTMLDivElement, aniCount: number | "infinite") => {
if (!aniListenerDom && aniCount !== "infinite") {
dom.addEventListener("animationend", aniListener);
aniListenerDom = dom;
}
};
const remove = () => {
if (aniListenerDom) {
aniListenerDom.removeEventListener("animationend", aniListener);
aniListenerDom = null;
}
};
return {
add,
remove,
};
})();
function renderScroll() {
nextTick(() => {
// 视窗的高度
const viewerDom: NonNullable<typeof viewer.value> = viewer.value!;
const containerDom: NonNullable<typeof container.value> = container.value!;
const { scrollWidth, scrollHeight, clientWidth, clientHeight } = viewerDom;
const { duration, timingFunc, aniCount } = props;
aniListenerManage.remove();
if (
(clientHeight >= scrollHeight && props.origin === "v") ||
(clientWidth >= scrollWidth && props.origin === "h")
) {
return;
}
viewerDom.style.setProperty("--cur-animation-duration", `${duration}s`);
viewerDom.style.setProperty("--cur-animation-count", `${aniCount}`);
if (props.origin === "v" && clientHeight < scrollHeight) {
// 开始滑动
const height = clientHeight - scrollHeight;
viewerDom.style.setProperty("--cur-animation-axis", `translate(0, ${height}px)`);
containerDom.style.setProperty("animation-timing-function", timingFunc);
containerDom.classList.add("scroll");
} else if (props.origin === "h" && clientWidth < scrollWidth) {
// 开始滑动
const width = clientWidth - scrollWidth;
viewerDom.style.setProperty("--cur-animation-axis", ` translate(${width}px, 0)`);
containerDom.style.setProperty("animation-timing-function", timingFunc);
containerDom.classList.add("scroll");
}
aniListenerManage.add(containerDom, aniCount);
});
}
onBeforeUnmount(() => {
aniListenerManage.remove();
});
watch(
() => props.data,
() => {
// * 当数据更新时,重新计算动画
renderScroll();
},
{
immediate: true,
}
);
</script>
<template>
<div overflow-hidden ref="viewer">
<div ref="container">
<slot v-bind="{ data }" />
</div>
</div>
</template>
<style lang="scss" scoped>
$-cur-animation-axis: var(--cur-animation-axis);
@keyframes translate-animate {
0% {
transform: translate(0, 0);
}
100% {
transform: $-cur-animation-axis;
}
}
@mixin active {
animation-play-state: running;
}
@mixin paused {
animation-play-state: paused;
}
.scroll {
animation:
translate-animate var(--cur-animation-duration) linear
var(--cur-animation-count);
&:hover {
@include paused;
}
&:not(:hover) {
@include active;
}
}
</style>
__EOF__

本文作者:damarkday知识库
本文链接:https://www.cnblogs.com/GoodMemoryBlog/articles/18237593.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
本文链接:https://www.cnblogs.com/GoodMemoryBlog/articles/18237593.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)