返回顶部
首页 > 资讯 > 精选 >怎么利用vue3仿苹果系统侧边消息提示效果
  • 180
分享到

怎么利用vue3仿苹果系统侧边消息提示效果

2023-06-22 03:06:47 180人浏览 泡泡鱼
摘要

这篇文章主要介绍怎么利用vue3仿苹果系统侧边消息提示效果,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!动效预览最近在做毕业设计, 想给毕设系统加上一个仿苹果系统的侧边消息提示框, 让我们先来看看效果.其他UI库熟悉

这篇文章主要介绍怎么利用vue3仿苹果系统侧边消息提示效果,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

动效预览

最近在做毕业设计, 想给毕设系统加上一个仿苹果系统的侧边消息提示框, 让我们先来看看效果.

怎么利用vue3仿苹果系统侧边消息提示效果

其他UI库

熟悉前端开发的同学可能发现了, 在 Element UI 中这个组件叫 Notification 通知; 在Bootstrap 中这个组件叫 Toasts.

开始

当初看到这个组件就觉得很酷炫, 今天就带大家看一下我是怎么一步一步实现的, 有不对或者可以优化的地方请各位大佬点评. ???? (本次组件基于 Vue3 实现)

组件目录结构

Toasts

|

| -- index.js         // 注册组件, 定义全局变量以便调用

|

| -- instance.js      // 手动实例创建前后的逻辑

| -- toasts.vue       // 消息提示 html 部分

|

| -- toastsBus.js     // 解决 vue3 去除 $on和$emit 的解决方案

toasts.vue

大概的DOM结构

怎么利用vue3仿苹果系统侧边消息提示效果

<!-- 弹窗 --><div class="toast-container">    <!-- icon图标 -->    <template>        ...    </template>    <!-- 主要内容 -->    <div class="toast-content">        <!-- 标题及其倒计时 -->        <div class="toast-head">            ...        </div>        <!-- body -->        <div class="toast-body">...</div>        <!-- 操作按钮 -->        <div class="toast-operate">            ...        </div>    </div>    <!-- 关闭 -->    <div class="toast-close">        <i class="fi fi-rr-cross-small"></i>    </div></div>

index.js

注册组件 & 定义全局变量

在这里我们注册组件, 定义全局变量以便调用

import toast from './instance'import Toast from './toasts.vue'export default (app) => {    // 注册组件    app.component(Toast.name, Toast);    // 注册全局变量, 后续只需调用 $Toast({}) 即可    app.config.globalProperties.$Toast = toast;}

instance.js

手动挂载实例

???????????? 这里是全文的重点 ????????????

首先我们学习如何将组件手动挂载至页面

import { createApp } from 'vue';import Toasts from './toasts'const toasts = (options) => {    // 创建父容器    let root = document.createElement('div');    document.body.appendChild(root)    // 创建Toasts实例    let ToastsConstructor = createApp(Toasts, options)    // 挂载父亲元素    let instance = ToastsConstructor.mount(root)    // 抛出实例本身给vue    return instance}export default toasts;

给每一个创建的 toasts 正确的定位

怎么利用vue3仿苹果系统侧边消息提示效果

如图所示, 每创建一个 toasts 将会排列到上一个 toasts 的下方(这里的间隙为16px). 想要做到这种效果我们需要知道 已存在 的toasts 的高度.

// instance.js// 这里我们需要定义一个数组来存放当前存活的 toastslet instances = []const toasts = (options) => {    ...    // 创建后将实例加入数组    instances.push(instance)        // 重制高度    let verticalOffset = 0    // 遍历获取当前已存活的 toasts 高度及其间隙 累加    instances.forEach(item => {        verticalOffset += item.$el.offsetHeight + 16    })    // 累加本身需要的间隙    verticalOffset += 16    // 赋值当前实例y轴方向便宜长度    instance.toastPosition.y = verticalOffset    ...}export default toasts;

加入 主动&定时 关闭功能

让我们先来分析一下这里的业务:

  • 定时关闭: 在 toast 创建时给一个自动关闭时间, 当计时器结束后自动关闭.

  • 主动关闭: 点击关闭按钮关闭 toast.

在这个基础上我们可以加上一些人性化的操作, 例如鼠标移入某个 toast 时停止它的自动关闭(其他 toast 不受影响), 当鼠标移开时重新启用它的自动关闭.

<!-- toasts.vue --><template>    <transition name="toast" @after-leave="afterLeave" @after-enter="afterEnter">        <div ref="container" class="toast-container" : v-show="visible" @mouseenter="clearTimer" @mouseleave="createTimer">            ...            <!-- 关闭 -->            <div class="toast-close"  @click="destruction">                <i class="fi fi-rr-cross-small"></i>            </div>        </div>    </transition></template><script>import Bus from './toastsBus'import {ref, computed, onMounted, onBeforeUnmount} from 'vue'export default {    props: {        // 自动关闭时间 (单位毫秒)        autoClose: {            type: Number,            default: 4500        }    },    setup(props){        // 是否显示        const visible = ref(false);                  // toast容器实例        const container = ref(null);        // toast本身高度        const height = ref(0);                // toast位置        const toastPosition = ref({            x: 16,            y: 16        })        const toastStyle = computed(()=>{            return {                top: `${toastPosition.value.y}px`,                right: `${toastPosition.value.x}px`,            }        })                // toast的id        const id = ref('')                // toast离开动画结束后        function afterLeave(){            // 告诉 instance.js 需要进行关闭操作 ()            Bus.$emit('closed',id.value);        }        // toast进入动画结束后        function afterEnter(){            height.value = container.value.offsetHeight        }        // 定时器        const timer = ref(null);        // 鼠标进入toast        function clearTimer(){             if(timer.value)                clearTimeout(timer.value)        }        // 鼠标移出toast        function createTimer(){           if(props.autoClose){                timer.value = setTimeout(() => {                    visible.value = false                }, props.autoClose)            }        }        // 销毁        function destruction(){            visible.value = false        }                onMounted(()=>{            createTimer();        })        onBeforeUnmount(()=>{            if(timer.value)                clearTimeout(timer.value)        })                        return {            visible,            container,            height,            toastPosition,            toastStyle,            id,            afterLeave,            afterEnter,            timer,            clearTimer,            createTimer,            destruction        }    }}</script>

我们来分析一下 instance.js 中 toast 关闭时的逻辑

  1. 将此 toast 从存活数组中删除, 并且遍历数组将从此条开始后面的 toast 位置向上位移.

  2. 从 <body> 中删除Dom元素.

  3. 调用 unmount() 销毁实例.

怎么利用vue3仿苹果系统侧边消息提示效果

// instance.jsimport { createApp } from 'vue';import Toasts from './toasts'import Bus from './toastsBus'let instances = []let seed = 1const toasts = (options) => {    // 手动挂载实例    let ToastsConstructor = createApp(Toasts, options)    let instance = ToastsConstructor.mount(root)    // 给实例加入唯一标识符    instance.id = id    // 显示实例    instance.visible = true        ...        // 监听 toasts.vue 传来关闭事件    Bus.$on('closed', (id) => {        // 因为这里会监听到所有的 ‘closed' 事件, 所以要匹配 id 确保        if (instance.id == id) {            // 调用删除逻辑            removeInstance(instance)            // 在 <body> 上删除dom元素            document.body.removeChild(root)            // 销毁实例            ToastsConstructor.unmount();        }    })        instances.push(instance)    return instance}export default toasts;// 删除逻辑const removeInstance = (instance) => {    if (!instance) return    let len = instances.length    // 找出当前需要销毁的下标    const index = instances.findIndex(item => {        return item.id === instance.id    })    // 从数组中删除    instances.splice(index, 1)    // 如果当前数组中还存在存活 Toasts, 需要遍历将下面的Toasts上移, 重新计算位移    if (len <= 1) return    // 获取被删除实例的高度    const h = instance.height    // 遍历被删除实例以后下标的 Toasts    for (let i = index; i < len - 1; i++) {        // 公式: 存活的实例将本身的 y 轴偏移量减去被删除高度及其间隙高度        instances[i].toastPosition.y = parseInt(instances[i].toastPosition.y - h - 16)    }}

完整代码

index.js

import toast from './instance'import Toast from './toasts.vue'export default (app) => {    app.component(Toast.name, Toast);    app.config.globalProperties.$Toast = toast;}

toastsBus.js

import emitter from 'tiny-emitter/instance'export default {    $on: (...args) => emitter.on(...args),    $once: (...args) => emitter.once(...args),    $off: (...args) => emitter.off(...args),    $emit: (...args) => emitter.emit(...args)}

instance.js

import { createApp } from 'vue';import Toasts from './toasts'import Bus from './toastsBus'let instances = []let seed = 1const toasts = (options) => {    // 创建父容器    const id = `toasts_${seed++}`    let root = document.createElement('div');    root.setAttribute('data-id', id)    document.body.appendChild(root)    let ToastsConstructor = createApp(Toasts, options)    let instance = ToastsConstructor.mount(root)    instance.id = id    instance.visible = true        // 重制高度    let verticalOffset = 0    instances.forEach(item => {        verticalOffset += item.$el.offsetHeight + 16    })    verticalOffset += 16    instance.toastPosition.y = verticalOffset    Bus.$on('closed', (id) => {        if (instance.id == id) {            removeInstance(instance)            document.body.removeChild(root)            ToastsConstructor.unmount();        }    })    instances.push(instance)    return instance}export default toasts;const removeInstance = (instance) => {    if (!instance) return    let len = instances.length    const index = instances.findIndex(item => {        return item.id === instance.id    })    instances.splice(index, 1)    if (len <= 1) return    const h = instance.height    for (let i = index; i < len - 1; i++) {        instances[i].toastPosition.y = parseInt(instances[i].toastPosition.y - h - 16)    }}

toast.vue

加入亿点点细节, 例如icon可以自定义或者是图片, 可以取消关闭按钮, 设置自动关闭时长, 或者停用自动关闭功能.

<template><transition name="toast" @after-leave="afterLeave" @after-enter="afterEnter">  <!-- 弹窗 -->  <div ref="container" class="toast-container" : v-show="visible" @mouseenter="clearTimer" @mouseleave="createTimer">    <!-- icon -->    <template v-if="type || type != 'custom' || type != 'img'">        <div class="toast-icon success" v-if="type==='success'">            <i class="fi fi-br-check"></i>        </div>        <div class="toast-icon warning" v-if="type==='warning'">            ?        </div>        <div class="toast-icon info" v-if="type==='info'">            <i class="fi fi-sr-bell-ring"></i>        </div>        <div class="toast-icon error" v-if="type==='error'">            <i class="fi fi-br-cross-small"></i>        </div>    </template>    <div : class="toast-icon" v-if="type==='custom'" v-html="customIcon"></div>    <img class="toast-custom-img" :src="customImg" v-if="type==='img'"/>    <!-- content -->    <div class="toast-content">        <!-- head -->        <div class="toast-head" v-if="title">            <!-- title -->            <span class="toast-title">{{title}}</span>            <!-- time -->            <span class="toast-countdown">{{countDown}}</span>        </div>        <!-- body -->        <div class="toast-body" v-if="message" v-html="message"></div>        <!-- operate -->        <div class="toast-operate">            <a class="toast-button-confirm"                :class="[{'success':type==='success'},                        {'warning':type==='warning'},                        {'info':type==='info'},                        {'error':type==='error'}]">{{confirmText}}</a>        </div>    </div>    <!-- 关闭 -->    <div v-if="closeIcon" class="toast-close"  @click="destruction">        <i class="fi fi-rr-cross-small"></i>    </div>  </div>  </transition></template><script>import Bus from './toastsBus'import {ref, computed, onMounted, onBeforeUnmount} from 'vue'export default {    props: {        title: String,        closeIcon: {            type: Boolean,            default: true        },        message: String,        type: {            type: String,            validator: function(val) {                return ['success', 'warning', 'info', 'error', 'custom', 'img'].includes(val);            }        },        confirmText: String,        customIcon: String,        customIconBackground: String,        customImg: String,        autoClose: {            type: Number,            default: 4500        }    },    setup(props){        // 显示        const visible = ref(false);        // 容器实例        const container = ref(null);        // 高度        const height = ref(0);        // 位置        const toastPosition = ref({            x: 16,            y: 16        })        const toastStyle = computed(()=>{            return {                top: `${toastPosition.value.y}px`,                right: `${toastPosition.value.x}px`,            }        })        // 倒计时        const countDown = computed(()=>{            return '2 seconds aGo'        })        const id = ref('')        // 离开以后        function afterLeave(){            Bus.$emit('closed',id.value);        }        // 进入以后        function afterEnter(){            height.value = container.value.offsetHeight        }        // 定时器        const timer = ref(null);        // 鼠标进入        function clearTimer(){             if(timer.value)                clearTimeout(timer.value)        }        // 鼠标移出        function createTimer(){           if(props.autoClose){                timer.value = setTimeout(() => {                    visible.value = false                }, props.autoClose)            }        }        // 销毁        function destruction(){            visible.value = false        }        onMounted(()=>{            createTimer();        })        onBeforeUnmount(()=>{            if(timer.value)                clearTimeout(timer.value)        })        return {            visible,            toastPosition,            toastStyle,            countDown,            afterLeave,            afterEnter,            clearTimer,            createTimer,            timer,            destruction,            container,            height,            id        }    }}</script><style lang="sCSS" scoped>// 外部容器.toast-container{    width: 330px;    box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 12px 0px;    background-color: rgba(#F7F7F7, .6);    border: 1px solid #E5E5E5;    padding: 14px 13px;    z-index: 1001;    position: fixed;    top: 0;    right: 0;    border-radius: 10px;    backdrop-filter: blur(15px);    display: flex;    align-items: stretch;    transition: all .3s ease;    will-change: top,left;}// -------------- icon --------------.toast-icon, .toast-close{    flex-shrink: 0;}.toast-icon{    width: 30px;    height: 30px;    border-radius: 100%;    display: inline-flex;    align-items: center;    justify-content: center;}// 正确.toast-icon.success{    background-color: rgba(#2BB44A, .15);    color: #2BB44A;}// 异常.toast-icon.warning{    background-color: rgba(#ffcc00, .15);    color: #F89E23;    font-weight: 600;    font-size: 18px;}// 错误.toast-icon.error{    font-size: 18px;    background-color: rgba(#EB2833, .1);    color: #EB2833;}// 信息.toast-icon.info{    background-color: rgba(#3E71F3, .1);    color: #3E71F3;}// 自定义图片.toast-custom-img{    width: 40px;    height: 40px;    border-radius: 10px;    overflow: hidden;    flex-shrink: 0;}// ------------- content -----------.toast-content{    padding: 0 8px 0 13px;    flex: 1;}// -------------- head --------------.toast-head{    display: flex;    align-items: center;    justify-content: space-between;}// title.toast-title{    font-size: 16px;    line-height: 24px;    color: #191919;    font-weight: 600;    overflow: hidden;    text-overflow: ellipsis;    white-space: nowrap;}// time.toast-countdown{    font-size: 12px;    color: #929292;    line-height: 18.375px;}// --------------- body -----------.toast-body{    color: #191919;    line-height: 21px;    padding-top: 5px;}// ---------- close -------.toast-close{    padding: 3px;    cursor: pointer;    font-size: 18px;    width: 24px;    height: 24px;    border-radius: 8px;    display: inline-flex;    align-items: center;    justify-content: center;}.toast-close:hover{    background-color: rgba(#E4E4E4, .5);}// --------- operate ----------.toast-button-confirm{    font-weight: 600;    color: #3E71F3;}.toast-button-confirm:hover{    color: #345ec9;}// 成功.toast-button-confirm.success{    color: #2BB44A;}.toast-button-confirm.success:hover{    color: #218a3a;}// 异常.toast-button-confirm.warning{    color: #F89E23;}.toast-button-confirm.warning:hover{    color: #df8f1f;}// 信息.toast-button-confirm.info{    color: #3E71F3;}.toast-button-confirm.info:hover{    color: #345ec9;}// 错误.toast-button-confirm.error{    color: #EB2833;}.toast-button-confirm.error:hover{    color: #c9101a;}.toast-enter-from,.toast-leave-to{  transfORM: translateX(120%);}.v-leave-from,.toast-enter-to{  transform: translateX(00%);}</style>

main.js

import { createApp } from 'vue'import App from './App.vue'const app = createApp(App)import '@/assets/font/UIcons/font.css'// 安装toastsimport toasts from './components/toasts'app.use(toasts).mount('#app')

使用

<template>    <button @click="clickHandle">发送</button></template><script>import { getCurrentInstance } from 'vue'export default {  setup(){    const instance = getCurrentInstance()    function clickHandle(){      // 这里调用 vue3 的全局变量时比较羞耻, 不知道各位大佬有没有其他好办法      instance.appContext.config.globalProperties.$Toast({        type: 'info',        title: '这是一句标题',        message: '本文就是梳理mount函数的主要逻辑,旨在理清基本的处理流程(Vue 3.1.1版本)。'      })    }    return {      clickHandle    }  }}</script>

以上是“怎么利用vue3仿苹果系统侧边消息提示效果”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网精选频道!

--结束END--

本文标题: 怎么利用vue3仿苹果系统侧边消息提示效果

本文链接: https://lsjlt.com/news/302123.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 怎么利用vue3仿苹果系统侧边消息提示效果
    这篇文章主要介绍怎么利用vue3仿苹果系统侧边消息提示效果,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!动效预览最近在做毕业设计, 想给毕设系统加上一个仿苹果系统的侧边消息提示框, 让我们先来看看效果.其他UI库熟悉...
    99+
    2023-06-22
  • 利用vue3仿苹果系统侧边消息提示效果实例
    目录动效预览其他UI库开始组件目录结构toasts.vue大概的DOM结构index.js注册组件 & 定义全局变量instance.js手动挂载实例完整代码总结动效预览 最...
    99+
    2024-04-02
  • 华为云服务器续费怎么取消掉了呢苹果系统还能用吗
    如果您想要取消云服务器的续费,您可以通过华为云服务器的在线客服或者电话联系华为云服务器的客户服务人员来完成。华为云服务器的客户服务人员会告诉您如何续费,并且可以帮助您解决任何续费问题。如果您选择了网页在线支付方式,您可以在华为云服务器的网站...
    99+
    2023-10-28
    华为 掉了 能用
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作