You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

结合Radix Primitives时,如何让framer-motion响应data属性?

如何让framer-motion响应Radix组件的data状态属性变化

核心思路

Radix这类无头UI通过data-state(如data-state="open"/data-state="closed")标记组件状态,framer-motion可通过同步组件状态到动画配置或监听DOM属性变化两种方式,实现对data属性状态切换的响应。


方法1:直接绑定状态到motion组件的动画变体

无需依赖data属性,直接将Radix组件的状态(可通过Radix自带钩子或自定义useState获取)绑定到motion组件的animate或variants配置:

import { motion } from "framer-motion";
import { Tooltip } from "@radix-ui/react-tooltip";
import { useState } from "react";

const TooltipDemo = () => {
  const [isOpen, setIsOpen] = useState(false);

  const tooltipVariants = {
    closed: { opacity: 0, y: -10 },
    open: { opacity: 1, y: 0, transition: { duration: 0.2 } }
  };

  return (
    <Tooltip.Root open={isOpen} onOpenChange={setIsOpen}>
      <Tooltip.Trigger>Hover me</Tooltip.Trigger>
      <Tooltip.Portal>
        <motion.div
          variants={tooltipVariants}
          animate={isOpen ? "open" : "closed"}
          initial="closed"
        >
          <Tooltip.Content>Tooltip content</Tooltip.Content>
        </motion.div>
      </Tooltip.Portal>
    </Tooltip.Root>
  );
};

方法2:监听DOM的data属性变化

如果必须依赖Radix自动添加的data-state属性(比如无法直接获取组件内部状态),可以用MutationObserver监听DOM属性变化,同步到motion的动画控制器:

import { motion, useAnimation } from "framer-motion";
import { Tooltip } from "@radix-ui/react-tooltip";
import { useEffect, useRef } from "react";

const TooltipDemo = () => {
  const controls = useAnimation();
  const contentRef = useRef(null);

  useEffect(() => {
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === "attributes" && mutation.attributeName === "data-state") {
          const newState = contentRef.current.dataset.state;
          controls.start(newState);
        }
      });
    });

    contentRef.current && observer.observe(contentRef.current, { attributes: true });
    return () => observer.disconnect();
  }, [controls]);

  const tooltipVariants = {
    closed: { opacity: 0, y: -10 },
    open: { opacity: 1, y: 0 }
  };

  return (
    <Tooltip.Root>
      <Tooltip.Trigger>Hover me</Tooltip.Trigger>
      <Tooltip.Portal>
        <motion.div
          variants={tooltipVariants}
          initial="closed"
          animate={controls}
          ref={contentRef}
        >
          <Tooltip.Content>Tooltip content</Tooltip.Content>
        </motion.div>
      </Tooltip.Portal>
    </Tooltip.Root>
  );
};

方法3:结合Radix动态CSS变量做动画

针对Radix提供的--radix-tooltip-content-available-height这类动态CSS变量,可直接在motion组件的样式中引用,结合状态变化驱动尺寸类动画:

import { motion } from "framer-motion";
import { Tooltip } from "@radix-ui/react-tooltip";
import { useState } from "react";

const TooltipDemo = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <Tooltip.Root open={isOpen} onOpenChange={setIsOpen}>
      <Tooltip.Trigger>Hover me</Tooltip.Trigger>
      <Tooltip.Portal>
        <motion.div
          initial={{ opacity: 0, height: 0 }}
          animate={{
            opacity: isOpen ? 1 : 0,
            height: isOpen ? "var(--radix-tooltip-content-available-height)" : 0
          }}
          style={{ overflow: "hidden" }}
        >
          <Tooltip.Content>Tooltip content</Tooltip.Content>
        </motion.div>
      </Tooltip.Portal>
    </Tooltip.Root>
  );
};

关键提示

  • 优先使用方法1,代码简洁且避免DOM监听的额外性能开销,是最推荐的实现方式
  • 方法2适合无法直接获取组件状态的场景(如第三方封装的Radix组件)
  • CSS变量结合方式适合需要基于Radix计算的动态尺寸做动画的场景

内容的提问来源于stack exchange,提问作者leon liu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 08:35:05