代码演示
如何引入
<script setup lang="ts">
import { Tooltip } from '@fibrous/semi-ui-vue';
</script>
注意事项
Tooltip 需要将触发属性和事件监听器应用到默认插槽的触发元素上,并以触发元素的真实 DOM 元素计算弹出层的定位,因此默认插槽支持如下类型:
- 单个真实 DOM 元素,如 span,div,p...
- 单个组件,需要让未声明的属性落到它的根 DOM 元素上(默认的 attrs 透传即可);根为多个节点的组件(如嵌套的 Tooltip)以它的第一个元素为锚点
- 文本或多个节点,Tooltip 会用一个 span 包裹它们作为触发元素
代码
<script setup lang="ts">
import { Space, Tag, Tooltip } from '@fibrous/semi-ui-vue';
const style = {
border: '2px solid var(--semi-color-border)',
paddingLeft: '4px',
paddingRight: '4px',
borderRadius: '4px',
};
</script>
<template>
<Space>
<Tooltip>
<Tag :style="style">Component</Tag>
<template #content>semi design</template>
</Tooltip>
<Tooltip>
Text
<template #content>semi design</template>
</Tooltip>
<Tooltip>
<span :style="style">DOM</span>
<template #content>semi design</template>
</Tooltip>
</Space>
</template>位置
可以通过 position 配置弹出层方向以及对齐位置,position 详细可选值请参考下方 API 文档
配置为 top 时 向上弹出
配置为 topLeft 时,向上弹出,且弹出层与 children 左对齐(当arrowPointAtCenter=false时)
配置为 topRight 时,向上弹出,且弹出层与 children 右对齐(当arrowPointAtCenter=false时)
其他方向同理
代码
<script setup lang="ts">
import { Tag, Tooltip, type Position } from '@fibrous/semi-ui-vue';
const tops: [Position, string][] = [
['topLeft', 'TL'],
['top', 'Top'],
['topRight', 'TR'],
];
const lefts: [Position, string][] = [
['leftTop', 'LT'],
['left', 'Left'],
['leftBottom', 'LB'],
];
const rights: [Position, string][] = [
['rightTop', 'RT'],
['right', 'Right'],
['rightBottom', 'RB'],
];
const bottoms: [Position, string][] = [
['bottomLeft', 'BL'],
['bottom', 'Bottom'],
['bottomRight', 'BR'],
];
</script>
<template>
<div>
<div :style="{ marginLeft: '80px', whiteSpace: 'nowrap' }">
<Tooltip v-for="[position, label] in tops" :key="position" :arrowPointAtCenter="false" :position="position">
<Tag :style="{ margin: '8px', padding: '20px' }">{{ label }}</Tag>
<template #content>
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
</template>
</Tooltip>
</div>
<div :style="{ width: '80px', float: 'left' }">
<Tooltip v-for="[position, label] in lefts" :key="position" :arrowPointAtCenter="false" :position="position">
<Tag :style="{ margin: '8px', padding: '20px', width: '60px' }">{{ label }}</Tag>
<template #content>
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
</template>
</Tooltip>
</div>
<div :style="{ width: '40px', marginLeft: '300px' }">
<Tooltip v-for="[position, label] in rights" :key="position" :arrowPointAtCenter="false" :position="position">
<Tag :style="{ margin: '8px', padding: '20px', width: '60px' }">{{ label }}</Tag>
<template #content>
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
</template>
</Tooltip>
</div>
<div :style="{ marginLeft: '80px', clear: 'both', whiteSpace: 'nowrap' }">
<Tooltip v-for="[position, label] in bottoms" :key="position" :arrowPointAtCenter="false" :position="position">
<Tag :style="{ margin: '8px', padding: '20px', width: '60px' }">{{ label }}</Tag>
<template #content>
<article>
<p>hi bytedance</p>
<p>hi bytedance</p>
</article>
</template>
</Tooltip>
</div>
</div>
</template>指向元素中心
默认情况下 arrowPointAtCenter=true,小三角始终指向 children 元素中心位置。你可以将其设置为 false,此时小三角将不再保持指向元素中心。弹出层与 children 边缘对齐
代码
<script setup lang="ts">
import { Button, Tooltip } from '@fibrous/semi-ui-vue';
</script>
<template>
<div>
<Tooltip position="topLeft">
<Button :style="{ marginRight: '8px' }" type="secondary">指向元素中心</Button>
<template #content>semi design tooltip</template>
</Tooltip>
</div>
<div :style="{ marginTop: '20px' }">
<Tooltip :arrowPointAtCenter="false" position="topLeft">
<Button :style="{ marginRight: '8px', width: '120px' }" type="secondary">边缘对齐</Button>
<template #content>semi design tooltip</template>
</Tooltip>
</div>
</template>触发时机
- 配置触发展示的时机,默认为
hover,可选hover/focus/click/custom/ 'contextMenu' - 设为
custom时,需要配合visible属性使用,此时显示与否完全受控 - contextMenu 为右键触发
代码
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Input, Radio, RadioGroup, Tooltip, type RadioValue } from '@fibrous/semi-ui-vue';
const visible = ref<RadioValue | undefined>(0);
</script>
<template>
<div>
<Tooltip>
<Button :style="{ marginBottom: '20px' }" theme="solid" type="tertiary">悬停显示</Button>
<template #content>hi bytedance</template>
</Tooltip>
<br />
<Tooltip trigger="click">
<Button :style="{ marginBottom: '20px' }">点击显示</Button>
<template #content>hi bytedance</template>
</Tooltip>
<br />
<Tooltip trigger="focus">
<Input aria-label="聚焦显示" placeholder="聚焦显示" :style="{ width: '100px', marginBottom: '20px' }" />
<template #content>hi bytedance</template>
</Tooltip>
<br />
<Tooltip trigger="contextMenu">
<Button :style="{ marginBottom: '20px' }" theme="solid" type="secondary">右键点击展示</Button>
<template #content>hi bytedance</template>
</Tooltip>
<br />
<Tooltip trigger="custom" :visible="visible === 1">
<span :style="{ display: 'inline-block' }">
<RadioGroup v-model="visible" type="button">
<Radio :value="1">受控显示</Radio>
<Radio :value="0">受控隐藏</Radio>
</RadioGroup>
</span>
<template #content>hi bytedance</template>
</Tooltip>
</div>
</template>condition 条件触发
当 :condition="false" 时,Tooltip 不响应 hover/click/focus 等触发行为(trigger="custom" 时仍按 visible 显示);condition 由 true 变为 false 时,已显示的弹出层立即关闭;visible 受控时只发出 update:visible 与 visibleChange(false),使用方回写 visible 后才关闭。
代码
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Button, Space, Switch, Tooltip, Typography } from '@fibrous/semi-ui-vue';
const { Text } = Typography;
const enabled = ref(true);
const content = computed(() => `condition is ${enabled.value ? 'true' : 'false'}`);
</script>
<template>
<Space align="center">
<Text id="tooltip-condition-label">condition</Text>
<Switch v-model="enabled" aria-labelledby="tooltip-condition-label" />
<Tooltip :condition="enabled" trigger="hover">
<Button>Hover me</Button>
<template #content>{{ content }}</template>
</Tooltip>
<Tooltip :condition="enabled" trigger="click">
<Button>Click me</Button>
<template #content>{{ content }}</template>
</Tooltip>
</Space>
</template>覆盖特定样式
你可以通过 class、style 为弹出层配置特定样式,例如覆盖默认的 maxWidth (240px)
代码
<script setup lang="ts">
import { Tag, Tooltip } from '@fibrous/semi-ui-vue';
</script>
<template>
<div>
<Tooltip class="another-classname" :style="{ maxWidth: '320px' }">
<Tag :style="{ marginRight: '8px' }">Custom Style And ClassName</Tag>
<template #content>hi semi semi semi semi semi semi semi</template>
</Tooltip>
</div>
</template>搭配 Popover 或 Popconfirm 使用
Tooltip、Popconfirm、Popover 都会在触发元素上绑定相关事件(mouseenter/mouseleave/click....),并注入 aria-describedby、data-popupid 等触发属性。
如果直接嵌套使用,外层注入的 aria 属性、tabindex、data-popupid 与方向键处理会被内层组件丢弃。
需要在中间加一层元素(div 或 span)作为外层的触发元素。
代码
<script setup lang="ts">
import { Button, Popconfirm, Tooltip } from '@fibrous/semi-ui-vue';
</script>
<template>
<Popconfirm :style="{ width: '320px' }">
<span :style="{ display: 'inline-block' }">
<Tooltip>
<Button type="danger">删除</Button>
<template #content>删除评价</template>
</Tooltip>
</span>
<template #title>确认</template>
<template #content>是否确认删除</template>
</Popconfirm>
</template>仅当内容宽度超出时展示 Tooltip
Semi 为这种场景提供了 Typography 组件,可以更简单快捷地满足需求。不需要自己再对 Tooltip 的出现做条件判断,详细的使用请参考Typography 组件文档
是一个很长很长很长很长5号标题
是一个很长很长很长很长的链接
多行截断,展示 Popover:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。
代码
<script setup lang="ts">
import { Typography } from '@fibrous/semi-ui-vue';
const { Paragraph, Title, Text } = Typography;
</script>
<template>
<div>
<Title :ellipsis="{ showTooltip: true }" :heading="5" :style="{ width: '250px' }"> 是一个很长很长很长很长5号标题 </Title>
<br />
<Text :ellipsis="{ showTooltip: true, pos: 'middle' }" link :style="{ width: '150px' }"> 是一个很长很长很长很长的链接 </Text>
<br />
<br />
<Paragraph :ellipsis="{ rows: 3, showTooltip: { type: 'popover', opts: { style: { width: '300px' } } } }" :style="{ width: '300px' }">
多行截断,展示 Popover:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。
</Paragraph>
</div>
</template>API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| motion | 是否展示弹出层动画。 | boolean | semiDefault("Tooltip", "motion", true) |
| autoAdjustOverflow | 弹出层被遮挡时是否自动调整方向。 | boolean | semiDefault("Tooltip", "autoAdjustOverflow", true) |
| position | 弹出层相对触发元素的位置。 | Type"top" | "bottom" | "left" | "right" | "topLeft" | "topRight" | "leftTop" | "leftBottom" | "rightTop" | "rightBottom" | "bottomLeft" | "bottomRight" | "leftTopOver" | "rightTopOver" | "leftBottomOver" | "rightBottomOver" | semiDefault("Tooltip", "position", "top") |
| prefixCls | 弹出层类名前缀,设置后弹出层不再带 Tooltip 的样式。 | string | semiDefault("Tooltip", "prefixCls", cssClasses.PREFIX) |
| spacing | 弹出层与触发元素的距离,单位为像素;对象形式分别设置水平与垂直方向。 | number | { x: number; y: number; } | semiDefault("Tooltip", "spacing", numbers.SPACING) |
| showArrow | 是否显示箭头三角形。 | boolean | semiDefault("Tooltip", "showArrow", true) |
| role | 弹出层的 ARIA role。 | string | semiDefault("Tooltip", "role", "tooltip") |
| ariaLabel | 弹出层 role 为 dialog 时的 aria-label。 | string | semiGlobalDefault("Tooltip", "ariaLabel") |
| ariaLabelledby | 弹出层 role 为 dialog 时的 aria-labelledby。 | string | semiGlobalDefault("Tooltip", "ariaLabelledby") |
| ariaDescribedby | 弹出层 role 为 dialog 时的 aria-describedby。 | string | semiGlobalDefault("Tooltip", "ariaDescribedby") |
| arrowPointAtCenter | 箭头是否指向触发元素中心,需要同时开启 showArrow。 | boolean | semiDefault("Tooltip", "arrowPointAtCenter", true) |
| wrapWhenSpecial | 触发元素禁用或加载中时,是否在外层包裹 span 以保证触发行为。 | boolean | semiDefault("Tooltip", "wrapWhenSpecial", true) |
| stopPropagation | 是否阻止弹出层上的 click、mousedown 与焦点进出事件冒泡。 | boolean | semiGlobalDefault("Tooltip", "stopPropagation") |
| wrapperClassName | 触发元素被 span 包裹时,该 span 的类名。 | string | semiGlobalDefault("Tooltip", "wrapperClassName") |
| wrapperId | 弹出层节点的 id,触发元素的 aria 属性指向它;缺省时自动生成。 | string | undefined |
| keepDOM | 关闭时是否保留弹出层内容而不销毁。 | boolean | semiDefault("Tooltip", "keepDOM", false) |
| clickToHide | 点击弹出层及其内部任一元素时是否关闭弹出层。 | boolean | semiGlobalDefault("Tooltip", "clickToHide") |
| clickTriggerToHide | 点击触发元素时是否关闭已打开的弹出层。 | boolean | semiGlobalDefault("Tooltip", "clickTriggerToHide") |
| closeOnEsc | 弹出层打开期间按 Esc 是否关闭弹出层;trigger 为 custom 时只触发 escKeyDown,由使用方决定是否关闭。 | boolean | semiDefault("Tooltip", "closeOnEsc", true) |
| condition | 是否允许触发显示;仅显式设为 false 时,hover、click、focus 等触发行为不生效,trigger 为 custom 时仍按 visible 显示。由 true 变为 false 时立即关闭已显示的弹出层;visible 受控时只发出 update:visible 与 visibleChange(false),使用方回写后才关闭。 | boolean | semiDefault("Tooltip", "condition", true) |
| disableArrowKeyDown | 是否禁用在触发元素上按方向键把焦点移入弹出层。 | boolean | semiDefault("Tooltip", "disableArrowKeyDown", false) |
| disableFocusListener | trigger 为 hover 时,是否不响应键盘聚焦打开弹出层。 | boolean | semiDefault("Tooltip", "disableFocusListener", false) |
| guardFocus | 焦点位于弹出层内时,Tab 是否在弹出层内循环。 | boolean | semiDefault("Tooltip", "guardFocus", false) |
| mouseEnterDelay | 指针移入后延迟显示的毫秒数,仅 trigger 为 hover 或 focus 时生效。 | number | semiDefault("Tooltip", "mouseEnterDelay", numbers.MOUSE_ENTER_DELAY) |
| mouseLeaveDelay | 指针移出后延迟隐藏的毫秒数,仅 trigger 为 hover 或 focus 时生效,不小于 mouseEnterDelay。 | number | semiDefault("Tooltip", "mouseLeaveDelay", numbers.MOUSE_LEAVE_DELAY) |
| preventScroll | 聚焦时是否阻止浏览器滚动到被聚焦元素。 | boolean | semiGlobalDefault("Tooltip", "preventScroll") |
| returnFocusOnClose | 弹出层关闭后焦点是否回到触发元素。 | boolean | semiDefault("Tooltip", "returnFocusOnClose", false) |
| trigger | 触发显示的时机。custom 时显示与否完全由 visible 决定。 | "hover" | "focus" | "click" | "custom" | "contextMenu" | semiDefault("Tooltip", "trigger", "hover") |
| visible | 弹出层是否显示,配合 v-model:visible 使用;传入后由该值决定显示。 | boolean | semiGlobalDefault("Tooltip", "visible") |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| open | 弹出层显示、进场动画开始时触发,每次打开一次,早于 afterOpen,受控与非受控相同;悬停打开后指针已离开而撤回的打开不触发。 | [] |
| update:visible | 弹出层需要显示或隐藏时触发,供 v-model:visible 回写。 | [visible: boolean] |
| visibleChange | 弹出层显示状态变化时触发;visible 受控时只报告用户交互发起的请求,父组件自己改变 visible 不触发。 | [visible: boolean] |
| clickOutSide | 弹出层打开时在触发元素与弹出层以外的区域按下鼠标时触发,仅 trigger 为 click、contextMenu、custom 或开启 clickTriggerToHide 时有效。 | [event: MouseEvent] |
| escKeyDown | closeOnEsc 为 true 时,弹出层打开期间按 Esc 触发。 | [event: KeyboardEvent] |
| afterOpen | 弹出层打开且进场动画结束后触发;没有进场动画时打开后即触发,受控与非受控相同;进场结束前被关闭的打开不触发。 | [] |
| afterClose | 弹出层关闭且离场动画结束后触发。 | [] |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| default | 触发元素。单个元素或组件直接接收触发属性,文本或多个节点会被 span 包裹。 | any |
| content | 弹出层内容,作用域参数 initialFocusRef 绑定到弹出层打开时需要聚焦的元素。 | TypeRenderContentProps<FocusTarget> |
| arrow | 自定义箭头。 | any |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| focusTrigger | 让触发元素获得焦点,仅在 returnFocusOnClose 为 true 且 trigger 不是 custom 时生效。 | Type() => void |
| getPopupId | 返回弹出层节点的 id。 | Type() => string |
| hide | 关闭弹出层;visible 受控时只发出 update:visible 与 visibleChange,由使用方回写。 | Type() => void |
| rePosition | 按触发器当前位置立即重新计算弹出层位置。弹出层已随滚动、窗口尺寸变化、触发器移动以及触发器或弹出层的尺寸变化自动重新定位,一般无需调用;跟随触发器移动会晚一帧,需要同一帧内对齐时调用。 | Type() => void |
class、style 落在弹出层上,其他未声明的属性会被丢弃;触发属性只加在触发元素上。所在子树被 <KeepAlive> 缓存(失活)时,弹出层按关闭处理并通知 update:visible、visibleChange(false);单向绑定 visible 为 true 的弹出层随缓存的子树隐藏,重新激活后再次显示。
Accessibility
ARIA
- Tooltip 具有
tooltiprole,遵循 WAI-ARIA 规范中对于 Tooltip 的定义 - Tooltip 的 content 与 children
- 关于 content
- content 的 wrapper 会被自动添加 id 属性,用于与 children 的
aria-describedby匹配,关联 content 与 children
- content 的 wrapper 会被自动添加 id 属性,用于与 children 的
- 关于 children
- Tooltip 的内容(content)与其触发器(children)之间应当具有显式联系。Tooltip 会自动为 children 元素添加
aria-describedby属性,值为 content wraper的 id - 若你 Tooltip的children 是Icon,不包含可见文本,我们推荐你在 children 上添加
aria-label属性进行相应描述
- Tooltip 的内容(content)与其触发器(children)之间应当具有显式联系。Tooltip 会自动为 children 元素添加
- 关于 content
<!-- 推荐做法:给 Tooltip 的触发元素添加 aria-label 描述 -->
<Tooltip>
<IconSetting aria-label="Settings" />
<template #content><p id="description">Edit your setting</p></template>
</Tooltip>
文案规范
- 只展示信息说明和引导,不展示报错信息
- 不在 tooltip 里只能是额外的链接和按钮
- 尽量精简至一句话进行说明,不展示标点符号
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $animation_duration-tooltip_out | 100ms | tooltip-显示及收起动画-动画持续时间 |
| $animation_function-tooltip_out | cubic-bezier(0.215, 0.61, 0.355, 1) | tooltip-显示及收起动画-动画插值函数 |
| $horizontal-rate | 0.24 | 水平方向矫正因子 |
| $vertical-rate | 0 | 垂直方向矫正因子 |
| $motion-zoom_scale-from | 0.8 | 进出场动画的初始缩放 |
| $color-tooltip-bg-default | var(--semi-grey-7) | 工具提示默认背景色 |
| $color-tooltip-text-default | var(--semi-color-bg-0) | 工具提示默认文字颜色 |
| $color-tooltip-icon-default | var(--semi-grey-7) | 工具提示默认图标颜色 |
| $color-tooltip_arrow-icon-default | $color-tooltip-icon-default | 工具提示默认小三角箭头颜色 |
| $radius-tooltip | var(--semi-border-radius-medium) | 工具提示默认圆角 |
| $spacing-tooltip_content-paddingLeft | $spacing-base-tight | 工具提示内容左侧内边距 |
| $spacing-tooltip_content-paddingRight | $spacing-base-tight | 工具提示内容右侧内边距 |
| $spacing-tooltip_content-paddingTop | $spacing-tight | 工具提示内容顶部内边距 |
| $spacing-tooltip_content-paddingBottom | $spacing-tight | 工具提示内容底部内边距 |
| $font-tooltip-fontSize | $font-size-regular | 工具提示文本字号 |
| $width-tooltip | 240px | 工具提示宽度 - 默认 |
| $width-tooltip_arrow | 24px | 工具提示小三角箭头宽度 - 水平 |
| $height-tooltip_arrow | 7px | 工具提示小三角箭头高度 - 水平 |
| $height-tooltip_arrow_vertical | 24px | 工具提示小三角箭头高度 - 垂直 |
| $width-tooltip_arrow_vertical | 7px | 工具提示小三角箭头宽度度 - 垂直 |
| $spacing-tooltip_arrow_offset-x | 1px | 水平方向渲染会有缝隙,所以加个偏移量 |
| $spacing-tooltip_arrow_offset-y | 1px | 垂直方向渲染会有缝隙,所以加个偏移量 |
| $spacing-tooltip_arrow_adjusted_offset-y | 6px | 垂直方向上的校正偏移量,主要修正三角形有棱角的问题 |
| $spacing-tooltip_arrow_adjusted_offset-x | math.round($horizontal-rate * $width-tooltip_arrow) | 水平方向上的校正偏移量,主要修正三角形有棱角的问题 |
| $filter-tooltip-bg | none |
FAQ
- 为什么 Tooltip content 配置很长很长的内容时,某些情况下内容会超出显示区域?
Tooltip 的弹出层设置了 overflow-wrap 为 break-word,过长的单词会断开换行。不同语言内容(纯英文、中文、中英文混合、其他语种混合)对换行的需求不一致,默认设置不符合预期时,可以通过 style/className(Vue 为 style/class)设置换行相关 CSS 属性进行调整。