结合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
相关产品推荐
相关产品推荐

