Vue3气泡卡片(Popover)

效果如下图:在线预览

在这里插入图片描述

APIs

参数说明类型默认值必传
title卡片标题string | slot‘’false
content卡片内容string | slot‘’false
maxWidth卡片内容最大宽度string | number‘auto’false
trigger卡片触发方式‘hover’ | ‘click’‘hover’false
overlayStyle卡片样式CSSProperties{}false

Events

事件名称说明参数
openChange显示隐藏的回调(visible: boolean) => void

注:组件引用方法 import { rafTimeout, cancelRaf } from ‘…/index’ 请参考该博客

创建气泡卡片组件Popover.vue

<script setup lang="ts">
import { ref, computed, useSlots } from 'vue'
import type { Slot, CSSProperties } from 'vue'
import { rafTimeout, cancelRaf } from '../index'
interface Props {title?: string|Slot // 卡片标题content?: string|Slot // 卡片内容maxWidth?: string|number // 卡片内容最大宽度trigger?: 'hover'|'click' // 卡片触发方式overlayStyle?: CSSProperties // 卡片样式
}
const props = withDefaults(defineProps<Props>(), {title: '',content: '',maxWidth: 'auto',trigger: 'hover',overlayStyle: () => ({})
})
const popMaxWidth = computed(() => {if (typeof props.maxWidth === 'number') {return props.maxWidth + 'px'}return props.maxWidth
})
const visible = ref(false)
const top = ref(0) // 提示框top定位
const left = ref(0) // 提示框left定位
const defaultRef = ref() // 声明一个同名的模板引用
const popRef = ref() // 声明一个同名的模板引用
function getPosition () {const defaultWidth = defaultRef.value.offsetWidth // 展示文本宽度const popWidth = popRef.value.offsetWidth // 提示文本宽度const popHeight = popRef.value.offsetHeight // 提示文本高度top.value = popHeight + 4left.value = (popWidth - defaultWidth) / 2
}
const emit = defineEmits(['openChange'])
const hideTimer = ref()
function onShow () {getPosition()cancelRaf(hideTimer.value)visible.value = trueemit('openChange', visible.value)
}
function onHide (): void {hideTimer.value = rafTimeout(() => {visible.value = falseemit('openChange', visible.value)}, 100)
}
const activeBlur = ref(false) // 是否激活 blur 事件
function onOpen () {visible.value = !visible.valueif (visible.value) {getPosition()}emit('openChange', visible.value)
}
function onEnter () {activeBlur.value = false
}
function onLeave () {activeBlur.value = truepopRef.value.focus()
}
function onBlur () {visible.value = falseemit('openChange', false)
}
</script>
<template><divclass="m-popover"@mouseenter="trigger === 'hover' ? onShow() : () => false"@mouseleave="trigger === 'hover' ? onHide() : () => false"><divref="popRef"tabindex="1"class="m-pop-content":class="{'show-pop': visible}":style="`max-width: ${popMaxWidth}; top: ${-top}px; left: ${-left}px;`"@blur="trigger === 'click' && activeBlur ? onBlur() : () => false"@mouseenter="trigger === 'hover' ? onShow() : () => false"@mouseleave="trigger === 'hover' ? onHide() : () => false"><div class="m-pop" :style="overlayStyle"><div class="m-title"><slot name="title">{{ title }}</slot></div><div class="m-content"><slot name="content">{{ content }}</slot></div></div><div class="m-pop-arrow"><span class="u-pop-arrow"></span></div></div><divref="defaultRef"@click="trigger === 'click' ? onOpen() : () => false"@mouseenter="trigger === 'click' ? onEnter() : () => false"@mouseleave="trigger === 'click' ? onLeave() : () => false"><slot></slot></div></div>
</template>
<style lang="less" scoped>
.m-popover {position: relative;display: inline-block;.m-pop-content {position: absolute;z-index: 999;width: max-content;padding-bottom: 12px;outline: none;pointer-events: none;opacity: 0;transform-origin: 50% 75%;transform: scale(.8);transition: transform .25s, opacity .25s;.m-pop {min-width: 32px;min-height: 32px;padding: 12px;font-size: 14px;color: rgba(0, 0, 0, .88);line-height: 1.5714285714285714;text-align: start;text-decoration: none;word-break: break-all;cursor: auto;user-select: text;background-color: #FFF;border-radius: 8px;box-shadow: 0 6px 16px 0 rgba(0, 0, 0, .08), 0 3px 6px -4px rgba(0, 0, 0, .12), 0 9px 28px 8px rgba(0, 0, 0, .05);.m-title {min-width: 176px;margin-bottom: 8px;color: rgba(0, 0, 0, .88);font-weight: 600;}.m-content {color: rgba(0, 0, 0, .88);}}.m-pop-arrow {position: absolute;z-index: 9;left: 50%;bottom: 12px;transform: translateX(-50%) translateY(100%) rotate(180deg);display: block;pointer-events: none;width: 16px;height: 16px;overflow: hidden;&::before {position: absolute;bottom: 0;inset-inline-start: 0;width: 16px;height: 8px;background-color: #FFF;clip-path: path('M 0 8 A 4 4 0 0 0 2.82842712474619 6.82842712474619 L 6.585786437626905 3.0710678118654755 A 2 2 0 0 1 9.414213562373096 3.0710678118654755 L 13.17157287525381 6.82842712474619 A 4 4 0 0 0 16 8 Z');content: "";}&::after {position: absolute;width: 8.970562748477143px;height: 8.970562748477143px;bottom: 0;inset-inline: 0;margin: auto;border-radius: 0 0 2px 0;transform: translateY(50%) rotate(-135deg);box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.1);z-index: 0;background: transparent;content: "";}}}.show-pop {pointer-events: auto;opacity: 1;transform: scale(1);}
}
</style>

在要使用的页面引入

<script setup lang="ts">
function openChange (visible: boolean) {console.log('visible:', visible)
}
</script>
<template><div class="ml60"><h1>{{ $route.name }} {{ $route.meta.title }}</h1><h2 class="mt30 mb10">基本使用</h2><Popover title="Title" @open-change="openChange"><template #content><p>Content</p><p>Content</p></template><Button type="primary">Hover me</Button></Popover><h2 class="mt30 mb10">不同的触发方式</h2><Popover title="Title" trigger="click"><template #content><p>Content</p><p>Content</p></template><Button type="primary">Click me</Button></Popover><h2 class="mt30 mb10">自定义样式</h2><Popovertitle="TitleTitleTitleTitleTitleTitleTitleTitleTitle":max-width="240":overlayStyle="{ padding: '12px 18px', borderRadius: '12px' }"><template #content><p>Content</p><p>Content</p></template><Button type="primary">Hover me</Button></Popover></div>
</template>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/573393.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS(六)

一、精灵图 1.1 为什么需要精灵图 一个网页中往往会应用很多小的背景图像作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁地接收和发送请求图片&#xff0c;造成服务器请求压力过大&#xff0c;这将大大降低页面的加载速度。 因此&#xff0c;为了有效…

解决VMWare Esxi 6.5.0 导出虚拟机时发生网络错误

解决办法&#xff1a;使用vmware ovftool工具导出。 1 先安装该工具到windows下面&#xff0c;有32位的和64位的 2 用管理员进入命令方式&#xff1a; 进入&#xff1a;c:\windows 进入工具命令当前文件夹&#xff08;具体看用户的安装路径&#xff09;&#xff1a; cd \p…

vue3全局控制Element plus所有组件的文字大小

项目框架vue-右上角有控制全文的文字大小 实现&#xff1a; 只能控制element组件的文字及输入框等大小变化&#xff0c;如果是自行添加div,text, span之类的控制不了。 配置流程 APP.vue 使用element的provide&#xff0c;包含app <el-config-provider :locale"loca…

C++零基础入门学习视频课程

教程介绍 本专题主要讲解C基础入门学习&#xff0c;所以不会涉及很深入的语法和机制。但会让你整体多面的了解和学习C的核心内容&#xff0c;快速学习使用C&#xff0c;我们的目标是先宏观整体把握&#xff0c;在深入各个击破&#xff01; 学习地址 链接&#xff1a;https:/…

ThreadPool-线程池使用及原理

1. 线程池使用方式 示例代码&#xff1a; // 一池N线程 Executors.newFixedThreadPool(int) // 一个任务一个任务执行&#xff0c;一池一线程 Executors.newSingleThreadExecutorO // 线程池根据需求创建线程&#xff0c;可扩容&#xff0c;遇强则强 Executors.newCachedThre…

ssrf利用

ssrf利用 查找内网存活主机ip&#xff08;文件读取&#xff09; 使用file://协议 file:// 从文件系统中获取文件内容&#xff0c;格式为file://文件路径 file:///etc/passwd 读取文件passwd file:///etc/hosts 显示当前操作系统网卡的IP file:///…

CVPR`24 | FRESCO:高质量、连贯的Zero-shot视频转换新方案(北大南洋理工)

论文链接&#xff1a;https://arxiv.org/pdf/2403.12962.pdf 代码链接&#xff1a;https://github.com/williamyang1991/FRESCO 工程地址&#xff1a;https://www.mmlab-ntu.com/project/fresco/ 文本到图像扩散模型在图像领域的显著功效激发了人们对其在视频领域应用潜力的广…

Ansible-1

Ansible是一款自动化运维、批量管理服务器的工具&#xff0c;批量系统配置、程序部署、运行命令等功能。基于Python开发&#xff0c;基于ssh进行管理&#xff0c;不需要在被管理端安装任何软件。Ansible在管理远程主机的时候&#xff0c;只有是通过各种模块进行操作的。 需要关…

Web开发基本流程

Web是全球广域网&#xff0c;能够通过浏览器访问的网站。我们要访问网站&#xff0c;首先要在浏览器输入对应的域名。 浏览器也是一个程序&#xff0c;京东的网站也是一个程序&#xff0c;在京东那边电脑运行着&#xff0c;我们只是通过浏览器远程访问。京东的程序由三个部分组…

安卓玩机工具推荐----MTK芯片读写分区 备份分区 恢复分区 制作线刷包 从0开始 工具操作解析【三】

同类博文; 安卓玩机工具推荐----MTK芯片读写分区 备份分区 恢复分区 制作线刷包 工具操作解析 安卓玩机工具推荐----MTK芯片读写分区 备份分区 恢复分区 制作线刷包 工具操作解析【二】-CSDN博客 回顾以往 在以前的博文简单介绍了这款工具的rom制作全程。今天针对这款工具的…

【ESP32S3 Sense接入语音识别+MiniMax模型对话】

1. 前言 围绕ESP32S3 Sense接入语音识别MiniMax模型对话展开&#xff0c;首先串口输入“1”字符&#xff0c;随后麦克风采集2s声音数据&#xff0c;对接百度在线语音识别&#xff0c;将返回文本结果丢入MiniMax模型&#xff0c;进而返回第二次结果文本&#xff0c;实现语言对话…

软考高级架构师:边缘计算概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…