当前位置: 首页 > news >正文

如何使用vue2设计提示框组件

下面是一个使用Vue2实现的Tooltip组件示例,支持自定义内容、不同方向定位和动画效果。

父组件

<template><div><!-- 基础用法 --><tooltiptrigger="<button class='btn'>Hover me</button>"content="This is a tooltip"/><!-- 点击触发 --><tooltiptrigger="<button class='btn'>Click me</button>"content="Click content"trigger-action="click"transition="zoom"/><!-- 自定义样式 --><tooltipstyle="top: 2px;"trigger="<div class='custom-trigger'>🛈</div>"content="<b style='color:#ff0'>HTML Content</b>":max-width="200"background-color="#666"color="#ffeb3b"/></div>
</template><script>
import tooltip  from "../components/toolTips.vue";
export default {name: 'VueMainjsToolTips',components: { tooltip },data() {return {};},mounted() {},methods: {},
};
</script>

子组件

<template><div class="tooltip-wrapper"><!-- 触发元素,使用v-html渲染自定义HTML --><divref="trigger"class="tooltip-trigger"v-html="trigger"@mouseenter="handleEnter"@mouseleave="handleLeave"@click="handleClick"/><!-- 动态过渡动画 --><transition :name="transitionName"><!-- 提示框,使用固定定位 --><divv-show="isVisible"ref="tooltip"class="tooltip-content":class="[computedPlacement, customClass]"     :style="contentStyles"role="tooltip"@mouseenter="handleTooltipEnter"@mouseleave="handleTooltipLeave"><!-- 内容区域 --><div class="tooltip-inner" v-html="content" /><!-- 箭头 --><div class="tooltip-arrow" :style="arrowStyles" /></div></transition></div>
</template><script>
export default {name: 'Tooltip',props: {trigger: {// 必须传入的触发元素HTMLtype: String,required: true},content: {// 必须传入的提示内容HTMLtype: String,required: true},placement: {// 默认定位方向type: String,default: 'top',validator: v => ['top', 'bottom', 'left', 'right'].includes(v)},disabled: Boolean,delay: {// 默认延迟100mstype: Number,default: 100},customClass: String,transition: {// 动画类型控制type: String,default: 'fade',validator: v => ['fade', 'slide', 'zoom'].includes(v)},triggerAction: {// 触发行为控制type: String,default: 'hover',validator: v => ['hover', 'click'].includes(v)},maxWidth: {// 最大宽度限制type: Number,default: 300},backgroundColor: {// 背景色配置type: String,default: '#333'},color: {// 文字颜色配置type: String,default: '#fff'}},data() {return {isVisible: false,// 显示状态控制timeout: null,// 用于延迟控制的定时器actualPlacement: this.placement,// 实际计算后的定位方向contentStyles: {},// 动态内容区域样式arrowStyles: {}// 动态箭头样式}},computed: {// 生成过渡类名(tooltip-fade/tooltip-slide...)transitionName() {return `tooltip-${this.transition}`},// 生成定位类名(tooltip-top/tooltip-bottom...)computedPlacement() {return `tooltip-${this.actualPlacement}`}},watch: {isVisible(val) {if (val) {this.$nextTick(() => {// 显示时计算位置并添加点击监听this.calculatePosition()this.addOutsideClickListener()})} else {// 隐藏时移除监听this.removeOutsideClickListener()}}},methods: {handleEnter() {/* 鼠标进入触发 */ if (this.disabled || this.triggerAction !== 'hover') returnthis.activate()},handleLeave() {/* 鼠标离开触发 */if (this.triggerAction !== 'hover') returnthis.deactivate()},handleClick() {/* 点击触发切换 */ if (this.disabled || this.triggerAction !== 'click') returnthis.toggle()},handleTooltipEnter() {if (this.triggerAction === 'hover') {clearTimeout(this.timeout)}},handleTooltipLeave() {if (this.triggerAction === 'hover') {this.deactivate()}},activate() {clearTimeout(this.timeout)this.timeout = setTimeout(() => {this.isVisible = true}, this.delay)},deactivate() {clearTimeout(this.timeout)this.timeout = setTimeout(() => {this.isVisible = false}, this.delay)},toggle() {this.isVisible = !this.isVisible},calculatePosition() {// 获取元素位置信息const triggerRect = this.$refs.trigger.getBoundingClientRect()const tooltipRect = this.$refs.tooltip.getBoundingClientRect()const positions = this.getAdjustedPosition(triggerRect, tooltipRect)this.actualPlacement = positions.placementthis.contentStyles = positions.contentStylesthis.arrowStyles = positions.arrowStyles},getAdjustedPosition(trigger, tooltip) {// 获取视口尺寸const viewportWidth = window.innerWidth;const viewportHeight = window.innerHeight;// 计算各方向可用空间const space = {top: trigger.top,// 顶部可用空间bottom: viewportHeight - trigger.bottom,// 底部可用空间left: trigger.left,// 左侧可用空间right: viewportWidth - trigger.right// 右侧可用空间};// 默认使用传入的定位方向let placement = this.placement;// 边界检测:自动调整位置避免超出屏幕if (placement === 'top' && space.top < tooltip.height) {placement = space.bottom >= tooltip.height ? 'bottom' : space.right >= tooltip.width ? 'right' : space.left >= tooltip.width ? 'left' : 'top';} else if (placement === 'bottom' && space.bottom < tooltip.height) {placement = space.top >= tooltip.height ? 'top' : space.right >= tooltip.width ? 'right' : space.left >= tooltip.width ? 'left' : 'bottom';} else if (placement === 'left' && space.left < tooltip.width) {placement = space.right >= tooltip.width ? 'right' : space.top >= tooltip.height ? 'top' : space.bottom >= tooltip.height ? 'bottom' : 'left';} else if (placement === 'right' && space.right < tooltip.width) {placement = space.left >= tooltip.width ? 'left' : space.top >= tooltip.height ? 'top' : space.bottom >= tooltip.height ? 'bottom' : 'right';}// 根据最终定位生成样式const positions = { placement, contentStyles: {}, arrowStyles: {} };// 箭头大小(与CSS中的border-width保持一致)const arrowSize = 6;// 计算各方向的位置switch (placement) {case 'top':positions.contentStyles = {left: `${trigger.left + trigger.width / 2}px`,// 水平居中于触发元素top: `${trigger.top - tooltip.height - arrowSize}px`,// 位于触发元素上方transform: 'translateX(-50%)'// 向左平移50%实现居中};positions.arrowStyles = {left: '50%',// 箭头水平居中top: '100%',// 箭头位于tooltip底部transform: 'translateX(-50%)',// 微调居中borderColor: `${this.backgroundColor} transparent transparent transparent`,borderWidth: `${arrowSize}px ${arrowSize}px 0 ${arrowSize}px`// 上箭头};break;case 'bottom':positions.contentStyles = {left: `${trigger.left + trigger.width / 2}px`,// 水平居中top: `${trigger.bottom + arrowSize}px`,// 位于触发元素下方transform: 'translateX(-50%)'};positions.arrowStyles = {left: '50%',top: `-${arrowSize}px`,// 箭头位于tooltip顶部transform: 'translateX(-50%)',borderColor: `transparent transparent ${this.backgroundColor} transparent`,// 下箭头borderWidth: `0 ${arrowSize}px ${arrowSize}px ${arrowSize}px`};break;case 'left':positions.contentStyles = {right: `${viewportWidth - trigger.left + arrowSize}px`,// 位于触发元素左侧top: `${trigger.top + trigger.height / 2}px`,// 垂直居中transform: 'translateY(-50%)'// 向上平移50%实现居中};positions.arrowStyles = {right: `-${arrowSize * 2}px`,// 箭头位于tooltip右侧top: '50%',transform: 'translateY(-50%)',borderColor: `transparent transparent transparent ${this.backgroundColor}`,// 左箭头borderWidth: `${arrowSize}px 0 ${arrowSize}px ${arrowSize}px`};break;case 'right':positions.contentStyles = {left: `${trigger.right + arrowSize}px`,// 位于触发元素右侧top: `${trigger.top + trigger.height / 2}px`,// 垂直居中transform: 'translateY(-50%)'};positions.arrowStyles = {left: `-${arrowSize * 2}px`,// 箭头位于tooltip左侧top: '50%',transform: 'translateY(-50%)',borderColor: `transparent ${this.backgroundColor} transparent transparent`,// 右箭头borderWidth: `${arrowSize}px ${arrowSize}px ${arrowSize}px 0`};break;}// 边界微调:防止tooltip超出屏幕边缘const contentRect = {left: positions.contentStyles.left ? parseFloat(positions.contentStyles.left) : 0,top: positions.contentStyles.top ? parseFloat(positions.contentStyles.top) : 0,width: tooltip.width,height: tooltip.height};// 检查并修正右侧越界if (contentRect.left + contentRect.width > viewportWidth) {positions.contentStyles.left = `${viewportWidth - contentRect.width - 10}px`;// 调整箭头位置if (placement === 'top' || placement === 'bottom') {positions.arrowStyles.left = `${trigger.left + trigger.width / 2 - (viewportWidth - contentRect.width - 10)}px`;}}// 检查并修正左侧越界if (contentRect.left < 0) {positions.contentStyles.left = '10px';// 调整箭头位置if (placement === 'top' || placement === 'bottom') {positions.arrowStyles.left = `${trigger.left + trigger.width / 2 - 10}px`;}}// 检查并修正底部越界if (contentRect.top + contentRect.height > viewportHeight) {positions.contentStyles.top = `${viewportHeight - contentRect.height - 10}px`;}// 检查并修正顶部越界if (contentRect.top < 0) {positions.contentStyles.top = '10px';}return positions;},addOutsideClickListener() {document.addEventListener('click', this.handleOutsideClick)},removeOutsideClickListener() {document.removeEventListener('click', this.handleOutsideClick)},handleOutsideClick(e) {// 点击外部关闭(仅click模式)if (!this.$el.contains(e.target)) {this.isVisible = false}}},beforeDestroy() {// 组件销毁前清理clearTimeout(this.timeout)this.removeOutsideClickListener()}
}
</script><style scoped>
.tooltip-wrapper {display: inline-block;position: relative;
}.tooltip-trigger {cursor: pointer;display: inline-block;
}.tooltip-content {position: fixed;/* 使用fixed定位脱离文档流 */z-index: 9999;background: v-bind(backgroundColor);/* 绑定动态背景色 */color: v-bind(color);/* 绑定文字颜色 */padding: 8px 12px;border-radius: 4px;font-size: 14px;line-height: 1.4;max-width: v-bind(maxWidth + 'px');/* 动态最大宽度 */text-align: center;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);pointer-events: auto;/* 允许与提示框交互 */
}.tooltip-inner {position: relative;z-index: 1;
}.tooltip-arrow {position: absolute;width: 0;height: 0;border-style: solid;border-width: 6px;/* 控制箭头大小 */
}/* 淡入淡出动画 */
.tooltip-fade-enter-active,
.tooltip-fade-leave-active {transition: opacity 0.2s;
}
.tooltip-fade-enter,
.tooltip-fade-leave-to {opacity: 0;
}
/* 滑动动画 */
.tooltip-slide-enter-active,
.tooltip-slide-leave-active {transition: transform 0.2s, opacity 0.2s;
}
.tooltip-slide-enter {opacity: 0;transform: translateY(-10px);/* 上方入场动画 */
}
.tooltip-slide-leave-to {opacity: 0;transform: translateY(10px);
}.tooltip-zoom-enter-active,
.tooltip-zoom-leave-active {transition: transform 0.2s, opacity 0.2s;
}
.tooltip-zoom-enter {opacity: 0;transform: scale(0.95);/* 缩小入场 */
}
.tooltip-zoom-leave-to {opacity: 0;transform: scale(1.05);
}
</style>

注意事项

  • 动态内容需要使用v-html时要确保内容安全。
  • 固定定位可能需要处理滚动容器的情况。
  • 复杂场景建议使用teleport(需要Vue3)。
  • 高频触发时建议添加防抖逻辑。
  • 移动端需要额外处理touch事件。

运行如下

在这里插入图片描述

相关文章:

  • python-docx 库教程
  • Linux内核学习资料-deepseek
  • OPenCV CUDA模块图形变换----构建透视变换映射表函数buildWarpPerspectiveMaps()
  • 【技术支持】Android11 中获取应用列表
  • AVCap视频处理成帧和音频脚本
  • React前端框架学习
  • Babylon.js引擎(二)
  • 【python】基于pycharm的海康相机SDK二次开发
  • 美团NoCode设计网站的尝试经验分享
  • 打卡第42天:简单CNN
  • 游戏日志统计操作次数前三的用户
  • Linux日志分割压缩实战指南
  • 手写RPC框架<四> 负载均衡
  • 不同厂商保障UEFI/BIOS安全的技术与机制详解
  • 界面控件DevExpress WPF v24.2新版亮点:报表等组件功能升级
  • thinkphp 一个系统在同一个域名下,一个文件夹下如何区分多站点——穷人的精致规划——仙盟创梦IDE
  • MyBatis实战指南(六)自动映射
  • 债券与股票:投资市场的两大基石
  • 用 OpenSSL 库实现 3DES(三重DES)加密
  • SSL错误无法建立安全连接
  • 免费网络电话在线拨打/郑州seo外包顾问
  • 企业网站源码 php/百度新闻发布平台
  • 做商城网站在哪里注册营业执照/网站生成app工具
  • 巴中做网站公司/想在百度上推广怎么做
  • 网站目录不能访问/百度线上推广
  • C 如何做简易网站/5月新冠病毒最新消息