Headless UI中Transition的用法

半兽人 发表于: 2025-04-01   最后更新时间: 2025-04-01 15:30:51  
{{totalSubscript}} 订阅, 29 游览

在 Headless UI 中,Transition 是一个专门用于处理组件进入和离开动画的工具。它本身不负责具体的 UI 功能,而是为其他组件(如 MenuDialog 或自定义元素)提供平滑的过渡效果。通过结合 CSS 动画或 Tailwind CSS 类名,Transition 可以让你轻松实现淡入淡出、滑动、缩放等动画效果。

Transition 的作用

Transition 的主要作用是管理元素在“显示”和“隐藏”时的动画状态。它会自动处理以下内容:

  • 进入动画(Enter):当组件从隐藏变为可见时的效果。
  • 离开动画(Leave):当组件从可见变为隐藏时的效果。
  • 状态同步:确保动画与组件的挂载/卸载状态保持一致,避免闪烁或不自然的跳跃。

它特别适合与 Headless UI 的其他组件(如 Menu.ItemsDialog)搭配使用,让用户体验更流畅。


Transition 的基本用法

Transition 组件需要配合 show 属性来控制显示状态,并通过 enterenterFromenterToleaveleaveFromleaveTo 等属性定义动画的类名。

示例:为下拉菜单添加淡入动画

以下是基于之前 Menu 示例的改进版,添加了 Transition

"use client";
import { Transition } from '@headlessui/react'
import { useState } from 'react'

export default function Example() {
    const [isOpen, setIsOpen] = useState(false)

    return (
        <div className="p-5">
            <button
                onClick={() => setIsOpen(!isOpen)}
                className="px-4 py-2 bg-blue-500 text-white rounded"
            >
                切换显示
            </button>

            <Transition
                show={isOpen} // 控制显示或隐藏
                enter="transition-opacity duration-300"
                enterFrom="opacity-0"
                enterTo="opacity-100"
                leave="transition-opacity duration-300"
                leaveFrom="opacity-100"
                leaveTo="opacity-0"
            >
                <div className="mt-4 p-4 bg-gray-100 rounded">
                    这是一个带有渐变动画的 div
                </div>
            </Transition>
        </div>
    )
}

Transition 组件的关键属性

属性 作用
show 控制元素是否可见(true=显示,false=隐藏)
enter 进入动画的整体类(可以是 Tailwind 的 transition-*
enterFrom 进入动画的初始状态
enterTo 进入动画的最终状态
leave 离开动画的整体类
leaveFrom 离开动画的初始状态
leaveTo 离开动画的最终状态

进阶:Transition.Child 处理多个动画元素

Transition.Child 可以用于更复杂的 UI 组件,例如 模态框 中,背景和内容可能需要不同的动画。

<Transition show={isOpen}>
  {/* 背景遮罩层动画 */}
  <Transition.Child
    enter="transition-opacity duration-300"
    enterFrom="opacity-0"
    enterTo="opacity-100"
    leave="transition-opacity duration-300"
    leaveFrom="opacity-100"
    leaveTo="opacity-0"
  >
    <div className="fixed inset-0 bg-black bg-opacity-50"></div>
  </Transition.Child>

  {/* 内容框动画 */}
  <Transition.Child
    enter="transition-transform duration-300"
    enterFrom="scale-95"
    enterTo="scale-100"
    leave="transition-transform duration-300"
    leaveFrom="scale-100"
    leaveTo="scale-95"
  >
    <div className="fixed inset-0 flex items-center justify-center">
      <div className="bg-white p-6 rounded shadow-lg">
        <p>模态框内容</p>
      </div>
    </div>
  </Transition.Child>
</Transition>

示例:为下拉菜单添加淡入动画

以下是基于之前 Menu 示例的改进版,添加了 Transition

import { Menu, Transition } from '@headlessui/react';
import { Fragment } from 'react';

function DropdownExample() {
  return (
    <Menu as="div" className="relative inline-block text-left">
      <Menu.Button className="px-4 py-2 text-white bg-blue-600 rounded hover:bg-blue-700">
        选项
      </Menu.Button>

      {/* 使用 Transition 包裹 Menu.Items */}
      <Transition
        as={Fragment} // 使用 Fragment,避免额外 DOM 节点
        enter="transition ease-out duration-100" // 进入动画
        enterFrom="transform opacity-0 scale-95" // 初始状态
        enterTo="transform opacity-100 scale-100" // 结束状态
        leave="transition ease-in duration-75" // 离开动画
        leaveFrom="transform opacity-100 scale-100" // 初始状态
        leaveTo="transform opacity-0 scale-95" // 结束状态
      >
        <Menu.Items className="absolute mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5">
          <div className="py-1">
            <Menu.Item>
              {({ active }) => (
                <a
                  href="#"
                  className={`block px-4 py-2 text-sm ${
                    active ? 'bg-gray-100 text-gray-900' : 'text-gray-700'
                  }`}
                >
                  编辑
                </a>
              )}
            </Menu.Item>
            <Menu.Item>
              {({ active }) => (
                <a
                  href="#"
                  className={`block px-4 py-2 text-sm ${
                    active ? 'bg-gray-100 text-gray-900' : 'text-gray-700'
                  }`}
                >
                  删除
                </a>
              )}
            </Menu.Item>
          </div>
        </Menu.Items>
      </Transition>
    </Menu>
  );
}

export default DropdownExample;

代码解释

  1. 导入 Transition

    • @headlessui/react 导入 Transition
  2. 包裹 Menu.Items

    • Menu.Items 放入 Transition 中,使其在显示和隐藏时有动画效果。
  3. 动画属性

    • enter:定义进入动画的过渡规则(如 ease-outduration-100 表示 100 毫秒的缓出效果)。
    • enterFrom:进入动画的起始状态(透明度 0,缩放 95%)。
    • enterTo:进入动画的结束状态(透明度 100%,缩放 100%)。
    • leaveleaveFromleaveTo:类似地定义离开动画。
  4. as={Fragment}

    • 使用 React 的 Fragment,避免在 DOM 中添加额外的包裹元素。如果需要特定的 HTML 标签,可以用 as="div"
  5. 效果

    • 点击按钮时,菜单会从透明且略微缩小的状态淡入并放大。
    • 关闭菜单时,会淡出并缩小。

更复杂的例子:滑动动画

如果你想要菜单从顶部滑入,可以调整类名:

<Transition
  as={Fragment}
  enter="transition ease-out duration-200"
  enterFrom="transform opacity-0 -translate-y-4"
  enterTo="transform opacity-100 translate-y-0"
  leave="transition ease-in duration-150"
  leaveFrom="transform opacity-100 translate-y-0"
  leaveTo="transform opacity-0 -translate-y-4"
>
  <Menu.Items>...</Menu.Items>
</Transition>
  • 这里使用了 translate-y 来实现垂直滑动效果。

注意事项

  • Tailwind CSS 支持:示例中的类名(如 opacity-0scale-95)依赖 Tailwind CSS。如果不用 Tailwind,可以直接写 CSS(如 style={{ opacity: 0 }})。
  • 与状态绑定Transition 自动与 Menu 的显示状态绑定,无需手动控制 show 属性。如果是自定义组件,则需要显式传递 show={boolean}
  • 性能:动画使用 CSS 过渡,避免了 JavaScript 的性能开销。
更新于 2025-04-01

查看Headless UI更多相关的文章或提一个关于Headless UI的问题,也可以与我们一起分享文章