全局通知组件

全局通知组件

  • Notify
    • _src
      • index.vue
      • instances.ts
      • notify.ts
      • props.ts
    • index.ts

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 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   DAmarkday  阅读(5)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示