Highlight 高亮块

在活动条目之间平移的高亮块。

<script setup lang="ts">
  import { ref, useId } from 'vue'
  import { Button, Highlight, Inline } from '@hina-ui/vue'

  const items = ['全部', 'Galgame', '轻小说', '漫画']
  const current = ref(0)
  const id = useId()
</script>

<template>
  <Inline gap="none" class="bg-inset relative isolate rounded-md p-1">
    <Button
      v-for="(item, index) in items"
      :key="item"
      variant="ghost"
      tone="neutral"
      size="sm"
      :aria-current="current === index ? 'true' : undefined"
      :class="current === index ? 'z-0' : 'z-[1]'"
      @click="current = index"
    >
      <Highlight
        v-if="current === index"
        :id="id"
        axis="x"
        class="bg-surface absolute inset-0 -z-10 rounded-md shadow-sm"
      />
      {{ item }}
    </Button>
  </Inline>
</template>
vue

用法

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

Highlight 用于标示当前条目。它不进行任何测量:调用方通过 class 把它定位在活动条目的范围内,活动条目变化时,它由 Motion 的布局动画从原位置连续平移到新位置,而不是在一处消失、在另一处出现。

组件本身没有外观,背景色、圆角与层级全部由调用方通过 class 指定。

在条目之间移动

将它渲染在当前活动条目的内部,用 v-if 与活动状态绑定,并提供一个 id。活动条目变化时,原有实例卸载,新实例在新条目内挂载,相同的 id 使 Motion 把它从原位置平移到新位置。id 应由 useId() 生成,以保证同一页面上的多个实例互不干扰。

页首的示例即为这种用法:每个按钮内部都可以渲染一个 absolute inset-0 的高亮块,只有活动的按钮实际渲染它。

位移动画轴向

设置 axis="x" 只对水平位移做过渡,axis="y" 只对垂直位移做过渡。另一轴的位置直接跟随当前布局,宽高变化仍保留动画。默认 axis="both" 对两个方向的位移都做过渡。

Tabs 与 SegmentedControl 根据 orientation 自动选择轴向;Anchor 使用 y。

覆盖一段范围

将列表排成单列网格,每一项显式占据一行,高亮块同样作为网格项,通过 grid-row 覆盖整段。此时它保持挂载,跨行范围变化时由布局动画连续伸缩。

简介角色制作人员发售信息
<script setup lang="ts">
  import { computed, ref } from 'vue'
  import { Button, Grid, Highlight, Inline, Stack, Text } from '@hina-ui/vue'

  const rows = ['简介', '角色', '制作人员', '发售信息']
  const from = ref(1)
  const to = ref(2)
  const span = computed(
    () => `${Math.min(from.value, to.value) + 1} / ${Math.max(from.value, to.value) + 2}`,
  )
</script>

<template>
  <Stack gap="md" class="w-full max-w-sm">
    <Inline gap="sm">
      <Button size="sm" variant="outline" tone="neutral" @click="from = (from + 1) % rows.length">
        移动起点
      </Button>
      <Button size="sm" variant="outline" tone="neutral" @click="to = (to + 1) % rows.length">
        移动终点
      </Button>
    </Inline>

    <Grid :cols="1" gap="none" class="border-line relative isolate rounded-lg border p-1">
      <Highlight
        axis="y"
        :style="{ gridRow: span }"
        class="bg-accent-soft col-start-1 -z-10 rounded-md"
      />
      <Text
        v-for="(row, index) in rows"
        :key="row"
        as="span"
        size="sm"
        class="col-start-1 block px-3 py-1.5"
        :style="{ gridRow: index + 1 }"
      >
        {{ row }}
      </Text>
    </Grid>
  </Stack>
</template>
vue

行为

  • 静止时的位置由 CSS 布局决定,随条目尺寸变化,不需要测量,也不需要监听尺寸变化。
  • 移动与伸缩都是连续的位移,缩放过程中圆角与阴影的变形由 Motion 自动修正。
  • 系统启用减弱动态效果时,直接呈现在目标位置,不做位移过渡。
  • 位移过程中高亮块位于目标条目内部。若各条目各自构成层叠上下文,应给非活动条目更高的层级(例如 z-[1]),活动条目使用 z-0,使高亮块从其他条目的文字下方经过。
  • 服务端渲染输出的就是真实的高亮块,首屏与水合后的状态一致。

无障碍

  • 组件带有 aria-hidden,仅作装饰,不进入无障碍树。
  • 当前位置必须另有语义表达,例如活动条目上的 aria-current 或者 aria-pressed,不能仅依赖高亮块。

API

Props

属性
类型
默认值
说明
id
string
—
共享布局动画的标识,在条目之间移动时必须提供
axis
'x' | 'y' | 'both'
'both'
位移动画的轴向,不影响宽高动画
as
'div' | 'li'
'div'
渲染的元素
class
string
—
追加至根元素的类名