FloatButton 浮动按钮

将一个常用操作放在页面或容器的边角,滚动时仍能找到。

工作笔记

本周设计回顾

下一版组件清单

<script setup lang="ts">
  import { ref } from 'vue'
  import { Plus } from '@lucide/vue'
  import {
    Button,
    Card,
    Dialog,
    FloatButton,
    FormField,
    Heading,
    Input,
    Stack,
    Text,
  } from '@hina-ui/vue'

  const open = ref(false)
  const title = ref('')
  const notes = ref(['本周设计回顾', '下一版组件清单'])
  function create() {
    if (!title.value.trim()) return
    notes.value.push(title.value.trim())
    title.value = ''
    open.value = false
  }
</script>

<template>
  <Card class="relative min-h-72 w-full max-w-md pb-24">
    <Stack gap="lg">
      <Heading :level="3" size="base">工作笔记</Heading>
      <Stack gap="sm">
        <Text v-for="(note, index) in notes" :key="index" class="border-line border-b pb-3">
          {{ note }}
        </Text>
      </Stack>
    </Stack>
    <FloatButton position="absolute" label="新建笔记" @click="open = true"><Plus /></FloatButton>
    <Dialog v-model:open="open" title="新建笔记" description="为新的笔记填写标题。">
      <template #content>
        <FormField label="笔记标题">
          <Input v-model="title" placeholder="例如:交互细节" @keydown.enter.prevent="create" />
        </FormField>
      </template>
      <template #footer>
        <Button variant="soft" tone="neutral" @click="open = false">取消</Button>
        <Button :disabled="!title.trim()" @click="create">创建</Button>
      </template>
    </Dialog>
  </Card>
</template>
vue

用法

import { FloatButton } from '@hina-ui/vue'
ts

默认固定在视口的逻辑右下角。通过 label 提供操作名称,默认插槽放图标。浮动按钮适合新建、帮助这类跨内容区域的常用操作;行内操作继续使用 Button 或 IconButton。

<FloatButton label="新建笔记" @click="createNote"><Plus /></FloatButton>
vue

整页使用时放在应用壳外层,避免祖先的 transform 改变固定定位的参照。组件不传送到 body,保留当前位置的主题、方向和组件上下文。上面的演示使用 position="absolute",将按钮限制在预览面板内。

示例

外观

支持实底、浅底和描边,shape="square" 使用圆角方形。图标按钮沿用现有 Tooltip;应用需提供 TooltipProvider,AppShell 已包含它。label 始终作为无障碍名称,不依赖 Tooltip。

悬停或键盘聚焦可以查看操作名称

<script setup lang="ts">
  import { ref } from 'vue'
  import { HelpCircle, Pencil, Plus } from '@lucide/vue'
  import { FloatButton, Flex, Stack, Text } from '@hina-ui/vue'

  const result = ref('悬停或键盘聚焦可以查看操作名称')
</script>

<template>
  <Stack align="center" gap="lg">
    <Flex wrap justify="center" gap="lg">
      <FloatButton position="static" label="新建项目" @click="result = '已选择:新建项目'">
        <Plus />
      </FloatButton>
      <FloatButton
        position="static"
        label="编辑项目"
        variant="soft"
        shape="square"
        @click="result = '已选择:编辑项目'"
      >
        <Pencil />
      </FloatButton>
      <FloatButton
        position="static"
        label="帮助中心"
        variant="outline"
        tone="neutral"
        @click="result = '已选择:帮助中心'"
      >
        <HelpCircle />
      </FloatButton>
    </Flex>
    <Text role="status" size="sm" tone="muted">{{ result }}</Text>
  </Stack>
</template>
vue

尺寸与文字

sm、md、lg 分别对应 40、48、56px 的默认高度。extended 将 label 显示在图标旁,此时不会重复显示 Tooltip。长文案在可用宽度内截断。

文字按钮使用同一高度,宽度随名称展开

<script setup lang="ts">
  import { ref } from 'vue'
  import { Plus } from '@lucide/vue'
  import { FloatButton, Flex, Stack, Text } from '@hina-ui/vue'

  const result = ref('文字按钮使用同一高度,宽度随名称展开')
</script>

<template>
  <Stack align="center" gap="lg">
    <Flex wrap align="center" justify="center" gap="lg">
      <FloatButton
        v-for="size in ['sm', 'md', 'lg'] as const"
        :key="size"
        position="static"
        :size="size"
        :label="`新建项目(${size})`"
        @click="result = `已选择:${size}`"
      >
        <Plus />
      </FloatButton>
      <FloatButton position="static" extended label="新建项目" @click="result = '已选择:新建项目'">
        <Plus />
      </FloatButton>
    </Flex>
    <Text role="status" size="sm" tone="muted">{{ result }}</Text>
  </Stack>
</template>
vue

定位与方向

placement 使用逻辑方向,start / end 随 RTL 切换。absolute 相对最近的定位祖先,static 参与正常布局,适合与 Stack 组合成一组操作。offset 控制边距,传 { x, y } 分别设置水平与垂直边距;固定定位时还会避开设备安全区。

start / end 随书写方向切换

<script setup lang="ts">
  import { ref } from 'vue'
  import { Plus } from '@lucide/vue'
  import { Card, Center, FloatButton, FormField, Stack, Switch, Text } from '@hina-ui/vue'

  const rtl = ref(false)
  const result = ref('start / end 随书写方向切换')
  const placements = ['top-start', 'top-end', 'bottom-start', 'bottom-end'] as const
</script>

<template>
  <Stack class="w-full max-w-md">
    <FormField label="从右向左" orientation="horizontal"><Switch v-model="rtl" /></FormField>
    <Card :dir="rtl ? 'rtl' : 'ltr'" class="relative h-64" :padded="false">
      <Center class="h-full px-16">
        <Text role="status" size="sm" tone="muted" class="text-center">{{ result }}</Text>
      </Center>
      <FloatButton
        v-for="placement in placements"
        :key="placement"
        position="absolute"
        :placement="placement"
        :label="placement"
        :offset="16"
        variant="soft"
        size="sm"
        @click="result = placement"
      >
        <Plus />
      </FloatButton>
    </Card>
  </Stack>
</template>
vue

状态与显隐

visible 控制出现和退场,退场期间按钮不能交互。loading 保留按钮尺寸并阻止重复操作,disabled 禁用按钮。

显示和隐藏带有过渡

<script setup lang="ts">
  import { ref } from 'vue'
  import { RefreshCw } from '@lucide/vue'
  import { Center, FloatButton, FormField, Stack, Switch, Text } from '@hina-ui/vue'

  const visible = ref(true)
  const loading = ref(false)
  const disabled = ref(false)
  const result = ref('显示和隐藏带有过渡')
</script>

<template>
  <Stack class="w-full max-w-xs">
    <FormField label="显示按钮" orientation="horizontal"><Switch v-model="visible" /></FormField>
    <FormField label="加载中" orientation="horizontal"><Switch v-model="loading" /></FormField>
    <FormField label="禁用" orientation="horizontal"><Switch v-model="disabled" /></FormField>
    <Center class="h-24">
      <FloatButton
        position="static"
        label="同步资料"
        :visible="visible"
        :loading="loading"
        :disabled="disabled"
        @click="result = '已执行:同步资料'"
      >
        <RefreshCw />
      </FloatButton>
    </Center>
    <Text role="status" size="sm" tone="muted" class="text-center">{{ result }}</Text>
  </Stack>
</template>
vue

API

Props

属性
类型
默认值
说明
label
string
必填
操作名称,同时用于无障碍和 Tooltip
visible
boolean
true
显示按钮,变化时播放过渡
position
'fixed' | 'absolute' | 'static'
'fixed'
定位方式
placement
'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'
'bottom-end'
定位角落,静态布局时无效
offset
number | string | { x?: number | string; y?: number | string }
6 × spacing
边距,数字单位为 px;对象形式分别设置水平 x 与垂直 y,未给出的方向使用默认值;固定定位取边距与安全区中的较大值
size
'sm' | 'md' | 'lg'
'md'
按钮尺寸
shape
'circle' | 'square'
'circle'
圆形或圆角方形
extended
boolean
false
显示文字标签
variant
'solid' | 'soft' | 'outline'
'solid'
按钮外观
tone
'accent' | 'neutral' | 'danger'
'accent'
按钮色调
tooltip
boolean
true
图标形态下显示 Tooltip
tooltipSide
'top' | 'right' | 'bottom' | 'left'
'top'
Tooltip 首选方向
loading
boolean
false
加载中并禁止操作
disabled
boolean
false
禁用操作
ripple
boolean
true
涟漪反馈
as
string | Component
'button'
实际操作元素或组件
type
'button' | 'submit' | 'reset'
'button'
按钮类型
class
string
—
按钮类名
style
StyleValue
—
按钮样式

原生属性和事件透传到按钮,例如 id、form 和 @click。

Slots

插槽
说明
default
操作图标;extended 时放在文字前面

Expose

名称
类型
说明
element
HTMLElement | undefined
当前操作节点
focus
() => void
聚焦可用的按钮