Vue Transition Group实现SVG树形节点个性化过渡动画的问题
Vue SVG树形节点个性化进入/移动动画实现方案
Vue完全可以实现你要的个性化节点动画需求,不用退回到D3的手动DOM操作。核心思路是利用TransitionGroup的JS钩子,结合节点数据中的父节点位置,给每个进入的节点设置专属的起始过渡参数,具体实现步骤如下:
1. 修正TransitionGroup的key值
你当前用index作为v-for的key是错误的——节点展开/折叠时,列表的index会重新排序,导致Vue无法正确跟踪节点实例,进而无法触发正确的进入/离开动画。必须改用节点的唯一标识(比如节点数据中的id字段)作为key:
<template> <svg class="TreeLayout fit" name="slide-left-centered"> <TransitionGroup name="tree-node" tag="g" id="tree-canvas" @before-enter="handleBeforeEnter" @enter="handleEnter" @before-leave="handleBeforeLeave" > <testNode v-for="item in h.descendants()" :key="item.id" <!-- 替换为节点唯一ID --> :x="item.x" :y="item.y" :width="rectW" :height="rectH" :node-data="item" @some-event="handleClick(item)" /> </TransitionGroup> </svg> </template>
2. 用TransitionGroup钩子实现个性化动画
TransitionGroup提供的JS钩子可以精准控制每个节点的动画过程,你可以在钩子中获取节点的父位置,设置起始状态,再过渡到目标位置:
<script setup> import { ref } from 'vue' const rectW = ref(100) const rectH = ref(40) // 进入前:设置节点初始位置为父节点位置 const handleBeforeEnter = (el, { props }) => { const parent = props.nodeData.parent if (parent) { // SVG的transform用translate语法,初始位置绑定父节点坐标 el.style.transform = `translate(${parent.x}px, ${parent.y}px)` el.style.opacity = '0' } else { // 根节点直接显示 el.style.opacity = '1' } } // 进入时:过渡到自身目标位置 const handleEnter = (el, done) => { const { x, y } = el.dataset // 用requestAnimationFrame确保DOM已更新 requestAnimationFrame(() => { el.style.transition = 'transform 0.3s ease, opacity 0.3s ease' el.style.transform = `translate(${x}px, ${y}px)` el.style.opacity = '1' }) // 动画结束后通知Vue完成过渡 el.addEventListener('transitionend', done, { once: true }) } // 离开时:过渡回父节点位置后移除 const handleBeforeLeave = (el, { props }) => { const parent = props.nodeData.parent if (parent) { el.style.transition = 'transform 0.3s ease, opacity 0.3s ease' el.style.transform = `translate(${parent.x}px, ${parent.y}px)` el.style.opacity = '0' } } const handleClick = (item) => { // 这里处理节点展开/折叠逻辑,更新tree数据触发响应式更新 } </script>
3. 子组件传递位置数据
在testNode子组件中,把目标位置绑定到DOM元素的data-*属性,方便钩子中获取:
<template> <g :data-x="x" :data-y="y" class="tree-node" > <rect :width="width" :height="height" :rx="4" fill="#fff" stroke="#333" /> <text :x="width/2" :y="height/2" text-anchor="middle" dominant-baseline="middle"> {{ nodeData.name }} </text> </g> </template> <script setup> defineProps({ x: Number, y: Number, width: Number, height: Number, nodeData: Object }) </script>
补充说明
- 你之前用refs操作没生效,是因为refs在节点刚进入时可能还未完成挂载,而
TransitionGroup的钩子是在Vue的过渡生命周期中触发的,能确保DOM元素已存在。 - 节点移动动画:如果父节点展开导致其他节点移位,可以给
testNode添加watch监听x/y的变化,触发过渡动画,或者结合TransitionGroup的@move钩子处理。 - 完全不需要回到D3的手动DOM操作,Vue的响应式+TransitionGroup钩子可以完美实现个性化的节点动画逻辑。
内容的提问来源于stack exchange,提问作者Maranderine
相关产品推荐
相关产品推荐

